has_and_belongs_to_many, avoiding dupes in the join table

前端 未结 12 796
南旧
南旧 2020-12-04 11:07

I have a pretty simple HABTM set of models

class Tag < ActiveRecord::Base 
   has_and_belongs_to_many :posts
end 

class Post < ActiveRecord::Base 
           


        
相关标签:
12条回答
  • 2020-12-04 11:47

    I would prefer to adjust the model and create the classes this way:

    class Tag < ActiveRecord::Base 
       has_many :taggings
       has_many :posts, :through => :taggings
    end 
    
    class Post < ActiveRecord::Base 
       has_many :taggings
       has_many :tags, :through => :taggings
    end
    
    class Tagging < ActiveRecord::Base 
       belongs_to :tag
       belongs_to :post
    end
    

    Then I would wrap the creation in logic so that Tag models were reused if it existed already. I'd probably even put a unique constraint on the tag name to enforce it. That makes it more efficient to search either way since you can just use the indexes on the join table (to find all posts for a particular tag, and all tags for a particular post).

    The only catch is that you can't allow renaming of tags since changing the tag name would affect all uses of that tag. Make the user delete the tag and create a new one instead.

    0 讨论(0)
  • 2020-12-04 11:47

    I worked around this by creating a before_save filter that fixes stuff up.

    class Post < ActiveRecord::Base 
       has_and_belongs_to_many :tags
       before_save :fix_tags
    
       def tag_list= (tag_list) 
          self.tags.clear 
          tag_list.strip.split(' ').each do 
            self.tags.build(:name => tag) 
          end
       end  
    
        def fix_tags
          if self.tags.loaded?
            new_tags = [] 
            self.tags.each do |tag|
              if existing = Tag.find_by_name(tag.name) 
                new_tags << existing
              else 
                new_tags << tag
              end   
            end
    
            self.tags = new_tags 
          end
        end
    
    end
    

    It could be slightly optimised to work in batches with the tags, also it may need some slightly better transactional support.

    0 讨论(0)
  • 2020-12-04 11:49

    This is really old but I thought I'd share my way of doing this.

    class Tag < ActiveRecord::Base 
        has_and_belongs_to_many :posts
    end 
    
    class Post < ActiveRecord::Base 
        has_and_belongs_to_many :tags
    end
    

    In the code where I need to add tags to a post, I do something like:

    new_tag = Tag.find_by(name: 'cool')
    post.tag_ids = (post.tag_ids + [new_tag.id]).uniq
    

    This has the effect of automatically adding/removing tags as necessary or doing nothing if that's the case.

    0 讨论(0)
  • 2020-12-04 11:55

    In Rails4:

    class Post < ActiveRecord::Base 
      has_and_belongs_to_many :tags, -> { uniq }
    

    (beware, the -> { uniq } must be directly after the relation name, before other params)

    Rails documentation

    0 讨论(0)
  • 2020-12-04 11:59

    You should add an index on the tag :name property and then use the find_or_create method in the Tags#create method

    docs

    0 讨论(0)
  • 2020-12-04 12:02

    Just add a check in your controller before adding the record. If it does, do nothing, if it doesn't, add a new one:

    u = current_user
    a = @article
    if u.articles.exists?(a)
    
    else
      u.articles << a
    end
    

    More: "4.4.1.14 collection.exists?(...)" http://edgeguides.rubyonrails.org/association_basics.html#scopes-for-has-and-belongs-to-many

    0 讨论(0)
提交回复
热议问题