Passing blocks into partials - ruby-on-rails

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

Related

Passing local variable into partial returns NameError

I am trying to pass the index of something into a partial and am getting a NameError.
Right now, this is my render statement and I am able to access builder just fine.
<%= render 'my_partial', :builder => form_helper %>
But when adding index like below, I get the error.
<%= render 'my_partial', :builder => form_helper, :locals => {:index => index } %>
Any thoughts?
Thanks!
edit:
While trying
<%= render :partial => 'my_partial', :locals => {:builder => form_helper, :index => index } %>
The specific error is "undefined local variable or method `index' for #<#:0x007fc37206ae40>"
Either:
<%= render 'my_partial', :builder => form_helper, :index => index %>
Or:
<%= render :partial => 'my_partial', :locals => {:index => index, :builder => form_helper } %>

put a partial inside a content_tag in Application Helper Function Rails

I have this in my view but I would like to clean it up and put it all inside a method in a helper.
I know how to put div and some content inside a content_tag. However how do I pass the partial?
<% if show_content?(flash[:invitation]) %>
<div id="invite_box">
<%= render :partial => 'user/invite', :locals => {:user => #user } %>
</div>
<% elsif show_content?(flash[:confirmation]) %>
<%= render :partial => 'user/invite_confirmation' %>
<% end %>
If the invite partial should always be wrapped in an invite_box div, it would make sense to just put that div inside the partial.
However, you can do this in your helper:
def show_invite_info
if show_content?(flash[:invitation])
content_tag(:div, :id => "invite_box") do
render :partial => "user/invite", :locals => {:user => #user}
end
elsif show_content?(flash[:confirmation])
render :partial => "user/invite_confirmation"
end
end
Then in your view just do:
<%= show_invite_info %>

Why do we need to pass :object to partials?

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

Sortable_element with RJS does not working

I have a list of images where user can arrange their orders.
When user uploaded an image, I want the list to still be sortable.
I am using a similar upload that was described here: http://kpumuk.info/ruby-on-rails/in-place-file-upload-with-ruby-on-rails/
Please help.
Here are the code for upload in view file:
<% form_for [:admin, #new_image], :html => { :target => 'upload_frame', :multipart => true } do |f| %>
<%= hidden_field_tag :update, 'product_images'%>
<%= f.hidden_field :image_owner_id %>
<%= f.hidden_field :image_owner_type %>
<%= f.file_field :image_file %><br />
or get image from this URL: <%= f.text_field :image_file_url %>
<%= f.hidden_field :image_file_temp %><br />
<%= f.submit "Upload Image" %>
<% end %>
And in controller view:
def create
#image = Image.new(params[:image])
logger.debug "params are #{params.inspect}"
if #image.save
logger.debug "initiating javascript now"
responds_to_parent do
render :update do |page|
logger.debug "javascript test #{sortable_element("product_images", :url => sort_admin_images_path, :handle => "handle", :constraint => false)}"
page << "show_notification('Image Uploaded');"
page.replace_html params[:update], :partial => '/admin/shared/editor/images', :locals => {:object => #image.image_owner, :updated_image => #image}
page << sortable_element("product_images", :url => sort_admin_images_path, :handle => "handle", :constraint => false)
end
end
#render :partial => '/admin/shared/editor/images', :locals => {:object => #image.image_owner, :updated_image => #image}
else
responds_to_parent do
render :update do |page|
page << "show_notification('Image Upload Error');"
end
end
end
end
Or, to rephrase the question:
Running this:
page.replace_html params[:update], :partial => '/admin/shared/editor/images', :locals => {:object => #image.image_owner, :updated_image => #image}
page << sortable_element("product_images", :url => sort_admin_images_path, :handle => "handle", :constraint => false)
Will NOT adding sortable list feature.
Please help,
Thank you
I think the problem may well be that the sortable is created when the page first loads. I have run into this issue a couple of times. Adding a new element to the list means you have to call the sortable function again to have the new element included in the sorting.
I am not across prototype/scriptaculous, but there may be a way to have the sortable element listen for events that add elements to the list. jQuery has a set of rebinding events that will respond to new elements added to the DOM, might be something simialr in the other libs.
Found it!
Instead of:
page.replace_html params[:update], :partial => '/admin/shared/editor/images', :locals => {:object => #image.image_owner, :updated_image => #image}
Use
page.replace params[:update], :partial => '/admin/shared/editor/images', :locals => {:object => #image.image_owner, :updated_image => #image}

testing (rspec) nested model partials in rails 2.3+

With the 2.3.x+ rails feature of nested models, I think I need to have access to a form builder instance to properly spec partials for rendering the nested models. Pulling from the complex-forms-examples:
For example, here is an enclosing form that creates and passes the form builder to the nested model rendering view:
<div class="children_fields tasks" id="<%= dom_id(f.object) %>_tasks"
data-context="<%= f.object_name %>">
<% f.fields_for :tasks do |task_form| %>
<%= render :partial => 'task', :locals => { :f => task_form } %>
<% end %>
</div>
The task partial is:
<%= f.label :name, "Task" %>
<%= f.text_field :name %>
<%= remove_child_link "remove", '#', f %>
<div class="children_fields assignments" id="<%= dom_id(f.object) %>_assignments"
data-context="<%= f.object_name %>">
<% f.fields_for :assignments do |assignment_form| %>
<%= render :partial => 'assignment', :locals => { :f => assignment_form } %>
<% end %>
</div>
When I try to spec a partial like this, I tried to catch the form builder as an instance var by doing:
before(:each) do
... # setup for target object
form_for [:foo, :bar, #project] do |f|
#f = f
end
end
This raises an error on the use of form_for
undefined method `polymorphic_path' for #<Spec::Rails::Example::ViewExampleGroup::Subclass_1:0x2cfeafc>
/Users/adamaig/.rvm/gems/ruby/1.8.7/gems/actionpack-2.3.5/lib/action_controller/test_process.rb:511:in `method_missing'
/Users/adamaig/.rvm/gems/ruby/1.8.7/gems/actionpack-2.3.5/lib/action_view/helpers/form_helper.rb:298:in `apply_form_for_options!'
/Users/adamaig/.rvm/gems/ruby/1.8.7/gems/actionpack-2.3.5/lib/action_view/helpers/form_helper.rb:272:in `form_for'
So, What is the right way to:
1) get a FormBuilder instance for specs like this?
2) spec nested models and their view forms?
This matters for the proper name generation.
Solution is:
describe 'tasks/_form' do
attr_accessor :output_buffer
before do
task = mock_model(Task, :mumbo_jumbo => 'foo bar')
#output_buffer = ''
form_for(task, :url => task_path(task)) do |f|
#f = f
end
end
it 'should have input mumbo_jumbo' do
render_partial_with_locals
response.should have_tag('input[name=?]', 'task[mumbo_jumbo]')
end
private
def render_partial_with_locals
render :partial => 'tasks/form', :locals => {:f => #f}
end
def protect_against_forgery?
false
end
end

Resources