snippetrubyMinor
Triggerful Ruby Gem: create dynamic callbacks to methods
Viewed 0 times
createrubymethodstriggerfuldynamiccallbacksgem
Problem
The main purpose of Ruby is to be readable. I hope I did a good job with this gem I made. If there's any kind of suggestion of how to make this better, then please tell me.
```
class Trigger
def initialize event, *callbacks
@callbacks = callbacks
@event = event
if @callbacks[0].is_a? TrueClass
@progression = true
@callbacks.delete_at(0)
elsif @callbacks[0].is_a? FalseClass
@progression = false
@callbacks.delete_at(0)
else
@progression = false
end
end
def trigger(*args)
case @event
when Proc
event_data = @event.call
when Method
event_data = @event.call
else
event_data = self.method(@event).call(*args)
end
@callbacks.each do |callback|
if callback.instance_of? Trigger
if @progression
callback.trigger(*args, event_data)
else
callback.trigger(*args)
end
else
case callback
when Proc
if @progression
callback.call(*args, event_data)
else
callback.call(*args)
end
when Method
if @progression
callback.call(*args, event_data)
else
callback.call(*args)
end
else
if @progression
method(callback).call(*args, event_data)
else
method(callback).call(*args)
end
end
end
end
end
#triggers the callbacks without executing the original method
def silent_trigger(*args)
@callbacks.each do |callback|
if callback.instance_of? Trigger
callback.trigger(*args)
else
case callback
when Proc
callback.call
when Method
callback.call
else
method(callback).call(*args)
end
end
end
end
# add callback(s) to instance
def add(*callbacks)
@callbacks.concat callbacks
end
def insert(index, *callbacks)
@callbacks.inse
```
class Trigger
def initialize event, *callbacks
@callbacks = callbacks
@event = event
if @callbacks[0].is_a? TrueClass
@progression = true
@callbacks.delete_at(0)
elsif @callbacks[0].is_a? FalseClass
@progression = false
@callbacks.delete_at(0)
else
@progression = false
end
end
def trigger(*args)
case @event
when Proc
event_data = @event.call
when Method
event_data = @event.call
else
event_data = self.method(@event).call(*args)
end
@callbacks.each do |callback|
if callback.instance_of? Trigger
if @progression
callback.trigger(*args, event_data)
else
callback.trigger(*args)
end
else
case callback
when Proc
if @progression
callback.call(*args, event_data)
else
callback.call(*args)
end
when Method
if @progression
callback.call(*args, event_data)
else
callback.call(*args)
end
else
if @progression
method(callback).call(*args, event_data)
else
method(callback).call(*args)
end
end
end
end
end
#triggers the callbacks without executing the original method
def silent_trigger(*args)
@callbacks.each do |callback|
if callback.instance_of? Trigger
callback.trigger(*args)
else
case callback
when Proc
callback.call
when Method
callback.call
else
method(callback).call(*args)
end
end
end
end
# add callback(s) to instance
def add(*callbacks)
@callbacks.concat callbacks
end
def insert(index, *callbacks)
@callbacks.inse
Solution
1) get rid of conditionals
I think your main problem here is the nested conditionals that litter your code. This increases complexity and tends to be less readable.
You can get rid of those conditionals using a method like this :
so you can do things like :
This way, all your callbacks will respond to
2) use inheritance
As i see it, your
As you can see, this simplifies the logic a lot, and makes clear that we have two different behaviors, which is invaluable for consumers of your API.
I think your main problem here is the nested conditionals that litter your code. This increases complexity and tends to be less readable.
You can get rid of those conditionals using a method like this :
def make_callable(object)
case object
when Proc, Method then object
when Trigger then ->(*args){ object.trigger(*args) }
else ->(*args){ public_send object, *args }
end
endso you can do things like :
@event = make_callable(event)
@callbacks = callbacks.map{ |c| make_callable c }This way, all your callbacks will respond to
call uniformly, so you won't need conditionals anymore.2) use inheritance
As i see it, your
@progression instance variable masks the need for two different behaviors, which means two different classes : a "silent" trigger, and a "verbose" one that extends the former.class Trigger
# factory method to instantiate the right type of callback.
# I slightly changed the signature from the original #initialize
# as I thought it would make more sense this way,
# but it is possible to keep the original one with minor tweaks
#
def self.factory(verbose, event, *callbacks)
verbose ? Verbose.new(event, *callbacks) : new(event, *callbacks)
end
def initialize(event, *callbacks)
@event = make_callable(event)
@callbacks = callbacks.map{ |c| make_callable c }
end
# SNIP : this class would also expose add_callback, remove_callback, etc.
def trigger(*args)
@callbacks.each{ |c| c.call(*args) }
end
private
def make_callable(object)
case object
when Proc, Method then object
when Trigger then ->(*args){ object.trigger(*args) }
else ->(*args){ public_send object, *args }
end
end
end
class Trigger::Verbose < Trigger
def trigger(*args)
event_data = @event.call(*args)
super(*args, event_data)
end
endAs you can see, this simplifies the logic a lot, and makes clear that we have two different behaviors, which is invaluable for consumers of your API.
Code Snippets
def make_callable(object)
case object
when Proc, Method then object
when Trigger then ->(*args){ object.trigger(*args) }
else ->(*args){ public_send object, *args }
end
end@event = make_callable(event)
@callbacks = callbacks.map{ |c| make_callable c }class Trigger
# factory method to instantiate the right type of callback.
# I slightly changed the signature from the original #initialize
# as I thought it would make more sense this way,
# but it is possible to keep the original one with minor tweaks
#
def self.factory(verbose, event, *callbacks)
verbose ? Verbose.new(event, *callbacks) : new(event, *callbacks)
end
def initialize(event, *callbacks)
@event = make_callable(event)
@callbacks = callbacks.map{ |c| make_callable c }
end
# SNIP : this class would also expose add_callback, remove_callback, etc.
def trigger(*args)
@callbacks.each{ |c| c.call(*args) }
end
private
def make_callable(object)
case object
when Proc, Method then object
when Trigger then ->(*args){ object.trigger(*args) }
else ->(*args){ public_send object, *args }
end
end
end
class Trigger::Verbose < Trigger
def trigger(*args)
event_data = @event.call(*args)
super(*args, event_data)
end
endContext
StackExchange Code Review Q#32485, answer score: 4
Revisions (0)
No revisions yet.