{"id":95,"date":"2010-05-25T18:48:33","date_gmt":"2010-05-25T18:48:33","guid":{"rendered":"http:\/\/grandmaster.student.utwente.nl\/?page_id=95"},"modified":"2011-12-17T23:14:51","modified_gmt":"2011-12-17T22:14:51","slug":"advanced-ruby-tutorial-01","status":"publish","type":"page","link":"https:\/\/www.grandmaster.nu\/blog\/?page_id=95","title":{"rendered":"Advanced Ruby Tutorial 01"},"content":{"rendered":"<p>Hi all, I&#8217;m getting better at Ruby at the moment and thought I should share some insights. First off, reactive programming &#8211; a powerful paradigm that loosely couples events and responses. Conceptually it&#8217;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&#8217;m going for a publisher\/subscriber model, which may be improved upon at a later time. So let&#8217;s start with an initial setup:<\/p>\n<pre lang=\"ruby\">module EventPublisher\r\n  def add_listener(var, func)\r\n    @callbacks ||= {}\r\n    @callbacks[var] ||= []\r\n    @callbacks[var] << func\r\n  end #add_listener\r\n\t\r\n  def publish(var, new_value)\r\n    return unless @callbacks\r\n    return unless @callbacks[var]\r\n    \r\n    @callbacks[var].each do |func|\r\n        func.call\r\n      end\r\n    end\r\n  end #publish\r\nend #EventPublisher\r\n<\/pre>\n<p>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):<\/p>\n<pre lang=\"ruby\">class Module\r\n  def hook(*members)\r\n    members.each do |member|\r\n      class_eval do\r\n        define_method(member) do\r\n          instance_variable_get(\"@#{member}\")\r\n        end\r\n      end #class_eval\r\n    end\r\n  end #hook\r\nend #Module<\/pre>\n<p>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:<\/p>\n<pre lang=\"ruby\">class Module\r\n  def hook(*members)\r\n    members.each do |member|\r\n      class_eval do\r\n        define_method(member) do\r\n          instance_variable_get(\"@#{member}\")\r\n        end\r\n\r\n        define_method(\"#{member}=\") do |new_value|\r\n          if (new_value != instance_variable_get(\"@#{member}\"))\r\n            publish(member.to_sym, new_value)\r\n          end\r\n          instance_variable_set(\"@#{member}\", new_value)\r\n        end              \r\n      end #class_eval\r\n    end\r\n  end #hook\r\nend #Module<\/pre>\n<p>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:<\/p>\n<pre lang=\"ruby\">module EventPublisher\r\n  def publish(var, new_value)\r\n    return unless @callbacks\r\n    return unless @callbacks[var]\r\n    \r\n    @callbacks[var].each do |func|\r\n      if (func.arity == 0)\r\n        func.call\r\n      else\r\n        func.call(new_value)\r\n      end\r\n    end\r\n  end #publish\r\nend #EventPublisher<\/pre>\n<p>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:<\/p>\n<pre lang=\"ruby\">class Module\r\n  def hook(*members)\r\n    members.each do |member|\r\n      class_eval do\r\n        define_method(member) do\r\n          instance_variable_get(\"@#{member}\")\r\n        end\r\n\r\n        define_method(\"#{member}=\") do |new_value|\r\n          if (new_value != instance_variable_get(\"@#{member}\"))\r\n            publish(member.to_sym, new_value)\r\n          end\r\n          instance_variable_set(\"@#{member}\", new_value)\r\n        end              \r\n      end #class_eval\r\n    end\r\n  end #hook\r\nend #Module<\/pre>\n<p>Here is an example of how this can be used as a simple callback system:<\/p>\n<pre lang=\"ruby\">class Example\r\n  include EventPublisher\r\n  \r\n  hook :test, :another_one\r\n  \r\n  def horrible_function(new_value)\r\n    puts \"horrible_function: #{new_value}\"\r\n  end\r\n  \r\n  def terrible_function\r\n    puts \"terrible_function\"\r\n  end\r\nend #Example\r\n\r\nex = Example.new\r\nex.test = 'test_one'\r\n\r\nex.add_listener(:test, ex.method(:horrible_function))\r\nex.add_listener(:test, ex.method(:terrible_function))\r\nex.add_listener(:test, lambda { |arg| puts \"lambda: \" + arg })\r\nex.add_listener(:test, lambda { puts \"lambda2\" })\r\n\r\nex.test = 'boo!'<\/pre>\n<p>This yields the following result:<\/p>\n<pre lang=\"ruby\">horrible_function: boo!\r\nterrible_function\r\nlambda: boo!\r\nlambda2<\/pre>\n<p>The final code, including example can be downloaded <a href=\"http:\/\/www.grandmaster.nu\/custom\/eventpublisher.rb\">[here]<\/a>. <\/p>\n<p>I'll leave it at that for now, feel free to add comments and let me know what you think.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hi all, I&#8217;m getting better at Ruby at the moment and thought I should share some insights. First off, reactive programming &#8211; a powerful paradigm that loosely couples events and responses. Conceptually it&#8217;s all about automatically propagating changes in input values. For example if a = b + 1 and b is later set to &hellip; <a href=\"https:\/\/www.grandmaster.nu\/blog\/?page_id=95\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Advanced Ruby Tutorial 01&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":25,"menu_order":0,"comment_status":"open","ping_status":"open","template":"","meta":{"ngg_post_thumbnail":0,"footnotes":""},"class_list":["post-95","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/pages\/95","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=95"}],"version-history":[{"count":1,"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/pages\/95\/revisions"}],"predecessor-version":[{"id":546,"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/pages\/95\/revisions\/546"}],"up":[{"embeddable":true,"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/pages\/25"}],"wp:attachment":[{"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=95"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}