{"id":205,"date":"2009-06-25T16:05:00","date_gmt":"2009-06-26T00:05:00","guid":{"rendered":"http:\/\/onehub.com\/blog\/posts\/decorating-activerecord-accessor-methods"},"modified":"2013-07-26T14:50:05","modified_gmt":"2013-07-26T21:50:05","slug":"decorating-activerecord-accessor-methods","status":"publish","type":"post","link":"https:\/\/www.onehub.com\/blog\/2009\/06\/25\/decorating-activerecord-accessor-methods\/","title":{"rendered":"Decorating ActiveRecord Accessor Methods"},"content":{"rendered":"<p>Recently, I was faced with a quandary. I needed to impart some of our ActiveRecord accessor methods with special functionality, but I also needed to maintain the default behavior for those fields. This was done to support a new input type which we added to the venerable <a href=\"http:\/\/github.com\/timcharper\/calendar_date_select\/tree\/master\">CalendarDateSelect plugin<\/a> which allows a user to enter a date, time, and meridian, all in separate fields.<\/p>\n<p><a href=\"https:\/\/onehub.com\/blog\/wp-content\/uploads\/2012\/01\/calendar_date_select_fields.png\"><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-medium wp-image-1880\" alt=\"calendar_date_select_fields\" src=\"https:\/\/onehub.com\/blog\/wp-content\/uploads\/2012\/01\/calendar_date_select_fields-300x75.png\" width=\"300\" height=\"75\" \/><\/a><\/p>\n<p>Though I could parse each of the fields in the controller and pass the resulting time to the model, this is messy, error-prone, and unnecessary.<\/p>\n<p style=\"clear: left;\">What I really want to be able to do is something like this:<\/p>\n<pre class=\"brush: ruby; title: ; notranslate\" title=\"\">\r\n&gt;&gt; CalendarEvent.new(\r\n:start_time =&gt; {:date =&gt; '6\/23\/2009', :time =&gt; '12:30', :meridian =&gt; 'pm'})\r\n=&gt; #&lt;CalendarEvent start_time: \"2009-06-23 19:30:00\"&gt;\r\n<\/pre>\n<p>My first thought was to simply override the accessor methods like so:<\/p>\n<pre class=\"brush: ruby; title: ; notranslate\" title=\"\">\r\nCalendarEvent &lt; ActiveRecord::Base\r\n...\r\ndef start_time=(new_time)\r\nself[:start_time] = parse_time_from_hash(new_time)\r\nend\r\nend\r\n<\/pre>\n<p>However, as I alluded to before, there&#8217;s a problem with this approach. Rails already does some magic behind the scenes to ensure that time values stored to your ActiveRecord objects are automatically cast to and from UTC. The code above will completely overwrite these default accessors, and time zone casting will cease to function for those fields. What I really want to do is <a href=\"http:\/\/en.wikipedia.org\/wiki\/Decorator_pattern\">decorate<\/a> these accessor methods, so that I can add to the existing functionality without completely circumventing it.<\/p>\n<p>One way we might think about accomplishing this would be to redefine our accessor method and add behavior to it using <a href=\"http:\/\/weblog.rubyonrails.org\/2006\/4\/26\/new-in-rails-module-alias_method_chain\">alias_method_chain<\/a>.<\/p>\n<pre class=\"brush: ruby; title: ; notranslate\" title=\"\">\r\ndef start_time_with_hash_parsing=(new_time)\r\nnew_time = parse_time_from_hash(new_time)\r\nstart_time_without_hash_parsing = new_time\r\nend\r\nalias_method_chain :start_time=, :hash_parsing\r\n<\/pre>\n<p>Try that, though, and you&#8217;ll get this fun little error<\/p>\n<pre class=\"brush: ruby; light: true; title: ; notranslate\" title=\"\">\r\n`alias_method':NameError: undefined method `start_time=' for class `CalendarEvent'\r\n<\/pre>\n<p>It doesn&#8217;t work because there is no original <code>start_time=<\/code> method to alias; ActiveRecord creates those methods only when the class is loaded, because it has to read the columns from the database at runtime. So, how do you decorate methods which haven&#8217;t even been created yet? Well, it turns out you have to decorate <code>ActiveRecord::Base.define_attribute_methods<\/code>, which is responsible for creating accessor methods for each model.<\/p>\n<p>There are a couple of ways you can go about doing this, depending on your goals. I wanted to be able to use these enhanced date\/time inputs with any model in our application and trigger it from an initializer, so my approach uses <code>alias_method_chain<\/code> to modify <code>ActiveRecord::Base<\/code> directly.<\/p>\n<pre class=\"brush: ruby; title: ; notranslate\" title=\"\">\r\nmodule TimeAttributeExtensions\r\nmodule ClassMethods\r\n\r\ndef define_attribute_methods_with_time_parsing\r\nif define_attribute_methods_without_time_parsing\r\n\r\ncolumns_hash.each do |name, column|\r\nif [:datetime, :timestamp].include?(column.type)\r\nunless method_defined?(:\"#{name}_without_time_parsing=\")\r\n\r\ndefine_method(\"#{name}_with_time_parsing=\") do |time|\r\nsend(:\"#{name}_without_time_parsing=\", parse_time_from_hash(time))\r\nend\r\n\r\nalias_method_chain :\"#{name}=\", :time_parsing\r\nend\r\nend\r\nend\r\n\r\nreturn true\r\nend\r\nend\r\nend\r\n\r\nmodule InstanceMethods\r\ndef parse_time_from_hash(time)\r\n# Do time parsing here\r\nend\r\nend\r\n\r\ndef self.included(receiver)\r\nreceiver.send(:include, InstanceMethods)\r\nreceiver.extend ClassMethods\r\n\r\nunless receiver.respond_to?(:define_attribute_methods_without_time_parsing)\r\nclass &lt;&lt; receiver\r\nalias_method_chain :define_attribute_methods, :time_parsing\r\nend\r\nend\r\nend\r\nend\r\n<\/pre>\n<p>Ok, so what exactly does that code do? First, we call the original version of <code>define_attribute_methods<\/code>. If that returns true, then we know that the attribute accessor methods have been generated. We loop over each column in the model, checking to see whether it stores either a datetime or a timestamp. If so, this is an accessor we&#8217;re interested in decorating. We do a quick check to make sure we haven&#8217;t already decorated the accessor (which can bite us in development mode), then we redefine the accessor <em>with<\/em> time parsing, and use the magic of <code>alias_method_chain<\/code> to alias our original method but still maintain a reference to it.<\/p>\n<p>At the bottom, we use the <code>Module#included<\/code> callback to add the methods contained in our module, and we once again use <code>alias_method_chain<\/code> to redefine our version of <code>define_attribute_methods<\/code>.<\/p>\n<p>Then, in the initializer, you would write something like<\/p>\n<pre class=\"brush: plain; light: true; title: ; notranslate\" title=\"\">\r\nActiveRecord::Base.send(:include, TimeAttributeExtensions)\r\n<\/pre>\n<p>At this point, any time-based field in any of your models will be able to accept a hash as a value, and properly parse it.<\/p>\n<p>This works well, but one thing I don&#8217;t like about this solution is its rampant use of <code>alias_method_chain<\/code>. There has been some <a href=\"http:\/\/yehudakatz.com\/2009\/03\/06\/alias_method_chain-in-models\/\">debate<\/a> in the Rails community over its validity as a design choice, though there really are no other options if you need to modify all subclasses of <code>ActiveRecord::Base<\/code> (as you would to support a plugin like CalendarDateSelect).<\/p>\n<h3>Another Approach<\/h3>\n<p>If modifying every subclass isn&#8217;t a design requirement, you can clean up the code significantly:<\/p>\n<pre class=\"brush: ruby; title: ; notranslate\" title=\"\">\r\nmodule TimeAttributeExtensions\r\nmodule ClassMethods\r\n\r\ndef define_attribute_methods\r\nif super\r\ncolumns_hash.each do |name, column|\r\nif [:datetime, :timestamp].include?(column.type)\r\ndefine_method(\"#{name}=\") do |time|\r\nsuper parse_time_from_hash(time)\r\nend\r\nend\r\nend\r\n\r\nreturn true\r\nend\r\nend\r\nend\r\n\r\nmodule InstanceMethods\r\ndef parse_time_from_hash(time)\r\n# Do time parsing here\r\nend\r\nend\r\n\r\ndef self.included(receiver)\r\nreceiver.send(:include, InstanceMethods)\r\nreceiver.extend ClassMethods\r\nend\r\nend\r\n<\/pre>\n<p>Notice here we&#8217;re just defining <code>define_attribute_methods<\/code> and calling <code>super<\/code> when we want access to the previous implementation. The same goes for decorating the actual accessor methods.<\/p>\n<p>The downside of this approach is that you have to explicitly include the module in your ActiveRecord models:<\/p>\n<pre class=\"brush: ruby; title: ; notranslate\" title=\"\">\r\nCalendarEvent &lt; ActiveRecord::Base\r\ninclude TimeAttributeExtensions\r\n...\r\n<\/pre>\n<p>As with any decision, the approach you take will depend on your requirements, and tradeoffs you&#8217;re willing to make between explicitness and ease of use.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Recently, I was faced with a quandary. I needed to impart some of our ActiveRecord accessor methods with special functionality, but I also needed to maintain the default behavior for those fields. This was done to support a new input [&hellip;]<\/p>\n","protected":false},"author":6,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_themeisle_gutenberg_block_has_review":false},"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/posts\/205"}],"collection":[{"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/comments?post=205"}],"version-history":[{"count":0,"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/posts\/205\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/media?parent=205"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/categories?post=205"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/tags?post=205"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}