Hi all, I’m getting better at Ruby at the moment and thought I should share some insights. First off, reactive programming – a powerful paradigm that loosely couples events and responses. Conceptually it’s all about automatically propagating changes in input values. For example if a = b + 1 and b is later set to 1, the value of a will be reevaluated and changes from undefined to 2. The main advantage of this is that you no longer have to think about propagating changes across interrelated variables. Think spreadsheet cell interactions and you know what I mean. A complete and efficient implementation of this concept will take some time, but initially I’m going for a publisher/subscriber model, which may be improved upon at a later time. So let’s start with an initial setup:
module EventPublisher
def add_listener(var, func)
@callbacks ||= {}
@callbacks[var] ||= []
@callbacks[var] << func
end #add_listener
def publish(var, new_value)
return unless @callbacks
return unless @callbacks[var]
@callbacks[var].each do |func|
func.call
end
end
end #publish
end #EventPublisher
Pretty nice and fairly unassuming. Now I'd like to add a DSL-like system that allows you to define class members as a 'hook' (some metaprogramming ahead):
class Module
def hook(*members)
members.each do |member|
class_eval do
define_method(member) do
instance_variable_get("@#{member}")
end
end #class_eval
end
end #hook
end #Module
By defining the hook method for the Method object, it becomes available for all modules and classes. The EventPublisher module will still need to be included before listeners can be added however, and failure to do so may result in something like 'method not found: publish'. Still, this is pretty close to what I wanted, but I'd like any modifications to automagically be published. The idea here is to overload the '=' method to publish whenever any changes occur:
class Module
def hook(*members)
members.each do |member|
class_eval do
define_method(member) do
instance_variable_get("@#{member}")
end
define_method("#{member}=") do |new_value|
if (new_value != instance_variable_get("@#{member}"))
publish(member.to_sym, new_value)
end
instance_variable_set("@#{member}", new_value)
end
end #class_eval
end
end #hook
end #Module
Now we're getting somewhere. The next step is to communicate the changes to the listeners, which requires some changes to the publisher's publish function:
module EventPublisher
def publish(var, new_value)
return unless @callbacks
return unless @callbacks[var]
@callbacks[var].each do |func|
if (func.arity == 0)
func.call
else
func.call(new_value)
end
end
end #publish
end #EventPublisher
By making use of arity, this remains compatible with listeners that do not take arguments. So now the hook code needs to include passing the new value to the publish function. While I'm at it, I'd like to only publish actually changed values, so let's check for that too:
class Module
def hook(*members)
members.each do |member|
class_eval do
define_method(member) do
instance_variable_get("@#{member}")
end
define_method("#{member}=") do |new_value|
if (new_value != instance_variable_get("@#{member}"))
publish(member.to_sym, new_value)
end
instance_variable_set("@#{member}", new_value)
end
end #class_eval
end
end #hook
end #Module
Here is an example of how this can be used as a simple callback system:
class Example
include EventPublisher
hook :test, :another_one
def horrible_function(new_value)
puts "horrible_function: #{new_value}"
end
def terrible_function
puts "terrible_function"
end
end #Example
ex = Example.new
ex.test = 'test_one'
ex.add_listener(:test, ex.method(:horrible_function))
ex.add_listener(:test, ex.method(:terrible_function))
ex.add_listener(:test, lambda { |arg| puts "lambda: " + arg })
ex.add_listener(:test, lambda { puts "lambda2" })
ex.test = 'boo!'
This yields the following result:
horrible_function: boo!
terrible_function
lambda: boo!
lambda2
The final code, including example can be downloaded [here].
I'll leave it at that for now, feel free to add comments and let me know what you think.
I am very glad to know about your post which is so informative about ruby on rails. I am looking to learn about ruby programming and your post is very useful in this case.
Thank you, cool article