From: Robert Klemme Date: 2006-07-06T23:55:39+09:00 Subject: Re: newbie Q: conditional object creation Daniel Schierbeck wrote: > Paul Van Delst wrote: >> I'm want to be able to create objects, but I want the object creation >> to only proceed if the values passed to the init method are in a valid >> list. E.g.: >> >> class MyObj >> VALID_NAMES=['bananas','dog','water'] >> attr_reader :name, :value >> def initialize(name,value) >> i=VALID_NAMES.index(name) >> if i then >> @name,@value=name,value >> else >> @name,@value=nil >> end >> end >> end >> >> This sorta does what I want, but looks clunky, ugly and quite >> un-ruby-ish. Is there a better way (idiom?) to achieve this sort of >> thing? I.e. is there a way to make the init method not create even an >> "empty" object (as happens above) based on the valid names list? > > class MyObj > VALID_NAMES = %w{bananas dog water} # array of strings > > # go into the metaclass > class << self > alias_method :__new__, :new > > def new(name, value) > raise unless VALID_NAMES.include? name > __new__(name, value) > end > end > > def initialize(name, value) > @name, @value = name, value > end > end > > This will raise an exception when MyObj.new is called with an invalid > name. You should read up on metaclasses if you haven't already[1]. > > If you just want MyObj.new to return nil when supplied with an invalid > argument, this will do the trick: > > def new(name, value) > __new__(name, value) if VALID_NAMES.include? name > end > > > Cheers, > Daniel > > [1] http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html The same effect can be achieved in two other way that do not involve meta classes: 1. make initialize throw an exception. Although the instance will be created, nobody ever gets to see it (unless initialize stores self somewhere). class MyClass VALID_NAMES=['bananas','dog','water'] attr_reader :name, :value def initialize(name, value) raise ArgumentError, "Invalid name: #{name}" unless VALID_NAMES.include? name @name = name @value = value end end 2. add a class method instead of aliasing new. class MyClass VALID_NAMES=['bananas','dog','water'] attr_reader :name, :value def self.create(name, value) raise ArgumentError, "Invalid name: #{name}" unless VALID_NAMES.include? name new name, value end def initialize(name, value) @name = name @value = value end end I'd probably favor approach 1 as it is more robust (you cannot forget to call "create" as in the second approach). Kind regards robert