How does form template work in Rails - ruby-on-rails

If we create the default scaffold in Rails, both the edit.html.erb and new.html.erb render the same _form.html.erb within. Both create forms with certain similarities and differences.
Such as:
Both create <form method="post" ...>
The submit buttons have different texts <input value='Create model'
.. and <input value='New model' ..
My questions:
How does the conditional rendering work?
How to display form elements conditionally? E.g., show this
<input> only if it is called via edit.html.erb, but do not show
it if called via new.html.erb.
If the method in q.2 possible, is it the right way? We are reusing
code instead of replicating the form all over again, isn't it?

Assuming you're following RESTful conventions, the differences you see between edit and new are based on the state of the object that you pass to the form. Rails can tell the difference between a new object and one that has been persisted by using the #new_record? method.
Model.new.new_record? # => true
Model.first.new_record? # => false
In your #new controller action, you probably have something like:
#model = Model.new
In your #edit action, you probably have something like:
#model = Mode.find(params[:id])
This #model object is then passed to the form, which handles the conditional logic internally. Another difference in the form you should notice is that the #edit version has a hidden input field that tells the server to use the PUT HTTP method.
Update
It looks like Rails actually uses the persisted? method internally as opposed to new_record?. The difference is that persisted? checks whether the record has been deleted. Otherwise, they are identical (but opposite)

You can do as it:
In new.html.erb:
<%= render :partial =>'form', :locals => {:action => 'new', :f => f } %>
In edit.html.erb:
<%= render :partial =>'form', :locals => {:action => 'edit', :f => f } %>
In _form.html.erb:
if action == 'new'
or
if action == 'edit'
Also you can send other parameters by :locals such as :show_mobile => false.

Related

wrong number of arguments (3 for 2) in form_for method

I'm upgraded to rails 3.2.13 and I'm migrating an old app to run in the new environment (ruby1.9.3). The app ran fine with ruby192 and rails 3.0.0.
I was receiving this error when trying to create a new record (a firefighter)
wrong number of arguments (3 for 2)
And here was my code for my form
<%= form_for :fire_fighter, #fire_fighter, :url => { :action => "create" } do |f| %>
based on reading other posts, they recommend to remove "fire_fighter" but so it would look like this
<%= form_for :#fire_fighter, :url => { :action => "create" } do |f| %>
This did actually allow the page to render but when I tried to enter fill in the text fields and submit or create the record in the database I get an error message that is built into the app that says:
All of the fields are setup as strings.
Oh and obviously i had all this fields filled out before I hit submit. So now I'm just stuck.
Any help would be appreciated thanks.
Although this answer may change when you post your page source code and the rest of the form code, you are trying to create a symbol from an instance variable.
<%= form_for :#fire_fighter, :url => { :action => "create" } do |f| %>
Notice :#fire_fighter. It really should be #fire_fighter. The correct code should then be
<%= form_for #fire_fighter, :url => { :action => "create" } do |f| %>
The reason why you use an instance variable like #fire_fighter is because in your controller there should be something like
def new
#fire_fighter = FireFighter.new
end
that way, the form is directly grabbing the instance variable from the controller onto the form. Symbols don't transverse from controllers to views, but instance variables do, hence the use of #fire_fighter as the first argument in the form_for method.

Rails - custom helper repeating code with fields_for

I have created a custom helper in my application.rb file, which looks like:
module ApplicationHelper
def add_feature_field(feature_type, object_form_builder, actions_visible)
object_form_builder.object.features.build
fields = object_form_builder.fields_for :features do |features_builder|
render :partial => "features/fixed_feature", :locals => {:feature => features_builder, :fixed_feature_type => feature_type, :form_actions_visible => actions_visible}
end
end
end
I am calling this helper from my view like so:
<%= add_feature_field("First Name", customer, false) %>
<%= add_feature_field("Last Name", customer, false) %>
<%= add_feature_field("Date of Birth", customer, false) %>
This is working pretty much as anticipated, except for one major hurdle: the second time the helper is called, it renders 2 fields instead of a single field, and the third time it renders 3 fields.
I assume that what is happening is that the fields_for loop in my helper is picking up the previously built objects, and also rendering those - can anyone suggest a way of preventing this?
EDIT: For clarity, as per the comments, this helper method is being used within the Customer form; the Features being created are nested attributes.

How to pass local variable to partial from application layout in rails 3?

