Skip to content
1 change: 1 addition & 0 deletions lib/enum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
require 'enum/token_not_found_error'
require 'enum/base'
require 'enum/predicates'
require 'enum/value'
29 changes: 27 additions & 2 deletions lib/enum/base.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'set'
require 'forwardable'

module Enum
class Base < BasicObject
Expand Down Expand Up @@ -37,13 +38,30 @@ 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

# 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

Expand Down Expand Up @@ -83,6 +101,13 @@ def add_value(val)
end

def init_child_class(child)
class << child
extend ::Forwardable
def_delegators :'self::Value', :default_value, :suppress_read_errors, :new
end
child.const_set :Value, ::Class.new(::Enum::Value)
child::Value.klass = child

child.store = self.store.clone
child.history = self.history.clone
end
Expand Down
149 changes: 149 additions & 0 deletions lib/enum/value.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
module Enum
class Value

# TODO: Perhaps stored_value should be renamed unsafe_value?
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's call it atom, wdyt? The idea borrow from this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I like atom.


include Comparable

attr_reader :stored_value, :error

class << self
#attr_accessor :default_value, :suppress_read_errors, :klass
attr_writer :default_value, :suppress_read_errors
attr_accessor :klass
end

def self.inherited(subclass)
# Value subclass settings and options
# @default_value => <:$error|:$any|:something|nil>
# @suppress_read_errors => <true|false>
subclass.suppress_read_errors = false
subclass.default_value = :$error
super
end

# 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_value
else
@default_value = args[0]
end
end

# Combined getter/setter for 'suppress_read_errors'.
def self.suppress_read_errors(*args)
if !args.empty?
@suppress_read_errors = args[0]
else
@suppress_read_errors
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.
# TODO: Add param: opts = {} for run-time temp settings changes (default_value, suppress_read_errors).
# TODO: Maybe convert all opts to @options => {default_value:<class or instance opts>, suppress_read_errors:<class or instance opts>}
# 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 || opts[:default_value] == :$error
raise _error
when self.class.default_value == :$any || opts[:default_value] == :$any
@stored_value = raw_val.freeze
else
@stored_value = self.class.default_value.freeze
end
end
self.freeze
self
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to return self from an initializer.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right... I probably thought I was working on .new 😅. Will fix.

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.suppress_read_errors
_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.suppress_read_errors
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
2 changes: 1 addition & 1 deletion test/base_test.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require 'test_helper'
require_relative 'test_helper'

describe Enum::Base do
describe Side do
Expand Down
2 changes: 1 addition & 1 deletion test/predicates_test.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require 'test_helper'
require_relative 'test_helper'

describe Enum::Predicates do
describe Table do
Expand Down
11 changes: 11 additions & 0 deletions test/support/fixtures.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,14 @@ class Side < Enum::Base
values :left, :right
end
end

class Suppressed < Enum::Base
values :left, :right, :whole
suppress_read_errors true
end

class LoadAnyValue < Enum::Base
values :left, :right, :whole
default_value :$any
suppress_read_errors true
end
Loading