0

In my Rails application. I have a module in which I am overriding .eql? method like below

# 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

Which is included in Item model

# app/models/item.rb

class Item < ApplicationRecord
  include ItemUtil
end

In my controller I want to check various conditions based on the attribute values. Ex:

@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

# ...

All this is working fine and I want to write ItemUtil module in a neater way like below:

  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

This returns TypeError (nil is not a symbol nor a string) for @field inside new_item method, as initialize wasn't invoked anywhere

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)

but I dont want to change the way I call .eql? on object i.e., I'd like to keep these lines intact

@item1.eql?(@item2, field: "discount", op: ">") # @item2.discount > @item1.discount
@item2.eql?(@item1, op: "<=") # @item1.cost <= @item2.cost
  • How do I get the new_item and current_item return the desired output?
  • How to invoke initialize method within .eql? method?
  • Is there an alternate approach for this?
  • Is there a way to access parameters in modules similar to before_action(in controllers)?
2
  • Note that equality is expected to be symmetric, i.e. a.eql?(b) usually implies b.eql?(a). This is no longer true the way you implemented eql?. Commented Oct 12, 2021 at 12:32
  • 1
    I have decided to rename it as .compare? Commented Oct 12, 2021 at 12:54

1 Answer 1

2

You cannot instantiate an instance of a module. (You can instantiate an instance of a class, but not a module.)

In fact, what you are actually doing here is overriding the definition of Item#initialize!!

Sticking with your general design pattern, what I would suggest is to abstract this logic into a new class - like, say, 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
Sign up to request clarification or add additional context in comments.

5 Comments

However, there are some aspects of this design that are dubious.... For instance, why is this a desired design?? @item1.eql?(@item2, field: "discount", op: ">") -- You want to check if two items are equal, judging by whether one field is actually greater than the other?! That's super misleading.
At a bare minimum, perhaps this method should be called something like compare? instead of eql?!!
I wanted to perform <=> operation within the method. After your suggestion compare? makes more sense. Thanks for the solution, its very helpful.
Wouldn't it be possible to instantiate even if I wrapped my module code inside class << self ... end block within module?
@ManjunathP You can not instantiate an instance of a module. This is the fundamental difference between a class and a module.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.