Class Side

Get Version

1.0.2

→ ‘contextr

In Ruby there are multiple ways of defining behaviour on the class side. That are messages that are send to the class, not to the instance. Class side behaviour is often useful for functional methods, i.e. methods that do not rely on inner state and have no side effects. Mathematical functions have these characteristics - that is where the name probably comes from.

Note: Java programmers may know these methods as static. It is similar but not exactly the same.

Using def self.method_name

The simpliest way of defining class side behaviour is prepending self. to the method definition. This way, the method is attached to the surrounding class and not instance. A simple example:

class SimpleMath
  def self.pi
    3.14159265
  end
end

example do
  result_of(SimpleMath.pi) == 3.14159265
end

Using class << self

When you are having lots of class side methods as well as instance side ones, it can be difficult to spot the little self. in front of the method name. Probably you like to group them more explicitly. You could use Ruby’s eigenclass principle for that. It will look like the following:

class SimpleMath
  class << self
    def e
      2.71828183
    end
  end
end

example do
  result_of(SimpleMath.e) == 2.71828183 
end

Note: Eigenclasses are also known as singleton class or meta class. I prefer eigenclass, because it not used with different meanings in other contexts, which eases talking about it with experts in different languages.

Using a module

For even more encapsulation you could also use modules and extend the class definition with them. I am using extend here on purpose. Module’s include method adds the behaviour to the instance side, extend to the class side. Or to rephrase it: Module’s include method adds the behaviour to instances, extend to the class itself.

class SimpleMath
  module ClassMethods
    def golden_ratio
      1.6180339887
    end
  end

  extend ClassMethods
end

example do
  result_of(SimpleMath.golden_ratio) == 1.6180339887 
end

The last method is e.g. used in the web framework Ruby on Rails. Often a variation of it is used to define class and instance side behaviour for mixin modules

module MathMixin
  def counter
    @counter ||= 0
    @counter += 1
  end

  module ClassMethods
    def sqrt(x)
      x ** 0.5
    end
  end

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

class SimpleMath
  include MathMixin
end

example do
  result_of(SimpleMath.sqrt(4)) == 2

  my_simple_math = SimpleMath.new
  result_of(my_simple_math.counter) == 1
  result_of(my_simple_math.counter) == 2
end

This is regarded as the most elegant way of defining class and instance methods for a mixin module. And the basic functionality is the same as in the previous example.

After we now know how to define class side behaviour, everybody is curious to know how to extend this behaviour using context-oriented programming and ContextR.

Additionial, context-dependent behaviour is defined in in_layer blocks. These are then attached to the layer, in which the behaviour should reside. For examples on the instance side have a look at the bottom of test_introduction.

Let’s look how we can achieve the same on the class side for each of the different methods of defining class side behaviour.

Using def self.method_name

Okay, we won’t get rid of the modules, used to encapsulate the context-dependent behaviour, so the extension is a bit noisier, than the basic notation.

class SimpleMath
  class << self
    in_layer :access_control do
      def pi
        "You are not allowed to access this method"
      end
    end
  end
end

But we can use the same principles, like we did for the instance side - just in side the singleton class.

example do
  result_of(SimpleMath.pi) == 3.14159265

  ContextR::with_layer :access_control do
    result_of(SimpleMath.pi) == "You are not allowed to access this method" 
  end
end

Using class << self

When your using the eigenclass, you are able to use to good old in_layer to manage the extension.

class SimpleMath
  class << self
    in_layer :english do
      def e
        "Euler's constant"
      end
    end

    in_layer :german do
      def e
        "Eulersche Zahl"
      end
    end
  end
end

example do
  result_of(SimpleMath.e) == 2.71828183 

  ContextR::with_layer :german do
    result_of(SimpleMath.e) == "Eulersche Zahl" 
  end
  ContextR::with_layer :english do
    result_of(SimpleMath.e) == "Euler's constant" 
  end
end

Using a module

It is only natural to have the same syntax to extend a class using in module for context-dependent behaviour. But for the sake of completeness, I will attach another example.

class SimpleMath
  module ClassMethods
    in_layer :exact_computation do
      def golden_ratio
        sleep(0.01) # In real life this would take a bit longer, 
                    # but I don't have the time.
        1.6180339887_4989484820_4586834365_6381177203_0917980576 
      end
    end
  end
end

example do
  result_of(SimpleMath.golden_ratio) == 1.6180339887

  ContextR::with_layer :exact_computation do
    result_of(SimpleMath.golden_ratio) == 
                  1.6180339887_4989484820_4586834365_6381177203_0917980576 
  end
end

Conclusion

In general, there are two options to define context-dependent class side behaviour. Use in_layer in the eigenclass or use extend anywhere else. Both options result in the same behaviour, just like the different options in plain ruby look different, but have the same effect.

The programmer is free to use, whatever suites best. This is still Ruby.

28th April 2008
Theme extended from Paul Battley