开发者

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"

开发者_C百科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!


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


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.


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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