February 24, 2015 by Daniel P. Clark

Writing methods for both class and instance levels

When perusing the Rails documentation for various methods I noticed something that seemed odd to me.  The method descriptions seemed to show an extra parameter as the first parameter that I was never using.  It’s strange when you use the methods all the time and it doesn’t seem to be implemented the way the documentation describes it.  So what’s going on here?

Well as it turns out the method is defined twice, but only in name.  Once at the class level, and then at the instance level; where they pass the work on forward.  How is this accomplished?  Well you can access all class level methods on the .class instance variable… why do I call it a variable?  Because it’s assigned to the Class from which the instance was instantiated.

"a string" == String
# => false
"a string".class == String
# => true

So any class level methods available in the constant of the class name (eg: String) is also available on the instance variable .class .

class String
  def self.spell(str)
    str.split('')
  end
end

String.methods - Object.methods
# => [:try_convert, :spell] 
"".class.methods - Object.methods
# => [:try_convert, :spell] 
String.spell "hello"
# => ["h", "e", "l", "l", "o"]
"hello".spell
# NoMethodError: undefined method `spell' for "hello":String
"hello".class.spell "cow"
# => ["c", "o", "w"]

So if we wanted to call the method spell on the string “hello” we would need to define a class instance method to do this.

class String
  def self.spell(str)
    str.split('')
  end

  def spell
    self.class.spell(self)
  end
end

"hello".spell
# => ["h", "e", "l", "l", "o"] 
String.spell "hello"
# => ["h", "e", "l", "l", "o"] 
"cow".class.spell "hello"
# => ["h", "e", "l", "l", "o"] 

Here you can see I’ve defined a class instance method spell without any parameters to be passed.  That is because we are passing self, the instance of the string itself, as the parameter to the class level method spell.  This is why you will see definition on methods with the object written in as a first parameter.  If you’re calling the method on the class itself you will need that first parameter.  If it’s an instance of the class then it evaluates self as the first method.

Mixins

When implementing this with modules to be included into other classes you need to write just a bit more.

module Cat
  def self.included(base)
    def base.speak(words)
      [words, "meow"].join
    end
  end

  def speak
    self.class.speak(self)
  end
end

class String
  include Cat
end

Now you have a cat that will always get the last word when you speak ;-).  Defining self.included(base) is the way Ruby lets you perform the same action as if you were writing a def self.my_method right in the class it gets included into.  The variable base is the class that called include.  So the last part in the example above where include Cat is called inside the String class will make the base variable String.

module Cat
  def self.included(base)
    puts base
  end
end

class String
  include Cat
end
# => String

So defining methods inside the self.include(base) definition on base is the same thing as writing the methods into the class.  self is the current Object you’re in wherever you are.  So in the example above self.include is the same as Cat.include .  And anything within the included defined on base is like self within the class called: def base.speak # =>  def String.speak

No Shovel

If you want class methods to be available on the .class instance variable then you can’t use the shovel operator (<<) like when you have seen class << self.  Why is that?  The reason is because you are not actually writing methods into the class, but you’ve opened up a singleton instance and are writing methods directly into there.  It’s easier if you see it.

module Crackle
  puts self
  puts self.name
end
# => Crackle
# => Crackle

module Crackle
  class << self
    puts self
    puts self.name
  end
end
# => Crackle
# =>

You’ll notice the name came out blank for the second item.  That’s because when using the shovel operator << you are opening a singleton instance.  These are anonymous objects.  When defining a method in here you’re only working on a single instance of the class/module/object in which you define it.  So in the second example the module Crackle isn’t only a Module now, but it has an anonymous singleton object which will only be accessed when you call the method on the class Crackle.  Nothing that includes Crackle, or extends it, will get these defined methods.  For an excellent presentation on this see Dave Thomas talk on “The Ruby Object Model

Lets look at it not work.

module Crackle
  class << self
    def pizza
      1
    end
  end
end

class String
  include Crackle
end

String.pizza
# NoMethodError: undefined method `pizza' for String:Class

"".pizza
# NoMethodError: undefined method `pizza' for "":String

Crackle.pizza
 => 1

So even though we included Crackle into String we inherited nothing from the singleton instance that exists on the module Crackle.  So don’t use the shovel operator for inheritance.

Rails

Rails has implemented a way for mixins to be done with what they call concerns.  You need to consider some things when using Rails concerns.  They aren’t as they seem.  They perform some “management” of your mixins rather than being a pure mixin feature.  For more details on that read Corey Haines blog post: Why I Don’t Use ActiveSupport::Concern or, why I actively replace ActiveSupport::Concern with Ruby in codebases I work on

