119

I couldn't really find this in Rails documentation but it seems like 'mattr_accessor' is the Module corollary for 'attr_accessor' (getter & setter) in a normal Ruby class.

Eg. in a class

class User
  attr_accessor :name

  def set_fullname
    @name = "#{self.first_name} #{self.last_name}"
  end
end

Eg. in a module

module Authentication
  mattr_accessor :current_user

  def login
    @current_user = session[:user_id] || nil
  end
end

This helper method is provided by ActiveSupport.

JasonOng
  • 3,268
  • 3
  • 21
  • 17

2 Answers2

195

Rails extends Ruby with both mattr_accessor (Module accessor) and cattr_accessor (as well as _reader/_writer versions). As Ruby's attr_accessor generates getter/setter methods for instances, cattr/mattr_accessor provide getter/setter methods at the class or module level. Thus:

module Config
  mattr_accessor :hostname
  mattr_accessor :admin_email
end

is short for:

module Config
  def self.hostname
    @hostname
  end
  def self.hostname=(hostname)
    @hostname = hostname
  end
  def self.admin_email
    @admin_email
  end
  def self.admin_email=(admin_email)
    @admin_email = admin_email
  end
end

Both versions allow you to access the module-level variables like so:

>> Config.hostname = "example.com"
>> Config.admin_email = "admin@example.com"
>> Config.hostname # => "example.com"
>> Config.admin_email # => "admin@example.com"
phil pirozhkov
  • 4,740
  • 2
  • 33
  • 40
Avdi
  • 18,340
  • 6
  • 53
  • 62
  • 2
    In your examples, you explain that `mattr_accessor` would be short for class instance variables (`@variable`s), but the source code seems to reveal they are actually setting/reading class variables. Could you please explain this difference? – sandre89 Jan 17 '18 at 01:19
40

Here's the source for cattr_accessor

And

Here's the source for mattr_accessor

As you can see, they're pretty much identical.

As to why there are two different versions? Sometimes you want to write cattr_accessor in a module, so you can use it for configuration info like Avdi mentions.
However, cattr_accessor doesn't work in a module, so they more or less copied the code over to work for modules also.

Additionally, sometimes you might want to write a class method in a module, such that whenever any class includes the module, it gets that class method as well as all the instance methods. mattr_accessor also lets you do this.

However, in the second scenario, it's behaviour is pretty strange. Observe the following code, particularly note the @@mattr_in_module bits

module MyModule
  mattr_accessor :mattr_in_module
end

class MyClass
  include MyModule
  def self.get_mattr; @@mattr_in_module; end # directly access the class variable
end

MyModule.mattr_in_module = 'foo' # set it on the module
=> "foo"

MyClass.get_mattr # get it out of the class
=> "foo"

class SecondClass
  include MyModule
  def self.get_mattr; @@mattr_in_module; end # again directly access the class variable in a different class
end

SecondClass.get_mattr # get it out of the OTHER class
=> "foo"
Community
  • 1
  • 1
Orion Edwards
  • 121,657
  • 64
  • 239
  • 328
  • This was a gotcha that bit me pretty hard when setting default_url_options directly (a mattr_accessor). Once class would set them one way and another would set them a different way, thus creating invalid links. – Eric Davis Feb 25 '09 at 07:20
  • In latest version of Rails `cattr_*` are now aliases for `mattr_*`. See the [`cattr_accessor` source](https://github.com/rails/rails/blob/4-2-stable/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb) – ouranos Apr 13 '16 at 02:03