Reputation: 14046
How would I achieve something like below so that when I set the s
variable within the block, it also sets the @subject
instance variable in my Topic class?
class Topic
def subject(&blk)
blk.call(@subject) if block_given?
@subject unless block_given?
end
end
my_topic = Topic.new
p my_topic.subject #=> nil
my_topic.subject do |s|
s = ['one', 'two', 'three']
s.pop
p s #=> ['one', 'two']
end
p my_topic.subject #=> nil... want it to be ['one, 'two']
Upvotes: 4
Views: 2692
Reputation: 25994
In addition to the given solutions, if you know the ivar is going to stay String/Array/Hash, whatever, you could do the following:
class Topic
def subject
@subject ||= 'sane default'
if block_given? then yield(@subject)
else @subject
end
end
end
t = Topic.new
t.subject { |s| s.replace 'fancy stuff' }
Though from what I guess you are doing, this is the most appropriate code:
class Topic
def subject
return @subject unless block_given?
@subject = yield(@subject)
end
end
t = Topic.new
t.subject { |s| 'fancy stuff' }
t.subject { |s| "very #{s}" }
t.subject # => "very fancy stuff"
Also, you could actually do that without a block:
class Topic
def subject(value = nil)
@subject = value % @subject if value
@subject = yield @subject if block_given?
@subject
end
end
t = Topic.new
t.subject 'fancy stuff' # => "fancy stuff"
t.subject 'very %s' # => "very fancy stuff"
t.subject { |s| s.sub 'fancy', 'freaky' } # => "very freaky stuff"
Keep in mind that the statement p s
you are using returns nil
.
Upvotes: 2
Reputation: 237110
You can't do it the way you want. The block argument references the same object as the instance variable, but they are completely different variables and setting one will never set the other. There are two options:
Set the variable to the result of the block, so it would be like:
class Topic
def subject
@subject = yield if block_given?
@subject unless block_given?
end
end
and inside the block:
my_topic.subject do
s = ['one', 'two', 'three']
s.pop
p s #=> ['one', 'two']
s
end
Have the subject
method instance_eval
the block so the block can set the instance variable explicitly
Upvotes: 4
Reputation: 370435
What you want to do is called pass-by-reference. That's not possible in ruby. You have two alternatives:
a) Do @subject = blk.call
and return s from the block. Usually the simplest and cleanest option.
b) Instead of s =
do @subject =
in the block and then use instance_eval(&blk)
instead of blk.call
. This will set the @subject
variable, however it requires the user of the subject method to know about the @subject
variable and it doesn't allow you to call the block multiple times to set different variables.
Upvotes: 3