active_value 0.1.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a56662308895b1d1a241aa5fb1bdce5fa1e6f31f2aac1bfb18e40494d1009e91
4
- data.tar.gz: faaa8dc8e12044fd001d10cc79e01fed871196f4c584020081bb9bb797c705d7
3
+ metadata.gz: 5072b5c5ad5e643c3bdc8c9986be317b3badfce1bfce29e79c07795046113957
4
+ data.tar.gz: 89956a9fefb5ba8a6e7a4ca70b3bb5cfab527fc0a133d830790659d8a5e99d5d
5
5
  SHA512:
6
- metadata.gz: 37f355f81ed2051dd54f097156d9a415f37f61df4861ec96c935543f894c2ee6ace4c4cce2a46eb016e5ac8cd00844d24353f7e694a7bae4f17093cab1f10a23
7
- data.tar.gz: '0758dec3aff5b9c595c9f8541361617cba4804a265797b4a4bb1ccb1b0e8156de1ff31e6f0056d5b0285f7c2d244b800d31d768ae9163c3f7c2b02cc4d2c604e'
6
+ metadata.gz: cb9ac9c497dce8199818f4d59a694f8fd8f016313eca4e8a893428d706ab233bf1d01cba0c258258a4b3dcf348c9cebc8a7a8dabd88072fa09043986374d7024
7
+ data.tar.gz: e61bc5891f72b87fff0ecf593b0037792cea63e1114f515d2c732e6ebdba47ebe1f55310a9ceef92a1821ff5eafebd008372addd1ed96f1f53c50e8940439451
@@ -0,0 +1,41 @@
1
+ name: Publish
2
+ on:
3
+ workflow_dispatch:
4
+ push:
5
+ branches: [ $default-branch ]
6
+ tags:
7
+ - v*
8
+ jobs:
9
+ build:
10
+ name: Build + Publish
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - name: Set up Ruby 3.0
15
+ uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: 3.0
18
+ bundler-cache: true
19
+ - name: Publish to GitHub Package Repository
20
+ run: |
21
+ mkdir -p $HOME/.gem
22
+ touch $HOME/.gem/credentials
23
+ chmod 0600 $HOME/.gem/credentials
24
+ printf -- "---\n:github: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
25
+ gem build *.gemspec
26
+ gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
27
+ env:
28
+ GEM_HOST_API_KEY: "Bearer ${{ secrets.GITHUB_TOKEN }}"
29
+ OWNER: ${{ github.repository_owner }}
30
+ continue-on-error: true
31
+ - name: Publish to RubyGems
32
+ run: |
33
+ mkdir -p $HOME/.gem
34
+ touch $HOME/.gem/credentials
35
+ chmod 0600 $HOME/.gem/credentials
36
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
37
+ gem build *.gemspec
38
+ gem push *.gem
39
+ env:
40
+ GEM_HOST_API_KEY: "${{ secrets.RUBYGEMS_AUTH_TOKEN }}"
41
+ continue-on-error: true
@@ -0,0 +1,16 @@
1
+ name: Test
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-latest
6
+ strategy:
7
+ fail-fast: false
8
+ matrix:
9
+ ruby: [3.0, 2.7, 2.3]
10
+ steps:
11
+ - uses: actions/checkout@v2
12
+ - uses: ruby/setup-ruby@v1
13
+ with:
14
+ ruby-version: ${{ matrix.ruby }}
15
+ bundler-cache: true
16
+ - run: bundle exec rake
data/README.md CHANGED
@@ -1,8 +1,12 @@
1
- # ActiveValue
1
+ ![TEST](https://img.shields.io/github/workflow/status/hiratai/active_value/Test?style=for-the-badge)
2
+ ![RELEASE_VERSION](https://img.shields.io/github/v/release/hiratai/active_value?style=for-the-badge)
3
+ ![GEM_VERSION](https://img.shields.io/gem/v/active_value?style=for-the-badge)
4
+ ![LICENSE](https://img.shields.io/github/license/hiratai/active_value?style=for-the-badge)
2
5
 
3
- Model for non database. (ValueObject)
4
- However like ActiveRecord.
6
+ # ActiveValue
5
7
 
8
+ ActiveValue::Base is base class for immutable value object that has interfaces like ActiveRecord.
9
+ In a class inherited this class, constant variables get the behavior like records of ActiveRecord.
6
10
 
7
11
  ## Installation
8
12
 
@@ -14,7 +18,7 @@ gem 'active_value'
14
18
 
15
19
  And then execute:
16
20
 
17
- $ bundle
21
+ $ bundle install
18
22
 
19
23
  Or install it yourself as:
20
24
 
@@ -23,19 +27,40 @@ Or install it yourself as:
23
27
 
24
28
  ## Usage
25
29
 
30
+ 1. Define the target class inherited from ActiveValue::Base
31
+ 2. List attributes of the object using attr_accessor
32
+ 3. Declare constant variables as this instance in this class
33
+
26
34
  ```ruby
27
- class QuestionType < ActiveValue::Base
28
-
35
+ class FormCategory < ActiveValue::Base
36
+
29
37
  attr_accessor :id, :symbol, :name
30
-
31
- Checkbox = new id: 1, symbol: :checkbox, name: "Check Box"
32
- Radio = new id: 2, symbol: :radio, name: "Radio Button"
33
- Selectbox = new id: 3, symbol: :select, name: "Select Box"
34
- Text = new id: 4, symbol: :text, name: "Text Box"
35
- TextArea = new id: 5, symbol: :textarea, name: "Text Area"
36
-
38
+
39
+ CHECK_BOX = new id: 1, symbol: :checkbox, name: "Check Box"
40
+ RADIO = new id: 2, symbol: :radio, name: "Radio Button"
41
+ SELECT_BOX = new id: 3, symbol: :select, name: "Select Box"
42
+ TEXT = new id: 4, symbol: :text, name: "Text Box"
43
+ TEXTAREA = new id: 5, symbol: :textarea, name: "Text Area"
44
+
37
45
  end
38
46
  ```
47
+ Then, constant variables in the class can be accessed by following methods.
48
+ ```ruby
49
+ FormCategory.find(1)
50
+ => <#FormCategory id: 1, symbol: :checkbox, name: "Check Box" >
51
+
52
+ FormCategory.find_by(symbol: :radio).name
53
+ => "Radio Button"
54
+
55
+ FormCategory.find(1).checkbox?
56
+ => true
57
+
58
+ FormCategory.pluck(:id, :name)
59
+ => [[1, "Check Box"], [2, "Radio Button"], [3, "Select Box"], ...]
60
+
61
+ FormCategory.find(2).to_h
62
+ => { :id => 2, :symbol => :radio, :name => "Radio Button" }
63
+ ```
39
64
 
40
65
 
41
66
  ## Contributing
@@ -7,22 +7,13 @@ Gem::Specification.new do |spec|
7
7
  spec.name = "active_value"
8
8
  spec.version = ActiveValue::VERSION
9
9
  spec.authors = ["Taiki Hiramatsu"]
10
- spec.email = ["ta.hi.samba@gmail.com"]
10
+ spec.email = ["info@hiramatsu-sa-office.jp"]
11
11
 
12
- spec.summary = %q{Model for non database.(ValueObject) However like ActiveRecord.}
13
- #spec.description = %q{}
12
+ spec.summary = %q{ActiveValue is base class for immutable value object that has interfaces like ActiveRecord.}
13
+ spec.description = %q{In a class inherited this class, constant variables get the behavior like records of ActiveRecord.}
14
14
  spec.homepage = "https://github.com/hiratai/active_value"
15
15
  spec.license = "MIT"
16
16
 
17
- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
- # to allow pushing to a single host or delete this section to allow pushing to any host.
19
- if spec.respond_to?(:metadata)
20
- spec.metadata['allowed_push_host'] = "https://rubygems.org"
21
- else
22
- raise "RubyGems 2.0 or newer is required to protect against " \
23
- "public gem pushes."
24
- end
25
-
26
17
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
18
  f.match(%r{^(test|spec|features)/})
28
19
  end
@@ -30,9 +21,11 @@ Gem::Specification.new do |spec|
30
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
22
  spec.require_paths = ["lib"]
32
23
 
33
- spec.add_development_dependency "bundler", "~> 1.13"
34
- spec.add_development_dependency "rake", "~> 10.0"
35
- spec.add_development_dependency "minitest", "~> 5.0"
24
+ spec.required_ruby_version = ">= 2.3"
25
+
26
+ spec.add_development_dependency "bundler", "> 1.0.0"
27
+ spec.add_development_dependency "rake", "> 10.0"
28
+ spec.add_development_dependency "minitest", "> 5.0"
36
29
 
37
- spec.add_dependency "activesupport"
30
+ spec.add_dependency "activesupport", "~> 5.2.4"
38
31
  end
@@ -1,5 +1,5 @@
1
+ require "active_support/dependencies/autoload"
1
2
  require "active_support/core_ext"
2
- require "active_support/dependencies"
3
3
  require "active_value/version"
4
4
 
5
5
  module ActiveValue
@@ -1,51 +1,82 @@
1
1
  require "active_support/core_ext/hash/keys"
2
+ require "json"
2
3
 
3
4
  module ActiveValue
4
- # TODO: English Translation
5
- # ActiveRecordライクに定数を定義するための基底クラス
6
- # このクラスを継承したクラス内で宣言した定数をレコードとして扱うことができる。
7
- # 継承先のクラスでattr_accessorで要素を定義することで、その要素をDBのカラムのように扱うことができる。
5
+ # ActiveValue::Base is base class for immutable value object that has interfaces like ActiveRecord.
6
+ # In a class inherited this class, constant variables get the behavior like records of ActiveRecord.
8
7
  #
9
- # いくつかの要素名は予約されていて、定義があれば対応したメソッドが使用可能。
10
- # 要素名 : メソッド
11
- # id : find
12
- # symbol : define_question_methods
8
+ # Usage.
9
+ # 1. Define the target class inherited from ActiveValue::Base
10
+ # 2. List attributes of the object using attr_accessor
11
+ # 3. Declare constant variables as this class
13
12
  #
13
+ # Example.
14
+ # class QuestionType < ActiveValue::Base
15
+ # attr_accessor :id, :symbol, :name
16
+ # CHECKBOX = new id: 1, symbol: :checkbox, name: "Check Box"
17
+ # RADIO = new id: 2, symbol: :radio, name: "Radio Button"
18
+ # SELECTBOX = new id: 3, symbol: :select, name: "Select Box"
19
+ # TEXT = new id: 4, symbol: :text, name: "Text Box"
20
+ # TEXTAREA = new id: 5, symbol: :textarea, name: "Text Area"
21
+ # end
22
+ # QuestionType.find(1)
23
+ # => QuestionType::CHECKBOX
24
+ # QuestionType.find(1).name
25
+ # => "Check Box"
26
+ # QuestionType.find(1).checkbox?
27
+ # => true
28
+ # QuestionType.pluck(:id, :name)
29
+ # => [[1, "Check Box"], [2, "Radio Button"], [3, "Select Box"], ...]
30
+
14
31
  class Base
15
32
 
16
- # 未定義のメソッド呼び出しはall(Array)に委譲
33
+ # Delegate undefined method calls to `all` method (returns Array).
17
34
  def self.method_missing(method, *args, &block)
18
35
  all.public_send(method, *args, &block)
19
36
  end
20
37
 
21
- # ActiveRecordライクに使えるfind, find_by, all, pluckを定義
22
- def self.find(index); find_by(id: index); end
38
+ # Getter interface by the id element like `find` method in ActiveRecord.
39
+ def self.find(index)
40
+ object = all.bsearch { |object| object.public_send(:id) >= index }
41
+ object&.id == index ? object : nil
42
+ end
43
+
44
+ # Getter interface by the argument element like `find_by` method in ActiveRecord.
23
45
  def self.find_by(conditions)
24
- all.find do |object|
46
+ unsorted_all.find do |object|
25
47
  conditions.all? { |key, value| object.public_send(key) == value }
26
48
  end
27
49
  end
50
+
51
+ # Get all constant instances. (Unsorted)
52
+ def self.unsorted_all
53
+ constants.collect { |name| const_get(name) }.select { |object| object.instance_of?(self) }
54
+ end
55
+
56
+ # Get all constant instances. (Sorted by the first defined accessor)
28
57
  def self.all
29
- constants.collect { |name| const_get(name) }.sort
58
+ unsorted_all.sort
30
59
  end
60
+
61
+ # Get attributes
31
62
  def self.pluck(*accessors)
32
- map { |record| accessors.size > 1 ? Array(accessors).map { |accessor| record.public_send(accessor) } : record.public_send(accessors.first) }
63
+ map { |record| Array(accessors).map { |accessor| record.public_send(accessor) } }.map { |array| array.size == 1 ? array.first : array }
33
64
  end
34
65
 
35
- # symbol要素の定義があれば、symbol名 + ? でインスタンスが同一のものか判別するメソッドを定義
66
+ # Automatically these methods are defined in this version. This method is remained only for compatibility.
36
67
  def self.define_question_methods(attr_name = :symbol)
37
- constants.collect { |name| const_get(name) }.each do |object|
68
+ unsorted_all.each do |object|
38
69
  define_method(object.public_send(attr_name).to_s + '?') { self == object } if object.respond_to?(attr_name)
39
70
  end
40
71
  end
41
72
 
42
- # 定義したaccessorを順序を保ったまま保持したいためオーバーライド。従来のattr_accessorの挙動は変更しない。
73
+ # Wrap default attr_accessor method in order to save accessor defined order.
43
74
  def self.attr_accessor(*several_variants)
44
75
  @accessors = *several_variants
45
76
  super
46
77
  end
47
78
 
48
- # accessor一覧を取得するメソッド
79
+ # Get accessors the overrided method saved.
49
80
  def self.accessors
50
81
  readers = instance_methods.reject { |attr| attr.to_s[-1] == '=' }
51
82
  writers = instance_methods.select { |attr| attr.to_s[-1] == '=' }.map { |attr| attr.to_s.chop.to_sym }
@@ -53,40 +84,53 @@ module ActiveValue
53
84
  Array(@accessors) | accessors.reverse!
54
85
  end
55
86
 
56
- # ActiveRecordのnewのようにハッシュで初期化を行える機能と
57
- # コピーコンストラクタ(に似た何か)を定義(Shallowコピー)
87
+ # Wrapper dup method for can't dup on Fixnum#dup (NilClass etc.) before Ruby 2.4
88
+ def self.patched_dup(object)
89
+ object.dup
90
+ rescue TypeError
91
+ object
92
+ end
93
+
94
+ # If self instance is passed as an argument, create a new instance that has copied attributes. (it's like copy constructor by shallow copy)
95
+ # Hash instance is passed, the hash attributes apply a new instance.
58
96
  def initialize(attributes = {})
59
97
  case attributes
60
- when self.class then self.class.accessors.stringify_keys.each { |attribute| public_send(attribute + '=', attributes.public_send(attribute)) }
98
+ when self.class then self.class.accessors.map(&:to_s).each { |attribute| public_send(attribute + '=', attributes.public_send(attribute)) }
61
99
  when Hash then attributes.stringify_keys.each { |key, value| public_send(key + '=', value) if respond_to?(key + '=') }
62
100
  end
63
101
  end
64
102
 
65
- # ActiveRecordと同様に要素へハッシュアクセスで取得できるようにアクセサを定義
66
- #def [](attribute); public_send(attribute); end
67
- #def []=(attribute, value); public_send(attribute.to_s + '=', value) end
103
+ # If objects have symbol attributes, the objects can be checked equivalence by the method named symbol + `?`
104
+ def method_missing(method, *args, &block)
105
+ object = self.class.unsorted_all.find { |object| object.respond_to?(:symbol) && method.to_s == object.public_send(:symbol).to_s + '?' }
106
+ if object.nil?
107
+ super
108
+ else
109
+ self == object
110
+ end
111
+ end
68
112
 
69
- # Hash型へ浅い変換を行う。値がコンテナ(Hash, Array)を含んでいた場合もその中の探索は行わずそのまま出力をする。
113
+ # Convert to hash with shallow copy. If values include collections(Hash, Array, etc.), search and convert without elements of the collection.
70
114
  def to_shallow_hash
71
- self.class.accessors.inject(Hash.new) { |hash, key| hash[key] = public_send(key); hash }.reject { |key, value| value.nil? }
115
+ self.class.accessors.each_with_object({}) { |key, hash| hash[key] = self.class.patched_dup(public_send(key)) }
72
116
  end
73
117
 
74
- # Hash型へ深い変換を行う。値がコンテナ(Hash, Array)を含んでいた場合、このクラスが含まれなくなるまで探索とHashへの変換を再帰的に行う。
118
+ # Convert to hash with deep copy. If values include collections(Hash, Array, etc.), search and convert into collections recursively.
75
119
  def to_deep_hash
76
120
  scan = ->(value) do
77
121
  case value
78
- when Hash then value.inject(Hash.new) { |h, (k, v)| h[k] = scan.call(v); h }
79
- when Array then value.map { |v| scan.call(v) }
80
- when ConstantRecord then scan.call(value.to_shallow_hash)
81
- else value
122
+ when Hash then value.each_with_object({}) { |(k, v), h| h[k] = scan.call(v) }
123
+ when Array then value.map { |v| scan.call(v) }
124
+ when Base then scan.call(value.to_shallow_hash)
125
+ else self.class.patched_dup(value)
82
126
  end
83
127
  end
84
- self.class.accessors.inject(Hash.new) { |hash, key| hash[key] = scan.call(public_send(key)); hash }
128
+ self.class.accessors.each_with_object({}) { |key, hash| hash[key] = scan.call(public_send(key)) }
85
129
  end
86
130
  alias_method :to_h, :to_deep_hash
87
131
 
88
132
  def to_json
89
- to_h.to_json
133
+ JSON.generate(to_h)
90
134
  end
91
135
 
92
136
  def inspect
@@ -94,11 +138,21 @@ module ActiveValue
94
138
  Hash === hash ? '#<' << self.class.name.split('::').last << ' ' << hash.map { |key, value| key.to_s << ': ' << value.inspect }.join(', ') << '>' : hash.inspect
95
139
  end
96
140
 
97
- # Spaceship Operatorを定義、id等の比較可能な識別子が最初に定義されることが前提、存在しなければobject_idを参照する。
98
- # TODO: ソート順序の基準となるattrの宣言を行うメソッドの実装
141
+ # Define the equal operator. `A == B` expression means every attribute has same value. (NOT object_id comparison)
142
+ def ==(another)
143
+ self.class.accessors.all? { |attr| public_send(attr) == another.public_send(attr) }
144
+ end
145
+ alias_method :eql?, :==
146
+
147
+ # Hash method for equivalence comparison with `eql?` method. (Referenced by `Enumerable#uniq` method etc.)
148
+ def hash
149
+ to_json.hash
150
+ end
151
+
152
+ # Define the spaceship operator. Compare self with another by the first defined accessor. (In many cases, it's `id` implicitly)
99
153
  def <=>(another)
100
154
  attr = self.class.accessors.first || :object_id
101
- public_send(attr) <=> another.public_send(attr)
155
+ public_send(attr) <=> another.public_send(attr) if respond_to?(attr) && another.respond_to?(attr)
102
156
  end
103
157
 
104
158
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveValue
2
- VERSION = "0.1.1"
2
+ VERSION = "1.0.0"
3
3
  end
metadata CHANGED
@@ -1,78 +1,81 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_value
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taiki Hiramatsu
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-16 00:00:00.000000000 Z
11
+ date: 2021-01-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.13'
19
+ version: 1.0.0
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.13'
26
+ version: 1.0.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '10.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '5.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: activesupport
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 5.2.4
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
69
- description:
68
+ version: 5.2.4
69
+ description: In a class inherited this class, constant variables get the behavior
70
+ like records of ActiveRecord.
70
71
  email:
71
- - ta.hi.samba@gmail.com
72
+ - info@hiramatsu-sa-office.jp
72
73
  executables: []
73
74
  extensions: []
74
75
  extra_rdoc_files: []
75
76
  files:
77
+ - ".github/workflows/publish.yml"
78
+ - ".github/workflows/test.yml"
76
79
  - ".gitignore"
77
80
  - ".travis.yml"
78
81
  - Gemfile
@@ -88,8 +91,7 @@ files:
88
91
  homepage: https://github.com/hiratai/active_value
89
92
  licenses:
90
93
  - MIT
91
- metadata:
92
- allowed_push_host: https://rubygems.org
94
+ metadata: {}
93
95
  post_install_message:
94
96
  rdoc_options: []
95
97
  require_paths:
@@ -98,15 +100,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
98
100
  requirements:
99
101
  - - ">="
100
102
  - !ruby/object:Gem::Version
101
- version: '0'
103
+ version: '2.3'
102
104
  required_rubygems_version: !ruby/object:Gem::Requirement
103
105
  requirements:
104
106
  - - ">="
105
107
  - !ruby/object:Gem::Version
106
108
  version: '0'
107
109
  requirements: []
108
- rubygems_version: 3.1.2
110
+ rubygems_version: 3.2.3
109
111
  signing_key:
110
112
  specification_version: 4
111
- summary: Model for non database.(ValueObject) However like ActiveRecord.
113
+ summary: ActiveValue is base class for immutable value object that has interfaces
114
+ like ActiveRecord.
112
115
  test_files: []