Skip to content

Instantly share code, notes, and snippets.

@stevecj
Last active September 30, 2015 20:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stevecj/1859422 to your computer and use it in GitHub Desktop.
Save stevecj/1859422 to your computer and use it in GitHub Desktop.
Ruby attr_accessor enhancement
# Include this module in a class to add enhanced attr_accessor
# functionality. With this module included, ...
#
# * You may provide a block to an attr_accessor call that accepts
# a base attribute name and returns a default value for each
# instance attribute.
# * Any attribute name consisting of a base name followed by a
# "?" suffix represents a boolean attribute.
#
# If the including class has its own #initialize method, that
# method must invoke super to ensure that the default values
# get assigned.
#
# Each boolean attribute has a typical setter method (e.g.
# #flagged=(value) ), a getter method with a "?" suffix (e.g.
# #flagged? ), and a truth setter method (e.g. flagged! ).
# Invoking a truth setter sets the attribute value to true.
module EnhancedAttributable
def self.included(klass)
klass.extend ClassMethods
klass.class_eval <<-DEFINE, __FILE__, __LINE__ + 1
class << self
attr_accessor :attr_default_procs
alias :attr_accessor_without_enhancement :attr_accessor
alias :attr_accessor :attr_accessor_with_enhancement
end
DEFINE
end
def initialize(*args)
(self.class.attr_default_procs || []).each do |name, p|
instance_variable_set "@#{name}", p.call(name)
end
super
end
module ClassMethods
def attr_accessor_with_enhancement(*names, &b)
names.each do |name|
name = name.to_s # To support #match in Ruby versions prior to 1.9.x
match = name.match( /([^?]*)(\?)?$/ ).to_a
if match && match[2]
name = match[1]
boolean_attr_accessor name
else
attr_accessor_without_enhancement(name)
end
if block_given?
@attr_default_procs ||= {}
@attr_default_procs[name.to_sym] = b
end
end
end
protected
def boolean_attr_accessor(name)
module_eval <<-DEFINE
def #{name}=(value) ; @#{name} = value ; end
def #{name}! ; @#{name} = true ; end
def #{name}? ; @#{name} && true || false ; end
DEFINE
end
end
end
require 'test/unit'
class TestEnhancedAttributable < Test::Unit::TestCase
def setup
@class = Class.new{
include EnhancedAttributable
attr_accessor :status, :disabled?
attr_accessor(:name){|attr| "Default for #{attr}" }
attr_accessor(:active?){ true }
}
@instance = @class.new
end
def test_creates_standard_accessor_methods
assert_equal [], [ 'status=' , 'status' ] - @class.instance_methods,
'Should implement status setter & getter'
assert_equal [], [ 'name=' , 'name' ] - @class.instance_methods,
'Should implement name setter & getter'
end
def test_creates_boolean_accessor_methods
assert_equal [], [ 'disabled=' , 'disabled!' , 'disabled?' ] - @class.instance_methods,
'Should implement "disabled" boolean accessors'
assert_equal [], [ 'active=' , 'active!' , 'active?' ] - @class.instance_methods,
'Should implement "active" boolean accessors'
end
def test_has_null_standard_default_with_none_specified
assert_nil @instance.status
end
def test_has_false_boolean_default_with_none_specified
assert_equal false, @instance.disabled?
end
def test_initializes_instance_attribute_defaults
assert_equal 'Default for name' , @instance.name
assert_equal true , @instance.active?
end
def test_attribute_is_true_when_set_to_set_truthy
@instance.disabled = 'a string'
assert_equal(true, @instance.disabled?)
end
def test_attribute_is_false_when_set_to_falsey
@instance.disabled = nil
assert_equal(false, @instance.disabled?)
end
def test_attribute_is_true_after_bang_setter_invoked
@instance.disabled!
assert_equal(true, @instance.disabled?)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment