IT TIP

has_and_belongs_to_many, 조인 테이블에서 중복 방지

itqueen 2020. 12. 14. 21:24
반응형

has_and_belongs_to_many, 조인 테이블에서 중복 방지


꽤 간단한 HABTM 모델 세트가 있습니다.

class Tag < ActiveRecord::Base 
   has_and_belongs_to_many :posts
end 

class Post < ActiveRecord::Base 
   has_and_belongs_to_many :tags

   def tags= (tag_list) 
      self.tags.clear 
      tag_list.strip.split(' ').each do 
        self.tags.build(:name => tag) 
      end
   end 
end 

이제는 Tags 테이블에 많은 중복 항목이 있다는 것을 제외하고는 모두 정상적으로 작동합니다.

태그 테이블에서 중복 (이름 기준)을 방지하려면 어떻게해야합니까?


뷰에서만 중복 방지 (Lazy 솔루션)

다음 데이터베이스에 중복 관계를 쓰는 것을 방지 하지 않으며find 메서드가 중복을 무시 하도록 합니다.

Rails 5 :

has_and_belongs_to_many :tags, -> { distinct }

참고 : Relation#uniqRails 5에서 감가 상각되었습니다 ( commit ).

Rails 4에서

has_and_belongs_to_many :tags, -> { uniq }

중복 데이터 저장 방지 (최상의 솔루션)

옵션 1 : 컨트롤러에서 중복 방지 :

post.tags << tag unless post.tags.include?(tag)

그러나 여러 사용자가 post.tags.include?(tag)동시에 시도 할 수 있으므로 경쟁 조건이 적용됩니다. 여기에서 설명 합니다 .

견고성을 위해 이것을 Post 모델 (post.rb)에 추가 할 수도 있습니다.

def tag=(tag)
  tags << tag unless tags.include?(tag)
end

옵션 2 : 고유 인덱스 생성

중복방지 하는 가장 확실한 방법은 데이터베이스 계층에서 중복 제약 조건을 갖는 것입니다. 이것은 unique index테이블 자체에 를 추가하여 달성 할 수 있습니다 .

rails g migration add_index_to_posts
# migration file
add_index :posts_tags, [:post_id, :tag_id], :unique => true
add_index :posts_tags, :tag_id

고유 인덱스가있는 경우 중복 레코드를 추가하려고하면 ActiveRecord::RecordNotUnique오류가 발생합니다. 이것을 처리하는 것은이 질문의 범위를 벗어납니다. 이 SO 질문 보기 .

rescue_from ActiveRecord::RecordNotUnique, :with => :some_method

추가로 위의 제안 :

  1. 추가 :uniq받는 has_and_belongs_to_many협회
  2. 조인 테이블에 고유 인덱스 추가

관계가 이미 존재하는지 확인하기 위해 명시 적으로 확인합니다. 예를 들면 :

post = Post.find(1)
tag = Tag.find(2)
post.tags << tag unless post.tags.include?(tag)

Rails4에서 :

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

( -> { uniq }이는 관계 이름 바로 뒤에 다른 매개 변수 앞에 있어야합니다.)

Rails 문서


문서에 설명 된대로:uniq 옵션을 전달할 수 있습니다 . 또한 옵션은 중복 관계 생성을 방지하지 않으며 접근 자 / 찾기 메서드가 한 번만 선택하도록합니다.:uniq

연관 테이블에서 중복을 방지하려면 고유 색인을 작성하고 예외를 처리해야합니다. 또한 validates_uniqueness_of가 예상대로 작동하지 않습니다. 첫 번째 요청이 중복을 확인하고 데이터베이스에 쓰는 시간 사이에 두 번째 요청이 데이터베이스에 쓰는 경우에 해당 할 수 있기 때문입니다.


uniq 옵션 설정 :

class Tag < ActiveRecord::Base 
   has_and_belongs_to_many :posts , :uniq => true
end 

class Post < ActiveRecord::Base 
   has_and_belongs_to_many :tags , :uniq => true

모델을 조정하고 다음과 같이 클래스를 만드는 것을 선호합니다.

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

그런 다음 태그 모델이 이미 존재하는 경우 재사용되도록 생성을 논리로 래핑합니다. 나는 그것을 시행하기 위해 태그 이름에 고유 한 제약을 둘 것입니다. 조인 테이블의 색인을 사용하기 만하면 (특정 태그에 대한 모든 게시물과 특정 게시물에 대한 모든 태그를 찾기 위해) 어느 쪽이든 검색하는 것이 더 효율적입니다.

유일한 문제는 태그 이름을 변경하면 해당 태그의 모든 사용에 영향을 미치므로 태그 이름 변경을 허용 할 수 없다는 것입니다. 사용자가 태그를 삭제하고 대신 새 태그를 생성하도록합니다.


문제를 해결하는 before_save 필터를 만들어이 문제를 해결했습니다.

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

태그와 함께 일괄 적으로 작업하도록 약간 최적화 될 수 있으며 약간 더 나은 트랜잭션 지원이 필요할 수도 있습니다.


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.


To me work

  1. adding unique index on the join table
  2. override << method in the relation

    has_and_belongs_to_many :groups do
      def << (group)
        group -= self if group.respond_to?(:to_a)
        super group unless include?(group)
      end
    end
    

Extract the tag name for security. Check whether or not the tag exists in your tags table, then create it if it doesn't:

name = params[:tag][:name]
@new_tag = Tag.where(name: name).first_or_create

Then check whether it exists within this specific collection, and push it if it doesn't:

@taggable.tags << @new_tag unless @taggable.tags.exists?(@new_tag)

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

docs


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

참고URL : https://stackoverflow.com/questions/1129781/has-and-belongs-to-many-avoiding-dupes-in-the-join-table

반응형