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 => [#] 

问题

  1. :004 =>来自:003的员工未添加到c.employees
  2. :006 =>来自:003的员工用c保存
  3. :010 =>未设置员工的城市属性
  4. :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)