January 5, 2015 by Daniel P. Clark

Dealing with Rails 4 forms and params

In the learning stage of Rails developments one will come across different scenarios of handling parameters from Rails forms that are submitted.  Also since Rails 4 introduced Strong Parameters there are some additional things to keep in mind.

Form Params 101

Parameters: {
  "utf8"=>"✓",
  "authenticity_token"=>"nqLr/ZukyXt5/sFNU9FGciBbSOSJ2MkE7Roo5WwfVEo=",
  "profile"=>{
    "first_name"=>"Daniel",
    "last_name"=>"Clark",
    "socials_attributes"=>{
      "0"=>{
        "kind"=>"twitter",
        "username"=>"@6ftdan"
        }
      }
    },
  "button"=>""
}

Submitted forms return a JSON response with data in it.  You can access it from your controller with the params Hash.  To access the first name here from params you would have to do params[‘profile’][‘first_name’] and you would get back “Daniel”.  With the introduction of Strong Parameters in Rails 4 you are asked to no longer directly use params for security reasons.  You will use a middle man method usually named after your model name such as profile_params.

The Strong Params method will look a lot like:

def profile_params
  params.require(:profile).permit( :first_name, :last_name,
    :socials_attributes => [:id,:kind,:username,:_destroy])
end

Consider this strong params method as a read-only parameter access used to help protect you.  Also note that this will step into the parameters one level into the params Hash it calls.  In this example it returns the “profile” subset of the Hash.  So profile_params returns the same results as params[‘profile’] unless additional fields exist that are not within the :permit method.  It will drop any items not in :permit.

If you want to modify any of the parameters before your controller saves or updates the Object then you need to perform your work on the params Hash before the save/update methods are invoked.  Note: I said to work directly on params for modification or changes.  Again; that’s because profile_params is a read-only method and it will be updated accordingly with any changes you commit to params.

Form to params

The form_for method will create the nested params before being submitted.  What I mean by that is the Object you give form_for will be the name of the Hash subset that the Strong Params method will have in require().  For example form_for(:profile). will have a params[‘profile’] Hash in the results.  In this example case :profile is assigned to an existing Profile Object.  Each of the standard input fields will be nested within params[‘profile’].  For example:

<%= form_for(@profile) do |f| %>
  <%= f.text_field :first_name %>
  <%= f.submit %>
<% end %>

Will submit a result that you can access first_name through params[‘profile’][‘first_name’] or through the read only strong params method profile_params[‘first_name’].  If you wanted to change the value before it gets saved you need to change it directly on params.  For example params[‘profile’][‘first_name’] = params[‘profile’][‘first_name’].reverse will work.  But trying the same with profile_params will do nothing in the end.

With form_for you assign it to an object like |f| and each item within it must then be called on f … such as f.text_field.  But there are times when you won’t want to do things in this way.  When dealing with custom situations in forms there are alternative methods with their counterpart postfixed _tag  method.

Dealing with _tag methods

One thing I’ve found when using _tag methods is that they don’t usually follow the same order of method parameters as their non _tag method counterparts.  So when using a _tag method it’s best to look up the online method usage reference.  My favorite online reference for these methods are apidock.com.  Beyond the documentation there are tips and comments on how these methods may be used.

The first thing you should know about _tag methods is that they aren’t nested for Strong Params on their own.  In other words you need to nest it yourself.  If I replace the f.text_field from the earlier example with text_field_tag I need to use the nested name representation of the parameter I’m setting.  For example:

<%= form_for(@profile) do |f| %>
  <%= text_field_tag 'profile[first_name]', f.object.first_name %>
  <%= f.submit %>
<% end %>

This will be true for any _tag method that you want to pass the Strong Parameters requirement.  Also notice my use of f.object.first_name.  Any time you want to access the values from the Object you’ve piped into the form through |f| you can simply use f.objectf.object is basically the same as accessing @profile directly.  There are times when this may come in handy for you.

