Search code examples
ruby-on-railsrubysunspot

Can someone please explain this block of Ruby code and why the variable is null inside the block?


2.0.0p247 :022 >   @post.title
 => "Breakfast Burrito"
2.0.0p247 :023 > solr = Refinery::Books::Book.search { fulltext @post.title.split(' ').join(' OR ') }
NoMethodError: undefined method `title' for nil:NilClass
        from (irb):23:in `block in irb_binding'
        from /usr/local/rvm/gems/ruby-2.0.0-p247@bk_development/gems/sunspot-2.1.0/lib/sunspot/util.rb:208:in `instance_eval'

There's something I'm not picking up. FYI fulltext is a method not a hash, search takes a block, not a hash.

This works...

2.0.0p247 :026 >   solr = Refinery::Books::Book.search { fulltext 'burrito' }
  SOLR Request (13.9ms)  ...

Solution

  • The block passed to .search may be evaluated in a different context with the help of BasicObject#instance_eval like (instance_exec, class_eval, class_exec) method, so that the method fulltext (and other DSL methods) defined for the receiver of instance_eval(maybe Refinery::Books::Book?) can be seen from the block. The magic here is Ruby changes the self binding inside the block. The receiver of instance_eval acts as the self there.

    Instance variable resolution also depends on self. When you refer to @post, you are actually fetching instance variable @post of self. Since the context of self has been modified in the block, and the actual self has no instance variable @post, nil is returned as the default value.

    The fix is assign @post.title to a local variable and use that variable in the .search block.

    title = @post.title
    solr = Refinery::Books::Book.search { fulltext title.split(' ').join(' OR ') }
    

    The block will capture the local binding at the place of its definition. In side the block, Ruby will first look up names in the local binding, and if missing, it will look up methods defined on self.