gpt4 book ai didi

ruby-on-rails - accepts_nested_attributes_for 可以基于复合键更新吗?

转载 作者:行者123 更新时间:2023-12-02 02:27:06 25 4
gpt4 key购买 nike

我的模型如下所示:

class Template < ActiveRecord::Base
has_many :template_strings

accepts_nested_attributes_for :template_strings
end

class TemplateString < ActiveRecord::Base
belongs_to :template
end

TemplateString 模型由language_idtemplate_id 上的复合键标识(它目前有一个id 也是主键,但如果需要可以删除)。

因为我正在使用 accepts_nested_attributes_for,所以我可以在创建新模板的同时创建新的字符串,该模板可以正常工作。但是,当我尝试更新现有模板中的字符串时,accepts_nested_attributes_for 尝试创建新的 TemplateString 对象,然后数据库提示违反了唯一约束(这是应该的)。

有没有办法让 accepts_nested_attributes_for 在确定是否应该创建新记录或加载现有记录时使用复合键?

最佳答案

我解决这个问题的方法是猴子补丁 accepts_nested_attributes_for 接受一个 :key 选项,然后 assign_nested_attributes_for_collection_associationassign_nested_attributes_for_one_to_one_association 检查对于基于这些关键属性的现有记录,如果未找到,则在继续正常操作之前。

module ActiveRecord
module NestedAttributes
class << self
def included_with_key_option(base)
included_without_key_option(base)
base.class_inheritable_accessor :nested_attributes_keys, :instance_writer => false
base.nested_attributes_keys = {}
end

alias_method_chain :included, :key_option
end

module ClassMethods
# Override accepts_nested_attributes_for to allow for :key to be specified
def accepts_nested_attributes_for_with_key_option(*attr_names)
options = attr_names.extract_options!
options.assert_valid_keys(:allow_destroy, :reject_if, :key)

attr_names.each do |association_name|
if reflection = reflect_on_association(association_name)
self.nested_attributes_keys[association_name.to_sym] = [options[:key]].flatten.reject(&:nil?)
else
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
end
end

# Now that we've set up a class variable based on key, remove it from the options and call
# the overriden method to continue setup
options.delete(:key)
attr_names << options
accepts_nested_attributes_for_without_key_option(*attr_names)
end

alias_method_chain :accepts_nested_attributes_for, :key_option
end

private

# Override to check keys if given
def assign_nested_attributes_for_one_to_one_association(association_name, attributes, allow_destroy)
attributes = attributes.stringify_keys

if !(keys = self.class.nested_attributes_keys[association_name]).empty?
if existing_record = find_record_by_keys(association_name, attributes, keys)
assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy)
return
end
end

if attributes['id'].blank?
unless reject_new_record?(association_name, attributes)
send("build_#{association_name}", attributes.except(*UNASSIGNABLE_KEYS))
end
elsif (existing_record = send(association_name)) && existing_record.id.to_s == attributes['id'].to_s
assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy)
end
end

# Override to check keys if given
def assign_nested_attributes_for_collection_association(association_name, attributes_collection, allow_destroy)
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
end

if attributes_collection.is_a? Hash
attributes_collection = attributes_collection.sort_by { |index, _| index.to_i }.map { |_, attributes| attributes }
end

attributes_collection.each do |attributes|
attributes = attributes.stringify_keys

if !(keys = self.class.nested_attributes_keys[association_name]).empty?
if existing_record = find_record_by_keys(association_name, attributes, keys)
assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy)
return
end
end

if attributes['id'].blank?
unless reject_new_record?(association_name, attributes)
send(association_name).build(attributes.except(*UNASSIGNABLE_KEYS))
end
elsif existing_record = send(association_name).detect { |record| record.id.to_s == attributes['id'].to_s }
assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy)
end
end
end

# Find a record that matches the keys
def find_record_by_keys(association_name, attributes, keys)
[send(association_name)].flatten.detect do |record|
keys.inject(true) do |result, key|
# Guess at the foreign key name and fill it if it's not given
attributes[key.to_s] = self.id if attributes[key.to_s].blank? and key = self.class.name.underscore + "_id"

break unless (record.send(key).to_s == attributes[key.to_s].to_s)
true
end
end
end
end
end

也许这不是最简洁的解决方案,但它确实有效(请注意覆盖基于 Rails 2.3)。

关于ruby-on-rails - accepts_nested_attributes_for 可以基于复合键更新吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5493053/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com