# Redefining @my\_attr=

**URL:** <https://www.ruby-forum.com/t/redefining-my-attr/160029>\
**Category:** Ruby\
**Created:** [February 11, 2009, 5:02am UTC](https://www.ruby-forum.com/t/redefining-my-attr/160029 "2009-02-11T05:02:37Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![mikec](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/977dab/32.png) [@mikec](https://www.ruby-forum.com/u/mikec)\
**Post date:** [February 11, 2009, 5:02am UTC](https://www.ruby-forum.com/t/redefining-my-attr/160029/1 "2009-02-11T05:02:37Z")

</div>

I want to write a generic validator that ensures an attr value is in a  
list before assigning, and if not, assigns a default. It works for  
self.attrib=‘value’, and Dummy.new.attrib=‘value’, but not  
@attrib=‘value’ within the class. Any way to do that? Here’s the code:

module Validator

def validate\_is\_member\_of( attrib, list, default )  
original\_method = instance\_method( “#{attrib}=”.to\_sym )  
define\_method( “#{attrib}=”.to\_sym ) do |val|  
instance\_variable\_set( “@#{attrib}”, (list.include?( val ) ? val :  
default ))  
end  
end

end

class Dummy  
extend Validator

attr\_accessor :name, :type  
validate\_is\_member\_of :type, [:fruit, :veggie, :dairy], :fruit

def initialize( name, type )  
@name = name  
@type = type  
end  
end

good = Dummy.new(‘good’, :veggie ) # =\> … @type=:veggie  
good.type = :ice\_cream # =\> …  
@type=:fruit. i know i overwrote a valid value. it’s ok.  
bad = Dummy.new(‘bad’, :chocolate ) # =\> … @type=:chocolate; I  
want :fruit here.

---

<div class="post-metadata">

**Author:** ![mikec](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/977dab/32.png) [@mikec](https://www.ruby-forum.com/u/mikec)\
**Post date:** [February 11, 2009, 5:37am UTC](https://www.ruby-forum.com/t/redefining-my-attr/160029/2 "2009-02-11T05:37:02Z")

</div>

Mike C. wrote:

> I want to write a generic validator that ensures an attr value is in a  
> list before assigning, and if not, assigns a default. It works for  
> self.attrib=‘value’, and Dummy.new.attrib=‘value’, but not  
> @attrib=‘value’ within the class.

The connection between a method foo=(val) and some instance variable  
@foo is essentially coincidental.

When you use a class method such as attr\_accessors it dynamically  
creates code that defines accessor methods, and in those methods uses an  
instance variable with a matching name.

But there’s no reason the instance variable in those accessors methods  
could not called something else. It just makes a certain sense to use an  
obvious naming convention; it’s so much easier to track what your code  
is doing. And it creates (for better or worse) the illusion of public  
“properties” (as, for example, what Java has).

There’s nothing to stop other methods from manipulating those instance  
variables; they have no intrinsic connection to any particular methods,  
no matter what they are named.

# Runs, but pedantic

def foo=(x); @bar=x;end

def baz; @bar; end

def foo; 47; end

When you do @attrib = 47 you are working directly with the instance  
variable, not with a method that might just happen to have a matching  
name.

–  
James B.

[www.happycamperstudios.com](http://www.happycamperstudios.com) - Wicked Cool Coding  
[www.jamesbritt.com](http://www.jamesbritt.com) - Playing with Better Toys  
[www.ruby-doc.org](http://www.ruby-doc.org) - Ruby Help & Documentation  
[www.rubystuff.com](http://www.rubystuff.com) - The Ruby Store for Ruby Stuff

---

<div class="post-metadata">

**Author:** ![mikec](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/977dab/32.png) [@mikec](https://www.ruby-forum.com/u/mikec)\
**Post date:** [February 11, 2009, 4:42pm UTC](https://www.ruby-forum.com/t/redefining-my-attr/160029/3 "2009-02-11T16:42:20Z")

</div>

On Tue, Feb 10, 2009 at 11:01 PM, Mike C.  
[removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)wrote:

> I want to write a generic validator that ensures an attr value is in a  
> list before assigning, and if not, assigns a default. It works for  
> self.attrib=‘value’, and Dummy.new.attrib=‘value’, but not  
> @attrib=‘value’ within the class. Any way to do that?

No, not in general. @attrib = value involves a primitive assignment  
operator which isn’t a method invocation and therefore can’t be  
overriden.

The best you can do is to impose a discipline and avoid direct iv  
assignment  
to the varlable(s) you want to validate within the methods of that  
class.

Here’s the code:

If I may have to temerity to offer some critique:

> module Validator
> 
> def validate\_is\_member\_of( attrib, list, default )  
> original\_method = instance\_method( “#{attrib}=”.to\_sym )

> define\_method( “#{attrib}=”.to\_sym ) do |val|  
> instance\_variable\_set( “@#{attrib}”, (list.include?( val ) ? val :  
> default ))  
> end  
> end

Not sure why you are doing with the original\_method variable since it’s  
never used. In the code below the getter method for the type attribute  
generated by attr\_accessor :name :type is just discarded.

> @name = name  
> @type = type  
> end  
> end
> 
> good = Dummy.new(‘good’, :veggie ) # =\> … @type=:veggie  
> good.type = :ice\_cream # =\> …  
> @type=:fruit. i know i overwrote a valid value. it’s ok.

@type here is not an instance varlable of an instance of Dummy, it’s an  
instance variable of the top-level object, so this line is moot.

> bad = Dummy.new(‘bad’, :chocolate ) # =\> … @type=:chocolate; I  
> want :fruit here.

Now, if I were to approach this I might change the dsl a bit and have  
the  
class method take on the job of attr\_accessor and generate the getter  
and  
setter methods, For clarity I’d change the name validate\_is\_a\_member\_of.  
Here’s another swing at this:

module Validator

def validated\_attr( attrib, list, default=nil)  
attr\_reader attrib  
define\_method( “#{attrib}=”.to\_sym ) do |val|  
instance\_variable\_set( “@#{attrib}”, (list.include?( val ) ? val  
:default ))  
end  
end

end

class Dummy  
extend Validator

attr\_accessor :name  
validated\_attr :type, [:fruit, :veggie, :dairy], :fruit

def initialize( name, type )  
@name = name

# here is an example of the discipline I mentioned, since initialize

is  
an instance method,

# it should use the setter method.

self.type = type  
end  
end

good = Dummy.new(‘good’, :veggie ) # =\> #\<Dummy:0x23eec  
@name=“good”,  
@type=:veggie\>  
good.type # =\> :veggie  
good.type = :ice\_cream  
good.type # =\> :fruit  
bad = Dummy.new(‘bad’, :chocolate ) # =\> #\<Dummy:0x23690 @name=“bad”,  
@type=:fruit\>  
“I want :fruit here:” # =\> “I want :fruit here:”  
bad.type # =\> :fruit  
“No chocolate fo you kid!” # =\> “No chocolate fo you kid!”

Note that this still doesn’t prevent someone from sending  
:instance\_variable\_set and bypassing this, using #send or # **send** to  
get  
around the privacy…

Here’s a slightly more complicated version which closes that hole, but  
IMHO  
this is really going a bridge too far.

module Validator

module ClassMethods  
def validated\_setters  
@validated\_setters ||= {}  
end

```
def validated_attr( attrib, list, default=nil)
  attr_reader attrib
  module_eval( "def #{attrib}=val;@#{attrib} =

```

#{list.inspect}.include?(val) ? val : #{default.inspect};end")  
self.validated\_setters[“@#{attrib}”.to\_sym] = :“#{attrib}=”  
end  
end

def self.included(other\_mod)  
other\_mod.extend ClassMethods  
end

def send(symbol, \*args, &block)  
if symbol == :instance\_variable\_set && setter =  
self.class.validated\_setters[args.first.to\_sym]  
send(setter, args[1], &block)  
else  
super  
end  
end

alias : **send** :send  
end

class Dummy  
include Validator

attr\_accessor :name  
validated\_attr :type, [:fruit, :veggie, :dairy], :fruit

def initialize( name, type )  
@name = name

# here is an example of the discipline I mentioned

self.type = type  
end  
end

good = Dummy.new(‘good’, :veggie ) # =\> #\<Dummy:0x21714  
@type=:veggie,  
@name=“good”\>  
good.type # =\> :veggie  
good.type = :ice\_cream  
good.type # =\> :fruit  
bad = Dummy.new(‘bad’, :chocolate ) # =\> #\<Dummy:0x20f44  
@type=:fruit,  
@name=“bad”\>  
“I want :fruit here:” # =\> “I want :fruit here:”  
bad.type # =\> :fruit  
“No chocolate fo you kid!” # =\> “No chocolate fo you kid!”  
good.send(:instance\_variable\_set, :@type, :dairy)  
good.type # =\> :dairy  
good.send(:instance\_variable\_set, :@type, :arsenic)  
good.type # =\> :fruit  
good. **send** (:instance\_variable\_set, :@type, :arsenic)  
good.type # =\> :fruit

–  
Rick DeNatale

Blog: [http://talklikeaduck.denhaven2.com/](http://talklikeaduck.denhaven2.com/)  
Twitter: [http://twitter.com/RickDeNatale](http://twitter.com/RickDeNatale)
