class ActiveValue::Base

ActiveValue::Base is base class for immutable value object that has interfaces like ActiveRecord. In a class inherited this class, constant variables get the behavior like records of ActiveRecord.

Usage.

1. Define the target class inherited from ActiveValue::Base
2. List attributes of the object using attr_accessor
3. Declare constant variables as this class

Example.

class QuestionType < ActiveValue::Base
  attr_accessor :id, :symbol, :name
  CHECKBOX  = new id: 1, symbol: :checkbox, name: "Check Box"
  RADIO     = new id: 2, symbol: :radio,    name: "Radio Button"
  SELECTBOX = new id: 3, symbol: :select,   name: "Select Box"
  TEXT      = new id: 4, symbol: :text,     name: "Text Box"
  TEXTAREA  = new id: 5, symbol: :textarea, name: "Text Area"
end
QuestionType.find(1)
=> QuestionType::CHECKBOX
QuestionType.find(1).name
=> "Check Box"
QuestionType.find(1).checkbox?
=> true
QuestionType.pluck(:id, :name)
=> [[1, "Check Box"], [2, "Radio Button"], [3, "Select Box"], ...]

Public Class Methods

accessors() click to toggle source

Get accessors the overrided method saved.

# File lib/active_value/base.rb, line 80
def self.accessors
  readers = instance_methods.reject { |attr| attr.to_s[-1] == '=' }
  writers = instance_methods.select { |attr| attr.to_s[-1] == '=' }.map { |attr| attr.to_s.chop.to_sym }
  accessors = readers & writers - [:!]
  Array(@accessors) | accessors.reverse!
end
all() click to toggle source

Get all constant instances. (Sorted by the first defined accessor)

# File lib/active_value/base.rb, line 57
def self.all
  unsorted_all.sort
end
attr_accessor(*several_variants) click to toggle source

Wrap default attr_accessor method in order to save accessor defined order.

Calls superclass method
# File lib/active_value/base.rb, line 74
def self.attr_accessor(*several_variants)
  @accessors = *several_variants
  super
end
define_question_methods(attr_name = :symbol) click to toggle source

Automatically these methods are defined in this version. This method is remained only for compatibility.

# File lib/active_value/base.rb, line 67
def self.define_question_methods(attr_name = :symbol)
  unsorted_all.each do |object|
    define_method(object.public_send(attr_name).to_s + '?') { self == object } if object.respond_to?(attr_name)
  end
end
find(index) click to toggle source

Getter interface by the id element like `find` method in ActiveRecord.

# File lib/active_value/base.rb, line 39
def self.find(index)
  object = all.bsearch { |object| object.public_send(:id) >= index }
  object&.id == index ? object : nil
end
find_by(conditions) click to toggle source

Getter interface by the argument element like `find_by` method in ActiveRecord.

# File lib/active_value/base.rb, line 45
def self.find_by(conditions)
  unsorted_all.find do |object|
    conditions.all? { |key, value| object.public_send(key) == value }
  end
end
method_missing(method, *args, &block) click to toggle source

Delegate undefined method calls to `all` method (returns Array).

# File lib/active_value/base.rb, line 34
def self.method_missing(method, *args, &block)
  all.public_send(method, *args, &block)
end
new(attributes = {}) click to toggle source

If self instance is passed as an argument, create a new instance that has copied attributes. (it's like copy constructor by shallow copy) Hash instance is passed, the hash attributes apply a new instance.

# File lib/active_value/base.rb, line 96
def initialize(attributes = {})
  case attributes
  when self.class then self.class.accessors.map(&:to_s).each { |attribute| public_send(attribute + '=', attributes.public_send(attribute)) }
  when Hash       then attributes.stringify_keys.each { |key, value| public_send(key + '=', value) if respond_to?(key + '=') }
  end
end
patched_dup(object) click to toggle source

Wrapper dup method for can't dup on Fixnum#dup (NilClass etc.) before Ruby 2.4

# File lib/active_value/base.rb, line 88
def self.patched_dup(object)
  object.dup
rescue TypeError
  object
end
pluck(*accessors) click to toggle source

Get attributes

# File lib/active_value/base.rb, line 62
def self.pluck(*accessors)
  map { |record| Array(accessors).map { |accessor| record.public_send(accessor) } }.map { |array| array.size == 1 ? array.first : array }
end
unsorted_all() click to toggle source

Get all constant instances. (Unsorted)

# File lib/active_value/base.rb, line 52
def self.unsorted_all
  constants.collect { |name| const_get(name) }.select { |object| object.instance_of?(self) }
end

Public Instance Methods

<=>(another) click to toggle source

Define the spaceship operator. Compare self with another by the first defined accessor. (In many cases, it's `id` implicitly)

# File lib/active_value/base.rb, line 153
def <=>(another)
  attr = self.class.accessors.first || :object_id
  public_send(attr) <=> another.public_send(attr) if respond_to?(attr) && another.respond_to?(attr)
end
==(another) click to toggle source

Define the equal operator. `A == B` expression means every attribute has same value. (NOT object_id comparison)

# File lib/active_value/base.rb, line 142
def ==(another)
  self.class.accessors.all? { |attr| respond_to?(attr) && another.respond_to?(attr) && public_send(attr) == another.public_send(attr) }
end
Also aliased as: eql?
eql?(another)
Alias for: ==
hash() click to toggle source

Hash method for equivalence comparison with `eql?` method. (Referenced by `Enumerable#uniq` method etc.)

# File lib/active_value/base.rb, line 148
def hash
  to_json.hash
end
inspect() click to toggle source
# File lib/active_value/base.rb, line 136
def inspect
  hash = to_shallow_hash
  Hash === hash ? '#<' << self.class.name.split('::').last << ' ' << hash.map { |key, value| key.to_s << ': ' << value.inspect }.join(', ') << '>' : hash.inspect
end
method_missing(method, *args, &block) click to toggle source

If objects have symbol attributes, the objects can be checked equivalence by the method named symbol + `?`

Calls superclass method
# File lib/active_value/base.rb, line 104
def method_missing(method, *args, &block)
  object = self.class.unsorted_all.find { |object| object.respond_to?(:symbol) && method.to_s == object.public_send(:symbol).to_s + '?' }
  if object.nil?
    super
  else
    self == object
  end
end
to_deep_hash() click to toggle source

Convert to hash with deep copy. If values include collections(Hash, Array, etc.), search and convert into collections recursively.

# File lib/active_value/base.rb, line 119
def to_deep_hash
  scan = ->(value) do
    case value
    when Hash  then value.each_with_object({}) { |(k, v), h| h[k] = scan.call(v) }
    when Array then value.map { |v| scan.call(v) }
    when Base  then scan.call(value.to_shallow_hash)
    else self.class.patched_dup(value)
    end
  end
  self.class.accessors.each_with_object({}) { |key, hash| hash[key] = scan.call(public_send(key)) }
end
Also aliased as: to_h
to_h()
Alias for: to_deep_hash
to_json() click to toggle source
# File lib/active_value/base.rb, line 132
def to_json
  JSON.generate(to_h)
end
to_shallow_hash() click to toggle source

Convert to hash with shallow copy. If values include collections(Hash, Array, etc.), search and convert without elements of the collection.

# File lib/active_value/base.rb, line 114
def to_shallow_hash
  self.class.accessors.each_with_object({}) { |key, hash| hash[key] = self.class.patched_dup(public_send(key)) }
end