Arthur Cavallari
Arthur Cavallari

Reputation: 101

Method sometimes returns true, sometimes returns instance of an object

So this method sometimes returns the instance of the Resource class, and sometimes it returns "true".

It seems I "fixed" the problem by adding the return keyword, but I am not too sure why it was happening in the first place. I stepped through the code using RubyMine's debugger and in both cases it seems to follow the same path, yet it returned a different value.

I have only just started coding in Ruby, so it's all a mystery to me..

It seems that the problem only happens when the validation passes.

Even though this is fixed, I would like some clarification as to why it works, or perhaps why it didn't work in the first place.

Here's the problematic method:

  def self.create_or_update(attributes = nil, options = {}, &block)
    if attributes.is_a?(Array)
      attributes.collect { |attr| create_or_update(attr, options, &block) }
    else
      begin
        object = Resource.find_by_article_id(attributes[:article_id])
        object.update_attributes!(attributes, options)
      rescue
        object = new(attributes, options, &block)
      ensure
        object.save
        return object # for some reason, if you just leave out the "return" keyword, sometimes it returns "true" instead of an instance of the Resource class.. ??????
      end
    end
  end

EDIT:

So after some more debugging and talking to my supervisor we've figured out why it was producing the odd behaviour.

The last statement inside 'ensure' wasn't being treated as the return value, even though it was the last statement to be processed in the method. What actually got treated as the return value was the return value from object.update_attributes!(attributes, options)

So basically..

def method
  true
ensure
  false
end

This method returns true, which seems counter-intuitive as 'false' gets executed last.

It seems to be an inconsistency in the language specification/method return value, doesn't it?

Upvotes: 0

Views: 400

Answers (3)

Arthur Cavallari
Arthur Cavallari

Reputation: 101

So after some more debugging and talking to my supervisor we've figured out why it was producing the odd behaviour.

The last statement inside 'ensure' wasn't being treated as the return value, even though it was the last statement to be processed in the method. What actually got treated as the return value was the return value from object.update_attributes!(attributes, options)

So basically..

def method
  true
ensure
  false
end

This method returns true, which seems counter-intuitive as 'false' gets executed last.

Upvotes: 0

lalameat
lalameat

Reputation: 754

As Kieran Andrews said, one thing is if and else return different thing, though collect should return a array.

Another thing is if the record doesn't exist, object is nil, then nil.update_attributes! will raise exception, it is easy to judge if the object is nil instead of catch exception. If update_attributes! raise exception, you just "new" and not to do anything other? If validation is failed, can "new" guarantee the "new" is valid and is it what you want?

If the record doesn't exist then "new" is easy to understand, but i am not sure if update_attributes! failed also need "new.

Finally, if

object.update_attributes!(attributes, options)

success, don't have to "object.save".

Upvotes: 1

Kieran Andrews
Kieran Andrews

Reputation: 5885

First up, a tip: I wouldnt use rescue without specifying the exception that you want to catch. This could cause all sorts of problems.

The reason this method returns different values is that in a ruby method, the value of the last statement executed is returned.

http://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Method_Calls#Return_Values

In your code you have a condition and also a rescue and an ensure. Depending on which part of the code you end up in, a different value will be returned. For example the attributes.collect will return something different than new

If you put object on the last line of your method:

def self.create_or_update(attributes = nil, options = {}, &block)
    if attributes.is_a?(Array)
      ..
    else
      begin
        ..
      end
    end
    object
  end

Then object will always be returned.

Upvotes: 1

Related Questions