Last active
September 30, 2015 20:47
-
-
Save stevecj/1859422 to your computer and use it in GitHub Desktop.
Ruby attr_accessor enhancement
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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