I have a couple of variables that need to be called in all controllers. Displaying latest news in the layout footer.
I create them in application_controller.rb
#hq_news_item = NewsItem.where(:branch_code => "CORP").first
#branch_news_item = NewsItem.where(:branch_code => "MN").first
In my layouts/application.html.haml
= render :partial => "layouts/footer_news" , :hq_news_item => #hq_news_item, :branch_news_item => #branch_news_item
And then in my layouts/_footer_news I style them
= hq_news_item.title
= hq_news_item.author.name
... etc
Here is the thing, no matter what I do - it keeps saying that hq_news_item is undefined in partial.
All my other partials work fine. I think it has to do with the fact that it's a layout not a view. Can't find anything meaningful in the docs.
Any ideas?
Thank you.
I think you need to pass the variables as local variables to the partial:
= render :partial => "layouts/footer_news", :locals => { :hq_news_item => #hq_news_item, :branch_news_item => #branch_news_item }
Otherwise Rails won't really understand what you are passing as a variable to the partial and what you are passing as an argument to the render function.
why don't you just use the member variable #hq_news_item?
I find I use the locals method when I have a local variable inside a view that's calling another.
This code works in rails 2.3.8
<% f.fields_for :member_collection do |builder| %>
<%= render "membrer_collection_fields", :form => builder %>
<% end %>
In this case, I have a partial _member_collection_fields that has local variable form

collection.build of nested attribute through a remote_link does not create params when submitting form

I have the following model:
class Activity < ActiveRecord::Base
has_many :clientships, :dependent => :destroy, :after_add => :default_client_info
accepts_nested_attributes_for :clientships, :allow_destroy => true
end
In my controller, if I perform the following
def new
#activity = IndividualActivity.new(params[:activity])
#activity.clientships.build(:client => Client.first)
...
end
and then save the form, it creates the relevant params and submits successfully.
However, if I chose to call the following through a remote link
#activity.clientships.build(:client => Client.last)
the view is updated with the new clientship record but when I submit the form, the params[:activity] is not created for the second nested attribute. (Why not!?)
This is the view:
%h1 Create a new Activity
- form_for #activity do |f|
%div
= render "activities/client_selector", :f => f
%div
= f.submit "Save!"
Here is the remote_link's controller action
def add_client
#activity = IndividualActivity.new(session[:individual_activity])
# Refresh client
#activity.clientships.build(:client => Client.find(params[:client_id]))
respond_to do |format|
format.js
end
end
This is the add_client.html.js:
page.replace_html "selected_clients", :partial => 'activities/clients'
This is the activities/clients partial:
- form_for #activity do |f|
- f.fields_for :clientships do |client_f|
%tr
%td= client_f.hidden_field :client_id
%td= client_f.object.client.full_name
Does anyone know how I can troubleshoot this further? I seem to have come to a dead-end with my debugging... One thing to note, there is a double use of the following form_for used in new.html.haml and the activities/clients partial (is this problematic?)
- form_for #activity do |f|
I am on rails v2.3.5
Thanks
You ask about debugging, so the first step may be looking at the server log (log/development.log).
There you should see the "params" hash.
Maybe your params contain "activity"=>{"client_id"=>..} instead of "client_id"=>.. ?
Also look at the generated HTML page - use a Firebug or just use a "view source" method of your browser. Look, especially, for input names.
If everything looks OK, put a few debug calls in your action, and look at the development.log for some database activity - do the SQL queries look like they are doing what you want?
In your question there is no 'save' method. The 'build' method does NOT save the created record. Maybe this is your problem?
def add_client
logger.debug "Creating Activity"
#activity = IndividualActivity.new(session[:individual_activity])
logger.debug "Building clientship"
# Refresh client
#activity.clientships.build(:client => Client.find(params[:client_id]))
logger.debug "#activity = #{#activity.inspect}"
# Maybe you were missing this part of code?
logger.debug "Saving #activity"
#activity.save! # use a ! to easily see any problems with saving.
# Remove in production and add a proper if
logger.debug "Saved. #activity = #{#activity.inspect}"
respond_to do |format|
format.js
end
end
You should create a functional test (in case you haven't already) and ensure that if you send proper parameters, your action works as intended.
The test will narrow your search. If the test fails, you know you have a problem in the action. If the test is OK, you need to ensure the parameters are sent properly, and you probably have the problem in your view.
UPDATE:
You said you have TWO forms on the page. This may be the problem, since only one form may be sent at a time. Otherwise it would need to work in a way which can send two requests in one request.
First thing (useful in all similar problems): validate whether your page has correct HTML structure - for example http://validator.w3.org would be a good start. Try to make the code validate. I know that some people treat a "green" status as a unachievable mastery, but just it's really not so hard. With valid code you may be sure that the browser really understands what you mean.
Second: Place all your inputs in a single form. You have problems with nested attributes. For start, try to manually insert inputs with name like <input name="activity[clientship_attributes][0][name]" value="John"/>, and for existing clientships ensure that there is an input with name = activity[clientship_attributes][0][id].
This is the way nested attributes are handled.
Your view may create such fields automagically. This construction should be what you need: (it worked in one of my old project in rails 2.x, I have just replaced the names with ones you use)
<% form_for(#activity) do |f| %>
<p><%= f.text_field :activity_something %></p>
<% #activity.clientships.each do |clientship| %>
<% f.fields_for :clientships, clientship do |cform| %>
<p><%= cform.text_field :name %></p>
<p><%= cform.text_fiels :something %></p>
<% end %>
<% end %>
<% end %>
If you really want to use a partial there, don't create a new form in the partial. Use only the parts of above code.
To pass a variable to the partial, use :locals attribute in the place where you call render :partial:
<%= render :partial => 'clientship', :locals => {:form => f} %>
Then, in your partial, you may use a local variable form where you would use f outside of the partial. You may, of course, map the variables to the same name: :locals => {:f => f}

collection counter in rails partials

I'm rendering a partial in a collection like this :
<%= render :partial => 'issues/issue', :collection => #issues %>
Inside the partial, I want to render a element unless it's the last in the collection. I could of course, render the partial like this
<%= render :partial => 'issues/issue', :collection => #issues, :locals => {:issue_count => #issues.length } %>
then put this inside my partial
<% unless issue_counter + 1 == issue_count %>
<hr />
<% end %>
but I don't want to have to explicitly set the local in the render call, and I the collection isn't always going to be called #issues, so I can't just access the instance varibale. Is there some way to access the length of the collection automatically inside the partial to tell where in the collection the object falls? If there's not already, is it possible to add this in such a way that I'll automatically get the issue_count local? Any help on this will be much appreciated.
Thx,
-C
You can supply the :spacer_template option to your render :partial => X, :collection => Y call. See the ActionController::Base documenation for usage.
It probably feels heavy-handed to specify an entire partial file for a simple <hr /> element, but going this route keeps your intention clear and keeps the item partial free of unrelated divider markup.
I think you can make this happen if you modify the render_partial_collection method.
As a side note: Seems to me like you should instead use CSS on a unordered list: I get the feeling you are inserting markup HRs to style instead of using it to semantically separate items in the collection (in mark-up semantics).

Resources