I'm trying to determine if it is possible in Ruby to determine when a chain of methods has finished being called.
Consider that I have a class which retrieves information from a database. I often just want to call an instance of the class to access all the included data, but in many situations I chain additional methods which apply filters etc. Currently the actual data retrieval is performed by a method which is always called last, like so:
all_data = data_retriever.run
filtered_data = data_retriever.condition({k1: v1}).run
filtered_data = data_retriever.condition({k1: v1}).condition({k2: v2}).run
This works fine, but I can't help but think that Ruby may have a way to do this without tacking a method like .run
on the end of every usage. Is it possible for my class to "know" when I've finished chaining methods? Such as a hook that executes after all the chained methods are called?
class DataRetriever
@conditions = {}
def initialize()
end
def condition(condition_hash)
@conditions << condition_hash
end
#...
def after_chains
# run the data retrieval code using the contents of @conditions
end
end
Is it possible for my class to "know" when I've finished chaining methods?
No, chaining the methods is equivalent to calling them one after another. The receiver cannot distinguish the call style. And this is actually good, consider the following:
retriever = data_retriever.condition({k1: v1})
retriever.condition({k2: v2}) if something
retriever.condition({k3: v3}) if something_else
data = retriever.run
In the above example, you probably wouldn't want the first line to already trigger the data retrieval.
Ruby may have a way to do this without tacking a method like
.run
on the end of every usage
Instead of run
, you could use a more subtle entry point to trigger the execution, e.g. each
which is Ruby's default enumeration method:
data = data_retriever.condition({k1: v1})
data.each do |entry|
# ...
end
Under the hood: (there are various ways to implement this)
class DataRetriever
def run
@data = # retrieve actual data
end
def each(&block)
run unless @data
@data.each(&block)
end
end
Just make sure to invalidate @data
when more conditions are added (or alternatively freeze
the object after run
).
You could even call each
from inspect
: (you might want to abbreviate long data)
class DataRetriever
def inspect
"#<DataRetriever data=#{each.to_a.inspect}>"
end
end
In irb:
filtered_data = DataRetriever.new.condition({k1: v1})
#=> #<DataRetriever data=[1, 2, 3]>