Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Write Inheritable Attribute versus Basic Assignment in Rails

Just curious what the difference is between these two in a Rails gem:

write_inheritable_attribute(:sample, "sample")
self.sample = "sample"

I couldn't find any good documentation on write_inheritable_attribute, and was just reading through some gem source and found the former used a few times. Thanks!

like image 430
Kevin Sylvestre Avatar asked Nov 27 '10 23:11

Kevin Sylvestre


3 Answers

Subclasses do not inherit instance variables:

>> class B ; @candy = 1 ; end
>> B.instance_variable_get :@candy          # => 1
>> class C < B ; end
>> C.instance_variable_get :@candy          # => nil

In rails, inheritable attributes provide a solution:

>> class B ; end
>> B.write_inheritable_attribute(:candy, 7) # => 7
>> class C < B ; end
>> C.read_inheritable_attribute(:candy)     # => 7
like image 117
Joseph Rodriguez Avatar answered Nov 13 '22 02:11

Joseph Rodriguez


For a simple class or module, there wouldn't be a difference, but with more complex modules that may be loaded with multiple other modules, methods like write_inheritable_attribute can help you modify objects easily and reliably without having to worry about scope, private/protected methods and all kinds of interference from ruby metaprogramming magic like method_missing.

In short, when you write foo.sample = "sample" there are all kinds of things that may happen before, after, or instead of setting the attribute, especially if the object uses ActiveModel or an ORM. When you use foo.write_inheritable_attribute(:sample, "sample") you have much greater control over what happens.

like image 35
bowsersenior Avatar answered Nov 13 '22 02:11

bowsersenior


Inheritable attribute was implemented mainly to address the problem where ruby class variable is shared across the class inheritance. Consider this example

class Counter
  @@count = 0
  def self.count
    @@count
  end

  def self.increment
    puts "==> #{self} increment"
    @@count += 1
  end
end

class DogCounter < Counter
end

puts "Counter.count:    #{Counter.count}"
puts "DogCounter.count: #{DogCounter.count} -> nice, DogCounter inherits @@count from Counter"

DogCounter.increment
puts "DogCounter.count: #{DogCounter.count} -> as expected"
puts "Counter.count:    #{Counter.count} -> but Counter.count is also changed!"

Counter.increment
puts "Counter.count:    #{Counter.count}"
puts "DogCounter.count: #{DogCounter.count} -> @@count is shared with all the descendants of Counter"

This will produce this output

Counter.count:    0
DogCounter.count: 0 -> nice, DogCounter inherits @@count from Counter
==> DogCounter increment
DogCounter.count: 1 -> as expected
Counter.count:    1 -> but Counter.count is also changed!
==> Counter increment
Counter.count:    2
DogCounter.count: 2 -> @@count is shared with all the descendants of Counter

Note that since Rails 3.2 write_inheritable_attribute has been removed. See http://dev.mensfeld.pl/2012/01/upgrading-to-rails-3-2-0-from-rails-3-1-3/

With class attribute (what used to be inheritable attribute) we can implement something like this:

class Counter
  class_attribute :count
  self.count = 0

  def self.increment
    puts "==> #{self} increment"
    self.count += 1
  end
end

class DogCounter < Counter
end

puts "Counter.count:    #{Counter.count}"
puts "DogCounter.count: #{DogCounter.count} -> nice, DogCounter inherits count from Counter"

DogCounter.increment
puts "DogCounter.count: #{DogCounter.count} -> as expected"
puts "Counter.count:    #{Counter.count} -> nice, it doesn't change count for Counter"

Counter.increment
puts "Counter.count:    #{Counter.count}"
puts "DogCounter.count: #{DogCounter.count} -> now each subclass can have their own class attribute that inherits default value from the superclass"

This will produce this output

Counter.count:    0
DogCounter.count: 0 -> nice, DogCounter inherits count from Counter
==> DogCounter increment
DogCounter.count: 1 -> as expected
Counter.count:    0 -> nice, it doesn't change count for Counter
==> Counter increment
Counter.count:    1
DogCounter.count: 1 -> now each subclass can have their own class attribute that inherits default value from the superclass
like image 40
Reynard Avatar answered Nov 13 '22 00:11

Reynard