Setting an instance variable from a block

How do I achieve something like below so that when I set the s variable in a block, it also sets the @subject instance @subject in my class 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 ps #=> ['one', 'two'] end p my_topic.subject #=> nil... want it to be ['one, 'two'] 
+4
source share
3 answers

You cannot do it the way you want. The block argument refers to 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 for the block result so that it looks like this:

     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 ps #=> ['one', 'two'] s end 
  • Use the subject instance_eval method so that the block can explicitly specify an instance variable

+4
source

What you want to do is called pass-by-reference. It is impossible in a ruby. You have two alternatives:

a) Make @subject = blk.call and return s from the block. Usually the easiest 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 does not allow you to call the block several times to set different variables.

+3
source

In addition to these solutions, if you know that ivar is going to remain String / Array / Hash, no matter what you do, you can 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' } 

Although from what I think you are doing, this is the most suitable 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" 

Alternatively, you could do this 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 ps expression you use returns nil .

+2
source

Source: https://habr.com/ru/post/1312912/


All Articles