Feature #8564
openExtend Module#attr... methods
Description
Extend #attr_reader, #attr_writer, #attr_accessor syntax to accept default values, such as:
attr_reader foo: 42, bar: 43
Possibility of closures evaluated at initialization time might also be considered:
attr_reader baz: -> { Time.now }, quux: 42
Updated by phluid61 (Matthew Kerwin) over 11 years ago
+1 very useful pattern
Updated by Anonymous over 11 years ago
-1 to overloading the same syntax with proc/lambda initialization.
Updated by matz (Yukihiro Matsumoto) over 11 years ago
We need to clarify how this intervene with #initialize
. Any opinion?
Matz
Updated by phluid61 (Matthew Kerwin) over 11 years ago
matz (Yukihiro Matsumoto) wrote:
We need to clarify how this intervene with
#initialize
. Any opinion?Matz
My suggestion is that it take effect before any explicit initialize
method.
Examples:
class A
attr_accessor foo: 3
def initialize
p @foo if defined? @foo
@foo = 4
end
end
p A.new.foo #=> prints 3, then 4
class B0
attr_accessor foo: 3
end
class B1 < B0
def initialize
p @foo if defined? @foo
@foo = 4
end
end
p B1.new.foo #=> prints 3, then 4
class C0
def initialize
p @foo if defined? @foo
@foo = 4
end
end
class C1 < C0
attr_accessor foo: 3
end
p C1.new.foo #=> prints 3, then 4
Updated by TylerRick (Tyler Rick) almost 10 years ago
I would love to see this added to Ruby too, so that I don't have to repeat myself by defining attributes in one place and then initializing them later in initialize
(as discussed in #5825 and #8563).
In the meantime, however, I've come across the fattr gem, which does exactly the same thing (plus a few extra things like defining a foo!
method to re-initialize; I don't think attr_reader
should do those extra things). (By the way, there was a great RubyTapas episode (subscription required) about fattr that got me excited about using it.)
Note that it's possible to support both initializing an attribute to a Proc and using a block to initialize a attribute to an expression that is evaluated during object initialization.
Here's how fattr solves that problem... If you pass a block to fattr
, it is evaluated during initialization:
class C
fattr foo: 42
fattr(:foo_2) { foo * 2 } # evaluated during initialization
fattr(:time) { Time.now } # evaluated during initialization
end
p C.new.foo #=> 42
p C.new.foo_2 #=> 84
p C.new.time #=> 2015-02-05 ...
... whereas if you pass a block as the value for a hash key, it leaves it as a Proc and doesn't evaluate it:
class C
fattr my_proc: ->{ 'my_proc' }
end
p C.new.my_proc #=> #<Proc:0x0...>
p C.new.my_proc.() #=> 'my_proc'
I can't think of any downside to supporting the block behavior for attr_accessor
. It doesn't appear that the block syntax of attr_accessor
is used for anything currently...
class C2
attr_accessor(:foo_2) { foo * 2 } # The block is never called
end
c = C2.new
p c.foo_2 #=> nil
Otherwise, you would be forced to add an initialize
method any time you needed to initialize something to an expression that needed to be evaluated at initialization time, which would require duplication and mixing of initialization methods, which I think should be avoided:
class C
fattr foo: 42
attr_accessor :foo_2
def initialize
@foo_2 = foo * 2
end
end
p C.new.foo #=> 42
p C.new.foo_2 #=> 84