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.
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.
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).
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.
john
April 22, 2020 - 9:48 pm
great post