find_or_create와 함께 accepts_nested_attributes_for?
Rails의 accepts_nested_attributes_for 메서드를 성공적으로 사용하고 있지만 레코드가 이미 존재하는 경우 새 레코드를 만들지 않도록하려면 어떻게해야 합니까?
예를 들어 :
팀, 멤버십, 플레이어의 세 가지 모델이 있고 각 팀은 멤버십을 통해 많은 플레이어를 보유하고 있으며 플레이어는 여러 팀에 속할 수 있습니다. 그러면 팀 모델은 플레이어에 대한 중첩 된 속성을 허용 할 수 있지만 이는 결합 된 팀 + 플레이어 양식을 통해 제출 된 각 플레이어가 새 플레이어 레코드로 생성됨을 의미합니다.
같은 이름의 플레이어가 아직없는 경우 새 플레이어 레코드 만 만들려면 어떻게해야합니까? 같은 이름을 가진 선수 가 있다면 , 새로운 선수 기록을 만들지 말고 대신 올바른 선수를 찾아서 새로운 팀 기록과 연관시켜야합니다.
자동 저장 연결에 대한 후크를 정의 할 때 일반 코드 경로를 건너 뛰고 대신 메서드가 호출됩니다. 따라서 다음을 수행 할 수 있습니다.
class Post < ActiveRecord::Base
belongs_to :author, :autosave => true
accepts_nested_attributes_for :author
# If you need to validate the associated record, you can add a method like this:
# validate_associated_record_for_author
def autosave_associated_records_for_author
# Find or create the author by name
if new_author = Author.find_by_name(author.name)
self.author = new_author
else
self.author.save!
end
end
end
이 코드는 테스트되지 않았지만 필요한 것입니다.
팀에 플레이어를 추가하는 것으로 생각하지 말고 팀에 멤버십을 추가하는 것으로 생각하십시오. 양식은 플레이어와 직접 작동하지 않습니다. 멤버십 모델은 player_name
가상 속성을 가질 수 있습니다 . 이면에서 이것은 플레이어를 찾거나 만들 수 있습니다.
class Membership < ActiveRecord::Base
def player_name
player && player.name
end
def player_name=(name)
self.player = Player.find_or_create_by_name(name) unless name.blank?
end
end
그런 다음 멤버십 양식 작성기에 player_name 텍스트 필드를 추가하면됩니다.
<%= f.text_field :player_name %>
이 방법은 accepts_nested_attributes_for에만 국한되지 않으며 모든 회원 양식에서 사용할 수 있습니다.
참고 :이 기술을 사용하면 유효성 검사가 발생하기 전에 플레이어 모델이 생성됩니다. 이 효과를 원하지 않으면 플레이어를 인스턴스 변수에 저장 한 다음 before_save 콜백에 저장하십시오.
를 사용할 때 기존 레코드를 :accepts_nested_attributes_for
제출 id
하면 ActiveRecord가 새 레코드를 만드는 대신 기존 레코드 를 업데이트 합니다. 마크 업이 어떤 것인지 잘 모르겠지만 대략 다음과 같이 시도해보십시오.
<%= text_field_tag "team[player][name]", current_player.name %>
<%= hidden_field_tag "team[player][id]", current_player.id if current_player %>
id
가 제공 되면 플레이어 이름이 업데이트 되지만 그렇지 않으면 생성됩니다.
방법을 정의하는 autosave_associated_record_for_
방법은 매우 흥미 롭습니다. 나는 확실히 그것을 사용할 것이다! 그러나이 더 간단한 솔루션도 고려하십시오.
질문 (find_or_create 참조)과 관련하여 문제를 해결하기 위해 Francois의 답변에서 if 블록을 다음과 같이 다시 표현할 수 있습니다.
self.author = Author.find_or_create_by_name(author.name) unless author.name.blank?
self.author.save!
이것은 당신이 has_one 또는 belongs_to 관계를 가지고 있다면 잘 작동합니다. 그러나 has_many 또는 has_many through로 부족했습니다.
has_many : through 관계를 활용하는 태깅 시스템이 있습니다. 여기에있는 솔루션 중 어느 것도 내가 필요한 곳으로 갈 수 없었기 때문에 다른 사람들에게 도움이 될 수있는 솔루션을 찾았습니다. 이것은 Rails 3.2에서 테스트되었습니다.
설정
다음은 내 모델의 기본 버전입니다.
위치 개체 :
class Location < ActiveRecord::Base
has_many :city_taggables, :as => :city_taggable, :dependent => :destroy
has_many :city_tags, :through => :city_taggables
accepts_nested_attributes_for :city_tags, :reject_if => :all_blank, allow_destroy: true
end
태그 개체
class CityTaggable < ActiveRecord::Base
belongs_to :city_tag
belongs_to :city_taggable, :polymorphic => true
end
class CityTag < ActiveRecord::Base
has_many :city_taggables, :dependent => :destroy
has_many :ads, :through => :city_taggables
end
해결책
실제로 autosave_related_recored_for 메서드를 다음과 같이 재정의했습니다.
class Location < ActiveRecord::Base
private
def autosave_associated_records_for_city_tags
tags =[]
#For Each Tag
city_tags.each do |tag|
#Destroy Tag if set to _destroy
if tag._destroy
#remove tag from object don't destroy the tag
self.city_tags.delete(tag)
next
end
#Check if the tag we are saving is new (no ID passed)
if tag.new_record?
#Find existing tag or use new tag if not found
tag = CityTag.find_by_label(tag.label) || CityTag.create(label: tag.label)
else
#If tag being saved has an ID then it exists we want to see if the label has changed
#We find the record and compare explicitly, this saves us when we are removing tags.
existing = CityTag.find_by_id(tag.id)
if existing
#Tag labels are different so we want to find or create a new tag (rather than updating the exiting tag label)
if tag.label != existing.label
self.city_tags.delete(tag)
tag = CityTag.find_by_label(tag.label) || CityTag.create(label: tag.label)
end
else
#Looks like we are removing the tag and need to delete it from this object
self.city_tags.delete(tag)
next
end
end
tags << tag
end
#Iterate through tags and add to my Location unless they are already associated.
tags.each do |tag|
unless tag.in? self.city_tags
self.city_tags << tag
end
end
end
The above implementation saves, deletes and changes tags the way I needed when using fields_for in a nested form. I'm open to feedback if there are ways to simplify. It is important to point out that I am explicitly changing tags when the label changes rather than updating the tag label.
A before_validation
hook is a good choice: it's a standard mechanism resulting in simpler code than overriding the more obscure autosave_associated_records_for_*
.
class Quux < ActiveRecord::Base
has_and_belongs_to_many :foos
accepts_nested_attributes_for :foos, reject_if: ->(object){ object[:value].blank? }
before_validation :find_foos
def find_foos
self.foos = self.foos.map do |object|
Foo.where(value: object.value).first_or_initialize
end
end
end
Answer by @François Beausoleil is awesome and solved a big problem. Great to learn about the concept of autosave_associated_record_for
.
However, I found one corner case in this implementation. In case of update
of existing post's author(A1
), if a new author name(A2
) is passed, it will end up changing the original(A1
) author's name.
p = Post.first
p.author #<Author id: 1, name: 'JK Rowling'>
# now edit is triggered, and new author(non existing) is passed(e.g: Cal Newport).
p.author #<Author id: 1, name: 'Cal Newport'>
Oringinal code:
class Post < ActiveRecord::Base
belongs_to :author, :autosave => true
accepts_nested_attributes_for :author
# If you need to validate the associated record, you can add a method like this:
# validate_associated_record_for_author
def autosave_associated_records_for_author
# Find or create the author by name
if new_author = Author.find_by_name(author.name)
self.author = new_author
else
self.author.save!
end
end
end
It is because, in case of edit, self.author
for post will already be an author with id:1, it will go in else, block and will update that author
instead of creating new one.
I changed the code(elsif
condition) to mitigate this issue:
class Post < ActiveRecord::Base
belongs_to :author, :autosave => true
accepts_nested_attributes_for :author
# If you need to validate the associated record, you can add a method like this:
# validate_associated_record_for_author
def autosave_associated_records_for_author
# Find or create the author by name
if new_author = Author.find_by_name(author.name)
self.author = new_author
elsif author && author.persisted? && author.changed?
# New condition: if author is already allocated to post, but is changed, create a new author.
self.author = Author.new(name: author.name)
else
# else create a new author
self.author.save!
end
end
end
@dustin-m's answer was instrumental for me - I am doing something custom with a has_many :through relationship. I have a Topic which has one Trend, which has many children (recursive).
ActiveRecord does not like it when I configure this as a standard has_many :searches, through: trend, source: :children
relationship. It retrieves topic.trend and topic.searches but won't do topic.searches.create(name: foo).
So I used the above to construct a custom autosave and am achieving the correct result with accepts_nested_attributes_for :searches, allow_destroy: true
def autosave_associated_records_for_searches searches.each do | s | if s._destroy self.trend.children.delete(s) elsif s.new_record? self.trend.children << s else s.save end end end
ReferenceURL : https://stackoverflow.com/questions/3579924/accepts-nested-attributes-for-with-find-or-create
'programing' 카테고리의 다른 글
C # 용 자동 코드 포맷터가 있습니까? (0) | 2021.01.14 |
---|---|
Spring Batch에서 Job의 여러 단계간에 데이터를 어떻게 공유 할 수 있습니까? (0) | 2021.01.14 |
두 개의 일반 숫자 값 비교 (0) | 2021.01.14 |
정수를 상수 참조로 전달하는 것과 복사하기 (0) | 2021.01.14 |
Visual Studio 2010 및 2008은 다른 폴더에서 동일한 이름을 가진 소스 파일을 처리 할 수 없습니까? (0) | 2021.01.14 |