在相关集合上使用send()

时间:2018-10-19 15:26:05

标签: ruby-on-rails ruby activerecord

我有一些列表,它们几乎相等,只是它们正在调用其他方法(相关模型上的作用域方法)。所以我想我可以使用ruby的send方法来调用该方法。但失败并显示以下错误:

  

错误的参数数量(给定0,应为1..3)

我的(简化的)班是:

class Zone < ApplicationRecord
  has_many :customers
  has_many :orders, through: :customers
end

class Order < ApplicationRecord
  scope :open, -> { where(status: 'open') }
end

因此,如果我致电@zone.orders.open,则会收到所有具有预期状态的订单。

但是,如果我做@zone.orders.send(:open),它将失败,并出现上述错误。

这个想法是使用.send()作为局部变量,只是传递一个符号来检索@zone.orders上的其他作用域。

在Rails控制台上:

  

2.5.1 :002 > zone = Zone.last => #<Zone id: ...> | > zone.orders.send(:open) | Creating scope :open. Overwriting existing method Order.open. | Creating scope :route. Overwriting existing method Order.route. | Traceback (most recent call last): | 3: from (irb):3 2: from (irb):3:in open' 1: from (irb):3:in initialize' ArgumentError (wrong number of arguments (given 0, expected 1..3))

1 个答案:

答案 0 :(得分:4)

这里的问题是名称与用于打开IO流的Kernel#open冲突。

irb(main):001:0> z = Zone.create
   (0.2ms)  BEGIN
  Zone Create (0.8ms)  INSERT INTO "zones" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id"  [["created_at", "2018-10-19 16:29:40.018339"], ["updated_at", "2018-10-19 16:29:40.018339"]]
   (0.7ms)  COMMIT
=> #<Zone id: 10, created_at: "2018-10-19 16:29:40", updated_at: "2018-10-19 16:29:40">
irb(main):002:0> z.orders.send(:open)
Creating scope :open. Overwriting existing method Order.open.
ArgumentError: wrong number of arguments (given 0, expected 1..3)
    from (irb):2:in `initialize'
    from (irb):2:in `open'
    from (irb):2
irb(main):003:0> z.orders.method(:open)
=> #<Method: Order::ActiveRecord_Associations_CollectionProxy(Kernel)#open>
irb(main):004:0> z.orders.method(:open).call
ArgumentError: wrong number of arguments (given 0, expected 1..3)
    from (irb):4:in `initialize'
    from (irb):4:in `open'
    from (irb):4:in `call'
    from (irb):4
irb(main):005:0> z.orders
  Order Load (0.8ms)  SELECT  "orders".* FROM "orders" INNER JOIN "customers" ON "orders"."customer_id" = "customers"."id" WHERE "customers"."zone_id" = $1 LIMIT $2  [["zone_id", 10], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy []>
irb(main):006:0> z.orders.open
  Order Load (0.9ms)  SELECT  "orders".* FROM "orders" INNER JOIN "customers" ON "orders"."customer_id" = "customers"."id" WHERE "customers"."zone_id" = $1 AND "orders"."status" = $2 LIMIT $3  [["zone_id", 10], ["status", "open"], ["LIMIT", 11]]
=> #<ActiveRecord::AssociationRelation []>

您可以看到,调用在加载集合之后先转到Kernel#open,然后转到范围定义的方法。我猜这是由于ActiveRecord::Associations::CollectionProxy对关联目标类进行了某种形式的延迟传播。由于CollectionProxy已经有一个open方法(Kernel#open),因此它使用它。

一种简单的解决方案,而不是动态调用,是使用带有参数的范围:

class Order < ApplicationRecord
  belongs_to :customer
  scope :with_status, ->(status){ where(status: status.to_s) }
end

或者甚至更好地使用ActiveRecord::Enum,它足够聪明以正确使用收集代理。

class Order < ApplicationRecord
  belongs_to :customer
  # you need to change the DB column to an integer type
  enum status: [:open, :closed]
end