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
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
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
Wrap default attr_accessor
method in order to save accessor defined order.
# File lib/active_value/base.rb, line 74 def self.attr_accessor(*several_variants) @accessors = *several_variants super end
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
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
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
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
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
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
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
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
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
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
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
# 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
If objects have symbol attributes, the objects can be checked equivalence by the method named symbol + `?`
# 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
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
# File lib/active_value/base.rb, line 132 def to_json JSON.generate(to_h) end
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