1. 6
  1.  

  2. 4

    The ideas here are interesting, but one peeve I have is that it compares the best example of functional strong params with the worst example of regular params. It’s saying

    friend = hash_of.(name: scalar,
              family: hash_of.(name: scalar),
              hobbies: array_of.(scalar))
    hash_of.({ name: scalar,
               emails: array_of.(scalar),
               friends: array_of.(friend)})
    

    is better than

    params.permit(:name, 
                  {:emails => []}, 
                  :friends => [ :name, 
                                { :family => [ :name ], 
                                  :hobbies => [] }])
    

    But it does three things to make the original look worse. First it adds the params.permit bit to the original but not the final version, which adds cruft. Second, it’s using hash rockets instead of colon syntax, which adds a lot of cruft. Finally, in the ‘functional’ style the author is using a helper variable, while everything’s inline in the original case. Cleaning everything up, you’d actually be comparing it to this:

    friend = [:name, family: [:name], hobbies: []]
    params.permit(:name, emails: [], friends: [friend])
    

    Which is a lot cleaner than the functional version.

    1. 3

      Even that params example seems more readable to me. The former example is essentially an embedded DSL.

    2. 1

      I find this approach really interesting. Personally, From a RoR perspective, I find this approach the easiset to maintain and to read. It keeps the important bits of the logic isolated into its own namespace and keeps the application fairly clutter free.

      First set a memoized helper in the application_controller.rb called permitted_params. This will now be accessible in the controllers.

      # application_controller.rb
      def permitted_params
        @permitted_params ||= Params::PermittedParams.new(params, current_user)
      end
      helper_method :permitted_params
      

      Within the controllers, I can use permitted_params.user instead of the params.

      # users_controller.rb create action
      @user = User.create(permitted_params.user)
      

      I’ll then have a separate folder for all of the strong params logic. Within the folder, I’ll create a new params for each ActiveRecord model.

      app/params/params.rb
      class Params::PermittedParams < Struct.new(:params, :current_user)
        include Params::User
      end
      

      Since our permitted_params helper takes in the params and the current_user, we can use that in our user.rb methods. I’ll access the params and first required the [:user] parameter to exist and then permit from a private method called user_attributes.

      This private method will return an array. We can build out the allowed parameters here as well as having access to limit what parameters a user can write to. In this case, I would only allow the admin attribute to be written to by user input if the current user is already an admin.

      # app/params/user.rb
      module Params
        module User
          def user
            params.require(:user).permit(*user_attributes)
          end
      
          private
      
          def user_attributes
            [].tap do |attributes|
              attributes << :first_name
              attributes << :last_name
              attributes << :admin if current_user.admin?
            end.flatten
          end
        end
      end
      
      1. 1

        Params::PermittedParams

        So this object ends up being a sort of global registry of params logic per-model? Hmm, I have some thoughts around this, thanks for sharing!

        1. 1

          I’ve used it in small apps as well as larger ones and it’s kept the code fairly clean. Since it’s also leveraging memoization, having it called multiple times in a controller/view/presenter keeps the footprint the same. I would probably have a concern if my app had thousands of AR models, but so far it’s been pretty efficient.