1

I am a newbie to rails. Today I met a problem to save associated models.

I have 2 models with the following association, The Tag model has a validation role for attribute 'name' to be uniqueness.

class Product < ActiveRecord::Base
  has_and_belongs_to_many :tags
  validates_associated :tags
end

class Tag < ActiveRecord::Base
  has_and_belongs_to_many :products
  validates :name, :presence => true, :uniqueness => true
end

What I want to archive is to create a product object with association to several tags. I used the function below:

def self.create_with_tags(value)
    tags = []
    if value.has_key? :tags
    tags = value[:tags]
    value.delete :tags
    end
    p = Product.new(value)
    tags.each do |tag|
      p.tags.build(:name => tag)
    end
    p.save!
    p
end

The test code is

p = Product.create_with_tags(:name => 'test product', :status => true, :tags =>['tag1','tag2','tag3','tag4']) 

The test code works fine when the tag names ['tag1','tag2','tag3','tag4'] does not exist in database ; if one of the tags already exists in database, for example, 'tag1', then the association validation will fails and the whole creation process rollback.

What I want to archive is: in case some of the tags already exists in database, the validation don't fails, instead, existing tag is found (but not created) and the association between product and existing tags are created. for example, if 'tag1' already in database, it won't be created again , but the association in products_tags table is created.

Stuart M
  • 11,458
  • 6
  • 45
  • 59
fuyi
  • 2,573
  • 4
  • 23
  • 46
  • find the best answer by referring to this post: [Rails: Has and belongs to many (HABTM) — create association without creating other records][1] [1]: http://stackoverflow.com/questions/4730493/rails-has-and-belongs-to-many-habtm-create-association-without-creating-ot – fuyi Apr 27 '13 at 20:06

1 Answers1

0

I would recommend structuring this a little differently than you have currently: instead of having a class method that acts as an alternative constructor method, I prefer to use the standard Product.new method as the entrypoint, and pass Tag instances to it as a standard attribute. What about this:

tag_ids = ['tag1','tag2','tag3','tag4'].map do |t|
  Tag.where(:name => t).first_or_create.id
end
p = Product(:name => 'test product', :status => true, :tag_ids => tag_ids)

Using first_or_create, you can first check if a Tag with the given name exists already, create one if not, and finally use the built-in Product.new method instead of having a separate constructor method (such as your self.create_with_tags method).

If you really want to continue using your self.create_with_tags method, you could still make use of the first_or_create method there too.

Stuart M
  • 11,458
  • 6
  • 45
  • 59