{"id":65,"date":"2014-10-17T17:10:05","date_gmt":"2014-10-17T17:10:05","guid":{"rendered":"https:\/\/redbooth.com\/engineering\/?p=65"},"modified":"2014-10-17T17:23:20","modified_gmt":"2014-10-17T17:23:20","slug":"dry-ruby-metaprogramming","status":"publish","type":"post","link":"https:\/\/redbooth.com\/engineering\/technical\/dry-ruby-metaprogramming","title":{"rendered":"Dry with Ruby metaprogramming"},"content":{"rendered":"<p>You&#8217;ve probably already heard of Ruby&#8217;s metaprogramming capabilities but you may either think it is only suitable for building frameworks and DSLs or you don&#8217;t know when to make use of it. Here we&#8217;ll show you how little doses of metaprogramming can help to dry up your Rails app. It&#8217;s definitely a tool to keep in your toolbelt but it generally raises the overall abstraction of your code. So, watch out!<\/p>\n<h3>Include mixins in your modules<\/h3>\n<p>Modules in Ruby, besides providing namespacing, implement the mixin facility. However, modules may depend on the object they are included in. You may want to execute funcionality of your brand new module from an ActiveRecord callback. Let&#8217;s start with the following model:<\/p>\n<pre>class Membership < ActiveRecord::Base\r\n  belongs_to :user\r\n  belongs_to :organization\r\nend<\/pre>\n<p>Let's say we want to track down whenever a membership is deleted. Easy enough. We just add the following:<\/p>\n<pre>class Membership < ActiveRecord::Base\r\n  include MembershipDestroyLogger\r\n  \r\n  belongs_to :user\r\n  belongs_to :organization\r\n  \r\n  # Warning! this couples Membership with MembershipDestroyLogger\r\n  after_destroy :log_model_deletion\r\nend\r\n\r\nmodule MembershipDestroyLogger\r\n  def log_model_deletion\r\n    Rails.logger.warn(\"[MembershipDestroyLogger] Deleted Membership #{id}\")\r\n  end\r\nend<\/pre>\n<p>The `MembershipDestroyLogger` nicely wraps it up. Note that `Membership` must implement the `after_destroy` callback in order for the module to work. Unfortunately, this couples `Membership` with `MembershipDestroyLogger`.<\/p>\n<p>Let's say that now we also want to keep track of the oauth applications that get removed. That'd involve to implement the `OauthApplicationDestroyLogger`. If we keep implementing more destroy loggers we will soon end up having a bunch of modules containing the same code along with the required callbacks. It's time to dry things up.<\/p>\n<h3>Included hook<\/h3>\n<p>According to the Ruby documentation<\/p>\n<blockquote><p>`included`: Callback invoked whenever the receiver is included in another module or class<\/p><\/blockquote>\n<p>It basically executes whatever code you specified whenever a module is included into a class.<\/p>\n<pre>module Foo\r\n  def self.included(base)\r\n    puts 'Foo has been included somewhere'\r\n  end\r\nend<\/pre>\n<p>Note the method receives an argument. That represents the class where the module has been included into. That's all we need. We can now abstract our destroy loggers.<\/p>\n<pre>module DestroyLogger\r\n  def self.included(base)\r\n    base.after_destroy :log_model_deletion\r\n  end\r\n\r\n  private\r\n\r\n  def log_model_deletion\r\n    Rails.logger.warn(\"[DestroyLogger] Deleted #{self.class} #{id}\")\r\n  end\r\nend<\/pre>\n<p>`base` is the model we include the module into, so the module can add the required `after_destroy` callback itself. Furthermore, as `self` points to model we can retrieve the model's class name. <\/p>\n<p>Voil\u00e0! we can remove all the previous destroy loggers and just drop the module in whatever module we want to keep track of. So, finally our models look like this:<\/p>\n<pre>class Membership < ActiveRecord::Base\r\n  include DestroyLogger\r\nend\r\n\r\nclass OauthApplication < ActiveRecord::Base\r\n  include DestroyLogger\r\nend<\/pre>\n<h3>Conclusions<\/h3>\n<p>As you can see, there is no need for digging into complex and abstract metaprogramming stuff to benefit from Ruby's features. Little doses of metaprogramming can already improve your code and lead you to a better world.<\/p>\n<p>The `included` hook is also the underlying implementation of <a href=\"https:\/\/github.com\/rails\/rails\/blob\/master\/activesupport\/lib\/active_support\/concern.rb#L124\">ActiveSupport::Concern's included method<\/a>. With this and other techniques, ActiveSupport::Concern eases the implementation of the mixin pattern considerably.<\/p>\n<h3>References<\/h3>\n<p>If you want to expand your Ruby metaprogramming skills further, <a href=\"https:\/\/pragprog.com\/book\/ppmetr2\/metaprogramming-ruby-2\">Metaprogramming Ruby 2: Program Like the Ruby Pros<\/a> is a recommended reading. Specially its <a href=\"http:\/\/media.pragprog.com\/titles\/ppmetr2\/concern.pdf\"><em>ActiveSupport's Concern Module<\/em><\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You&#8217;ve probably already heard of Ruby&#8217;s metaprogramming capabilities but you may either think it is only suitable for building frameworks and DSLs or you don&#8217;t know when to make use of it. Here we&#8217;ll show you how little doses of metaprogramming can help to dry up your Rails app. It&#8217;s definitely a tool to keep <a class=\"read-more\" href=\"https:\/\/redbooth.com\/engineering\/technical\/dry-ruby-metaprogramming\">&hellip;&nbsp;<span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":42,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[24,7],"tags":[],"class_list":["post-65","post","type-post","status-publish","format-standard","hentry","category-ruby","category-technical"],"_links":{"self":[{"href":"https:\/\/redbooth.com\/engineering\/wp-json\/wp\/v2\/posts\/65","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/redbooth.com\/engineering\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/redbooth.com\/engineering\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/redbooth.com\/engineering\/wp-json\/wp\/v2\/users\/42"}],"replies":[{"embeddable":true,"href":"https:\/\/redbooth.com\/engineering\/wp-json\/wp\/v2\/comments?post=65"}],"version-history":[{"count":0,"href":"https:\/\/redbooth.com\/engineering\/wp-json\/wp\/v2\/posts\/65\/revisions"}],"wp:attachment":[{"href":"https:\/\/redbooth.com\/engineering\/wp-json\/wp\/v2\/media?parent=65"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/redbooth.com\/engineering\/wp-json\/wp\/v2\/categories?post=65"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/redbooth.com\/engineering\/wp-json\/wp\/v2\/tags?post=65"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}