Another thing to keep in mind with _tag methods is they don’t understand how to get the value for the string ‘profile[first_name]’ to show in the page’s input field.  This is why you need to specifically declare the value for it.

Nested Attributes

In the parameter example I gave at the beginning of this you may have noticed:

"socials_attributes"=>{
  "0"=>{
    "kind"=>"twitter",
    "username"=>"@6ftdan"
  }
}

When you have items of something belonging to another you use nested attributes.  In the example above this is a Social Network that belongs to the Profile.  So Profile has_many Social Networks and Social Networks belongs_to Profile.  Here are the models:

# app/models/profile.rb
class Profile < ActiveRecord::Base
  has_many :socials, as: :sociable, dependent: :destroy
  accepts_nested_attributes_for :socials
end

# app/models/social.rb
class Social < ActiveRecord::Base
  enum kind: [ :twitter, :google_plus, :facebook]
  belongs_to :sociable, polymorphic: true
end

You’ll notice the word polymorphicWhenever you create an Object model that can belong to more than one other kind of Object model you will want to use polymorphic.  If you didn’t use it you would have to reference profile via a profile_id field in your Social model.  With polymorphic it keeps to pieces of information for you: the type and the id.  In this case the sociable_type will be “Profile” and the id will reference whatever the owners Profile id is for this “Social Network” Object.

The form that would submit these parameters would look as follows:

<%= form_for(@profile) do |f| %>
  First Name:<%= f.text_field :first_name %><br />
  <%= f.text_field :last_name %><br />
  <%= f.fields_for :socials, one_by_kind(@profile.socials,
                             @profile.socials.kinds[:twitter]) do |a| %>
    <%= a.hidden_field :kind, {value: :twitter} %>
    Twitter: <%= a.text_field :username %><br />
  <% end %>
  <%= f.submit %>
<% end %>

The one_by_kind is my own method.  I’ve replicated first_or_create with a first or build syntax.  You can see my code for this method in a previous article Manual Polymorphic Creation in Rails.

You’ll notice nested attributes are generally given through the fields_for method.  fields_for is for all of your belongs_to/has child Objects.   You can also replicate the nested fields by using _tag methods.  I won’t be covering that at this time though.

Things to Keep in Mind

  1. Be careful about saving user_id from parameters.  Think that they may be compromised.  Before the save/update commands are committed assign them to the current_user in the controller.  Example:
    # /app/models/card.rb
    class Card < ActiveRecord::Base
      belongs_to :user
    end
    
    # /app/controllers/cards_controller.rb
    class CardsController < ApplicationController
      def create
        @card = Card.new(card_params)
        @card.user = current_user
        respond_to do |format|
          if @card.save
            ...
  2. _tag methods are done differently then their counterpart methods.  For example:
    # value is assigned in a options parameter
    f.hidden_field(:store, {value: 'EatMart'})
    
    # value is assigned by the second parameter given
    hidden_field_tag('shopping[store]', 'EatMart')
  3. The Strong Params method is read only.  Do any necessary modification on params and please still use the strong_params method to save with.
  4. Strong Parameters requires you to declare any field you want to allow in to be saved.  Sometimes this will slip your mind.
  5. If you’re using form_for or form_tag and the form is closing itself off prematurely (before your inputs) then you can put a tag for a submit button right before the <% end %>.  If you don’t want a submit button for the form simply make it invisible.
      <%= submit_tag('',style: 'width:0;height:0;display:none;') %>
    <% end %>

There are still many things I haven’t covered here.  I’ve addressed what I’ve seen as some of the biggest issues when first learning to implement forms in Rails.  Things only get more technical when you delve into JavaScript submitted forms via AJAX.  It’s not hard, just a bit more to learn.  As always I hope what I’ve written has been both helpful to you, and enjoyable to read!

Please comment, share, subscribe to my RSS Feed, and follow me on twitter @6ftdan!

