Class: Module

Inherits:
Object
  • Object
show all
Defined in:
lib/chattr.rb

Overview

The checked attribute methods are added to Module. You can use them as a safer replacement for attr_accessor.

Instance Method Summary collapse

Instance Method Details

#array_attr(*names, &block) ⇒ Object

call-seq:

array_attr :attr
array_attr Class, :attr, Class2, :attr2
array_attr Class, :attr do |val| val.isvalid end
array_attr(:attr) {|val| val.isvalid }

array_attr is like attr_accessor, but the attributes it creates are checked Arrays, see checked Array.

The parameter list is processed in order, and may contain:

  • a class. The following array attributes will require values that are kind_of? this class

  • a Symbol, which is used as the name for an array attribute.

In addition, array_attr may be given a block. Any value to be used as a member of the array will be passed to this block, which must not return false/nil or an exception will be raised. You’ll need to parenthesize the parameter list for a {|| } block, or just use a do…end block.

There’s no need to initialize the attribute with an empty array, that comes for free.

nil checking and default values are not provided as with typed_attr.

Here’s an example:

class MyClass
    array_attr String, :attr1 do|s|
            s.size >= 5             # All array members must have >=5 characters
        end
    array_attr(:attr2) {|e|         # Array members must be Integers or Strings
            e.kind_of?(String) || e.kind_of?(Integer)
        }
end

c = MyClass.new
c.attr1 << "ouch"                   # Error, string is too short
c.attr2 << { :hi => "there" }       # Error, value must be Integer or String


358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/chattr.rb', line 358

def array_attr(*names, &block)
	klass = Object
	names.each{|name|
 case name
 when Class
		klass = name
 when Symbol
		define_method(name) { 
  instance_variable_get("@#{name}") ||
			instance_variable_set("@#{name}", Array(klass, &block).new)
		}
		define_method("#{name}=") {|val|
  a = instance_variable_get("@#{name}") ||
			instance_variable_set("@#{name}", Array(klass, &block).new)
  saved = a.clone
  a.clear
  begin
			a.concat(val)   # If conversion is legal, this will do it
  rescue
			instance_variable_set("@#{name}", saved)
			raise
  end
		}
 else
		raise "Parameter to array_attr must be Class or Symbol"
 end
	}
end

#typed_attr(*names, &block) ⇒ Object

call-seq:

typed_attr :attr
typed_attr nil, :attr
typed_attr Class, :attr
typed_attr Class, nil, 'default', :attr
typed_attr Class, nil, :attr do |val| val.isvalid end
typed_attr(Integer, 0, :attr) {|val| (0..5).include?(val) }
... and combinations

typed_attr is like attr_accessor, but with optional value checking using either class.kind_of? or by calling your block, or both. Assignment of any value that fails the checks causes an assertion to be raised.

The parameter list is processed in order, and may contain:

  • a class. The following attributes will require values that are kind_of? this class

  • nil. Adding nil to the argument list allows nil as a value for the following attributes, which otherwise would disallow nil,

  • a Symbol, which is used as the name for an attribute,

  • any other value, which is used as a default value for following attributes.

In addition, typed_attr may be given a block. Any value to be assigned will be passed to this block, which must not return false/nil or an exception will be raised. You’ll need to parenthesize the parameter list for a {|| } block, or just use a do…end block.

Here’s an example:

class MyClass
    typed_attr nil, :attr1          # This attribute is unchecked
    typed_attr :attr2               # This attribute may be assigned any non-nil value
    typed_attr String, "hi", :attr3 # This attribute must be a string and defaults to "hi"
    typed_attr Integer, 0, :attr4 do |i|
            (0..5).include?(i)      # This attribute must be an Integer in 0..5
        end
    typed_attr String, :foo, Integer, :bar # Two attributes of different types. Don't do this please!
    typed_attr(String, :attr5) {|s|
            s.size >= 4             # Values must have at least 4 characters
        }
end

Note that if you don’t allow nil, you should use a default value, or a new object will break your rules. This won’t cause an exception, as typed_attr assumes you’ll initialise the value in the object’s constructor.



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/chattr.rb', line 281

def typed_attr(*names, &block)
	klass = Object
	nil_ok = false
	def_val = nil

	names.each{|name|
 case name
 when Class
		klass = name
 when NilClass
		nil_ok = true
 when Symbol
		define_method(name) { 
  v = instance_variable_get("@#{name}")
  # This is awkward if nil is a valid value:
  if (v == nil && (!nil_ok || !instance_variables.include?("@#{name}")))
			v = instance_variable_set("@#{name}", def_val)
  end
  v
		}
		define_method("#{name}=") {|val|
  if val == nil
			unless nil_ok
   raise "Can't assign nil to #{name} which is restricted to class #{klass}"
			end
  else
			if !val.kind_of?(klass)
   raise "Can't assign #{val.inspect} of class #{val.class} to attribute #{name} which is restricted to class #{klass}"
			elsif (block && !block.call(val))
   raise "Invalid value assigned to #{name}: #{val.inspect}"
			end
  end
  instance_variable_set("@#{name}", val)
		}
 else
		def_val = name	    # Save this as a default value
 end
	}
end