From b7f529b033fc57ac633c5313e9a8217ca86a0a52 Mon Sep 17 00:00:00 2001 From: wbr Date: Tue, 5 Dec 2017 19:59:11 -0800 Subject: [PATCH 01/11] Add Value class to create immutable instances of enum values. --- lib/enum.rb | 1 + lib/enum/base.rb | 33 ++++++++++- lib/enum/value.rb | 142 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 lib/enum/value.rb diff --git a/lib/enum.rb b/lib/enum.rb index f0dc84a..8832902 100644 --- a/lib/enum.rb +++ b/lib/enum.rb @@ -2,3 +2,4 @@ require 'enum/token_not_found_error' require 'enum/base' require 'enum/predicates' +require 'enum/value' diff --git a/lib/enum/base.rb b/lib/enum/base.rb index e5d7f33..1fa4bdf 100644 --- a/lib/enum/base.rb +++ b/lib/enum/base.rb @@ -1,4 +1,5 @@ require 'set' +require 'forwardable' module Enum class Base < BasicObject @@ -37,13 +38,36 @@ def enum(t) ts end - def name(t) - translate(enum(t)) + # Allow Class.name to work if no args are given. + def name(*t) + if t.empty? + super + else + translate(enum(t[0])) + end end def index(token) history.index(enum(token)) end + + # Get a new Value instance + def new(new_value) + new_instance = self::Value.new(new_value) + new_instance + end + + # Render value given interger, string, or symbol. + def [](val) + case + when val.is_a?(::Integer) + self.all[val] + when val.is_a?(::Symbol) || val.is_a?(::String) + self.enum(val) + else + self.enum(val) + end + end protected @@ -83,6 +107,11 @@ def add_value(val) end def init_child_class(child) + child.send :extend, ::Forwardable + child.def_delegators :'self::Value', :default, :read_any_value + child.const_set :Value, ::Class.new(::Enum::Value) + child::Value.klass = child + child.store = self.store.clone child.history = self.history.clone end diff --git a/lib/enum/value.rb b/lib/enum/value.rb new file mode 100644 index 0000000..b72d5d3 --- /dev/null +++ b/lib/enum/value.rb @@ -0,0 +1,142 @@ +module Enum + class Value + + include Comparable + + attr_reader :stored_value, :error + + class << self + # subclass-value options + # :default => <:ERROR|:ANY|:something|nil> + # :read_any_value => + attr_accessor :default, :read_any_value, :klass + end + + def self.inherited(subclass) + # With no defaults, Value subclass settings will be thus: + # subclass.read_any_value = nil + # subclass.default = nil + end + + # Combined getter/setter for 'default'. + def self.default(*args) + # Can't use .any? because [nil].any? is false (ruby 2.4), + # and nil is a valid argument here. + # [nil].empty? is also false, which is what we want. + if !args.empty? + @default = args[0] + else + @default + end + end + + # Combined getter/setter for 'read_any_value'. + def self.read_any_value(*args) + if !args.empty? + @read_any_value = args[0] + else + @read_any_value + end + end + + # Load a primitive (symbol, string, integer) into new enum Value instance, + # taking into consideration enum constraints, default value setting. + # Returns frozen Value instance. + def initialize(new_val) #, enum_class) + begin + @stored_value = klass[new_val].to_sym.freeze + rescue Enum::TokenNotFoundError => _error + @error = _error.freeze + case + when self.class.default == :ERROR + raise _error + when self.class.default == :ANY + @stored_value = new_val.freeze + else + @stored_value = self.class.default.freeze + end + end + self.freeze + self + end + + # Returns the Enum::Base subclass attached through Enum::Value subclass. + def klass + self.class.klass + end + + # Convenience method to pass arguments to klass[]. + # Returns enum value given symbol, string, or integer. + def enum_value(_value=stored_value) + begin + klass[_value] + rescue Enum::TokenNotFoundError => _error + if self.class.read_any_value + _value + else + raise _error + end + end + end + private :enum_value + + # Returns result of Enum::Base subclass.enum(self) as string. + def to_s + enum_value.to_s + end + alias_method :to_str, :to_s + + # Returns result of Enum::Base subclass.enum(self) as symbol. + def to_sym + val = enum_value + val.to_sym if val.respond_to?(:to_sym) + end + + # Returns result of Enum::Base subclass.enum(self). + def value + enum_value + end + + # Is stored value nil? + def nil? + @stored_value.nil? + end + + # Is stored value valid for this enum class? + def valid? + klass.enum(@stored_value) + true + rescue Enum::TokenNotFoundError + false + end + + # This (or other) value's enum index. + def index(_value=stored_value) + begin + klass.index(_value) + rescue Enum::TokenNotFoundError => _error + raise _error unless self.class.read_any_value + end + end + alias_method :to_i, :index + + # Enable comparisons between Value instances, strings, symbols, and integers. + def <=>(other) + case + when other.is_a?(Symbol) + index <=> index(other) + when other.is_a?(String) + index <=> index(other) + when other.is_a?(Integer) + index <=> other + when other.is_a?(self.class) && other.klass == klass + index <=> other.index + else + # Nil will be uncomparable with strings, symbols, or integers, + # and will raise exception, as it should. + nil + end + end + + end # Value +end # Enum \ No newline at end of file From 184b930c95edd2b673987f2c1e17075fa9087f9d Mon Sep 17 00:00:00 2001 From: wbr Date: Tue, 5 Dec 2017 20:34:49 -0800 Subject: [PATCH 02/11] Fix delegation bug in init_child_class. --- lib/enum/base.rb | 6 ++++-- lib/enum/value.rb | 18 +++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/enum/base.rb b/lib/enum/base.rb index 1fa4bdf..a8bd15f 100644 --- a/lib/enum/base.rb +++ b/lib/enum/base.rb @@ -107,8 +107,10 @@ def add_value(val) end def init_child_class(child) - child.send :extend, ::Forwardable - child.def_delegators :'self::Value', :default, :read_any_value + class << child + extend ::Forwardable + def_delegators :'self::Value', :default, :supress_read_errors + end child.const_set :Value, ::Class.new(::Enum::Value) child::Value.klass = child diff --git a/lib/enum/value.rb b/lib/enum/value.rb index b72d5d3..22417b9 100644 --- a/lib/enum/value.rb +++ b/lib/enum/value.rb @@ -8,13 +8,13 @@ class Value class << self # subclass-value options # :default => <:ERROR|:ANY|:something|nil> - # :read_any_value => - attr_accessor :default, :read_any_value, :klass + # :supress_read_errors => + attr_accessor :default, :supress_read_errors, :klass end def self.inherited(subclass) # With no defaults, Value subclass settings will be thus: - # subclass.read_any_value = nil + # subclass.supress_read_errors = nil # subclass.default = nil end @@ -30,12 +30,12 @@ def self.default(*args) end end - # Combined getter/setter for 'read_any_value'. - def self.read_any_value(*args) + # Combined getter/setter for 'supress_read_errors'. + def self.supress_read_errors(*args) if !args.empty? - @read_any_value = args[0] + @supress_read_errors = args[0] else - @read_any_value + @supress_read_errors end end @@ -71,7 +71,7 @@ def enum_value(_value=stored_value) begin klass[_value] rescue Enum::TokenNotFoundError => _error - if self.class.read_any_value + if self.class.supress_read_errors _value else raise _error @@ -115,7 +115,7 @@ def index(_value=stored_value) begin klass.index(_value) rescue Enum::TokenNotFoundError => _error - raise _error unless self.class.read_any_value + raise _error unless self.class.supress_read_errors end end alias_method :to_i, :index From b83bd9961ebdde12bcb6a2b450bbf3ad9f114299 Mon Sep 17 00:00:00 2001 From: wbr Date: Wed, 6 Dec 2017 03:21:57 -0800 Subject: [PATCH 03/11] Add value_test.rb and several specs. --- lib/enum/base.rb | 2 +- lib/enum/value.rb | 18 +++++++-------- test/support/fixtures.rb | 5 +++++ test/value_test.rb | 48 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 test/value_test.rb diff --git a/lib/enum/base.rb b/lib/enum/base.rb index a8bd15f..e62dce8 100644 --- a/lib/enum/base.rb +++ b/lib/enum/base.rb @@ -109,7 +109,7 @@ def add_value(val) def init_child_class(child) class << child extend ::Forwardable - def_delegators :'self::Value', :default, :supress_read_errors + def_delegators :'self::Value', :default, :suppress_read_errors end child.const_set :Value, ::Class.new(::Enum::Value) child::Value.klass = child diff --git a/lib/enum/value.rb b/lib/enum/value.rb index 22417b9..1b6c017 100644 --- a/lib/enum/value.rb +++ b/lib/enum/value.rb @@ -8,13 +8,13 @@ class Value class << self # subclass-value options # :default => <:ERROR|:ANY|:something|nil> - # :supress_read_errors => - attr_accessor :default, :supress_read_errors, :klass + # :suppress_read_errors => + attr_accessor :default, :suppress_read_errors, :klass end def self.inherited(subclass) # With no defaults, Value subclass settings will be thus: - # subclass.supress_read_errors = nil + # subclass.suppress_read_errors = nil # subclass.default = nil end @@ -30,12 +30,12 @@ def self.default(*args) end end - # Combined getter/setter for 'supress_read_errors'. - def self.supress_read_errors(*args) + # Combined getter/setter for 'suppress_read_errors'. + def self.suppress_read_errors(*args) if !args.empty? - @supress_read_errors = args[0] + @suppress_read_errors = args[0] else - @supress_read_errors + @suppress_read_errors end end @@ -71,7 +71,7 @@ def enum_value(_value=stored_value) begin klass[_value] rescue Enum::TokenNotFoundError => _error - if self.class.supress_read_errors + if self.class.suppress_read_errors _value else raise _error @@ -115,7 +115,7 @@ def index(_value=stored_value) begin klass.index(_value) rescue Enum::TokenNotFoundError => _error - raise _error unless self.class.supress_read_errors + raise _error unless self.class.suppress_read_errors end end alias_method :to_i, :index diff --git a/test/support/fixtures.rb b/test/support/fixtures.rb index f87dc1e..5570b19 100644 --- a/test/support/fixtures.rb +++ b/test/support/fixtures.rb @@ -21,3 +21,8 @@ class Side < Enum::Base values :left, :right end end + +class Suppressed < Enum::Base + values :left, :right, :whole + suppress_read_errors true +end \ No newline at end of file diff --git a/test/value_test.rb b/test/value_test.rb new file mode 100644 index 0000000..d1fc008 --- /dev/null +++ b/test/value_test.rb @@ -0,0 +1,48 @@ +require 'test_helper' + +describe Enum::Value do + describe '#klass' do + describe 'returns Base child' do + specify { assert_equal Side, Side.new(:left).klass } + end + end + + describe '#enum_value' do + describe 'sends symbol or string to Base#enum' do + specify { assert_equal 'right', Side.new(:right).send(:enum_value, :right) } + specify { assert_equal 'right', Side.new(:right).send(:enum_value, 'right') } + end + + describe 'sends integer to Base#index' do + specify { assert_equal 'right', Side.new(:right).send(:enum_value, 1) } + end + + describe 'sends self.stored_value as default' do + specify { assert_equal 'whole', Side.new(:whole).send(:enum_value) } + end + + describe 'wihtout :suppress_read_errors' do + describe 'raises TokenNotFoundError if given invalid token' do + specify do + assert_raises Enum::TokenNotFoundError do + Side.new(:right).send(:enum_value, :invalid) + end + end + end + + describe 'returns nil given out-of-bounds integer' do + specify { assert_nil Side.new(:right).send(:enum_value, 9) } + end + end + + describe 'with :suppress_read_errors' do + describe 'returns any given invalid token' do + specify do + suppressed = Suppressed.new(:right) + assert_equal :invalid, suppressed.send(:enum_value, :invalid) + end + end + end + end + +end # Enum::Value \ No newline at end of file From 310c762108407a72b0d8a7b60457039f6d7ba350 Mon Sep 17 00:00:00 2001 From: wbr Date: Wed, 6 Dec 2017 14:31:33 -0800 Subject: [PATCH 04/11] Add tests for Value instance methods. --- lib/enum/value.rb | 10 ++-- test/support/fixtures.rb | 6 +++ test/value_test.rb | 98 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 5 deletions(-) diff --git a/lib/enum/value.rb b/lib/enum/value.rb index 1b6c017..7b76a59 100644 --- a/lib/enum/value.rb +++ b/lib/enum/value.rb @@ -12,11 +12,11 @@ class << self attr_accessor :default, :suppress_read_errors, :klass end - def self.inherited(subclass) - # With no defaults, Value subclass settings will be thus: - # subclass.suppress_read_errors = nil - # subclass.default = nil - end + # def self.inherited(subclass) + # # With no defaults, Value subclass settings will be thus: + # # subclass.suppress_read_errors = nil + # # subclass.default = nil + # end # Combined getter/setter for 'default'. def self.default(*args) diff --git a/test/support/fixtures.rb b/test/support/fixtures.rb index 5570b19..60c736d 100644 --- a/test/support/fixtures.rb +++ b/test/support/fixtures.rb @@ -25,4 +25,10 @@ class Side < Enum::Base class Suppressed < Enum::Base values :left, :right, :whole suppress_read_errors true +end + +class LoadAnyValue < Enum::Base + values :left, :right, :whole + default :ANY + suppress_read_errors true end \ No newline at end of file diff --git a/test/value_test.rb b/test/value_test.rb index d1fc008..7b27073 100644 --- a/test/value_test.rb +++ b/test/value_test.rb @@ -1,6 +1,12 @@ require 'test_helper' describe Enum::Value do + describe '.default' do; end + + describe '.suppress_read_errors' do; end + + describe '#initialize' do; end + describe '#klass' do describe 'returns Base child' do specify { assert_equal Side, Side.new(:left).klass } @@ -43,6 +49,98 @@ end end end + end # #enum_value + + describe '#to_s' do + describe 'gets enum_value as string' do + specify { assert_instance_of String, Side.new(:left).send(:to_s) } + specify { assert_equal 'left', Side.new(:left).send(:to_s) } + end + end + + describe '#to_sym' do + describe 'gets enum_value as symbol' do + specify { assert_instance_of Symbol, Side.new(:left).send(:to_sym) } + specify { assert_equal :left, Side.new(:left).send(:to_sym) } + end + + describe 'returns nil if enum_value does not repond_to to_sym' do + specify { assert_nil LoadAnyValue.new(nil).send(:to_sym) } + end + end + + describe '#value' do + describe 'gets enum_value of self' do + specify { assert_equal 'left', Side.new(:left).send(:value) } + end + end + + describe '#nil?' do + describe 'returns true if @stored_value is nil' do + specify { assert LoadAnyValue.new(nil).send(:nil?) } + specify { assert_equal false, LoadAnyValue.new(:something).send(:nil?) } + end + end + + describe '#valid?' do + describe 'returns true if @stored_value is valid enum' do + specify { assert LoadAnyValue.new(:left).send(:valid?) } + specify { assert_equal false, LoadAnyValue.new(nil).send(:valid?) } + end + end + + describe '#index' do + describe 'returns index of given valid token' do + specify { assert_equal 2, Side.new(:left).send(:index, :whole) } + end + + describe 'raises TokenNotFoundError if token invalid' do + specify do + assert_raises Enum::TokenNotFoundError do + Side.new(:left).send(:index, :invalid) + end + end + end + + describe 'returns index of @stored_value' do + specify { assert_equal 1, Side.new(:right).send(:index) } + end + + describe 'returns nil if token invalid with suppress_read_errors' do + specify { assert_nil LoadAnyValue.new(:right).send(:index, :invalid) } + end + end + + describe '#<=>' do + describe 'comparable with symbol' do + specify { assert_equal -1, Side.new(:right) <=> :whole } + specify { assert_equal 0, Side.new(:right) <=> :right } + specify { assert_equal 1, Side.new(:right) <=> :left } + end + + describe 'comparable with string' do + specify { assert_equal -1, Side.new(:right) <=> 'whole' } + specify { assert_equal 0, Side.new(:right) <=> 'right' } + specify { assert_equal 1, Side.new(:right) <=> 'left' } + end + + describe 'comparable with integer' do + specify { assert_equal -1, Side.new(:right) <=> 2 } + specify { assert_equal 0, Side.new(:right) <=> 1 } + specify { assert_equal 1, Side.new(:right) <=> 0 } + end + + describe 'comparable with other Value object of same enum class' do + specify { assert_equal -1, Side.new(:right) <=> Side.new(:whole) } + specify { assert_equal 0, Side.new(:right) <=> Side.new(:right) } + specify { assert_equal 1, Side.new(:right) <=> Side.new(:left) } + end + + describe 'returns nil if uncomarable with other' do + specify { assert_nil Side.new(:right) <=> Object.new } + specify { assert_nil Side.new(:right) <=> nil } + specify { assert_nil Side.new(:right) <=> LoadAnyValue.new(:right) } + end end end # Enum::Value \ No newline at end of file From f4cb96d825ca79a4cfea903034afca229a7f0055 Mon Sep 17 00:00:00 2001 From: wbr Date: Wed, 6 Dec 2017 22:46:18 -0800 Subject: [PATCH 05/11] Change .default to .default_value. Change default settings of Enum::Value subclass. --- lib/enum/base.rb | 2 +- lib/enum/value.rb | 35 ++++++++++++++++++----------------- test/support/fixtures.rb | 2 +- test/value_test.rb | 2 +- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/lib/enum/base.rb b/lib/enum/base.rb index e62dce8..c888cc8 100644 --- a/lib/enum/base.rb +++ b/lib/enum/base.rb @@ -109,7 +109,7 @@ def add_value(val) def init_child_class(child) class << child extend ::Forwardable - def_delegators :'self::Value', :default, :suppress_read_errors + def_delegators :'self::Value', :default_value, :suppress_read_errors end child.const_set :Value, ::Class.new(::Enum::Value) child::Value.klass = child diff --git a/lib/enum/value.rb b/lib/enum/value.rb index 7b76a59..386db72 100644 --- a/lib/enum/value.rb +++ b/lib/enum/value.rb @@ -1,32 +1,33 @@ module Enum class Value + # TODO: Perhaps stored_value should be renamed unsafe_value? + include Comparable attr_reader :stored_value, :error class << self - # subclass-value options - # :default => <:ERROR|:ANY|:something|nil> - # :suppress_read_errors => - attr_accessor :default, :suppress_read_errors, :klass + attr_accessor :default_value, :suppress_read_errors, :klass end - # def self.inherited(subclass) - # # With no defaults, Value subclass settings will be thus: - # # subclass.suppress_read_errors = nil - # # subclass.default = nil - # end + def self.inherited(subclass) + # Value subclass settings and options + # @default_value => <:ERROR|:ANY|:something|nil> + # @suppress_read_errors => + subclass.suppress_read_errors = false + subclass.default_value = :ERROR + end - # Combined getter/setter for 'default'. - def self.default(*args) + # Combined getter/setter for 'default_value'. + def self.default_value(*args) # Can't use .any? because [nil].any? is false (ruby 2.4), # and nil is a valid argument here. # [nil].empty? is also false, which is what we want. if !args.empty? - @default = args[0] + @default_value = args[0] else - @default + @default_value end end @@ -40,7 +41,7 @@ def self.suppress_read_errors(*args) end # Load a primitive (symbol, string, integer) into new enum Value instance, - # taking into consideration enum constraints, default value setting. + # taking into consideration enum constraints, default_value setting. # Returns frozen Value instance. def initialize(new_val) #, enum_class) begin @@ -48,12 +49,12 @@ def initialize(new_val) #, enum_class) rescue Enum::TokenNotFoundError => _error @error = _error.freeze case - when self.class.default == :ERROR + when self.class.default_value == :ERROR raise _error - when self.class.default == :ANY + when self.class.default_value == :ANY @stored_value = new_val.freeze else - @stored_value = self.class.default.freeze + @stored_value = self.class.default_value.freeze end end self.freeze diff --git a/test/support/fixtures.rb b/test/support/fixtures.rb index 60c736d..6527dca 100644 --- a/test/support/fixtures.rb +++ b/test/support/fixtures.rb @@ -29,6 +29,6 @@ class Suppressed < Enum::Base class LoadAnyValue < Enum::Base values :left, :right, :whole - default :ANY + default_value :ANY suppress_read_errors true end \ No newline at end of file diff --git a/test/value_test.rb b/test/value_test.rb index 7b27073..acf9358 100644 --- a/test/value_test.rb +++ b/test/value_test.rb @@ -1,7 +1,7 @@ require 'test_helper' describe Enum::Value do - describe '.default' do; end + describe '.default_value' do; end describe '.suppress_read_errors' do; end From 566832ff88fd3e8cd4c8217c88fe07153b03cec0 Mon Sep 17 00:00:00 2001 From: wbr Date: Thu, 7 Dec 2017 17:25:06 -0800 Subject: [PATCH 06/11] Update test labels. --- test/value_test.rb | 50 +++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/test/value_test.rb b/test/value_test.rb index acf9358..e42cb1c 100644 --- a/test/value_test.rb +++ b/test/value_test.rb @@ -15,8 +15,8 @@ describe '#enum_value' do describe 'sends symbol or string to Base#enum' do - specify { assert_equal 'right', Side.new(:right).send(:enum_value, :right) } - specify { assert_equal 'right', Side.new(:right).send(:enum_value, 'right') } + specify("with_symbol") { assert_equal 'right', Side.new(:right).send(:enum_value, :right) } + specify("with_string") { assert_equal 'right', Side.new(:right).send(:enum_value, 'right') } end describe 'sends integer to Base#index' do @@ -53,15 +53,15 @@ describe '#to_s' do describe 'gets enum_value as string' do - specify { assert_instance_of String, Side.new(:left).send(:to_s) } - specify { assert_equal 'left', Side.new(:left).send(:to_s) } + specify('is_string') { assert_instance_of String, Side.new(:left).send(:to_s) } + specify('return_correct_value') { assert_equal 'left', Side.new(:left).send(:to_s) } end end describe '#to_sym' do describe 'gets enum_value as symbol' do - specify { assert_instance_of Symbol, Side.new(:left).send(:to_sym) } - specify { assert_equal :left, Side.new(:left).send(:to_sym) } + specify('is_symbol') { assert_instance_of Symbol, Side.new(:left).send(:to_sym) } + specify('return_correct_value') { assert_equal :left, Side.new(:left).send(:to_sym) } end describe 'returns nil if enum_value does not repond_to to_sym' do @@ -77,15 +77,15 @@ describe '#nil?' do describe 'returns true if @stored_value is nil' do - specify { assert LoadAnyValue.new(nil).send(:nil?) } - specify { assert_equal false, LoadAnyValue.new(:something).send(:nil?) } + specify('given_nil') { assert LoadAnyValue.new(nil).send(:nil?) } + specify('given_non_nil') { assert_equal false, LoadAnyValue.new(:something).send(:nil?) } end end describe '#valid?' do describe 'returns true if @stored_value is valid enum' do - specify { assert LoadAnyValue.new(:left).send(:valid?) } - specify { assert_equal false, LoadAnyValue.new(nil).send(:valid?) } + specify('given_valid_value') { assert LoadAnyValue.new(:left).send(:valid?) } + specify('given_invalid_value') { assert_equal false, LoadAnyValue.new(nil).send(:valid?) } end end @@ -113,33 +113,33 @@ describe '#<=>' do describe 'comparable with symbol' do - specify { assert_equal -1, Side.new(:right) <=> :whole } - specify { assert_equal 0, Side.new(:right) <=> :right } - specify { assert_equal 1, Side.new(:right) <=> :left } + specify('less_than') { assert_equal -1, Side.new(:right) <=> :whole } + specify('equal_to') { assert_equal 0, Side.new(:right) <=> :right } + specify('greater_than') { assert_equal 1, Side.new(:right) <=> :left } end describe 'comparable with string' do - specify { assert_equal -1, Side.new(:right) <=> 'whole' } - specify { assert_equal 0, Side.new(:right) <=> 'right' } - specify { assert_equal 1, Side.new(:right) <=> 'left' } + specify('less_than') { assert_equal -1, Side.new(:right) <=> 'whole' } + specify('equal_to') { assert_equal 0, Side.new(:right) <=> 'right' } + specify('greater_than') { assert_equal 1, Side.new(:right) <=> 'left' } end describe 'comparable with integer' do - specify { assert_equal -1, Side.new(:right) <=> 2 } - specify { assert_equal 0, Side.new(:right) <=> 1 } - specify { assert_equal 1, Side.new(:right) <=> 0 } + specify('less_than') { assert_equal -1, Side.new(:right) <=> 2 } + specify('equal_to') { assert_equal 0, Side.new(:right) <=> 1 } + specify('greater_than') { assert_equal 1, Side.new(:right) <=> 0 } end describe 'comparable with other Value object of same enum class' do - specify { assert_equal -1, Side.new(:right) <=> Side.new(:whole) } - specify { assert_equal 0, Side.new(:right) <=> Side.new(:right) } - specify { assert_equal 1, Side.new(:right) <=> Side.new(:left) } + specify('less_than') { assert_equal -1, Side.new(:right) <=> Side.new(:whole) } + specify('equal_to') { assert_equal 0, Side.new(:right) <=> Side.new(:right) } + specify('greater_than') { assert_equal 1, Side.new(:right) <=> Side.new(:left) } end describe 'returns nil if uncomarable with other' do - specify { assert_nil Side.new(:right) <=> Object.new } - specify { assert_nil Side.new(:right) <=> nil } - specify { assert_nil Side.new(:right) <=> LoadAnyValue.new(:right) } + specify('with_object') { assert_nil Side.new(:right) <=> Object.new } + specify('with_nil') { assert_nil Side.new(:right) <=> nil } + specify('with_invalid_other_instance') { assert_nil Side.new(:right) <=> LoadAnyValue.new(:right) } end end From c19dae2aa8f52438fa92dd483be21a61818c7839 Mon Sep 17 00:00:00 2001 From: wbr Date: Fri, 8 Dec 2017 17:28:54 -0800 Subject: [PATCH 07/11] Change new_val to raw_val in Enum::Value#initialize. Clean up comments. Test coverage for all instance & class methods of Enum::Value. --- lib/enum/value.rb | 13 +++---- test/value_test.rb | 90 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 93 insertions(+), 10 deletions(-) diff --git a/lib/enum/value.rb b/lib/enum/value.rb index 386db72..17e52bb 100644 --- a/lib/enum/value.rb +++ b/lib/enum/value.rb @@ -17,6 +17,7 @@ def self.inherited(subclass) # @suppress_read_errors => subclass.suppress_read_errors = false subclass.default_value = :ERROR + super end # Combined getter/setter for 'default_value'. @@ -24,10 +25,10 @@ def self.default_value(*args) # Can't use .any? because [nil].any? is false (ruby 2.4), # and nil is a valid argument here. # [nil].empty? is also false, which is what we want. - if !args.empty? - @default_value = args[0] - else + if args.empty? @default_value + else + @default_value = args[0] end end @@ -43,16 +44,16 @@ def self.suppress_read_errors(*args) # Load a primitive (symbol, string, integer) into new enum Value instance, # taking into consideration enum constraints, default_value setting. # Returns frozen Value instance. - def initialize(new_val) #, enum_class) + def initialize(raw_val) begin - @stored_value = klass[new_val].to_sym.freeze + @stored_value = klass[raw_val].to_sym.freeze rescue Enum::TokenNotFoundError => _error @error = _error.freeze case when self.class.default_value == :ERROR raise _error when self.class.default_value == :ANY - @stored_value = new_val.freeze + @stored_value = raw_val.freeze else @stored_value = self.class.default_value.freeze end diff --git a/test/value_test.rb b/test/value_test.rb index e42cb1c..d0d6b29 100644 --- a/test/value_test.rb +++ b/test/value_test.rb @@ -1,11 +1,93 @@ require 'test_helper' describe Enum::Value do - describe '.default_value' do; end + + describe '.inherited' do + describe 'sets subclass.default_value to :ERROR' do + specify { assert_equal :ERROR, Class.new(Enum::Value).instance_variable_get(:@default_value) } + end + describe 'sets subclass.suppress_read_errors to false' do + specify { assert_equal false, Class.new(Enum::Value).instance_variable_get(:@suppress_read_errors) } + end + end - describe '.suppress_read_errors' do; end + describe '.default_value' do + before { @side = Class.new(Side) } + describe 'sets class-level @default_value to args[0] if args[0] exist' do + specify { @side::Value.default_value(:something); assert_equal :something, @side::Value.instance_variable_get(:@default_value) } + end + describe 'returns class-level @default_value if args.empty?' do + specify { assert_equal :ERROR, @side::Value.instance_variable_get(:@default_value) } + end + end - describe '#initialize' do; end + describe '.suppress_read_errors' do + before { @side = Class.new(Side) } + describe 'sets class-level @suppress_read_errors to args[0] if args[0] exist' do + specify { @side::Value.suppress_read_errors(true); assert_equal true, @side::Value.instance_variable_get(:@suppress_read_errors) } + end + describe 'returns class-level @suppress_read_errors if args.empty?' do + specify { assert_equal false, @side::Value.instance_variable_get(:@suppress_read_errors) } + end + end + + describe '#initialize' do + describe 'always returns frozen object, unless exception raised' do + specify { assert Side::Value.allocate.send(:initialize, :left).frozen? } + end + + describe 'given valid enum token' do + it "sets @stored_value with frozen token" do + assert_equal :left, Side::Value.allocate.send(:initialize, :left).instance_variable_get(:@stored_value) + assert Side::Value.allocate.send(:initialize, :left).instance_variable_get(:@stored_value).frozen? + end + end + + describe 'given invalid enum token' do + before do + @side_class = Class.new(Side) + @val_class = @side_class::Value + @val_class.default_value :ERROR + #@invalid_val = @val_class.allocate.send(:initialize, :invalid) + end + + it 'sets @error with TokenNotFoundError' do + @val_class.default_value :ANY + @invalid_val = @val_class.allocate.send(:initialize, :invalid) + assert_kind_of Enum::TokenNotFoundError, @invalid_val.instance_variable_get(:@error) + end + + describe 'when default_value == :ERROR' do + it 'raises TokenNotFoundError' do + assert_raises(Enum::TokenNotFoundError) do + @val_class.allocate.send(:initialize, :invalid) + end + end + end + + describe 'when default_value == :ANY' do + before do + @val_class.default_value :ANY + @invalid_value = @val_class.allocate.send(:initialize, :invalid) + end + it 'sets @stored_value = raw_val.freeze' do + assert_equal :invalid, @invalid_value.instance_variable_get(:@stored_value) + assert @invalid_value.instance_variable_get(:@stored_value).frozen? + end + end + + describe 'when default_value is anything else' do + before do + @val_class.default_value :none + @invalid_value = @val_class.allocate.send(:initialize, :invalid) + end + it 'sets @stored_value = self.class.default_value.freeze' do + assert_equal :none, @invalid_value.instance_variable_get(:@stored_value) + assert @invalid_value.instance_variable_get(:@stored_value).frozen? + end + end + end + end describe '#klass' do describe 'returns Base child' do @@ -49,7 +131,7 @@ end end end - end # #enum_value + end # enum_value describe '#to_s' do describe 'gets enum_value as string' do From ccec43c0648adbd2737d33a8f9039219c4f487d7 Mon Sep 17 00:00:00 2001 From: wbr Date: Sat, 17 May 2025 20:09:19 -0700 Subject: [PATCH 08/11] Changed :ERROR and :ANY to :$error and :$any --- lib/enum/base.rb | 8 +------- lib/enum/value.rb | 13 ++++++++----- test/support/fixtures.rb | 2 +- test/value_test.rb | 18 ++++++++++-------- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/lib/enum/base.rb b/lib/enum/base.rb index c888cc8..76ea5a3 100644 --- a/lib/enum/base.rb +++ b/lib/enum/base.rb @@ -51,12 +51,6 @@ def index(token) history.index(enum(token)) end - # Get a new Value instance - def new(new_value) - new_instance = self::Value.new(new_value) - new_instance - end - # Render value given interger, string, or symbol. def [](val) case @@ -109,7 +103,7 @@ def add_value(val) def init_child_class(child) class << child extend ::Forwardable - def_delegators :'self::Value', :default_value, :suppress_read_errors + def_delegators :'self::Value', :default_value, :suppress_read_errors, :new end child.const_set :Value, ::Class.new(::Enum::Value) child::Value.klass = child diff --git a/lib/enum/value.rb b/lib/enum/value.rb index 17e52bb..8cfd6b7 100644 --- a/lib/enum/value.rb +++ b/lib/enum/value.rb @@ -13,10 +13,10 @@ class << self def self.inherited(subclass) # Value subclass settings and options - # @default_value => <:ERROR|:ANY|:something|nil> + # @default_value => <:$error|:$any|:something|nil> # @suppress_read_errors => subclass.suppress_read_errors = false - subclass.default_value = :ERROR + subclass.default_value = :$error super end @@ -44,15 +44,18 @@ def self.suppress_read_errors(*args) # Load a primitive (symbol, string, integer) into new enum Value instance, # taking into consideration enum constraints, default_value setting. # Returns frozen Value instance. - def initialize(raw_val) + # TODO: Add param: opts = {} for run-time temp settings changes (default_value, suppress_read_errors). + # TODO: Maybe convert all opts to @options => {default_value:, suppress_read_errors:} + # This would be easier to maintain but would require some special getter/setter methods. + def initialize(raw_val, opts={}) begin @stored_value = klass[raw_val].to_sym.freeze rescue Enum::TokenNotFoundError => _error @error = _error.freeze case - when self.class.default_value == :ERROR + when self.class.default_value == :$error || opts[:default_value] == :$error raise _error - when self.class.default_value == :ANY + when self.class.default_value == :$any || opts[:default_value] == :$any @stored_value = raw_val.freeze else @stored_value = self.class.default_value.freeze diff --git a/test/support/fixtures.rb b/test/support/fixtures.rb index 6527dca..588b384 100644 --- a/test/support/fixtures.rb +++ b/test/support/fixtures.rb @@ -29,6 +29,6 @@ class Suppressed < Enum::Base class LoadAnyValue < Enum::Base values :left, :right, :whole - default_value :ANY + default_value :$any suppress_read_errors true end \ No newline at end of file diff --git a/test/value_test.rb b/test/value_test.rb index d0d6b29..66fdbdd 100644 --- a/test/value_test.rb +++ b/test/value_test.rb @@ -3,8 +3,8 @@ describe Enum::Value do describe '.inherited' do - describe 'sets subclass.default_value to :ERROR' do - specify { assert_equal :ERROR, Class.new(Enum::Value).instance_variable_get(:@default_value) } + describe 'sets subclass.default_value to :$error' do + specify { assert_equal :$error, Class.new(Enum::Value).instance_variable_get(:@default_value) } end describe 'sets subclass.suppress_read_errors to false' do specify { assert_equal false, Class.new(Enum::Value).instance_variable_get(:@suppress_read_errors) } @@ -17,7 +17,7 @@ specify { @side::Value.default_value(:something); assert_equal :something, @side::Value.instance_variable_get(:@default_value) } end describe 'returns class-level @default_value if args.empty?' do - specify { assert_equal :ERROR, @side::Value.instance_variable_get(:@default_value) } + specify { assert_equal :$error, @side::Value.instance_variable_get(:@default_value) } end end @@ -32,6 +32,8 @@ end describe '#initialize' do + # TODO: test for params opts. + describe 'always returns frozen object, unless exception raised' do specify { assert Side::Value.allocate.send(:initialize, :left).frozen? } end @@ -47,17 +49,17 @@ before do @side_class = Class.new(Side) @val_class = @side_class::Value - @val_class.default_value :ERROR + @val_class.default_value :$error #@invalid_val = @val_class.allocate.send(:initialize, :invalid) end it 'sets @error with TokenNotFoundError' do - @val_class.default_value :ANY + @val_class.default_value :$any @invalid_val = @val_class.allocate.send(:initialize, :invalid) assert_kind_of Enum::TokenNotFoundError, @invalid_val.instance_variable_get(:@error) end - describe 'when default_value == :ERROR' do + describe 'when default_value == :$error' do it 'raises TokenNotFoundError' do assert_raises(Enum::TokenNotFoundError) do @val_class.allocate.send(:initialize, :invalid) @@ -65,9 +67,9 @@ end end - describe 'when default_value == :ANY' do + describe 'when default_value == :$any' do before do - @val_class.default_value :ANY + @val_class.default_value :$any @invalid_value = @val_class.allocate.send(:initialize, :invalid) end it 'sets @stored_value = raw_val.freeze' do From 41d96c085b984c1a4530476a9f4f99f462207291 Mon Sep 17 00:00:00 2001 From: wbr Date: Sat, 17 May 2025 20:32:50 -0700 Subject: [PATCH 09/11] Fixed code that was producing method redefinition warnings. Fixed code that was producing ambiguous argument warnings. --- lib/enum/value.rb | 4 +++- test/value_test.rb | 30 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/enum/value.rb b/lib/enum/value.rb index 8cfd6b7..27317f1 100644 --- a/lib/enum/value.rb +++ b/lib/enum/value.rb @@ -8,7 +8,9 @@ class Value attr_reader :stored_value, :error class << self - attr_accessor :default_value, :suppress_read_errors, :klass + #attr_accessor :default_value, :suppress_read_errors, :klass + attr_writer :default_value, :suppress_read_errors + attr_accessor :klass end def self.inherited(subclass) diff --git a/test/value_test.rb b/test/value_test.rb index 66fdbdd..2c3b954 100644 --- a/test/value_test.rb +++ b/test/value_test.rb @@ -197,33 +197,33 @@ describe '#<=>' do describe 'comparable with symbol' do - specify('less_than') { assert_equal -1, Side.new(:right) <=> :whole } - specify('equal_to') { assert_equal 0, Side.new(:right) <=> :right } - specify('greater_than') { assert_equal 1, Side.new(:right) <=> :left } + specify('less_than') { assert_equal(-1, Side.new(:right) <=> :whole) } + specify('equal_to') { assert_equal(0, Side.new(:right) <=> :right) } + specify('greater_than') { assert_equal(1, Side.new(:right) <=> :left) } end describe 'comparable with string' do - specify('less_than') { assert_equal -1, Side.new(:right) <=> 'whole' } - specify('equal_to') { assert_equal 0, Side.new(:right) <=> 'right' } - specify('greater_than') { assert_equal 1, Side.new(:right) <=> 'left' } + specify('less_than') { assert_equal(-1, Side.new(:right) <=> 'whole') } + specify('equal_to') { assert_equal(0, Side.new(:right) <=> 'right') } + specify('greater_than') { assert_equal(1, Side.new(:right) <=> 'left') } end describe 'comparable with integer' do - specify('less_than') { assert_equal -1, Side.new(:right) <=> 2 } - specify('equal_to') { assert_equal 0, Side.new(:right) <=> 1 } - specify('greater_than') { assert_equal 1, Side.new(:right) <=> 0 } + specify('less_than') { assert_equal(-1, Side.new(:right) <=> 2) } + specify('equal_to') { assert_equal(0, Side.new(:right) <=> 1) } + specify('greater_than') { assert_equal(1, Side.new(:right) <=> 0) } end describe 'comparable with other Value object of same enum class' do - specify('less_than') { assert_equal -1, Side.new(:right) <=> Side.new(:whole) } - specify('equal_to') { assert_equal 0, Side.new(:right) <=> Side.new(:right) } - specify('greater_than') { assert_equal 1, Side.new(:right) <=> Side.new(:left) } + specify('less_than') { assert_equal(-1, Side.new(:right) <=> Side.new(:whole)) } + specify('equal_to') { assert_equal(0, Side.new(:right) <=> Side.new(:right)) } + specify('greater_than') { assert_equal(1, Side.new(:right) <=> Side.new(:left)) } end describe 'returns nil if uncomarable with other' do - specify('with_object') { assert_nil Side.new(:right) <=> Object.new } - specify('with_nil') { assert_nil Side.new(:right) <=> nil } - specify('with_invalid_other_instance') { assert_nil Side.new(:right) <=> LoadAnyValue.new(:right) } + specify('with_object') { assert_nil(Side.new(:right) <=> Object.new) } + specify('with_nil') { assert_nil(Side.new(:right) <=> nil) } + specify('with_invalid_other_instance') { assert_nil(Side.new(:right) <=> LoadAnyValue.new(:right)) } end end From 81bb94980a2321b9e80b6fc64f342a1b22b5981f Mon Sep 17 00:00:00 2001 From: wbr <862810+ginjo@users.noreply.github.com> Date: Sat, 17 May 2025 20:50:01 -0700 Subject: [PATCH 10/11] Updated test files to make use of require_relative, which makes it easier to run individual tests: bundle exec ruby -I./ test/value_test.rb Added require 'enum' to value.rb. --- lib/enum/value.rb | 2 ++ test/base_test.rb | 2 +- test/predicates_test.rb | 2 +- test/value_test.rb | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/enum/value.rb b/lib/enum/value.rb index 27317f1..64cd577 100644 --- a/lib/enum/value.rb +++ b/lib/enum/value.rb @@ -1,3 +1,5 @@ +require 'enum' + module Enum class Value diff --git a/test/base_test.rb b/test/base_test.rb index 8c759ad..fb9fdc0 100644 --- a/test/base_test.rb +++ b/test/base_test.rb @@ -1,4 +1,4 @@ -require 'test_helper' +require_relative 'test_helper' describe Enum::Base do describe Side do diff --git a/test/predicates_test.rb b/test/predicates_test.rb index 0ccf4ce..4b48335 100644 --- a/test/predicates_test.rb +++ b/test/predicates_test.rb @@ -1,4 +1,4 @@ -require 'test_helper' +require_relative 'test_helper' describe Enum::Predicates do describe Table do diff --git a/test/value_test.rb b/test/value_test.rb index 2c3b954..3add367 100644 --- a/test/value_test.rb +++ b/test/value_test.rb @@ -1,4 +1,4 @@ -require 'test_helper' +require_relative 'test_helper' describe Enum::Value do From 5d23f9b6be02b0af00609d9462bf0e9289852521 Mon Sep 17 00:00:00 2001 From: wbr <862810+ginjo@users.noreply.github.com> Date: Sat, 17 May 2025 20:53:28 -0700 Subject: [PATCH 11/11] Removed require 'enum' to value.rb... it was circular. --- lib/enum/value.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/enum/value.rb b/lib/enum/value.rb index 64cd577..27317f1 100644 --- a/lib/enum/value.rb +++ b/lib/enum/value.rb @@ -1,5 +1,3 @@ -require 'enum' - module Enum class Value