Rails Model-Association Form Question
Hey guys, I'm having trouble understanding a Rails construct. I'm using Rails 3 but I doubt this is specific to this version.
I have a model, Goal
that has_many :commits
and naturally, a Commit
model that belongs_to :goal
. I created the proper migration so that commits_table.references :goal
.
I am actually going through the Rails Getting Started guide, except in that article they use a Post
and Comment
respectively.
Now that I've described the situation I can express my confusion. On the Goal
's show view, I have embedded a form to create a new Commit which is 'attached' to the currently viewed Goal. This works fine and all. However, I am having trouble understanding why we do this
<%= form_for([@goal, @goal.commits.build]) do |f| %>
Shouldn't it be form_for(@commit)
? I understand why we want the @goal
, to provide some context since the commit is a nested resource. However, in the actual generated source, the form is appropriately named as commit, that is, the fields are named commit_blah
. How did Rails know this? I understand that there's this whole system of "trust and magic" and all, but I mean at least at the high level, what from this code hinted to Rails that I wanted a commit?
I looked at the documentation for form_for and it seems like one of the parameters could be the action to take for the form. I imagine that in this case, that's what the @goal.commits.build
parameter is for? To designate the action to take? Is this how Rails deduces that I want a commit? Would this also explain why this form is handled by the Commit
controller even though this code is in the Goal's view?
Also, why are these parameters passed as an array ([]
)? In Ruby, will the method still just take it as two separate parameters, or is there a reason开发者_JAVA百科 why this was passed this way?
Finally, rails generate
automatically gave me some error showing code in my other _form.html.erb
partials:
<% if @commit.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@commit.errors.count, "error") %> prohibited this commit from being saved:</h2>
<ul>
<% @commit.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
If I want to add this to this embedded form, how would I do so? I guess my question is, what would I use in place of @commit
?
Thanks. I'm just trying to get my head around these new concepts.
If you go back to the documentation and click 'show source', you'll see
def form_for(record_or_name_or_array, *args, &proc)
...
case record_or_name_or_array
when String, Symbol
...
when Array
object = record_or_name_or_array.last
object_name = options[:as] || ActiveModel::Naming.singular(object)
apply_form_for_options!(record_or_name_or_array, options)
args.unshift object
else
...
end
...
output << fields_for(object_name, *(args << options), &proc)
...
For form_for, the first parameter can be a record, name or an array. In your case, you pass it an array. The code then determines the 'object' as the last member of that array, which is your @goal.commits.build object. The object name is determined from the ActiveModel::Naming.singular method.
console > ActiveModel::Naming.singular(@goal.commits.build)
=> "commit"
Then it generated the appropriate form fields using fields_for and 'commit'.
It looks like you are using nested resources. Check your routes.rb file to see if you have something like:
map.resources :commits, :has_many => :goals
or perhaps:
map.resources :commits do |commit| commit.resources :goals end
If that is the case, then you will need to supply both the commit and goal objects to the form_for method.
精彩评论