Rails - custom helper repeating code with fields_for - ruby-on-rails

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.

Related

How does form template work in 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.

Where to put code of "smart request" do database in rails application?

I have something like this:
in my view:
<%= render :partial => 'show', :locals => { :employee => flash[:selection][:employee] } %>
in my partial 'show':
<p>
<b>Manager:</b>
<%= Employee.find(employee.employee_id).first_name %> <%= Employee.find(employee.employee_id).last_name %>
</p>
Question:
I feel like it's not good approach to ask something from model (like: Employee.find(employee.employee_id).first_name) in the partial... And seems like in a view also... Is it right?
Where should I put such code of "smart request" to database and how then should I call it from partial?
UPD1
I did something like you advised previously...
In my controller before rendering that view (which consequently will render partial), I find this employee and put it into flash[:selection][:employee]...
This employee has much columns, like: first_name, last_name, ..., and finally employee_id. The latter employee_id is the manager of current employee (flash[:selection][:employee]).
I want to show all employee's fields in my partial. So I passed flash[:selection][:employee] like employee to the partial, then show there all stuff like: employee.first_name, ... And finally i want to show this employee.employee_id not like integer id, but actually find in database correspondent employee and put its first and last names...
Ok before rendering my view, i can set not only #employee, but also its #manager, and then pass these values to my partial...
But... this seems a bit "too much done just to show in one place this #manager's first and last names...
Am i right? or this is common usage in rails, isn't it?
I would put it in the controller that is rendering that view. Initialize an instance variable in your controller action and this will be accessible from your view and partials. Something like this:
#employee = Employee.find(...)
In recent version of Rails, passing that instance variable to a partial would be as easy as:
<%= render 'show', :employee => #employee %>
Then in your partial, you would do:
<p>
<b>Manager:</b>
<%= employee.first_name %> <%= employee.last_name %>
</p>
You are correct. You shouldn't be using finders in views. Initialize the variable that the view needs to work with in the controller.
http://guides.rubyonrails.org/action_controller_overview.html
Thanks to all, I solved the problem, providing the following association:
class Employee < ActiveRecord::Base
#...
belongs_to :manager, :class_name => "Employee", :foreign_key => "employee_id"
end
and then make the correspondent call from partial:
employee.manager.first_name

rails partial or helper

I'm programming a large application using Rails 3 and I keep creating search forms like so:
= form_tag search_companies_path, :method => "get" do
= label_tag :search
= text_field_tag :search
= submit_tag "Search"
Should this be put into a Helper method or Partial?
I tried to get it working through a Helper:
module ApplicationHelper
def search_form(path)
form_tag path, :method => "get" do
label_tag :search
text_field_tag :search
submit_tag "Search"
end
end
end
This creates a form with a button, am I on the right track here?
Putting it into a helper - in my humble opinion - is not a good practice since helpers are supposed to take code out of views, not to take views excerpts - which is the case for partials.
I would definitely use partials for this function!
If you want to share a partial between different parts of your application, you can store them in a folder called "shared" (or whatever name you like) and insert them into the view by calling render :partial => '/shared/name_of_the_partial'.
i think that partial is a more manutenible way to accomplish that task, because it's easier updating the code and also because you should save memory because the application helper is include in all helper.
You can create that partial on shared, naming it _search.html.erb.

Rails: Restoring contents of non-model form that uses form_tag

I'm making good progress with my first Rails app (using Rails 3). The MVC interaction is all going fine, but I'm having difficulty with a form that doesn't relate directly to a model.
I'm using form_tag, and in the case of success, everything behaves fine. However, the handling of errors is somewhat unfriendly. The actual error message is stored in the flash and displayed fine by layouts/application.html, but I'd really like it if the form could remember the contents that the user had just filled in. But it doesn't: all the fields reset to their default values.
I love the way that forms for RESTful actions on objects automatically remember their submitted values, and get highlighted in red if there are errors. I'm fine without the red highlight, but I'd really like it if I could make the form's fields keep the submitted values.
Can anyone advise how to do this?
Excerpts from the relevant files:
views/cardsets/import.html.erb:
<%= form_tag :action => :import_data, :id => #cardset do %>
...
<%= text_field_tag "separator", "", :maxlength => 1 %>
...
<%= text_field_tag "formatting_line" %>
...etc (more fields)
controllers/cardsets_controller.rb:
# POST /cardsets/1/import_data
def import_data
success, message = #cardset.import_data(params, current_user)
if success
redirect_to(#cardset, :notice => message)
else
flash.now[:error] = message
render :import
end
end
The second arg to text_field_tag is the value to fill in the field with. Try something like this:
<%= text_field_tag "separator", params[:separator], :maxlength => 1 %>
If your field has a default, you will want to set it from the "show" action for the form:
# GET
def show_form
params[:key] = 'default'
end
# POST
def validate_form_and_act
# Don't set it here to reuse what the user passed.
end
or directly on the template (less good because uses an || every time and adds more controller data to view):
<%= text_field_tag 'name', params[:key] || 'default' %>

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}

Resources