开发者

Enforcing a Uniqueness Constraint in a Nested Form

I'm trying not to fight the defaults here and 开发者_如何转开发use Rails built-in support for nested attributes (from http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes). I'm labeling Things with Tags, and all works swell, and for each Thing I have a form with a nested field that creates a new Tag by a name. Trouble is, I need to make sure that each Tag has a unique name. Instead of creating a new Tag, if a user enters the name of one that already exists, I need to create the associate with that pre-existing Tag. How do I do this?


There's probably a better way to do this but this is about the best I can come up with for now.

In a has_many(:through) association accepts_nested_arguments_for uses an assignment to the virtual attribute #{association}_attributes to work its magic. It expects an array of hashes, where each hash contains attribute keys and their values. Any hashes with an id will be updated (or deleted if there is a :_delete key with the value of true). Any hashes missing an id will be used to create new items of that association. So the key is to intercept the call to tags_associations= and check any of the hashes that are missing ids for an existing tag with the same name value, and replace it with an entry that tags_attributes will use to make the association to the existing tag. N.B. for has_one and belongs_to relationships tag_attributes will expect a single hash. The code will be similar, but much simpler.

class Thing < ActiveRecord::Base
  has_many :tags, :through => :taggings
  has_many :taggings
  accepts_nested_attributes_for :tags

  def tags_attributes_with_recycling=(attributes)
    existing_attributes = attributes.reject{|attribute| attribute[:id].nil?}
    new_attributes = attributes - existing_attributes
    new_and_recycled_attributes = new_attributes.map { |attribute| 
      tag_id = Tag.find_by_name(attribute[:name]).id
      tag_id ? {:id => tag_id) : attribute
    }
    tags_attributes_without_recycling= (existing_attributes + new_and_recycled_attributes)
  end

  alias_method_chain :tags_attributes=, :recycling
end

It's untested, so no guarantees. But it should at least put you on track for a solution.


In your Thing class, define a tags_attributes= method to override the default and use Tag.find_or_create

def tags_attributes=(attributes)
  attributes.each do |attr|
    Tag.find_or_create_by_name(attr[:name])
  end
end

Not sure what the attributes hash will look like but you get the idea.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