Post by Peter Cooper on May 26th, 2006

Applying conditions to attribute setters

An anonymous commenter contributed a cute Ruby example on this post talking about Java's verbosity. The original poster lamented on how much code you have to write to create some basic accessors on a Java class.

With Ruby you can simply use attr, attr_accessor, attr_reader and attr_writer to create various types of accessors with a single line of code. They lack logic though, and you need to write your own accessors from scratch if you want even basic logic included. Mr. Anonymous came up with a solution, which I've slightly changed to produce this:

class Class
        def attr(name, &conditions)
                varname = '@' + name.to_s
                conditions ||= lambda {true}
                define_method(name) do
                        instance_variable_get(varname)
                end

                define_method(name.to_s + '=') do |val|
                        raise ArgumentError, "#{name} can't be #{val}" unless conditions.call(val)
                        instance_variable_set(varname, val)
                end
        end
end

class AClass
        attr(:my_int) { |i| i >= 0 }
        attr(:my_string)
end

With this code, 'attr' has been extended to support accepting a code block as a parameter. When you try to set my_int on an AClass object, if i >= 0 is true, my_int is set. If it's under zero, Ruby throws an exception. This is great if you need to make sure the data is totally safe.

One Response to “Applying conditions to attribute setters”

  1. #1
    Jonathan Conway Says:

    Cool piece of code, though I think people should be warned about the dangers of accessors/mutators as far as encapsulation is concerned.

    When I left the land of Java and it was no longer expected of me to write code that needed to be a "bean" I found it a joyful day. Exposing the interals of an objects state via accessors and mutators leads to bad OO design and not to mention an extremely unhealthy amount of coupling. Its a dark path I tell thee!! *runs off jibbering like a mad man*