views:

516

answers:

3

I want to do some checking in a writer accessor. My first idea was returning a boolean.

class MyClass
  def var=(var)
    @var = var
    # some checking
    return true
  end
end

m = MyClass.new
retval = (m.var = 'foo')

=> "foo"

Can I set a return value in a writer accessor? If yes, how can I get this value?

+8  A: 

I would use set_var(var) instead of what you are trying to do, an attribute writer is assumed to just work. What you are trying to do is nonstandard and non-obvious to the next poor person to use your code. (It may just be yourself) I would throw an exception if bad input is sent or something rather exceptional happens.

You want this behavior

Correct
>>temp = object.var = 7
=> 7 

Wrong
>>temp = object.var = 7
=> false

The = operator should always return the value that was passed to it. Ruby uses implicit returns which is uncommon in programming languages. Double check the returns when you use method=().

epochwolf
I agree completely ;)
Christoph Schiessl
+2  A: 

Also, per your example, to clean things up you could do the following:

class MyClass
  attr_accessor :var
end

m = MyClass.new
m.var = "Test"
puts m.var # => "Test"

In regards to your question though, are you asking if you can return a value other than what you set for the value of the object?

Update

Check out this project: Validatable. It adds ActiveRecord like validations to your class attributes.

Quick scenario:

class Person
  include Validatable
  validates_presence_of :name
  attr_accessor :name
end

class PersonPresenter
  include Validatable
  include_validations_for :person
  attr_accessor :person

  def initialize(person)
    @person = person
  end
end

presenter = PersonPresenter.new(Person.new)
presenter.valid? #=> false
presenter.errors.on(:name) #=> "can't be blank"
mwilliams
I need some checking before I save the value, which is why I'm not using attr_accessor. Yes, you understood my question. A better question might be "how should the class respond to an invalid value?"
Christian Lescuyer
+3  A: 
class Test
  def var=(var)
    @var = var
    return true
  end
end

t1, t2 = Test.new, Test.new

t1.var = 123 # evaluates to 123

# Why is it impossible to return something else:
t1.var = t2.var = 456

As stated in the comment: I believe it's impossible to change the return value in order to allow chained assignments. Changing the return value would probably be unexpected by the majority of Ruby programmers anyway.

Disclaimer: I tested the code above but I've found no explicit references to verify my statement.

Update

class Test
  def method_missing(method, *args)
    if method == :var=
      # check something
      @var = args[0]
      return true
    else
      super(method, *args)
    end
  end

  def var
    @var
  end
end

t = Test.new
t.var = 123 # evaluates to 123
t.does_not_exists # NoMethodError

It really seems to impossible! The above sample suggests that the return value isn't related to the var= method at all. You cannot change this behavior - it's the fundamental way how Ruby assignments work.

Update 2: Solution found

You could raise an exception!

class Test
  def var=(var)
    raise ArgumentError if var < 100 # or some other condition
    @var = var
  end
  def var
    @var
  end
end

t = Test.new
t.var = 123 # 123
t.var = 1 # ArgumentError raised
t.var # 123
Christoph Schiessl
Exception raising is definitely the way to do this.
rampion