how form_for works in Ruby on Rails

13.6k Views Asked by At

I am an newbie. I have read the API documentation. But still don't understand how form_for works.

Firstly, from Ruby on Rails Tutorial, the form for follow button:

<%= form_for(current_user.relationships.build(followed_id: @user.id)) do |f| %>
  <div><%= f.hidden_field :followed_id %></div>
  <%= f.submit "Follow", class: "btn btn-large btn-primary" %>
<% end %>

I understand current_user.relationships.build(followed_id: @user.id) means a new record. But why can we not just submit and trigger controller to save the record without hidden_field? Why do we still need to post followed_id to controller?

Secondly, in hidden_field, what does :followed_id means? I believe that is a symbol, i.e. it equals only "followed_id" not a variable of id. If that is only the name of the input field, then what is its value?

Thirdly, how does form_for know where the submission should be sent to? Which controller and action the form_for will post to?

Fourth, how does params work with form_for? In this follow button case, params[:relationship][:followed_id] will return @user.id in controller. How does it know the first hash attribute is :relationship? We have neither mentioned form_for :relationship nor form_for @relationship.

I know these questions can be very dumb, but I am really stuck. Any help will be appreciated.

2

There are 2 best solutions below

4
On BEST ANSWER

I didnt do that tutorial so mind me if i dont answer directly to your question.

Take a look at the rails guide about form helpers and it explains in details your questions, probably in a more articulate way than i can.

form_for(path/to/your/controller/action) is a helper method to create HTML form elements with the url path to the POST or GET request. The helper knows if it should be a new record or an update record based on what you are asking to do in your controller action.

For example In your controller

def new
  @my_instance_variable = Myobject.new
end

In your view new.html.erb

<%= form_for @my_instance_variable do |f| %>
...
<% end %>

In your case the logic was directly written in the helper and you could also directly write

<%= form_for Myobject.new %>

Both will result with the following html

<form action="/myobjects/new" method="post">
# in this case rails knows its a `POST` request because the route new action
# is by default a POST request. You can check these routes and their request 
# by using `rake routes` in terminal.

Then the hidden_field is another helper to contain a value, in your case the @user.id that will be passed as parameter then saved as a Create or update action for the given object. The reason it doesnt add the value in the hidden field tag is because you already have a model association that knows the id of user since the link of form uses the build method with user id.

Last part you need to understand the form_for link logic

current_user.relationships 
# implies the association of the current_user has many relationships 
current_user.relationships.build 
# .build is a method to populate a new object that can be save as a new record
# means you will create a new relationship record by populating the user_id 
# column with the current_user.id and the followed_id with the target @user.id
0
On

After reading the book The Rails 4 Way, I understand form_for better now.

11.9.1.5 Displaying Existing Values. If you were editing an existing instance of Person, that object’s attribute values would have been filled into the form.

in this way, when we build the relationship by usingcurrent_user.relationships.build(followed_id: @user.id), the relationship instance will be created and gain attribute followed_id. So that, instead of "creating" a relationship, we are actually editing the relationship by the form.

Then Rails will know you are editing and load the existing attribute "followed_id" to the field. Therefore, we don't need to assign value to the field like using f.hidden_field :followed_id, value: @user.id.

And the reason why we have to use a field to pass followed_id to params is because HTTP server is stateless, it doesn't remember you are creating a relationship with which user.

One of the advantages of writing form_for current_user.relationships.build(followed_id: @user.id) instead of standard form_for @relationship is we don't need to write "if-condition" in controller like this:

unless current_user.nil?
  if current_user.following?(@user)
    @relationship=current_user.relationships.find_by(followed_id: @user.id)
  else
    @relationship=current_user.relationships.new
  end
end

params will be sent to the controller which belongs to the instance's model. "post" method will go to action create, "delete" will go to destroy, "patch" will go to update, etc.

params will be a hash with another hash inside like { instace_name: { field_1: value1, field_2:value2 } } or full params as below

   Parameters: {"utf8"=>"✓",
     "authenticity_token"=>"afl+6u3J/2meoHtve69q+tD9gPc3/QUsHCqPh85Z4WU=",
     "person"=>{"first_name"=>"William", "last_name"=>"Smith"},
     "commit"=>"Create"}