Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting an instance variable from a block

Tags:

ruby

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']
like image 903
RyanScottLewis Avatar asked Jan 22 '23 21:01

RyanScottLewis


2 Answers

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:

  1. 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
    
  2. Have the subject method instance_eval the block so the block can set the instance variable explicitly

like image 78
Chuck Avatar answered Mar 07 '23 18:03

Chuck


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.

like image 34
sepp2k Avatar answered Mar 07 '23 19:03

sepp2k