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
andcurrent_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)?
CodePudding user response:
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