2

Every example I have found of the setter= syntax is very simple, ie;

def name=(value)
  @name = value
end

Can you do something more complex with this syntax? In particular, can you use it without having a corresponding instance variable? Can it be private? Can you do some sort of validation?

I am writing a Rails Controller that can be extended to give basic CRUD functionality to a model that is routed as a resource. It uses a class_attribute to set the name of the Model it will be controlling, and it creates an instance variable based on the name of the Model. It looks something like this;

class ResourceController
  class_attribute :model_class_name

  def new
    resource = self.model_class_name.new
  end

  protected
    def self.init_resource(options={})
      self.model_class_name = options[:model_class_name]
    end 

  private
    def resource
      instance_variable_get(resource_instance_var)
    end

    def resource=(value)
      instance_variable_set(resource_instance_var ,value)
    end

    def resource_instance_var 
      "@#{self.resource_class.name.underscore}".to_sym
    end
end

With the above code structure I get a NoMethodError in the View because the View has a Nil instance variable. Using logger.debug I can trace the stack all the way to resource = ..., but resource= is not being called.

If I drop the sugar and useset_resource(value) everything works fine. Am I asking too much from setter= syntax, or is there some other problem I am missing?

Garrett Berneche
  • 1,049
  • 1
  • 9
  • 13

2 Answers2

1
  • Can you use it without having a corresponding instance variable?

    Yes. Assign it to an instance variable with any name.

  • Can it be private?

    Yes and No. You can define a private method of the form foo=. , but you will never be able to call it in an ordinary way (i.e., without using send or adding parentheses). When an expression is ambiguous between reference/assignment to a local variable and a method call, it will be interpreted as a reference/assignment to a local variable. Setter method of this kind always needs an explicit receiver. That is also why you got the error. The setter method was not called because you did not write the receiver explicitly.

  • Can you do some sort of validation?

    Yes. Put whatever validation code before instance variable assignment.

sawa
  • 165,429
  • 45
  • 277
  • 381
  • This makes great sense. Thank you. One additional question though, you say `.send` is the only way to call the method, but from inside an instance method in the same class, why wouldn't `self.resource =` work? Or, say the method is protected instead of private, why wouldn't `super.resource = ` work in an extending class? – Garrett Berneche Sep 18 '13 at 16:46
  • @BorisStitnicky I think you are right. I missed that. Added that to the answer. – sawa Sep 18 '13 at 16:59
  • @user2754741 I don't understand why you are asking about `self.resource =`. I thought you wanted the method to be private. If it is protected, then I think `super.resource =` will work. – sawa Sep 18 '13 at 17:01
  • My understanding of private is that it is intended to be called only from within the class where it is defined, but as per your answer, even from within the defining class it needs an explicit receiver. From an instance method in a class isn't `self` the explicit receiver? When you call a method without an explicit reciever, isn't `self` the implied receiver? – Garrett Berneche Sep 18 '13 at 17:16
  • "it needs an explicit receiver" => No, it cannot have an explicit receiver. You are writing completely the opposite. – sawa Sep 18 '13 at 17:17
  • Now I am confused. Why can it not have an explicit receiver? For what it is worth, taking the sample code from my question, changing the `new` method to use `self.resource = self.model_class_name.new` works. – Garrett Berneche Sep 18 '13 at 17:48
  • Not only **can it not** but it **cannot**. It is because you specified the method as private. – sawa Sep 18 '13 at 17:49
  • @sawa - Okay, I did a quick google to try and understand your comment, and I understand where you are coming from - but apparently this case is an exception. – Garrett Berneche Sep 18 '13 at 18:06
  • To clarify for any future readers, Ruby is implements `private` such that declaring a method as private prevents it from ever being called on an explicit receiver, thus forcing `self` to always be the implicit receiver. There is one exception to this rule, and it exists to address the assignment ambiguity sawa detailed in the above answer; `private setter=(value)` can be called as `self.setter = value`. See [link] (http://stackoverflow.com/questions/4293215/understanding-private-methods-in-ruby) for more details. – Garrett Berneche Sep 18 '13 at 18:13
  • No. It is not an exception. The setter method `foo=` can be public, under which case you can call it by `self.foo = value`. It becomes private when you make it private. In that case, you cannot call it by `self.foo = value`. – sawa Sep 18 '13 at 18:52
  • @sawa - Code is king. I edited the answer and added a code snippet that proves the exception. Feel free to run it. – Garrett Berneche Sep 18 '13 at 22:58
  • As for setter methods of the form `foo =`, you only have `self.model_class_name =` with explicit receiver, which is public. I don't see the private setter `resource =` used publicly as `self.resource =` anywhere in your code. I don't understand what you are mentioning. – sawa Sep 19 '13 at 03:54
  • "You can define a private method of the form foo=, but you will never be able to call it in an ordinary way" – This is wrong. Private setters *can* be called with an explicit receiver of `self` precisely *because* otherwise they couldn't be called *at all*. – Jörg W Mittag Sep 20 '13 at 01:25
  • Okay. Sorry. I was wrong. I found `parse.y (attrset): "self.foo=x" can be legal even when "foo=" is private.` in http://multivac.fatburen.org/localdoc/ruby18/ChangeLog-1.8.0. – sawa Sep 20 '13 at 04:40
0
def something=( arg )
  # do anything
end

Methods ending with = have certain specifics, making them especially suitable for setters. These are:

  • They expect 1 argument
  • They always automatically return this argument as their return value.

But there is no rule saying that they must be used for setters. If one finds another use for their behavior, there is no limitation on what code can be put in them.

Boris Stitnicky
  • 12,444
  • 5
  • 57
  • 74