God Bless!
-Daniel P. Clark

#_tag#101#article#attributes#blog#field#fields#fields_for#form#form_for#forms#hidden_field#hidden_field_tag#input#nested#parameters#params#post#rails#rails 4#strong#Strong Parameters

Comments

  1. Dan Shultz
    January 8, 2015 - 4:23 pm

    Good blog post, it covers and details strong parameters well. I wanted to add one critique because I think it affects the whole mental model of how forms and rails works.

    You mentioned that, “Submitted forms return a JSON response with data in it…” What happens in the case of forms, they are submitted normally via the browser with a content type usually of `application/x-www-form-urlencoded` and there is a rails middleware called `ActionDispatch::ParamsParser` which reads this content type and takes the appropriate action to populate the `params` for your controller. In the case of a content type of `application/json`, the middleware parses your request body as JSON and populates the `params` for your controller.

    • Daniel P. Clark
      January 8, 2015 - 7:58 pm

      Thanks for your input! I also believe the params Object that is generated is an instance of ActiveSupport::HashWithIndifferentAccess thereby allowing you to access each params key by either a Symbol or a String. So there is quite a bit being done with the data that is returned.

      params.class.ancestors
      => [ActionController::Parameters,
      ActiveSupport::HashWithIndifferentAccess,
      Hash,
      HashRecursiveMerge,
      V8::Conversion::Hash,
      JSON::Ext::Generator::GeneratorMethods::Hash,
      Enumerable,
      Object,
      V8::Conversion::Object,
      ActiveSupport::Dependencies::Loadable,
      PP::ObjectMixin,
      JSON::Ext::Generator::GeneratorMethods::Object,
      Kernel,
      BasicObject]

  2. avadh
    December 3, 2015 - 3:53 pm

    Hi Daniel,

    Great to find your blogpost. I am having issues with an API only app where I can not use form_for in this case how should I test the API, Postman seems not to work with the Key Wrapping.

    Any thoughts suggestion would be helpful.

    • Daniel P. Clark
      December 3, 2015 - 5:44 pm

      You haven’t given me enough information to understand what you’re working with. What test suite? And postman is email server related. When testing any server via GET/POST requests there is a command line tool called curl. In Ruby one of the best tools like curl is typhoeus. Although in Rails itself there are plenty of ways to test with a simple get/post command in your integration/controller tests… you just need to inherit or include the right class/module.

  3. Larry Lawal
    May 20, 2016 - 1:02 pm

    how do i find a record in rails using a value sent in a nested parameter request?

    • Daniel P. Clark
      May 21, 2016 - 11:11 am

      Can you give an example? In my JSON code at the top of this post accessing the Twitter username can be done in two ways.

      
      params["profile"]["socials_attributes"]["0"]["username"]
      
      # and strong params (the right way)
      profile_params["socials_attributes"]["0"]["username"]
      

      It is recommended to handle multiple nested attributes with an each loop.

      
      profile_params["socials_attributes"].each do |_, value|
        # here the underscore takes the "0" key and value gets the kind & username hash
        value["username"]
      end
      

      This way you can create/handle each item as you choose. To find a record of the Twitter username you should use a secure way of using where. Either:

      
      Social.where(kind: value["kind"], username: value["username"])
      # or
      Social.where("kind = ?", value["kind"]).where("username = ?", value["username"])
      

      Do not use string interpolation for where queries as that would allow for SQL injection attacks.

  4. MSCAU
    July 12, 2016 - 4:05 am

    Outstanding! You have saved my day. The official Rails docs don’t explain this well at all so where did you learn this from?

    • Daniel P. Clark
      January 28, 2017 - 2:35 pm

      Mostly trial and error and by picking through subtle hints in looking up online how methods worked. Also examining the console log from `rails server` will give you insight into which parameters are having issues.

Leave a Reply

Your email address will not be published / Required fields are marked *