【问题标题】:In an one to many association, what's the difference between Model.new and models.new?在一对多关联中,Model.new 和 models.new 有什么区别?
【发布时间】:2017-05-13 22:04:37
【问题描述】:

我有 3 个模型,Cart、CartItem、Product。在每个他们中,我定义了这样的关联:

cart.rb

has_many :cart_items
has_many :products, through: :cart_items, source: :product

def add_product_to_cart(product)
  ci = cart_items.new
  ci.product = product
  ci.quantity = 1
  ci.save
end

cart_item.rb

belongs_to :cart
belongs_to :product

这里是cart_item的相关列,db/schema.rb

create_table "cart_items", force: :cascade do |t|

   t.integer  "cart_id"
   t.integer  "product_id"
   t.integer  "number",     default: 1
   .
   .
   .

end

然后我在控制台中这样做了:

>> p = Product.first
>> c = Cart.create
>> c.add_product_to_cart(p)
>> CartItem.last

我的问题是:

cart.rb 中第一行 add_product_to_cart(product)

当我使用 ci = cart_items.new 时,它可以正常工作,新的 cart_item 有一个 :cart_id = 2

但是当我使用 ci = CartItem.new 时,新的 cart_item 的 :cart_id => nil ,这次 cart_item 没有得到 cart_id。

为什么会这样?在 controller.rb 中,new 方法似乎遵循大写的 Class_name。

有什么区别?什么时候应该使用cart_items.new,什么时候应该使用CartItem.new

【问题讨论】:

    标签: ruby-on-rails


    【解决方案1】:

    我不知道models.new,但我可以验证它是否有效。它是 build 的别名,您可以在此处看到:https://github.com/rails/rails/blob/5-0-stable/activerecord/lib/active_record/associations/collection_proxy.rb#L292

    我认为 Rails 在这里非常方便,因为我从未见过 new 的实例方法实现,这就是为什么我最初认为这是您的写作错误。

    对我来说,new 是一个用于构造类实例的保留字。

    那我在说什么?

    在 Ruby 中,没有用于实例化类的保留关键字,例如在 Java 中,您可以编写 new CartItem() 来构建实例。相反,所有类都提供了一个名为 new 的(类/静态)方法,该方法将构建一个新对象并从类中调用 initialize 实例方法来设置实例。

    ci = CartItem.new 无法按预期工作的原因是因为您在没有任何上下文的情况下初始化了 CartItem 的新实例。您从cart.rb 内部调用它的事实根本不重要。无法推断您希望这个新的 CartItem 属于特定的购物车记录。

    当您调用 cart.cart_items 时,您会得到一个称为 ActiveRecord 关联集合代理的特殊对象,它本身不是一个实体,而是一个带有必要上下文的对象,用于调用链中的下一个方法。

    这个对象知道你的上下文是 CartItems,它知道它是在特定购物车的上下文中构建的,在你的例子中,cart_id 为 2。

    当您在集合代理上调用build 时,例如

    my_new_cart_item = cart.cart_items.build
    

    它会自动将新购物车项目实例的 cart_id 设置为构建它的购物车的 id。

    或者,您可以调用 CartItem.new(cart_id: 2) 来获得相同的结果。

    总之:

    我认为你应该从不使用cart_items.new,因为它是反 ruby​​ 约定,应该只抛出 NoMethodError。将它合并到 Rails 团队似乎是个脑残,但在 Rails 还年轻的时候,为了方便起见,添加了很多聪明的东西。我建议你使用cart_items.build,它做同样的事情,但其他 Rails 开发人员可以清楚地识别。

    当您想要构建一个应该属于现有购物车的新实例时,请使用 cart_items.build。如果您想构建 CartItem 而不将其设置为属于特定购物车,请使用 CartItem.new

    在这种情况下,听起来后者没有多大意义,因为没有购物车的购物车项目并不是真正的事情。但是如果你想象你有一辆汽车,而一辆汽车有很多轮子,那么拥有一个不属于特定汽车的轮子(目前)可能是有意义的,因为它可能只是存入库存中。

    【讨论】:

    • 谢谢尼尔斯。非常清楚!我爱你的回答。我倾向于写cart_items.new,但我使用了build,很抱歉这个错误。正如你所说,它是一个别名。
    • 我试过这个:在 ci = CartItem.new 下添加 ci.cart_id = self.id 。然后我在控制台中测试了它,它也可以工作。
    【解决方案2】:

    在 ActiveRecord 对象上调用 .create 方法时,您是从头开始创建对象、实现它并一步保存所有内容。由于正在保存,它将进入数据库并分配:cart_id。如果您的模型中有 AR 对象的验证,您将看到如果您不通过这些验证,调用 .create 方法将失败。

    或者,在 AR 对象上调用 .new 方法只会启动该对象,允许您在必须专门调用该对象上的 .save 方法之前以您认为合适的方式实现它。在您调用.save 之前,您实际上还没有创建任何要插入数据库的内容,因此您的:cart_id 将是nil

    进入控制台尝试不同的方法,包括.create 对一个对象和.new.save 对一个单独的对象。

    【讨论】:

    • 感谢您的回答,Dithanial。我会试试看。 :)
    • 好的。您也不应该使用 id 初始化 AR 对象; Rails 会为您做到这一点,并且会做得更好。如另一个答案中所述,您也不应尝试通过表名创建数据库条目,而应使用它所附加的模型。
    猜你喜欢
    • 2011-05-23
    • 2014-03-20
    • 2011-12-30
    • 1970-01-01
    • 1970-01-01
    • 2014-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多