ActiveRecord find_or_build_by - PullRequest
       2

ActiveRecord find_or_build_by

9 голосов
/ 26 июля 2011

Я хотел бы выполнить:

    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
 => #<Company id: nil, name: nil> 
:002> c.employees
 => []
:003> e = c.employees.find_or_initialize_by_name(:name => 'foo', :city => 'bar')
 => #<Employee id: nil, name: "foo", city: "bar", company_id: nil>
:004> c.employees
 => []
:005> c.save
 => true
:006> c.employees
 => []
:007> e.save
 => true
:008> c = Company.first
 => #<Company id: 1, name: nil>
:009> c.employees
 => [#<Employee id: 1, name: "foo", city: "bar", company_id: 1>]
:010> e = c.employees.find_or_initialize_by_name(:name => 'foo', :city => 'baz')
 => #<Employee id: 1, name: "foo", city: "bar", company_id: 1> 
:011> e.city = 'baz'
 => "baz"
:012> c.employees
 => [#<Employee id: 1, name: "foo", city: "bar", company_id: 1>] 
:013 > c.save
 => true 
:014> c.employees
 => [#<Employee id: 1, name: "foo", city: "bar", company_id: 1>]

Проблемы

  1. : 004 => Сотрудник с: 003 не добавлен в c.employees
  2. : 006 => Сотрудник с: 003 сохраняется с помощью c
  3. : 010 => Городской атрибут сотрудника не установлен
  4. : 014 => Городской атрибут сотрудника не обновляется, когдаспасательная компания

Ответы [ 3 ]

11 голосов
/ 01 августа 2011

Как насчет этого?

employee_attrs = {:name => 'foo', :city => 'bar'}
e = c.employees.where(employee_attrs).first || c.employees.build(employee_attrs)
1 голос
/ 02 июня 2015

Я попробовал следующий код для моего приложения 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)
1 голос
/ 01 августа 2011

Для справки, вот реализация, с которой я пришел.Возможно, это может быть проще, но это соответствует моим потребностям:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...