members/new.html.erb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<%= f.text_field :first_name, :size => 15 %> <%= f.text_field :last_name, :size => 15 %> <div id="phones">Phone Numbers <%= render :partial => 'phone', :collection => @member.phones %> </div> <%= add_another_link "phone" %><br><br> <div id="emails">Email Addresses <%= render :partial => 'email', :collection => @ member.emails %> </div> <%= add_another_link 'email' %><br><br> <div id="locations">Locations <%= render :partial => 'location', :collection => @ member.locations %> </div> <%= add_another_link 'location' %><br><br>
volunteers/new.html.erb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<%= f.text_field :first_name, :size => 15 %> <%= f.text_field :last_name, :size => 15 %> <div id="phones">Phone Numbers <%= render :partial => 'phone', :collection => @ volunteer.phones %> </div> <%= add_another_link "phone" %><br><br> <div id="emails">Email Addresses <%= render :partial => 'email', :collection => @ volunteer.emails %> </div> <%= add_another_link 'email' %><br><br> <div id="locations">Locations <%= render :partial => 'location', :collection => @ volunteer.locations %> </div> <%= add_another_link 'location' %><br><br>
sponsors/new.html.erb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<%= f.text_field :first_name, :size => 15 %> <%= f.text_field :last_name, :size => 15 %> <div id="phones">Phone Numbers <%= render :partial => 'phone', :collection => @sponsor.phones %> </div> <%= add_another_link "phone" %><br><br> <div id="emails">Email Addresses <%= render :partial => 'email', :collection => @ sponsor.emails %> </div> <%= add_another_link 'email' %><br><br> <div id="locations">Locations <%= render :partial => 'location', :collection => @ sponsor.locations %> </div> <%= add_another_link 'location' %><br><br>
_phone.html.erb
1 2 3 4 5 6 7
<div class="phone"> <% fields_for "member[phone_attributes][]", phone do |p| %> <%= p.text_field :number, :index => nil %> <%= p.select :contact_type, Member::Contact_types, {}, :index => nil %> <% end %> </div>
_email.html.erb
1 2 3 4 5 6 7
<div class="email"> <% fields_for "member[email_attributes][]", email do |e| %> <%= e.text_field :address, :index => nil %> <%= e.select :contact_type, Member::Contact_types, {}, :index => nil %> <% end %> </div>
_location.html.erb
1 2 3 4 5 6
<div class="location"> <% fields_for "member[location_attributes][]", location do |l| %> <%= l.text_field :address, :index => nil %> <%= l.select :contact_type, Member::Contact_types, {}, :index => nil %> <% end %> </div>
Refactorings
No refactoring yet !
Kevin Ansfield
October 25, 2007, October 25, 2007 18:30, permalink
Whenever I've had partials that are the same across multiple models, I've just created an app/views/shared folder and put the partials in there. You can then reference the partials with 'shared/phone', 'shared/email', etc.
members/new.html.erb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<%= f.text_field :first_name, :size => 15 %> <%= f.text_field :last_name, :size => 15 %> <div id="phones">Phone Numbers <%= render :partial => 'shared/phone', :collection => @member.phones %> </div> <%= add_another_link "phone" %><br><br> <div id="emails">Email Addresses <%= render :partial => 'shared/email', :collection => @ member.emails %> </div> <%= add_another_link 'email' %><br><br> <div id="locations">Locations <%= render :partial => 'shared/location', :collection => @ member.locations %> </div> <%= add_another_link 'location' %><br><br>
volunteers/new.html.erb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<%= f.text_field :first_name, :size => 15 %> <%= f.text_field :last_name, :size => 15 %> <div id="phones">Phone Numbers <%= render :partial => 'shared/phone', :collection => @ volunteer.phones %> </div> <%= add_another_link "phone" %><br><br> <div id="emails">Email Addresses <%= render :partial => 'shared/email', :collection => @ volunteer.emails %> </div> <%= add_another_link 'email' %><br><br> <div id="locations">Locations <%= render :partial => 'shared/location', :collection => @ volunteer.locations %> </div> <%= add_another_link 'location' %><br><br>
sponsors/new.html.erb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<%= f.text_field :first_name, :size => 15 %> <%= f.text_field :last_name, :size => 15 %> <div id="phones">Phone Numbers <%= render :partial => 'shared/phone', :collection => @sponsor.phones %> </div> <%= add_another_link "phone" %><br><br> <div id="emails">Email Addresses <%= render :partial => 'shared/email', :collection => @ sponsor.emails %> </div> <%= add_another_link 'email' %><br><br> <div id="locations">Locations <%= render :partial => 'shared/location', :collection => @ sponsor.locations %> </div> <%= add_another_link 'location' %><br><br>
shared/_phone.html.erb
1 2 3 4 5 6
<div class="phone"> <% fields_for "member[phone_attributes][]", phone do |p| %> <%= p.text_field :number, :index => nil %> <%= p.select :contact_type, Member::Contact_types, {}, :index => nil %> <% end %> </div>
shared/_email.html.erb
1 2 3 4 5 6
<div class="email"> <% fields_for "member[email_attributes][]", email do |e| %> <%= e.text_field :address, :index => nil %> <%= e.select :contact_type, Member::Contact_types, {}, :index => nil %> <% end %> </div>
shared/_location.html.erb
1 2 3 4 5 6
<div class="location"> <% fields_for "member[location_attributes][]", location do |l| %> <%= l.text_field :address, :index => nil %> <%= l.select :contact_type, Member::Contact_types, {}, :index => nil %> <% end %> </div>
Etandrib
October 26, 2007, October 26, 2007 20:30, permalink
I also had to change the partial for adding a new model (location, email, etc.) you have to have a slash before shared or rails thinks that it is just the name of the partial. This is different than what the render => :partial syntax wants. I found this out by a proposed change in the Rails code. I'm also using Rails 2.0 Preview.
ApplicationHelper
1 2 3 4 5 6 7 8
def add_another(model_name, label = "Add another") link_to_function label do |page| page.insert_html :bottom, model_name.pluralize, :partial => "/shared/#{model_name}", :object => model_name.camelize.constantize.new end end
rpheath
October 26, 2007, October 26, 2007 22:33, permalink
You could probably also do some sort of block helper to avoid the redundancy of explaining the div and your "add_another_link" helper over and over. This is untested, but I believe it should be pretty close if not correct...
sponsors/new.html.erb
1 2 3 4 5 6 7
<%= f.text_field :first_name, :size => 15 %> <%= f.text_field :last_name, :size => 15 %> <% list_sponsors ['member','volunteer','sponsor'] do |partial, model| %> <%= render :partial => "shared/#{partial}", :collection => model.send("#{partial.pluralize}") -%> <%= add_another_link partial -%> <% end -%>
application_helper.rb
1 2 3 4 5 6 7 8 9 10
def list_sponsors(models, &block) partials = %w(phone email location) models.each do |model| 3.times do |i| concat("<div class='#{partials[i].pluralize}'>", block.binding) yield partials[i], instance_variable_get("@#{model}") concat("</div>", block.binding) end end end
Etandrib
October 30, 2007, October 30, 2007 20:41, permalink
A problem with the solution Kevin presented is that the partial also includes the model name and if you are sharing the partials with other models then you'll need to dynamically swap out the model - in this case below it is member. I'm not sure how to do this. Any suggestions?
views/shared/_email
1 2 3 4 5 6
<div class="phone"> <% fields_for "member[phone_attributes][]", phone do |p| %> <%= p.text_field :number, :index => nil %> <%= p.select :contact_type, Member::Contact_types, {}, :index => nil %> <% end %> </div>
I have three classes of people: Members, Volunteers, and Sponsors. They each can have multiple phones, emails, and locations. Right now I have a partial for each phone/email/location for each class. This doesn't seem very DRY since nothing changes except the model. How can this be refactored to share partials between these classes? Or is there an even better way I'm overlooking? Code follows…