Why do we need to pass :object to partials? - ruby-on-rails

I have a model Category has_many :subcategories
I am using code to populate value of subcategories drop down according to value of categories drop down in a view using Ajax. Here is the code:
_form.html.erb
<%= f.select :category_id, #categories, {}, :tab_index => 4, :onchange => "#{remote_function(:url => {:action => "populate_subcategories"},
:with => "'category_id='+value")}" %>
<div id = "subcategories_div">
<%= render :partial => "subcategories", :object => #subcategories %>
</div>
deals_controller.rb
def new
#deal = Deal.new
#categories = Category.all.map { |category| [category.name, category.id]}
#subcategories = #categories.first.subcategories.map { |subcategory| [subcategory.name, subcategory.id] }
end
def populate_subcategories
subcategories = Subcategory.where(:category_id => params[:category_id]).map { |subcategory| [subcategory.name, subcategory.id] }
render :update do |page|
page.replace_html 'subcategories_div', :partial => 'subcatgories', :object => subcategories
end
end
and finally _subcategories.html.erb
<%= f.select :subcategory_id, subcategories, {}, :tab_index => 5 %>
My question is, in the code page.replace_html 'subcategories_div', :partial => 'subcatgories', :object => subcategories why are we defining subcategories as local variable and passing it as an object to the partial? We could have written like this
def populate_subcategories
#subcategories = Subcategory.where(:category_id => params[:category_id]).map { |subcategory| [subcategory.name, subcategory.id] }
render :update do |page|
page.replace_html 'subcategories_div', :partial => 'subcategories'
end
end
use #subcategories as the instance variable so that it is available in the partial as in the case of normal views in Rails.
Also in the _subcategories.html.erb
<%= f.select :subcategory_id, #subcategories, {}, :tab_index => 5 %>
and in _form.html.erb
<div id = "subcategories_div">
<%= render :partial => "subcategories" %>
</div>
Why is first method preferred over the second one? Is it because we have only one variable to pass to the partial? Is there any performance improvement for first method?

The reason is that you want your partials to be code-independent from your views. By using #subcategories you create a dependency between your partial and your view that is unnecessary. It means that to use that same partial again you must ensure that you have #subcategories defined, and that might not be the case. For example, you might only have #my_subcategories, or #filtered_subcategories defined. Even worse, you might have #subcategories and #filtered_subcategories and you might want to display both using the same partial. In this case it's better to be able to pass your subcategories to the partial instead of depending on instance variables being set. That way your partials are really modular and conform to the object oriented principle of encapsulation.
For example:
<h1><%= My Subcategories %></h1>
render :partial => 'subcatgories', :object => #my_subcategories
<h1><%= Other Subcategories %></h1>
render :partial => 'subcatgories', :object => #other_subcategories

