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#uniq
Rails 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
추가로 위의 제안 :
- 추가
:uniq
받는has_and_belongs_to_many
협회 - 조인 테이블에 고유 인덱스 추가
관계가 이미 존재하는지 확인하기 위해 명시 적으로 확인합니다. 예를 들면 :
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 }
이는 관계 이름 바로 뒤에 다른 매개 변수 앞에 있어야합니다.)
문서에 설명 된대로: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
- adding unique index on the join table
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
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
'IT TIP' 카테고리의 다른 글
Python : 하위 디렉토리의 모든 소스 파일에 대해 unittest.main ()을 실행하는 방법은 무엇입니까? (0) | 2020.12.14 |
---|---|
JavaScript 개발 용 IDE (0) | 2020.12.14 |
다중 조인을 만들 때 tmp 테이블에 대한 MySQL 잘못된 키 파일 (0) | 2020.12.14 |
jQuery에서 사용자 정의 함수 호출 (0) | 2020.12.14 |
CSS에서 6 자리 색상 코드 대신 3 자리 색상 코드를 사용하는 방법은 무엇입니까? (0) | 2020.12.14 |