使用ActiveRelation:连接多态has_one以隐式创建关系

时间:2013-10-26 12:16:07

标签: ruby-on-rails ruby activerecord polymorphic-associations active-relation

我们可以像这样使用ActiveRelation:

MyModel.where(:field => "test").create => #<Message ... field:"test">

但它对多态has_one关联的连接不起作用:

class RelatedModel < AR::Base
  # has :some_field
  belongs_to :subject, :polymorphic => true
end

class MyModel < AR::Base
  # need some dirty magic here
  # to build default related_model with params from active_relation
  has_one :related_model, :as => :subject, :dependent => :destroy
end

describe MyModel do
  it "should auto-create has_one association with joins" do
    test = MyModel.joins(:related_model).where("related_models.subject_type" => "MyModel", "related_models.some_field" => "chachacha").create
    test.related_model.should_not be_nil
    test.related_model.some_field.should == "chachacha"
    test.related_model.subject_type.should == "MyModel"
    test.related_model.subject_id.should == test.id
    # fails =)
  end
end

是否可以提取active_relation参数,将它们传递给MyModel以便在before_create中使用并与它们一起构建RelatedModel?

1 个答案:

答案 0 :(得分:0)

潜入ActiveRecord来源我发现

ActiveRecord :: Relation用'范围'方法覆盖'create'。

ActiveRecord :: Persistance'创建'调用'初始化'来自ActiveRecord :: Core。

ActiveRecord :: Core'initialize'call'populate_with_current_scope_attributes'

在ActiveRecord :: Scoping中声明的此方法使用在ActiveRecord :: Scoping :: Named中声明的'scope_attributes'。

scope_attributes创建关系'all'并在其上调用'scope_for_create'。

'ActiveRecord :: Relation的'scope_for_create'仅使用来自current_scope的'where_values_hash',其中不包含'related_models.subject_type'等规则(此值包含在where_clauses中)。所以我们需要在ActiveRecord :: Relation上使用简单的键值来与'create'一起使用。但ActiveRecord不够聪明,不知道where子句中的'some_field'应该与join table一起使用。

我发现它只能通过访问MyModel上'before_create'中self.class.current_scope.where_clauses的选项,解析它们并设置属性来实现。

class MyModel < AR::Base
  before_create :create_default_node
  def create_default_node
    clause = self.class.current_scope.where_clauses.detect{|clause| clause =~ /\`related_models\`.\`some_field\`/}
    value = clause.scan(/\=.+\`([[:word:]]+)\`/).flatten.first
    self.create_node(:some_field => value)
  end
end

但是它太脏了,然后我决定找到更简单的解决方案和反向依赖,如Railscast Pro#394中所述,使用STI将RelatedModel功能移动到MyModel。实际上我需要这样复杂的关系创建,因为RelatedModel具有一些所有模型共同的功能(充当树)。我决定将'祖先'和'孩子'委托给RelatedModel。反转依赖解决了这个问题。

class MyModel < AR::Base
  acts_as_tree
  belongs_to :subject, :polymorphic => true
end

class MyModel2 < MyModel
end

class RelatedModel < AR::Base
  # has :some_field
  has_one :my_model, :as => :subject, :dependent => :destroy
end

MyModel.create{|m| m.subject = RelatedModel.create(:some_field => "chachacha")}
MyModel.ancestors # no need to proxy relations