ActiveRecord find_or_build_by
我想表演:
XXX.find_or_build_by_language_id(attributes)
我发现
XXX.find_or_initialize_by_language_id(attributes)
但那只设置了language_id而没有其他属性。 即使我手动设置属性,执行XXX.save时也不会保存记录。
我只是阅读Rails – 查找或创建 – 是否有查找或构建? ,这似乎与我的问题有关,但不符合我的需要。
编辑
让我们使用这个场景
# db/migrations/create_models.rb class CreateModels < ActiveRecord::Migration def self.up create_table :companies do |t| t.string :name end create_table :employees do |t| t.string :name t.string :city t.references :company end end end
–
# app/models/employee.rb class Employee < ActiveRecord::Base belongs_to :company end
–
# app/models/company.rb class Company < ActiveRecord::Base has_many :employees end
–
# rails console :001> c = Company.new => # :002> c.employees => [] :003> e = c.employees.find_or_initialize_by_name(:name => 'foo', :city => 'bar') => # :004> c.employees => [] :005> c.save => true :006> c.employees => [] :007> e.save => true :008> c = Company.first => # :009> c.employees => [#] :010> e = c.employees.find_or_initialize_by_name(:name => 'foo', :city => 'baz') => # :011> e.city = 'baz' => "baz" :012> c.employees => [#] :013 > c.save => true :014> c.employees => [#]
问题
- :004 =>来自:003的员工未添加到c.employees
- :006 =>来自:003的员工用c保存
- :010 =>未设置员工的城市属性
- :014 =>保存公司时员工的城市属性未更新
这个怎么样?
employee_attrs = {:name => 'foo', :city => 'bar'} e = c.employees.where(employee_attrs).first || c.employees.build(employee_attrs)
为了记录,这是我带来的实现。 它可能更简单,但它符合我的需要:
module ActiveRecord module Associations class AssociationCollection < AssociationProxy alias_method :old_method_missing, :method_missing def method_missing(method_id, *arguments, &block) if /^find_or_build_by_([_a-zA-Z]\w*)$/ =~ method_id.to_s names = $1.split('_and_') find_or_build_by(names, *arguments) else old_method_missing(method_id, *arguments, &block) end end def find_or_build_by(names, *arguments) values = arguments[0] throw InvalidArgument unless values.keys.first.kind_of?(String) record = Array.new(self).find do |r| names.inject(true) do |memo, name| memo && (values[name].to_s == r.send(name).to_s) end end if record sanitized_values = record.send(:sanitize_for_mass_assignment, values) sanitized_values.each {|k, v| record.send("#{k}=", v)} else record = build(values) end return record end end end end
我为我的Rails 4.2.x应用程序尝试了以下代码。
#config/initializers/collection_proxy.rb ActiveRecord::Associations::CollectionProxy.class_eval do alias_method :old_method_missing, :method_missing def method_missing(method_id, *arguments, &block) if /^find_or_build_by([_a-zA-Z]\w*)$/ =~ method_id.to_s names = $1.split('_and_') find_or_build_by(names, *arguments) else old_method_missing(method_id, *arguments, &block) end end def find_or_build_by(names, *arguments) where(names).first || build(names) end end
你可以像这样使用它。
XXX.find_or_build_by(attributes)