Here’s how you can change your mixin to a ActiveSupport::Concern.  You take something like:

module Dog
  def self.included(base)
    def base.speak(words)
      [words, "woof"].join
    end
  end

  def speak
    self.class.speak(self)
  end
end

And change it to

module Dog
  extend ActiveSupport::Concern
  included do
    def self.speak(words)
      [words, "woof"].join
    end
  end

  def speak
    self.class.speak(self)
  end
end

So consider for yourself if it’s what you want to do.  Notice we’re using self instead of a parameter like base.  In a standard mixin you can’t use self to refer to the caller.  But with Rails’ concern self becomes as if you are in the class that is/(will be) inheriting this.

Summary

So now you know how to write methods in a DRY way and make them available on both instance and class levels.  I like to use this wherever I can.  self can be passed where it is the object that will be worked on.  This works on objects where they hold any kind of state, query, or action.  Even if size only make sense to you on an instance of the Object, it could just as easily be handed self via the class method and get the same job done.  It’s up to you whether you like this as a pattern to follow.  Yes it may be slightly confusing at first have the same method name take a different number of parameters.  But it’s easy to see if it’s an instance or not based on the parameters or the error message.  Just think about and try it out.

As always, I hope this was both enjoyable and insightful for you!  Please feel free to comment, share, subscribe to my RSS Feed, and follow me on twitter @6ftdan!

God Bless!
-Daniel P. Clark

Image by Ian Sane via the Creative Commons Attribution 2.0 Generic License.

#class#inheritance#mixin#rails#ruby#self#singleton

Comments

  1. Josh Bodah
    February 26, 2015 - 1:24 pm

    Good post!

    I can’t say that I’m a big fan of the “included” and “extended” callbacks, mostly because it means that something different happens depending on whether you use the module via “include” or “extend”. I would only recommend using them when you are using them to manipulate the singleton as in the examples above (e.g. we use the included callback to modify the singleton which our module relies on)

    Some things that help me think about this sort of thing:

    1) Only modules/classes can be the owners of methods

    2) Every object has a singleton class (note: modules/classes are objects with a singleton too). When you call “self.whatever” you are referencing the singleton

    3) “include” will copy all of the instance methods of the target module into the class. “extend” will copy all of the instance methods of the target module into the singleton class

    4) instance methods defined in a class are callable to instances of that class

    There are a couple more interesting about Ruby’s design regarding methods being bound vs. unbound, when that binding is made, and how a method’s owner and receiver are determined. This can make it a bit more difficult to do things like transplant one method from one class to another or dynamically wrap and unwrap methods (which is trivial in languages like Javascript).

    • Daniel P. Clark
      February 26, 2015 - 11:04 pm

      Thanks! Excellent short list.

      I’ve run into a few cases where I’ve needed to get the binding from a different scope. That can be interesting.

  2. Yevhene Shemet
    February 26, 2015 - 2:25 pm

    2 important notices about ActiveSupport::Concern:
    1. ActiveSupport::Concern can be included to another ActiveSupport::Concern.
    2. It is better to use module ClassMethods inside ActiveSupport::Concern instead of using self.method in included block.

    • Daniel P. Clark
      February 26, 2015 - 10:44 pm

      Thanks for tips! Might I ask you why you believe your point #2 to be better?

      • Yevhene Shemet
        March 2, 2015 - 4:42 am

        Because (1) don’t work without (2). Because “included” would be triggered for concern included in, but not for target class.
        You can read detailed description of ActiveSupport::Concern in Chaper 10 of Metaprogramming Ruby 2: Program Like the Ruby Pros in (https://pragprog.com/book/ppmetr2/metaprogramming-ruby-2).

  3. estum
    March 1, 2015 - 3:35 am

    When method doesn’t dependent on the instance’s condition, but should be available for both layers, I declare it as a class method and simply use `delegate :some_method, :to => :class` construction. When I want to use it just as shorthand, I add `private :some_method` after.

    If the method will be dependent on the instance’s state, then, firstly, I’ll decide whether this is really necessary to have it in the class layer, and whether it complex to move this method into a separate class (using `SimpleDelegator`, some kind of `#call` object or something else). In the most cases the answer is “no”.

    • Daniel P. Clark
      March 1, 2015 - 9:23 am

      Very good points. In one of my gems I’m not able to use delegate as I’m supporting Ruby 1.8. But I do really like to use it when the situation allows for it.

  4. john
    April 22, 2020 - 9:48 pm

    great post

Leave a Reply

Your email address will not be published / Required fields are marked *