【问题标题】:Initialize from within instance method call从实例方法调用中初始化
【发布时间】:2021-10-12 10:09:07
【问题描述】:

在我的 Rails 应用程序中。我有一个模块,我在其中重写 .eql? 方法,如下所示

# lib/item_util.rb

module ItemUtil
  def eql?(item, field: "cost", op: "==")
    item.send(field).present? &&
      item.send(field).send(op, self.send(field))
  end
end

Item 模型中包含的内容

# app/models/item.rb

class Item < ApplicationRecord
  include ItemUtil
end

在我的控制器中,我想根据属性值检查各种条件。例如:

@item1 = Item.find(:id)
@item2 = Item.find(:id)

@item1.eql?(@item2, field: "discount", op: ">") # @item2.discount > @item1.discount
@item2.eql?(@item1, op: "<=") # @item1.cost <= @item2.cost

# ...

所有这一切都很好,我想以更简洁的方式编写ItemUtil 模块,如下所示:

  module ItemUtil
    attr_accessor :item, :field

    def initialize(item, field: "cost")
      @item = item
      @field = field
    end

    def eql?(item, field: "cost", op: "==")
      new_item.present? && new_item.send(op, current_item)
    end

    def new_item
      @item.send(@field)
    end

    def current_item
      self.send(@field)
    end
  end

这会在 new_item 方法中为 @field 返回 TypeError (nil is not a symbol nor a string),因为 initialize 没有在任何地方调用

Traceback (most recent call last):
        2: from lib/item_util.rb:12:in `eql?'
        1: from lib/item_util.rb:17:in `new_item'
TypeError (nil is not a symbol nor a string)

但我不想改变我在对象上调用.eql? 的方式,即我想保持这些行不变

@item1.eql?(@item2, field: "discount", op: ">") # @item2.discount > @item1.discount
@item2.eql?(@item1, op: "<=") # @item1.cost <= @item2.cost
  • 如何让new_item 和current_item 返回所需的输出?
  • 如何在.eql?方法中调用initialize方法?
  • 是否有替代方法?
  • 有没有办法访问类似于before_action(在控制器中)的模块中的参数?

【问题讨论】:

  • 请注意,相等应该是对称的,即a.eql?(b) 通常暗示b.eql?(a)。这不再是您实现eql? 的方式。
  • 我决定将其重命名为.compare?

标签: ruby-on-rails ruby ruby-on-rails-6.1


【解决方案1】:

您不能实例化模块的实例。 (您可以实例化 class 的实例,但不能实例化 module。)

事实上,你在这里实际做的是覆盖Item#initialize的定义!!

坚持您的一般设计模式,我建议将此逻辑抽象为 新类 - 比如,ItemComparator:

class ItemComparator
  attr_reader :item, :other

  def initialize(item, other)
    @item = item
    @other = other
  end

  def eql?(field:, op:)
    item_field = item.send(field)
    other_field = other.send(:field)

    other_field.present? && item_field.send(op, other_field)
  end
end

module ItemUtil
  def eql?(other, field: "cost", op: "==")
    ItemComparator.new(self, other).eql?(field: field, op: op)
  end
end

class Item < ApplicationRecord
  include ItemUtil
end

【讨论】:

  • 然而,这个设计的某些方面是可疑的....例如,为什么这是一个理想的设计? @item1.eql?(@item2, field: "discount", op: "&gt;") -- 你想检查两个项目是否相等,判断一个字段是否真的大于另一个?!这是超级误导。
  • 至少,这个方法应该被称为compare?而不是eql?!!
  • 我想在方法中执行&lt;=&gt; 操作。在你的建议之后compare? 更有意义。感谢您的解决方案,它非常有帮助。
  • 即使我将模块代码封装在模块内的class &lt;&lt; self ... end 块中,是否也无法实例化?
  • @ManjunathP 您不能实例化模块的实例。这是类和模块之间的根本区别。
猜你喜欢
  • 2014-11-30
  • 1970-01-01
  • 1970-01-01
  • 2014-06-28
  • 2016-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-06
相关资源
最近更新 更多