6b5a68d41436ce28831a0e0ca6bcd124

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…

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 !

D9ff9c1eba9a908e3454d414b61f874f

Kevin Ansfield

October 25, 2007, October 25, 2007 18:30, permalink

1 rating. Login to rate!

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>
6b5a68d41436ce28831a0e0ca6bcd124

Etandrib

October 26, 2007, October 26, 2007 20:30, permalink

No rating. Login to rate!

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
E635ccff7389d9070f5e7e9fe8b36beb

rpheath

October 26, 2007, October 26, 2007 22:33, permalink

1 rating. Login to rate!

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
6b5a68d41436ce28831a0e0ca6bcd124

Etandrib

October 30, 2007, October 30, 2007 20:41, permalink

No rating. Login to rate!

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>

Your refactoring





Format Copy from initial code

or Cancel