使用ActiveRecord执行此操作的正确方法是什么?

时间:2012-02-02 19:11:31

标签: ruby-on-rails ruby activerecord

说我有两个班,

形象和信用

class Image < ActiveRecord::Base
  belongs_to :credit
  accepts_nested_attributes_for :credit
end

class Credit < ActiveRecord::Base
  #has a field called name
  has_many :images
end

我希望在创建Image时关联一个Credit,它有点像标记。本质上,我想要像Credit.find_or_create_by_name这样的行为,但是在使用Credit的客户端代码中,如果它只是一个Create,它会更加清晰。我似乎无法找到将其烘焙到模型中的方法。

2 个答案:

答案 0 :(得分:0)

试试这个:

class Image < ActiveRecord::Base
  belongs_to :credit

  attr_accessor :credit_name
  after_create { Credit.associate_object(self) }
end

class Credit < ActiveRecord::Base
  #has a field called name
  has_many :images

  def self.associate_object(object, association='images')
    credit = self.find_or_create_by_name(object.credit_name)
    credit.send(association) << object
    credit.save
  end

然后,当您创建图像时,您可以执行的操作类似于

Image.create(:attr1 => 'value1', :attr2 => 'value2', ..., :credit_name => 'some_name')

它会将您输入:credit_name值的名称用于after_create回调中。

请注意,如果您决定稍后使用与Credit相关联的其他对象(假设有一个名为Text的类),您仍然可以像这样使用此方法:

class Text < ActiveRecord::Base
  belongs_to :credit

  attr_accessor :credit_name
  before_create { Credit.associate_object(self, 'texts') }
end

虽然在那时你可能会考虑为所有属于信用的类创建一个SuperClass,并且让超类处理关联。您可能还想查看polymorphic relationships

答案 1 :(得分:0)

这可能比它的价值更麻烦,而且很危险,因为它涉及覆盖Credit类的initialize方法,但我认为这可能有效。我给你的建议是尝试我之前建议的解决方案,抛弃这些宝石或修改它们,以便他们可以使用你的方法。话虽如此,这里什么都没有:

首先,您需要一种方法来获取Credit初始化程序的方法调用程序。我们使用类I found on the web called CallChain,但我们会为了我们的目的修改它。您可能希望将其放在lib文件夹中。

class CallChain
  require 'active_support'

  def self.caller_class
    caller_file.split('/').last.chomp('.rb').classify.constantize
  end

  def self.caller_file(depth=1)
    parse_caller(caller(depth+1).first).first
  end

  private

  #Stolen from ActionMailer, where this was used but was not made reusable
  def self.parse_caller(at)
    if /^(.+?):(\d+)(?::in `(.*)')?/ =~ at
      file   = Regexp.last_match[1]
      line   = Regexp.last_match[2].to_i
      method = Regexp.last_match[3]
      [file, line, method]
    end
  end
end

现在我们需要覆盖Credit类初始值设定项,因为当您从另一个类(在本例中为Credit.new类)调用Credit.createImage时,它正在调用该类的初始值设定项。您还需要确保在拨打Credit.createCredit.new时将:caller_class_id => self.id提供给属性参数,因为我们无法从初始化程序获取它。

class Credit < ActiveRecord::Base 
  #has a field called name
  has_many :images
  attr_accessor :caller_class_id

  def initialize(args = {})
    super
    # only screw around with this stuff if the caller_class_id has been set
    if caller_class_id
      caller_class = CallChain.caller_class
      self.send(caller_class.to_param.tableize) << caller_class.find(caller_class_id)
    end
  end
end

现在我们有了这个设置,我们可以在Image类中创建一个简单的方法,它将创建一个新的Credit并正确设置关联:

class Image < ActiveRecord::Base
  belongs_to :credit
  accepts_nested_attributes_for :credit

  # for building
  def build_credit
    Credit.new(:attr1 => 'val1', etc.., :caller_class_id => self.id)
  end

  # for creating
  # if you wanted to have this happen automatically you could make the method get called by an 'after_create' callback on this class.
  def create_credit
    Credit.create(:attr1 => 'val1', etc.., :caller_class_id => self.id)
  end
end

同样,我真的不会推荐这个,但我想看看是否有可能。如果您不介意覆盖initialize上的Credit方法,请尝试一下,我相信这是一个符合您所有标准的解决方案。