从块设置实例变量

时间:2010-06-15 19:49:36

标签: ruby

我如何实现类似下面的内容,以便在我在块中设置s变量时,它还在我的Topic类中设置@subject实例变量?

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']

3 个答案:

答案 0 :(得分:4)

你不能按照你想要的方式去做。 block参数引用与实例变量相同的对象,但它们是完全不同的变量,设置一个永远不会设置另一个。有两种选择:

  1. 将变量设置为块的结果,如下所示:

    class Topic
      def subject
        @subject = yield if block_given?
        @subject unless block_given?
      end
    end
    

    并在街区内:

    my_topic.subject do
      s = ['one', 'two', 'three']
      s.pop
      p s #=> ['one', 'two']
      s
    end
    
  2. 使用subject方法instance_eval块,以便块可以显式设置实例变量

答案 1 :(得分:3)

您想要做的是被称为传递引用。这在红宝石中是不可能的。您有两种选择:

a)执行@subject = blk.call并从块中返回s。通常是最简单,最干净的选择。

b)代替s =执行@subject =,然后使用instance_eval(&blk)代替blk.call。这将设置@subject变量,但是它需要主题方法的用户知道@subject变量,并且它不允许您多次调用块来设置不同的变量。

答案 2 :(得分:2)

除了给定的解决方案,如果您知道ivar将保留String / Array / Hash,无论如何,您可以执行以下操作:

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' }

虽然我猜你正在做的事情,但这是最合适的代码:

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"

另外,你可以在没有阻止的情况下实现这一点:

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"

请注意,您使用的语句p s会返回nil