Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Another first four weeks: concerned and delegated

James’ post from a couple of weeks ago inspired me to write up my own experiences of my first couple of weeks at Pivotal. However, instead of telling you how it felt, I will tell you about stuff I learned.

ActiveSupport::Concern

I have seen and used this pattern of adding both class and instance methods from a module to a class:

module Foo
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def foo
      'classy'
    end
  end

  def foo
    'instancy'
  end
end

class Bar
  include Foo
end

Bar.foo # => "classy"
Bar.new.foo # => "instancy"

While the syntax for including Foo in Bar is nice and succinct, the definition of Foo contains the funny looking “included” hook to also extend the base class.

If you happen to have ActiveSupport around, you can instead define Foo as follows while achieving the same result:

module Foo
  extend ActiveSupport::Concern

  module ClassMethods
    def foo
      'classy'
    end
  end

  def foo
    'instancy'
  end
end

The documentation explains the more convenient feature of Concern, which is module dependency. Using Concern, module dependencies are correctly loaded without the need for you to be concerned.

DelegateClass

In one of the projects I worked on, we were exporting data from a Rails app. We wanted access to the business logic of the models while not littering the application’s code with export related code. We decided to extend the models using DelegateClass (see Jeff’s blog post on DelegateClass as a starting point). And DelegateClass rocked our world, too.

A couple of things to note:

  • It turns out, that these days there is no need to manually ensure that id gets delegated. It works out of the box.
  • Class methods need manual delegation.

Here is a basic delegation example:

require 'delegate'

class Foo
  def to_s
    "foo"
  end
end

module Export
  class Foo < DelegateClass(Foo)
    def bar
      "#{self} bar"
    end
  end
end

foo = Foo.new

foo.to_s # => "foo"

Export::Foo.new(foo).to_s # => "foo"
Export::Foo.new(foo).bar # => "foo bar"

When delegating ActiveRecord models with associations between them, the following pattern can be helpful to make sure you get the delegated classes when traversing relationships:

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

module Export
  class Post < DelegateClass(Post)
    alias_method :original_comments, :comments
    def comments
      original_comments.map { |comment| Export::Comment.new(comment) }
    end

    #some methods for exporting
  end
end

module Export
  class Comment < DelegateClass(Comment)
    alias_method :original_post, :post
    def post
      Export::Post.new(original_post)
    end

    #some more methods for exporting
  end
end

original_post = Post.new(comments: [Comment.new])
original_post.comments.first.class # => Comment(...)
post = Export::Post.new(original_post)
post.comments.first.class # => Export::Comment

#Be aware!
original_post == post # => true
original_post.class == post.class # => false

Comments
  1. oem says:

    Heya – you might also find the ruby rogues podcast about modules of interest. While they are mostly talking about modules in general they are also mentioning Concern and delegation and other interesting stuff.
    I especially like the idea of having all the methods you will delegate too encapsulated in its own module and then included into the object you delegate too (of course that doesn’t always make sense but the idea “feels” neat).
    Hope you are having a great time in Boulder :)

    http://rubyrogues.com/022-rr-when-to-use-modules/

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *