0

I hope for an explanation why the example code behaves the way it does. The example is taken from a Rails project, where the Example class has a database field called settings with type text.

As far as I know Ruby, calling settings, same as self.settings, would read the database field (as there is no local settings variable), while settings = ... would create a local variable and self.settings = ... would set a value in the database. With this knowledge, I can't explain the behavior I encountered and am trying to show in this example. I hope the inline comments are sufficient to explain the cases.

class Example
    # db field: t.text "settings", with current value "" (empty string)

    def example
      details = {status: "something"}
      
      self.settings = settings.merge(details) # => {:status=>"something"}
      settings # => {:status=>"something"}

      # ok so far

      settings = settings.merge(details) # => NoMethodError: undefined method `merge' for nil:NilClass

      # why would `settings` after the equal sign suddenly be nil? It wasn't above.
    end

    def example2
      details = {status: "something"}
        
      # running this not valid line on purpose
      settings = settings.merge(details) # => NoMethodError: undefined method `merge' for nil:NilClass
      
      self.settings = settings.merge(details) # => NoMethodError: undefined method `merge' for nil:NilClass

      # after running the second (merge) line, the valid third (merge) line (was valid in the first `example` method) suddenly throws an error, unlike the first time.
      # why isn't it valid anymore?
    end
end
Linus
  • 4,643
  • 8
  • 49
  • 74
  • Running `settings = whatever` creates a local variable `settings` which shadows the method `settings`. If you want to call the method and not the variable, use syntax that unambiguously refers to the method. For example, `settings()` or `self.settings` – Sergio Tulentsev Jul 20 '20 at 09:27
  • 1
    it's right there in your own question: "As far as I know Ruby, calling `settings`, same as `self.settings`, would read the database field (as there is no local `settings` variable)". But what happens when there _is_ a local variable `settings`? – Sergio Tulentsev Jul 20 '20 at 09:29
  • @SergioTulentsev my questions were not answered and the linked question is not related to my questions either. I would like to ask you to undo the closing of the question. – Linus Jul 20 '20 at 11:04
  • My first comment does answer your question. That line creates a local variable that shadows the method with the same name from that point on. And in the linked post you can read about detailed mechanics of this. If you still feel that this doesn't answer your question, can you clarify what exactly remains unanswered? – Sergio Tulentsev Jul 20 '20 at 11:25
  • @Linus It does but I agree it's not obvious. `self.settings = settings.merge` is call to an existing method `settings=` and `settings` (equivalent to `settings()`) is a call to an existing method `settings`. `settings = settings.merge(details)` is a declaration of a local variable `settings` initialized to `nil` which shadows over existing `settings=` and `settings` methods. In the end, it's kind of like if you did `settings = nil.merge(details)`. – Jiří Pospíšil Jul 20 '20 at 11:28

0 Answers0