you can gracefully name your ivars in partials,
render 'subcatgories', :subcategories => #hashtags
or
render 'subcatgories', :subcategories => #legals
then you can use subcategories in your partial, using ivar as like its name pretends to be
go and see ActionView::Rendering#render
there is longer params form to achieve the same behavior ... (:partial => 'subcatgories', :locals => {:subcategories => #hashtags})
I believe :subcategories => #hashtags has superior readability over :object => #hashtags

Related

Passing blocks into partials

I would like to render partials into a view based on conditions. At first I have this view which renders well
<%= simple_form_for [#customer, #reading], :html => { :class => 'form-horizontal' } do |f| %>
<%= f.input :customer_id, :editable => false, :value => #customer.id %>
<%= f.input :date_of_reading, :as => :date %>
<%= render 'readings/single_phase', f: f %>
<% end %>
I now want to render partials into the view based on conditions. So i create a helper method in application.rb to do the conditional checking
module ApplicationHelper
def render_readings_conditionally
if #customer.phase_type == 'Single Phase'
render :partial => 'readings/single_phase'
else
render :partial => 'readings/three_phase'
end
end
end
And in my view, I fix the method in there
<%= simple_form_for [#customer, #reading], :html => { :class => 'form-horizontal' } do |f| %>
<%= f.input :customer_id, :editable => false, :value => #customer.id %>
<%= f.input :date_of_reading, :as => :date %>
<%= render_readings_conditionally %>
<% end %>
However, this will not work because I have not passed the block argument i had earlier on to my partials.
How can i pass it to my partials?
Just rewrite it this way :
def render_readings_conditionally(form)
if #customer.phase_type == 'Single Phase'
render 'readings/single_phase', f: form
else
render 'readings/three_phase', f: form
end
end
And in your view :
<%= render_readings_conditionally(f) %>
Have a look at view partial locals :: http://guides.rubyonrails.org/layouts_and_rendering.html#passing-local-variables
You can pass any number of variables using locals.
The answer above is passing view locals, although they are being defined shorthand (not explicitly).
You could re-write the code like this, which makes it more clear which variables are being passed to your view partials. You can then access the "form_obj" object in your partial ::
module ApplicationHelper
def render_readings_conditionally
if #customer.phase_type == 'Single Phase'
render :partial => 'readings/single_phase', :locals => { :form_obj => form }
else
render :partial => 'readings/three_phase', :locals => { :form_obj => form }
end
end

Rails render a partial with collection twice?

I have this:
<%= render :partial => "rewards", :collection => #rewards, :as => :reward %>
when I count the items in the collection with <%= #rewards.count %>, it shows 1, and I check ed the db that there is only one reward. However, the partial is rendered twice in my master view, with the second one empty (reward == nil). Any clue on this?
Updates:
Found the reason, before this render statement, there is a user.rewards.build statement for creating a form:
<%= render :partial => "form_reward", :locals => {:user => #user, :reward => #user.rewards.build } %>
<%= render :partial => "rewards", :collection => #rewards, :as => :reward, :locals => {:user => #user } %>
somehow the partial rendered this object also! I guess this is because name pollution. Now how to overcome this problem then?
<%= render :partial => 'rewards', :object => #rewards, :as => :reward %>
You should be able to access it as reward to test try this:
<%= reward.inspect %>
Ohh and of cause if it should be repeatet you should pass it by as a :collection instead of :object like its in my example
I would suggest you to check the query where the #rewards is been populated. It seems like there are two objects in the #reward collection one Reward object and a nil object.
You may use the compact method of the collection to remove the nil object.

How do you pass options through a fields_for?

I am trying to run this..
- f.fields_for :referrals do |qf|
But I would like to pass this, #organization.referrals.select{|ref|ref.new_record?} as well. This is so that the forms passed are exclusively new objects, and not older ones.
I've tried to do this..
- f.fields_for :referrals do |qf|
- if qf.object.new_record?
= render :partial => 'referral_fields', :locals => {:qf => qf}
Which makes it display correctly in the view, but the params are still populated with every single previously created nested object.
Which leads me to believe that I need to pass this option within the fields_for statement itself.
I have also tried this :
- f.fields_for #organization.referrals.select{|ref|ref.new_record?} do |qf|
= render :partial => 'referral_fields', :locals => {:qf => qf}
As well as this :
- f.fields_for :referrals, #organization.referrals.select{|ref|ref.new_record?} do |qf|
= render :partial => 'referral_fields', :locals => {:qf => qf}
The first example will show and only allows to pass one object. Where as my form is to allow a dynamic number of duplicate nested forms.
The second will display and pass all nested objects
App Info
#organization.rb
has_many :referrals
accepts_nested_attributes_for :referrals, :reject_if => proc { |attributes| attributes.all? { |k, v| v.blank? } }, :allow_destroy => true
#referral.rb
belongs_to :organization
#referrals_controller.rb
def new
2.times { #organization.referrals.build }
....
def create
#referral = Referral.new(params[:referral])
if #referral.valid? && #organization.referrals << #referral
flash[:notice] = "Referrals saved."
redirect_to new_organization_referrals_path(#organization)
else
render :action => :new, :layout => 'manage'
end
end
Here's what you want:
- f.fields_for :referrals, #organization.referrals.select{|ref|ref.new_record?} do |qf|
= render :partial => 'referral_fields', :locals => {:qf => qf}
The first parameter is the association name, which rails needs in order to know how to structure the params. If your first parameter is a collection, rails can usually infer the association name from that collection.
Your collection however, has been filtered into a regular array, where the association can't be as easily inferred. So you pass the specific collection as the second parameter.
Good luck!
UPDATE
I've built out a small rails app to analyze the problem, and the solution above is working just fine for me - the edit form doesn't display existing referrals, only new ones. I'll post the relevant code, so we can see where you and I might differ. One caveat, this is all in erb since I rarely work with haml and wouldn't want a typo to mess up the solution :)
My models:
# app/models/organization.rb
class Organization < ActiveRecord::Base
has_many :referrals
accepts_nested_attributes_for :referrals
end
# app/models/referral.rb
class Referral < ActiveRecord::Base
belongs_to :organization
end
My controller's edit action:
# app/controllers/organizations_controller.rb
class OrganizationsController < ApplicationController
def edit
#organization = Organization.find(params[:id])
2.times { #organization.referrals.build }
end
end
My views:
# app/views/organizations/edit.html.erb
<h1>Editing <%= #organization.name %></h1>
<% form_for(#organization) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<% f.fields_for :referrals, #organization.referrals.select{|ref| ref.new_record?} do |referral_fields| %>
<%= render :partial => 'referral', :locals => {:f => referral_fields} %>
<% end %>
<p>
<%= f.submit %>
</p>
<% end %>
# app/views/organizations/_referral.html.erb
<p>
<%= f.label :name, 'Referral Name' %><br />
<%= f.text_field :name %>
</p>
Of course, I just read your new comments, and maybe you don't need this anymore. Oh well, more documentation for posterity :)

Rails 2 - partials: what does #comment = Comment.new mean?

I am working through a tutorial with the following code:
<h3>New Comment</h3>
<%= render :partial => #comment = Comment.new,
:locals => { :button_name => "Create" } %>
I believe that 'render :partial => #comment' works like 'render :partial => "comment", :object => #comment'
Where does ' = Comment.new' fit in?
Is it shorthand for :object?
Alan
In Ruby terms,
#obj = Object.new # returns #obj
So you're rendering a comment partial and creating a new comment object that it can work with at the same time.
See http://apidock.com/rails/ActionView/Partials section "Rendering objects with the RecordIdentifier":
# <%= render :partial => "accounts/account", :locals => { :account => #buyer } %>
<%= render :partial => #account %>
Though documented, this is hardly used. The new+assignation (as explained by aharon) works, but it seems a bit tricky. In a tutorial you would expect to find a more orthodox approach:
Create objects in controllers not in views.
Use render :partial => 'mypartial', :locals => {...}

How can I dynamically call the named route in a :partial in rails?

I have the following partial. It can be called from three different times in a view as follows:
<%= render :partial => "contact_event",
:collection => #contacts,
:locals => {:event => email} %>
Second time:
<%= render :partial => "contact_event",
:collection => #contacts,
:locals => {:event => call} %>
Third time:
<%= render :partial => "contact_event",
:collection => #contacts,
:locals => {:event => letter} %>
In each instance, call, email, letter refer to a specific instance of a Model Call, Email, or Letter.
Here is what I tried to do and conceptually what I'd like to do: assign the route based on the class name that has been passed to the :event from the :partial.
What I did was create what the actual url should be. The 'text' of it is correct, but doesn't seem to recognize it as a named route.
<!-- provide a link to skip this item -->
<% url = "skip_contact_#{event.class.name.tableize.singularize}_url" %>
<%= link_to_remote "Skip #{url} Remote",
:url => send("#{url}(contact_event, event)")
:update => "update-area-#{contact_event.id}-#{event.id}" %>
<span id='update-area-<%="#{contact_event.id}-#{event.id}"%>'> </span>
The result of the above: when event has been passed an email instance, for example, it says:
skip_contact_email_url not a method.
The url is right, but it doesn't recognize as a method.
How can I dynamically define skip_contact_email_url to be skip_contact_letter_url if the local variable is letter?
Even better, how can I have a single named route that would do the appropriate action?
You can use polymorphic_url. It generates corresponding route based on item types:
Edit: The route is generated based on record's class, so if you pass :event => call or :event => email, it will work like this:
# event.class == Email
polymorphic_url([contact_event, event], :action => :skip)
#=> /contact_events/:contact_event_id/emails/:id/skip
# event.class == Call
polymorphic_url([contact_event, event], :action => :skip)
#=> /contact_events/:contact_event_id/calls/:id/skip
etc.
Edit2:
Routes:
map.resources :contacts do |contact|
contact.with_options :member => {:skip => : ... [get/post - what you have] } do |c|
c.resources :letter
c.resources :emails
c.resources :calls
end
end

Resources