通过关联定义自定义ActiveRecord创建者

时间:2017-05-20 01:21:18

标签: ruby-on-rails ruby activerecord rails-activerecord

我的某些模型之间存在has_manybelongs_to关联,类似于以下内容:

class Customer < ActiveRecord::Base
  # attributes id, name, hash_salt etc
  has_many :contacts

end

class Contact < ActiveRecord::Base
  # attributes id, customer_id, email_hash, email etc
  belongs_to :customer

end

我希望有一个自定义创建器方法,允许某些逻辑在创建新对象之前运行查找现有记录,类似于以下内容:

class Contact < ActiveRecord::Base
  belongs_to :customer

  class << self
    def find_or_create(customer_id, params = {})
      customer = Customer.find(customer_id)
      email_hash = SCrypt::Engine.hash_secret(params[:email], customer.hash_salt)
      record = Contact.where(customer_id: customer_id).where(email_hash: email_hash).first
      if !record
        record = new(params)
      end

      record
    end
  end
end

如果我通过

等关联来调用自定义创建者
cust = Customer.find(1)
cust.contacts.find_or_create(cust.id, contact_params)

有什么方法可以省略customer_id param并以某种方式从调用者那里获取它?当我致电cust.contact.create(contact_params)

时,我对Rails如何做到这一点感到困惑

2 个答案:

答案 0 :(得分:3)

这里有一些不同的东西,但我认为你所关注的关键是如何“扩展”关联以在集合代理上定义自定义方法。如果这听起来像希腊语,我有好消息......这很容易:

class Customer
  has_many :contacts do 
    def find_or_create(params = {})
      proxy_association.owner #=> Customer instance
    end
  end
end

这就是说,你可能想要在find_or_create上重构那个Contact方法,所以它没有那么多。

class Customer
  has_many :contacts do
    def by_email(email)
      proxy_association.target.by_email proxy_association.owner.hashed_value(email)
    end
  end

  def hashed_value(value)
    SCrypt::Engine.hash_secret(value, hash_salt)
  end
end

class Contact
  scope :by_email, ->(email) { where(email_hash: email) }
end

现在你可以得到这样的联系人:

@customer.contacts.by_email(params[:email])

或者你可以检查这样的存在:

@customer.contacts.by_email(params[:email]).any?

由于你已经得到了这种散列前驱,因此幂等创造更具挑战性。通常你可以这样做:

@customer.contacts.where(params).first_or_create
# won't work for your case

在这种情况下,您需要预先散列电子邮件并将其从参数中删除。您也可以扩展与此关联,但这是要点:

contact = @customer.contacts.by_email(params.delete(:email))
contact = contact.where(params).first_or_create

祝你好运。

答案 1 :(得分:0)

您可以在浏览相关行时使用first_or_create。试试这样: -

cust.contacts.where(contact_params).first_or_create