testing (rspec) nested model partials in rails 2.3+ - ruby-on-rails

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

Related

Rails form - multiple nested routes undefined method '_path'

This app has the following models:
Farm (has_many :crops)
Crop (belongs_to :farm, has_many :issues)
Issue (belongs_to :crop)
Here are the routes:
resources :farms do
resources :crops do
resources :issues
end
end
I want a user to be able to create a new "issue" from the Farm#show page that lists all the farm's crops. Here is the form that is causing the error on the Farm#show page:
undefined method `crop_issues_path' for #<#:0x007fa814a3cc30>
#from the show action on the controller:
##farm = Farm.find(params[:id])
##crops = #farm.crops
<% #crops.each do |crop| %>
<%= crop.id %>
<%= form_for([crop, crop.issues.build]) do |f| %>
<%= f.select(:issue_type, options_for_select([['mold'], ['pests'], ['dehydration'], ['other']])) %>
<%= f.text_area :notes %><br>
<%= f.submit "New Issue", :class => "button" %>
<% end %>
<% end %>
My create action on issues controller:
def create
#crop = Crop.find(params[:crop_id])
#issues = #crop.issues.create(params[:issue].permit(:issue_type, :notes, :crop_id))
redirect_to :back
end
I have used nearly identical code when the crops and issues were not nested under farms, and it works. I believe the issue is because of the nesting, but cannot figure out a solution.
I think your problem is with the object you're binging the form to. It should be #farm, as you're in the #farms show action.
I modified it to this:
<% #crops.each do |crop| %>
<%= crop.id %>
<%= form_for([#farm, crop, crop.issues.build]) do |f| %>
<%= f.text_area :notes %><br>
<%= f.submit "New Issue", :class => "button" %>
<% end %>
<% end %>
with my controller like this:
class FarmsController < ApplicationController
def index
end
def show
#farm = Farm.find_by_id(params[:id])
#crops = #farm.try(:crops)
end
end

Remove Paperclip Attachment from Nested Attribute (Cocoon)

I have an INCIDENT with an attached WITNESS.
I am trying to show a link to remove an attachment from a nested attribute, but my link is pulling the :id of the parent record (invoice.id) instead of the nested/child record (invoice.witness_id).
I know I'm doing something wrong in my routes or in calling the correct id number from the controller or view... any help is appreciated!
incident.rb
has_many :witnesses
accepts_nested_attributes_for :witnesses, :reject_if => :all_blank, :allow_destroy => true
witness.rb
belongs_to :incident
has_attached_file :statement
routes.rb
match 'witness/:id' => 'witnesses#remove_statement', via: [:get, :post], as: 'remove_statement'
witnesses_controller
def index
#witnesses = #incident.witnesses.all
end
def remove_statement
#witness = Witness.find(params[:id])
#witness.statement = nil
respond_to do |format|
if #witness.save
format.html { redirect_to :back, notice: 'Attachment was removed.' }
format.json { head :no_content }
else
format.html { redirect_to :back, error: 'Attachment could not be removed.' }
format.json { render json: #witness.errors, status: :unprocessable_entity }
end
end
end
private
def set_witness
#witness = #incident.witnesses.find(params[:id])
end
def witness_params
params[:witness].permit(:first_name, :last_name, :phone, :email, :statement, :incident_id)
end
_witness_fields partial
<div class="nested-fields">
<div class="form-group">
....
<%= link_to "Remove Attachment", remove_statement_path, :id => :witness_id %>
...
incidents/_form.html.erb
<%= form_for(#incident, html: { :multipart => true , class: 'form-horizontal' }) do |f| %>
<%= f.error_notification %>
<% if #incident.errors.any? %>
<div class="red">
<% #incident.errors.full_messages.each do |msg| %>
<%= msg %><hr>
<% end %>
</div>
<% end %>
.....
<!-- WITNESS SECTION -->
<div class="span6">
<hr>
<fieldset id="witnesses">
<%= f.fields_for :witnesses do |builder| %>
<%= render 'witness_fields', :f => builder %>
<% end %>
</fieldset>
<p class="links">
<%= link_to_add_association 'Add Witness/Contact', f, :witnesses, { class:"btn btn-primary" } %>
</p>
</div>
</div>
<!-- END WITNESSES SECTION -->
.....
In your _withness_fields partial, you write
<%= link_to "Remove Attachment", remove_statement_path, :id => :witness_id %>
That should be something like
<%= link_to "Remove Attachment", remove_statement_path(f.object.id) %>
So two things: the path helper remove_statement_path needs the id as a parameter, and secondly, you need to actually give it the correct id of the object for which you are currently rendering.
Please note, since you dynamically add these, for new records this will not be valid (since there is no idea).
So you will have to check if the record is a new_record? and only show that link if it is not (because then you will have a valid id). If it is not a new record, you can just use the cocoon helper to remove it.

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

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 %>

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 :)

Resources