anima 0.1.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +32 -24
- data/lib/anima.rb +86 -112
- data/lib/anima/attribute.rb +7 -50
- data/lib/anima/error.rb +10 -20
- metadata +38 -56
- data/.gitignore +0 -5
- data/.travis.yml +0 -14
- data/Changelog.md +0 -20
- data/Gemfile +0 -6
- data/Gemfile.devtools +0 -55
- data/Guardfile +0 -18
- data/Rakefile +0 -2
- data/TODO +0 -3
- data/anima.gemspec +0 -21
- data/config/flay.yml +0 -3
- data/config/flog.yml +0 -2
- data/config/mutant.yml +0 -3
- data/config/reek.yml +0 -93
- data/config/roodi.yml +0 -18
- data/config/rubocop.yml +0 -85
- data/config/yardstick.yml +0 -2
- data/lib/anima/update.rb +0 -29
- data/spec/integration/simple_spec.rb +0 -56
- data/spec/spec_helper.rb +0 -8
- data/spec/unit/anima/attribute_spec.rb +0 -98
- data/spec/unit/anima/error_spec.rb +0 -16
- data/spec/unit/anima/update_spec.rb +0 -25
- data/spec/unit/anima_spec.rb +0 -134
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 56e64bf9fa6d98eacb6fbee0c76efd33a60e7493bff45d1672ae5c2f16dcb69d
|
4
|
+
data.tar.gz: 560ecd27777601ad003518606d67031875c8cf58f337054d39d89ae1eb5815ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69d90b65d31f7214b28a9b2b70dd6f9f9248adca8c95438d41b172971548fca719bc8421e227a4c21dcb7472c51edecf79103c2bbb9c0f05195bfd8bf44c4d6f
|
7
|
+
data.tar.gz: 7cfdcef4ca9bbc3f82e43530885643af1a9c5ea87cc77014c883714aead23ff47091c8265ea3b626914d439e3b5b37417eb01d5051c8c44f436324c154f4d469
|
data/README.md
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
anima
|
2
2
|
=====
|
3
3
|
|
4
|
-
|
5
|
-
[](https://gemnasium.com/mbj/anima)
|
6
|
-
[](https://codeclimate.com/github/mbj/anima)
|
4
|
+

|
7
5
|
|
8
|
-
Simple library to declare read only attributes on value-objects that are initialized via attributes hash.
|
6
|
+
Simple library to declare read only attributes on value-objects that are initialized via attributes hash.
|
9
7
|
|
10
8
|
Installation
|
11
9
|
------------
|
@@ -15,7 +13,9 @@ Install the gem `anima` via your preferred method.
|
|
15
13
|
Examples
|
16
14
|
--------
|
17
15
|
|
18
|
-
```
|
16
|
+
```ruby
|
17
|
+
require 'anima'
|
18
|
+
|
19
19
|
# Definition
|
20
20
|
class Person
|
21
21
|
include Anima.new(:salutation, :firstname, :lastname)
|
@@ -23,28 +23,30 @@ end
|
|
23
23
|
|
24
24
|
# Every day operation
|
25
25
|
a = Person.new(
|
26
|
-
:
|
27
|
-
:
|
28
|
-
:
|
26
|
+
salutation: 'Mr',
|
27
|
+
firstname: 'Markus',
|
28
|
+
lastname: 'Schirp'
|
29
29
|
)
|
30
30
|
|
31
|
+
# Returns expected values
|
31
32
|
a.salutation # => "Mr"
|
32
33
|
a.firstname # => "Markus"
|
33
34
|
a.lastname # => "Schirp"
|
34
|
-
a.frozen? # =>
|
35
|
+
a.frozen? # => false
|
35
36
|
|
36
37
|
b = Person.new(
|
37
|
-
:
|
38
|
-
:
|
39
|
-
:
|
38
|
+
salutation: 'Mr',
|
39
|
+
firstname: 'John',
|
40
|
+
lastname: 'Doe'
|
40
41
|
)
|
41
42
|
|
42
|
-
|
43
|
-
:
|
44
|
-
:
|
45
|
-
:
|
43
|
+
c = Person.new(
|
44
|
+
salutation: 'Mr',
|
45
|
+
firstname: 'Markus',
|
46
|
+
lastname: 'Schirp'
|
46
47
|
)
|
47
48
|
|
49
|
+
# Equality based on attributes
|
48
50
|
a == b # => false
|
49
51
|
a.eql?(b) # => false
|
50
52
|
a.equal?(b) # => false
|
@@ -53,19 +55,25 @@ a == c # => true
|
|
53
55
|
a.eql?(c) # => true
|
54
56
|
a.equal?(c) # => false
|
55
57
|
|
56
|
-
# Functional updates
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
d = b.update(
|
62
|
-
:salutation => 'Mrs',
|
63
|
-
:firstname => 'Sue',
|
58
|
+
# Functional-style updates
|
59
|
+
d = b.with(
|
60
|
+
salutation: 'Mrs',
|
61
|
+
firstname: 'Sue',
|
64
62
|
)
|
65
63
|
|
66
64
|
# It returns copies, no inplace modification
|
67
65
|
d.equal?(b) # => false
|
68
66
|
|
67
|
+
# Hash representation
|
68
|
+
d.to_h # => { salutation: 'Mrs', firstname: 'Sue', lastname: 'Doe' }
|
69
|
+
|
70
|
+
# Disallows initialization with incompatible attributes
|
71
|
+
|
72
|
+
Person.new(
|
73
|
+
# :saluatation key missing
|
74
|
+
"firstname" => "Markus", # does NOT coerce this by intention
|
75
|
+
:lastname => "Schirp"
|
76
|
+
) # raises Anima::Error with message "Person attributes missing: [:salutation, :firstname], unknown: ["firstname"]
|
69
77
|
```
|
70
78
|
|
71
79
|
Credits
|
data/lib/anima.rb
CHANGED
@@ -1,45 +1,37 @@
|
|
1
|
-
require 'backports'
|
2
1
|
require 'adamantium'
|
3
2
|
require 'equalizer'
|
4
3
|
require 'abstract_type'
|
5
4
|
|
6
5
|
# Main library namespace and mixin
|
6
|
+
# @api private
|
7
7
|
class Anima < Module
|
8
8
|
include Adamantium::Flat, Equalizer.new(:attributes)
|
9
9
|
|
10
10
|
# Return names
|
11
11
|
#
|
12
|
-
# @return [
|
13
|
-
#
|
14
|
-
# @api private
|
15
|
-
#
|
12
|
+
# @return [AttributeSet]
|
16
13
|
attr_reader :attributes
|
17
14
|
|
18
15
|
# Initialize object
|
19
16
|
#
|
20
17
|
# @return [undefined]
|
21
|
-
#
|
22
|
-
# @api private
|
23
|
-
#
|
24
18
|
def initialize(*names)
|
25
|
-
@attributes = names.uniq.map
|
19
|
+
@attributes = names.uniq.map(&Attribute.method(:new)).freeze
|
26
20
|
end
|
27
21
|
|
28
|
-
# Return new anima with attributes
|
22
|
+
# Return new anima with attributes added
|
29
23
|
#
|
30
24
|
# @return [Anima]
|
31
25
|
#
|
32
26
|
# @example
|
33
27
|
# anima = Anima.new(:foo)
|
34
|
-
# anima.add(:
|
28
|
+
# anima.add(:bar) # equals Anima.new(:foo, :bar)
|
35
29
|
#
|
36
|
-
|
37
|
-
|
38
|
-
def remove(*names)
|
39
|
-
new(attribute_names - names)
|
30
|
+
def add(*names)
|
31
|
+
new(attribute_names + names)
|
40
32
|
end
|
41
33
|
|
42
|
-
# Return new anima with attributes
|
34
|
+
# Return new anima with attributes removed
|
43
35
|
#
|
44
36
|
# @return [Anima]
|
45
37
|
#
|
@@ -47,10 +39,8 @@ class Anima < Module
|
|
47
39
|
# anima = Anima.new(:foo, :bar)
|
48
40
|
# anima.remove(:bar) # equals Anima.new(:foo)
|
49
41
|
#
|
50
|
-
|
51
|
-
|
52
|
-
def add(*names)
|
53
|
-
new(attribute_names + names)
|
42
|
+
def remove(*names)
|
43
|
+
new(attribute_names - names)
|
54
44
|
end
|
55
45
|
|
56
46
|
# Return attributes hash for instance
|
@@ -58,9 +48,6 @@ class Anima < Module
|
|
58
48
|
# @param [Object] object
|
59
49
|
#
|
60
50
|
# @return [Hash]
|
61
|
-
#
|
62
|
-
# @api private
|
63
|
-
#
|
64
51
|
def attributes_hash(object)
|
65
52
|
attributes.each_with_object({}) do |attribute, attributes_hash|
|
66
53
|
attributes_hash[attribute.name] = attribute.get(object)
|
@@ -70,9 +57,6 @@ class Anima < Module
|
|
70
57
|
# Return attribute names
|
71
58
|
#
|
72
59
|
# @return [Enumerable<Symbol>]
|
73
|
-
#
|
74
|
-
# @api private
|
75
|
-
#
|
76
60
|
def attribute_names
|
77
61
|
attributes.map(&:name)
|
78
62
|
end
|
@@ -85,124 +69,114 @@ class Anima < Module
|
|
85
69
|
# @param [Hash] attribute_hash
|
86
70
|
#
|
87
71
|
# @return [self]
|
88
|
-
#
|
89
|
-
# @api private
|
90
|
-
#
|
91
72
|
def initialize_instance(object, attribute_hash)
|
73
|
+
assert_known_attributes(object.class, attribute_hash)
|
92
74
|
attributes.each do |attribute|
|
93
75
|
attribute.load(object, attribute_hash)
|
94
76
|
end
|
77
|
+
self
|
78
|
+
end
|
95
79
|
|
96
|
-
|
80
|
+
# Static instance methods for anima infected classes
|
81
|
+
module InstanceMethods
|
82
|
+
# Initialize an anima infected object
|
83
|
+
#
|
84
|
+
# @param [#to_h] attributes
|
85
|
+
# a hash that matches anima defined attributes
|
86
|
+
#
|
87
|
+
# @return [undefined]
|
88
|
+
def initialize(attributes)
|
89
|
+
self.class.anima.initialize_instance(self, attributes)
|
90
|
+
end
|
97
91
|
|
98
|
-
|
99
|
-
|
92
|
+
# Return a hash representation of an anima infected object
|
93
|
+
#
|
94
|
+
# @example
|
95
|
+
# anima.to_h # => { :foo => : bar }
|
96
|
+
#
|
97
|
+
# @return [Hash]
|
98
|
+
#
|
99
|
+
# @api public
|
100
|
+
def to_h
|
101
|
+
self.class.anima.attributes_hash(self)
|
100
102
|
end
|
101
103
|
|
102
|
-
|
103
|
-
|
104
|
+
# Return updated instance
|
105
|
+
#
|
106
|
+
# @example
|
107
|
+
# klass = Class.new do
|
108
|
+
# include Anima.new(:foo, :bar)
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# foo = klass.new(:foo => 1, :bar => 2)
|
112
|
+
# updated = foo.with(:foo => 3)
|
113
|
+
# updated.foo # => 3
|
114
|
+
# updated.bar # => 2
|
115
|
+
#
|
116
|
+
# @param [Hash] attributes
|
117
|
+
#
|
118
|
+
# @return [Anima]
|
119
|
+
#
|
120
|
+
# @api public
|
121
|
+
def with(attributes)
|
122
|
+
self.class.new(to_h.update(attributes))
|
123
|
+
end
|
124
|
+
end # InstanceMethods
|
104
125
|
|
105
|
-
private
|
126
|
+
private
|
106
127
|
|
107
|
-
#
|
128
|
+
# Infect the instance with anima
|
108
129
|
#
|
109
130
|
# @param [Class, Module] scope
|
110
131
|
#
|
111
132
|
# @return [undefined]
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
define_anima_method(scope)
|
117
|
-
define_initializer(scope)
|
118
|
-
define_attribute_readers(scope)
|
119
|
-
define_attribute_hash_reader(scope)
|
120
|
-
define_equalizer(scope)
|
121
|
-
end
|
133
|
+
def included(descendant)
|
134
|
+
descendant.instance_exec(self, attribute_names) do |anima, names|
|
135
|
+
# Define anima method
|
136
|
+
define_singleton_method(:anima) { anima }
|
122
137
|
|
123
|
-
|
124
|
-
|
125
|
-
# @param [Enumerable<Symbol>] attributes
|
126
|
-
#
|
127
|
-
# @return [Anima]
|
128
|
-
#
|
129
|
-
# @api private
|
130
|
-
#
|
131
|
-
def new(attributes)
|
132
|
-
self.class.new(*attributes)
|
133
|
-
end
|
138
|
+
# Define instance methods
|
139
|
+
include InstanceMethods
|
134
140
|
|
135
|
-
|
136
|
-
|
137
|
-
# @param [Class, Module] scope
|
138
|
-
#
|
139
|
-
# @return [undefined]
|
140
|
-
#
|
141
|
-
# @api private
|
142
|
-
#
|
143
|
-
def define_anima_method(scope)
|
144
|
-
anima = self
|
141
|
+
# Define attribute readers
|
142
|
+
attr_reader(*names)
|
145
143
|
|
146
|
-
|
147
|
-
|
144
|
+
# Define equalizer
|
145
|
+
include Equalizer.new(*names)
|
148
146
|
end
|
149
147
|
end
|
150
148
|
|
151
|
-
#
|
152
|
-
#
|
153
|
-
# @param [Class, Module] scope
|
154
|
-
#
|
155
|
-
# @return [undefined]
|
149
|
+
# Fail unless keys in +attribute_hash+ matches #attribute_names
|
156
150
|
#
|
157
|
-
# @
|
158
|
-
#
|
159
|
-
def define_equalizer(scope)
|
160
|
-
scope.send(:include, Equalizer.new(*attribute_names))
|
161
|
-
end
|
162
|
-
|
163
|
-
# Define attribute readers
|
151
|
+
# @param [Class] klass
|
152
|
+
# the class being initialized
|
164
153
|
#
|
165
|
-
# @param [
|
154
|
+
# @param [Hash] attribute_hash
|
155
|
+
# the attributes to initialize +object+ with
|
166
156
|
#
|
167
157
|
# @return [undefined]
|
168
158
|
#
|
169
|
-
# @
|
170
|
-
|
171
|
-
|
172
|
-
attributes.each do |attribute|
|
173
|
-
attribute.define_reader(scope)
|
174
|
-
end
|
175
|
-
end
|
159
|
+
# @raise [Error]
|
160
|
+
def assert_known_attributes(klass, attribute_hash)
|
161
|
+
keys = attribute_hash.keys
|
176
162
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
#
|
183
|
-
# @api private
|
184
|
-
#
|
185
|
-
def define_initializer(scope)
|
186
|
-
scope.send(:define_method, :initialize) do |attributes|
|
187
|
-
self.class.anima.initialize_instance(self, attributes)
|
163
|
+
unknown = keys - attribute_names
|
164
|
+
missing = attribute_names - keys
|
165
|
+
|
166
|
+
unless unknown.empty? && missing.empty?
|
167
|
+
fail Error.new(klass, missing, unknown)
|
188
168
|
end
|
189
169
|
end
|
190
170
|
|
191
|
-
#
|
192
|
-
#
|
193
|
-
# @param [Class, Module] scope
|
194
|
-
#
|
195
|
-
# @return [undefined]
|
171
|
+
# Return new instance
|
196
172
|
#
|
197
|
-
# @
|
173
|
+
# @param [Enumerable<Symbol>] attributes
|
198
174
|
#
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
end
|
175
|
+
# @return [Anima]
|
176
|
+
def new(attributes)
|
177
|
+
self.class.new(*attributes)
|
203
178
|
end
|
204
|
-
end
|
179
|
+
end # Anima
|
205
180
|
|
206
181
|
require 'anima/error'
|
207
182
|
require 'anima/attribute'
|
208
|
-
require 'anima/update'
|
data/lib/anima/attribute.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
class Anima
|
2
|
-
|
3
2
|
# An attribute
|
4
3
|
class Attribute
|
5
4
|
include Adamantium::Flat, Equalizer.new(:name)
|
@@ -7,38 +6,28 @@ class Anima
|
|
7
6
|
# Initialize attribute
|
8
7
|
#
|
9
8
|
# @param [Symbol] name
|
10
|
-
#
|
11
|
-
# @api private
|
12
|
-
#
|
13
9
|
def initialize(name)
|
14
|
-
@name = name
|
10
|
+
@name, @instance_variable_name = name, :"@#{name}"
|
15
11
|
end
|
16
12
|
|
17
13
|
# Return attribute name
|
18
14
|
#
|
19
15
|
# @return [Symbol]
|
20
|
-
#
|
21
|
-
# @api private
|
22
|
-
#
|
23
16
|
attr_reader :name
|
24
17
|
|
18
|
+
# Return instance variable name
|
19
|
+
#
|
20
|
+
# @return [Symbol]
|
21
|
+
attr_reader :instance_variable_name
|
22
|
+
|
25
23
|
# Load attribute
|
26
24
|
#
|
27
25
|
# @param [Object] object
|
28
26
|
# @param [Hash] attributes
|
29
27
|
#
|
30
28
|
# @return [self]
|
31
|
-
#
|
32
|
-
# @api private
|
33
|
-
#
|
34
29
|
def load(object, attributes)
|
35
|
-
|
36
|
-
|
37
|
-
value = attributes.fetch(attribute_name) do
|
38
|
-
raise Error::Missing.new(object.class, attribute_name)
|
39
|
-
end
|
40
|
-
|
41
|
-
set(object, value)
|
30
|
+
set(object, attributes.fetch(name))
|
42
31
|
end
|
43
32
|
|
44
33
|
# Get attribute value from object
|
@@ -46,9 +35,6 @@ class Anima
|
|
46
35
|
# @param [Object] object
|
47
36
|
#
|
48
37
|
# @return [Object]
|
49
|
-
#
|
50
|
-
# @api private
|
51
|
-
#
|
52
38
|
def get(object)
|
53
39
|
object.public_send(name)
|
54
40
|
end
|
@@ -59,39 +45,10 @@ class Anima
|
|
59
45
|
# @param [Object] value
|
60
46
|
#
|
61
47
|
# @return [self]
|
62
|
-
#
|
63
|
-
# @api private
|
64
|
-
#
|
65
48
|
def set(object, value)
|
66
49
|
object.instance_variable_set(instance_variable_name, value)
|
67
50
|
|
68
51
|
self
|
69
52
|
end
|
70
|
-
|
71
|
-
# Return instance variable name
|
72
|
-
#
|
73
|
-
# @return [Symbol]
|
74
|
-
# returns @ prefixed name
|
75
|
-
#
|
76
|
-
# @api private
|
77
|
-
#
|
78
|
-
def instance_variable_name
|
79
|
-
:"@#{name}"
|
80
|
-
end
|
81
|
-
memoize :instance_variable_name
|
82
|
-
|
83
|
-
# Define reader
|
84
|
-
#
|
85
|
-
# @param [Class, Module] scope
|
86
|
-
#
|
87
|
-
# @return [self]
|
88
|
-
#
|
89
|
-
# @api private
|
90
|
-
#
|
91
|
-
def define_reader(scope)
|
92
|
-
scope.send(:attr_reader, name)
|
93
|
-
self
|
94
|
-
end
|
95
|
-
|
96
53
|
end # Attribute
|
97
54
|
end # Anima
|