rails validate nested attributes
Artists have many Events. Events have many Artists. The join between these two models is called Performances.
Currently the Event form creates the Performance but creates a new artist for each Artist added to the Event form.
I would like the Event form to:
- Validate that an Artist can only be added to an Event once
- If an Artist with the same name already exists in the Artists table, create the association in the join table (Performances), but do not create another Artist
- If an Artist with the same name does not already exist, create it and the Performance
I've tried adding 'validates_uniqueness_of :name' to artist.rb but this prevents the event from being saved. The join (performance) should be created if it does not already exist and the artist should be created if it does not already exist, but the existence of the artist should not prevent the join/association from being created.
event.rb
validates_presence_of :name, :location
has_many :performances, :dependent => :destroy
has_many :artists, :through => :performances
accepts_nested_attributes_for :artists, :reject_if => proc {|a| a['name'].blank?}, :allow_destroy => true
artist.rb
has_many :performances
has_many :events, :through => :performances
perfomance.rb
belongs_to :artist
belongs_to :event
events_controller.rb
def create
@event = Event.new(params[:event])
respond_to do |format|
if @event.save
flash[:notice] = 'Event was successfully created.'
format.html { redirect_to(admin_events_url) }
format.xml { render :xml => @event, :status => :created, :location => @event }
else
format.html { render :action => "new" }
format.xml { render :xml => @event.errors, :status => :unprocessable_entity }
end
end
end
_form.html.erb
<% form_for([:admin,@event]) do |f| %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<p>
<%= f.label :location %><br/>
<%= f.text_field :location %>
</p>
<p>
<%= f.label :date %><br />
<%= f.date_select :date %>
</p>
<p>
<%= f.labe开发者_运维百科l :description %><br />
<%= f.text_area :description %>
</p>
<% f.fields_for :artists do |builder| %>
<%= render 'artist_fields', :f => builder %>
<% end %>
<p><%= link_to_add_fields "Add Artist", f, :artists %></p>
<p>
<%= f.submit 'Submit' %> <%= link_to 'Cancel', admin_events_path %>
</p>
<% end %>
artist_fields.html.erb
<p class="fields">
<%= f.label :name, "Artist"%><br/>
<%= f.text_field :name %>
<%= link_to_remove_fields "remove", f %>
</p>
You should really take a look at these Railscasts:
Don't create various artists. Just use the existing one (or create if needed):
http://railscasts.com/episodes/167-more-on-virtual-attributes
You can also check these nested form railscasts (part one linked here):
http://railscasts.com/episodes/196-nested-model-form-part-1
For the validation, you can just have a method do the just-once-in-the-event validation for you. Something like (at Event.rb):
validate :artists_appear_just_once private def artists_appear_just_once self.artists.size == self.artists.uniq.size end
Alternatively you can make the artsits appear just once by default using the uniq! method before saving. Just call a before_save hook and process the artists array...
before_save :make_artists_unique
private
def make_artists_unique
artists.uniq!
end
Hope I got what you need right :P
You have the proc to reject artist attributes if the name is blank: you could also reject it if the artist already exists on the model, but that doesn't solve the problem of duplicate Artists. Essentially you want to do a find_or_create_by_name when adding them to your event.
I think in your case it would be better to define your own artist_attributes=
method instead of relying on accepts_nested. This way you can do your lookup for each artist name and add it only as needed:
def artist_attributes=(params)
if existing_artist = Artist.find_by_name(params[:name])
self.artists << existing_artist unless self.artists.include? existing_artist
else
self.build_artist(params)
end
end
精彩评论