anima 0.0.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.
- data/.gitignore +2 -0
- data/.travis.yml +14 -0
- data/Changelog.md +3 -0
- data/Gemfile +6 -0
- data/Gemfile.devtools +44 -0
- data/Guardfile +18 -0
- data/README.md +56 -0
- data/Rakefile +3 -0
- data/TODO +3 -0
- data/anima.gemspec +21 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +3 -0
- data/config/roodi.yml +18 -0
- data/config/site.reek +93 -0
- data/config/yardstick.yml +2 -0
- data/lib/anima.rb +167 -0
- data/lib/anima/attribute.rb +99 -0
- data/lib/anima/error.rb +29 -0
- data/spec/integration/simple_spec.rb +56 -0
- data/spec/shared/command_method_behavior.rb +7 -0
- data/spec/shared/each_method_behaviour.rb +15 -0
- data/spec/shared/hash_method_behavior.rb +17 -0
- data/spec/shared/idempotent_method_behavior.rb +7 -0
- data/spec/shared/invertible_method_behaviour.rb +9 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/unit/anima/attribute/define_reader_spec.rb +24 -0
- data/spec/unit/anima/attribute/get_spec.rb +25 -0
- data/spec/unit/anima/attribute/instance_variable_name_spec.rb +9 -0
- data/spec/unit/anima/attribute/load_spec.rb +31 -0
- data/spec/unit/anima/attribute/set_spec.rb +18 -0
- data/spec/unit/anima/attributes_hash_spec.rb +13 -0
- data/spec/unit/anima/error/message_spec.rb +16 -0
- data/spec/unit/anima/included_spec.rb +36 -0
- data/spec/unit/anima/initialize_instance_spec.rb +41 -0
- metadata +162 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Changelog.md
ADDED
data/Gemfile
ADDED
data/Gemfile.devtools
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
group :development do
|
2
|
+
gem 'rake', '~> 0.9.2'
|
3
|
+
gem 'rspec', '~> 2.12.0'
|
4
|
+
gem 'yard', '~> 0.8.3'
|
5
|
+
end
|
6
|
+
|
7
|
+
group :guard do
|
8
|
+
gem 'guard', '~> 1.5.4'
|
9
|
+
gem 'guard-bundler', '~> 1.0.0'
|
10
|
+
gem 'guard-rspec', '~> 2.1.1'
|
11
|
+
gem 'rb-inotify', :git => 'https://github.com/mbj/rb-inotify'
|
12
|
+
end
|
13
|
+
|
14
|
+
group :benchmarks do
|
15
|
+
gem 'rbench', '~> 0.2.3'
|
16
|
+
end
|
17
|
+
|
18
|
+
platform :jruby do
|
19
|
+
group :jruby do
|
20
|
+
gem 'jruby-openssl', '~> 0.7.4'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
group :metrics do
|
25
|
+
gem 'flay', '~> 1.4.2'
|
26
|
+
gem 'flog', '~> 2.5.1'
|
27
|
+
gem 'reek', '~> 1.2.8', :git => 'https://github.com/dkubb/reek.git'
|
28
|
+
gem 'roodi', '~> 2.1.0'
|
29
|
+
gem 'yardstick', '~> 0.8.0'
|
30
|
+
gem 'mutant', '~> 0.2.5'
|
31
|
+
|
32
|
+
platforms :ruby_18, :ruby_19 do
|
33
|
+
# this indirectly depends on ffi which does not build on ruby-head
|
34
|
+
gem 'yard-spellcheck', '~> 0.1.5'
|
35
|
+
end
|
36
|
+
|
37
|
+
platforms :mri_19 do
|
38
|
+
gem 'simplecov', '~> 0.7'
|
39
|
+
end
|
40
|
+
|
41
|
+
platforms :rbx do
|
42
|
+
gem 'pelusa', '~> 0.2.1'
|
43
|
+
end
|
44
|
+
end
|
data/Guardfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
guard :bundler do
|
4
|
+
watch('Gemfile')
|
5
|
+
end
|
6
|
+
|
7
|
+
guard :rspec, :version => 2 do
|
8
|
+
# run all specs if the spec_helper or supporting files files are modified
|
9
|
+
watch('spec/spec_helper.rb') { 'spec' }
|
10
|
+
watch(%r{\Aspec/(?:lib|support|shared)/.+\.rb\z}) { 'spec' }
|
11
|
+
|
12
|
+
# run unit specs if associated lib code is modified
|
13
|
+
watch(%r{\Alib/(.+)\.rb\z}) { |m| Dir["spec/unit/#{m[1]}"] }
|
14
|
+
watch("lib/#{File.basename(File.expand_path('../', __FILE__))}.rb") { 'spec' }
|
15
|
+
|
16
|
+
# run a spec if it is modified
|
17
|
+
watch(%r{\Aspec/(?:unit|integration)/.+_spec\.rb\z})
|
18
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
anima
|
2
|
+
=====
|
3
|
+
|
4
|
+
[](http://travis-ci.org/mbj/anima)
|
5
|
+
[](https://gemnasium.com/mbj/anima)
|
6
|
+
[](https://codeclimate.com/github/mbj/anima)
|
7
|
+
|
8
|
+
Simple library to declare read only attributes on objects that are initializable via attributes hash.
|
9
|
+
|
10
|
+
Installation
|
11
|
+
------------
|
12
|
+
|
13
|
+
There is no gem release and the code is under development and unusable.
|
14
|
+
|
15
|
+
Examples
|
16
|
+
--------
|
17
|
+
|
18
|
+
See specs.
|
19
|
+
|
20
|
+
Credits
|
21
|
+
-------
|
22
|
+
|
23
|
+
Contributing
|
24
|
+
-------------
|
25
|
+
|
26
|
+
* Fork the project.
|
27
|
+
* Make your feature addition or bug fix.
|
28
|
+
* Add tests for it. This is important so I don't break it in a
|
29
|
+
future version unintentionally.
|
30
|
+
* Commit, do not mess with Rakefile or version
|
31
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
32
|
+
* Send me a pull request. Bonus points for topic branches.
|
33
|
+
|
34
|
+
License
|
35
|
+
-------
|
36
|
+
|
37
|
+
Copyright (c) 2012 Markus Schirp
|
38
|
+
|
39
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
40
|
+
a copy of this software and associated documentation files (the
|
41
|
+
"Software"), to deal in the Software without restriction, including
|
42
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
43
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
44
|
+
permit persons to whom the Software is furnished to do so, subject to
|
45
|
+
the following conditions:
|
46
|
+
|
47
|
+
The above copyright notice and this permission notice shall be
|
48
|
+
included in all copies or substantial portions of the Software.
|
49
|
+
|
50
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
51
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
52
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
53
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
54
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
55
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
56
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/TODO
ADDED
data/anima.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'anima'
|
5
|
+
s.version = '0.0.2'
|
6
|
+
|
7
|
+
s.authors = ['Markus Schirp']
|
8
|
+
s.email = 'mbj@seonic.net'
|
9
|
+
s.summary = 'Attributes for Plain Old Ruby Objects Experiment'
|
10
|
+
s.homepage = 'http://github.com/mbj/anima'
|
11
|
+
|
12
|
+
s.files = `git ls-files`.split("\n")
|
13
|
+
s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
|
14
|
+
s.require_paths = %w(lib)
|
15
|
+
s.extra_rdoc_files = %w(README.md)
|
16
|
+
|
17
|
+
s.add_dependency('backports', '~> 2.6.4')
|
18
|
+
s.add_dependency('adamantium', '~> 0.0.3')
|
19
|
+
s.add_dependency('equalizer', '~> 0.0.1')
|
20
|
+
s.add_dependency('abstract_type', '~> 0.0.2')
|
21
|
+
end
|
data/config/flay.yml
ADDED
data/config/flog.yml
ADDED
data/config/mutant.yml
ADDED
data/config/roodi.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
AbcMetricMethodCheck: { score: 11.0 }
|
3
|
+
AssignmentInConditionalCheck: { }
|
4
|
+
CaseMissingElseCheck: { }
|
5
|
+
ClassLineCountCheck: { line_count: 293 }
|
6
|
+
ClassNameCheck: { pattern: !ruby/regexp '/\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/' }
|
7
|
+
ClassVariableCheck: { }
|
8
|
+
CyclomaticComplexityBlockCheck: { complexity: 2 }
|
9
|
+
CyclomaticComplexityMethodCheck: { complexity: 4 }
|
10
|
+
EmptyRescueBodyCheck: { }
|
11
|
+
ForLoopCheck: { }
|
12
|
+
# TODO: decrease line_count to 5 to 10
|
13
|
+
MethodLineCountCheck: { line_count: 14 }
|
14
|
+
MethodNameCheck: { pattern: !ruby/regexp '/\A(?:[a-z\d](?:_?[a-z\d])+[?!=]?|\[\]=?|==|<=>|[+*&|-])\z/' }
|
15
|
+
ModuleLineCountCheck: { line_count: 295 }
|
16
|
+
ModuleNameCheck: { pattern: !ruby/regexp '/\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/' }
|
17
|
+
# TODO: decrease parameter_count to 2 or less
|
18
|
+
ParameterNumberCheck: { parameter_count: 3 }
|
data/config/site.reek
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
---
|
2
|
+
UncommunicativeParameterName:
|
3
|
+
accept: []
|
4
|
+
exclude: []
|
5
|
+
enabled: true
|
6
|
+
reject:
|
7
|
+
- !ruby/regexp /^.$/
|
8
|
+
- !ruby/regexp /[0-9]$/
|
9
|
+
- !ruby/regexp /[A-Z]/
|
10
|
+
LargeClass:
|
11
|
+
max_methods: 10
|
12
|
+
exclude: []
|
13
|
+
enabled: true
|
14
|
+
max_instance_variables: 2
|
15
|
+
UncommunicativeMethodName:
|
16
|
+
accept: []
|
17
|
+
exclude: []
|
18
|
+
enabled: true
|
19
|
+
reject:
|
20
|
+
- !ruby/regexp /^[a-z]$/
|
21
|
+
- !ruby/regexp /[0-9]$/
|
22
|
+
- !ruby/regexp /[A-Z]/
|
23
|
+
LongParameterList:
|
24
|
+
max_params: 2 # TODO: decrease max_params to 2
|
25
|
+
exclude: []
|
26
|
+
enabled: true
|
27
|
+
overrides: {}
|
28
|
+
FeatureEnvy:
|
29
|
+
exclude:
|
30
|
+
- Anima#attributes_hash
|
31
|
+
enabled: true
|
32
|
+
ClassVariable:
|
33
|
+
exclude: []
|
34
|
+
enabled: true
|
35
|
+
BooleanParameter:
|
36
|
+
exclude: []
|
37
|
+
enabled: true
|
38
|
+
IrresponsibleModule:
|
39
|
+
exclude: []
|
40
|
+
enabled: true
|
41
|
+
UncommunicativeModuleName:
|
42
|
+
accept: []
|
43
|
+
exclude: []
|
44
|
+
enabled: true
|
45
|
+
reject:
|
46
|
+
- !ruby/regexp /^.$/
|
47
|
+
- !ruby/regexp /[0-9]$/
|
48
|
+
NestedIterators:
|
49
|
+
ignore_iterators: []
|
50
|
+
exclude:
|
51
|
+
- Anima::Attribute#define_reader # 2 levels
|
52
|
+
enabled: true
|
53
|
+
max_allowed_nesting: 1
|
54
|
+
LongMethod:
|
55
|
+
max_statements: 7 # TODO: decrease max_statements to 5 or less
|
56
|
+
exclude: []
|
57
|
+
enabled: true
|
58
|
+
Duplication:
|
59
|
+
allow_calls: []
|
60
|
+
exclude: []
|
61
|
+
enabled: true
|
62
|
+
max_calls: 1
|
63
|
+
UtilityFunction:
|
64
|
+
max_helper_calls: 1
|
65
|
+
exclude: []
|
66
|
+
enabled: true
|
67
|
+
Attribute:
|
68
|
+
exclude: []
|
69
|
+
enabled: false
|
70
|
+
UncommunicativeVariableName:
|
71
|
+
accept: []
|
72
|
+
exclude: []
|
73
|
+
enabled: true
|
74
|
+
reject:
|
75
|
+
- !ruby/regexp /^.$/
|
76
|
+
- !ruby/regexp /[0-9]$/
|
77
|
+
- !ruby/regexp /[A-Z]/
|
78
|
+
SimulatedPolymorphism:
|
79
|
+
exclude: []
|
80
|
+
enabled: true
|
81
|
+
max_ifs: 1
|
82
|
+
DataClump:
|
83
|
+
exclude: []
|
84
|
+
enabled: true
|
85
|
+
max_copies: 1
|
86
|
+
min_clump_size: 3
|
87
|
+
ControlCouple:
|
88
|
+
exclude: []
|
89
|
+
enabled: true
|
90
|
+
LongYieldList:
|
91
|
+
max_params: 1
|
92
|
+
exclude: []
|
93
|
+
enabled: true
|
data/lib/anima.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'backports'
|
2
|
+
require 'adamantium'
|
3
|
+
require 'equalizer'
|
4
|
+
require 'abstract_type'
|
5
|
+
|
6
|
+
# Main library namespace and mixin
|
7
|
+
class Anima < Module
|
8
|
+
include Adamantium::Flat
|
9
|
+
|
10
|
+
# Return names
|
11
|
+
#
|
12
|
+
# @return [AttriuteSet]
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
#
|
16
|
+
attr_reader :attributes
|
17
|
+
|
18
|
+
# Initialize object
|
19
|
+
#
|
20
|
+
# @return [undefined]
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
#
|
24
|
+
def initialize(*names)
|
25
|
+
@attributes = names.map { |name| Attribute.new(name) }.freeze
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return attributes hash for instance
|
29
|
+
#
|
30
|
+
# @param [Object] object
|
31
|
+
#
|
32
|
+
# @return [Hash]
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
#
|
36
|
+
def attributes_hash(object)
|
37
|
+
attributes.each_with_object({}) do |attribute, attributes_hash|
|
38
|
+
attributes_hash[attribute.name] = attribute.get(object)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return attribute names
|
43
|
+
#
|
44
|
+
# @return [Enumerable<Symbol>]
|
45
|
+
#
|
46
|
+
# @api private
|
47
|
+
#
|
48
|
+
def attribute_names
|
49
|
+
attributes.map(&:name)
|
50
|
+
end
|
51
|
+
memoize :attribute_names
|
52
|
+
|
53
|
+
# Initialize instance
|
54
|
+
#
|
55
|
+
# @param [Object] object
|
56
|
+
#
|
57
|
+
# @param [Hash] attribute_hash
|
58
|
+
#
|
59
|
+
# @return [self]
|
60
|
+
#
|
61
|
+
# @api private
|
62
|
+
#
|
63
|
+
def initialize_instance(object, attribute_hash)
|
64
|
+
attributes.each do |attribute|
|
65
|
+
attribute.load(object, attribute_hash)
|
66
|
+
end
|
67
|
+
|
68
|
+
overflow = attribute_hash.keys - attribute_names
|
69
|
+
|
70
|
+
unless overflow.empty?
|
71
|
+
raise Error::Unknown.new(object.class, overflow)
|
72
|
+
end
|
73
|
+
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# Hook called when module is included
|
80
|
+
#
|
81
|
+
# @param [Class, Module] scope
|
82
|
+
#
|
83
|
+
# @return [undefined]
|
84
|
+
#
|
85
|
+
# @api private
|
86
|
+
#
|
87
|
+
def included(scope)
|
88
|
+
define_anima_method(scope)
|
89
|
+
define_initializer(scope)
|
90
|
+
define_attribute_readers(scope)
|
91
|
+
define_attribute_hash_reader(scope)
|
92
|
+
define_equalizer(scope)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Define anima method on scope
|
96
|
+
#
|
97
|
+
# @param [Class, Module] scope
|
98
|
+
#
|
99
|
+
# @return [undefined]
|
100
|
+
#
|
101
|
+
# @api private
|
102
|
+
#
|
103
|
+
def define_anima_method(scope)
|
104
|
+
anima = self
|
105
|
+
|
106
|
+
scope.define_singleton_method(:anima) do
|
107
|
+
anima
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Define equalizer on scope
|
112
|
+
#
|
113
|
+
# @param [Class, Module] scope
|
114
|
+
#
|
115
|
+
# @return [undefined]
|
116
|
+
#
|
117
|
+
# @api private
|
118
|
+
#
|
119
|
+
def define_equalizer(scope)
|
120
|
+
scope.send(:include, Equalizer.new(*attribute_names))
|
121
|
+
end
|
122
|
+
|
123
|
+
# Define attribute readers
|
124
|
+
#
|
125
|
+
# @param [Class, Module] scope
|
126
|
+
#
|
127
|
+
# @return [undefined]
|
128
|
+
#
|
129
|
+
# @api private
|
130
|
+
#
|
131
|
+
def define_attribute_readers(scope)
|
132
|
+
attributes.each do |attribute|
|
133
|
+
attribute.define_reader(scope)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Define initializer
|
138
|
+
#
|
139
|
+
# @param [Class, Module] scope
|
140
|
+
#
|
141
|
+
# @return [undefined]
|
142
|
+
#
|
143
|
+
# @api private
|
144
|
+
#
|
145
|
+
def define_initializer(scope)
|
146
|
+
scope.send(:define_method, :initialize) do |attributes|
|
147
|
+
self.class.anima.initialize_instance(self, attributes)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Define attribute hash reader
|
152
|
+
#
|
153
|
+
# @param [Class, Module] scope
|
154
|
+
#
|
155
|
+
# @return [undefined]
|
156
|
+
#
|
157
|
+
# @api private
|
158
|
+
#
|
159
|
+
def define_attribute_hash_reader(scope)
|
160
|
+
scope.define_singleton_method(:attribute_hash) do |object|
|
161
|
+
anima.attributes_hash(object)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
require 'anima/error'
|
167
|
+
require 'anima/attribute'
|
@@ -0,0 +1,99 @@
|
|
1
|
+
class Anima
|
2
|
+
|
3
|
+
# An attribute
|
4
|
+
class Attribute
|
5
|
+
include Adamantium::Flat
|
6
|
+
|
7
|
+
# Return attribute name
|
8
|
+
#
|
9
|
+
# @return [Symbol]
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
#
|
13
|
+
attr_reader :name
|
14
|
+
|
15
|
+
# Load attribute
|
16
|
+
#
|
17
|
+
# @param [Object] object
|
18
|
+
# @param [Hash] attributes
|
19
|
+
#
|
20
|
+
# @return [self]
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
#
|
24
|
+
def load(object, attributes)
|
25
|
+
attribute_name = name
|
26
|
+
|
27
|
+
value = attributes.fetch(attribute_name) do
|
28
|
+
raise Error::Missing.new(object.class, attribute_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
set(object, value)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get attribute value from object
|
35
|
+
#
|
36
|
+
# @param [Object] object
|
37
|
+
#
|
38
|
+
# @return [Object]
|
39
|
+
#
|
40
|
+
# @api private
|
41
|
+
#
|
42
|
+
def get(object)
|
43
|
+
object.public_send(name)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Set attribute value in object
|
47
|
+
#
|
48
|
+
# @param [Object] object
|
49
|
+
# @param [Object] value
|
50
|
+
#
|
51
|
+
# @return [self]
|
52
|
+
#
|
53
|
+
# @api private
|
54
|
+
#
|
55
|
+
def set(object, value)
|
56
|
+
object.instance_variable_set(instance_variable_name, value)
|
57
|
+
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
# Return instance variable name
|
62
|
+
#
|
63
|
+
# @return [Symbol]
|
64
|
+
# returns @ prefixed name
|
65
|
+
#
|
66
|
+
# @api private
|
67
|
+
#
|
68
|
+
def instance_variable_name
|
69
|
+
:"@#{name}"
|
70
|
+
end
|
71
|
+
memoize :instance_variable_name
|
72
|
+
|
73
|
+
# Define reader
|
74
|
+
#
|
75
|
+
# @param [Class, Module] scope
|
76
|
+
#
|
77
|
+
# @return [self]
|
78
|
+
#
|
79
|
+
# @api private
|
80
|
+
#
|
81
|
+
def define_reader(scope)
|
82
|
+
scope.send(:attr_reader, name)
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
|
89
|
+
# Initialize attribute
|
90
|
+
#
|
91
|
+
# @param [Symbol] name
|
92
|
+
#
|
93
|
+
# @api private
|
94
|
+
#
|
95
|
+
def initialize(name)
|
96
|
+
@name = name
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/anima/error.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class Anima
|
2
|
+
|
3
|
+
# Abstract base class for anima errors
|
4
|
+
class Error < RuntimeError
|
5
|
+
include AbstractType
|
6
|
+
|
7
|
+
# Initialize object
|
8
|
+
#
|
9
|
+
# @param [Class] model
|
10
|
+
# @param [Enumerable<Symbol>] names
|
11
|
+
#
|
12
|
+
# @return [undefined]
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
#
|
16
|
+
def initialize(model, names)
|
17
|
+
super("#{self.class.name.split('::').last} attribute(s) #{names.inspect} for #{model.name}")
|
18
|
+
end
|
19
|
+
|
20
|
+
# Error for unknown attributes
|
21
|
+
class Unknown < self
|
22
|
+
end
|
23
|
+
|
24
|
+
# Error for missing attributes
|
25
|
+
class Missing < self
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Anima, 'simple integration' do
|
4
|
+
subject { class_under_test.new(attributes) }
|
5
|
+
|
6
|
+
let(:class_under_test) do
|
7
|
+
Class.new do
|
8
|
+
include Anima.new(:firstname, :lastname)
|
9
|
+
|
10
|
+
def self.name
|
11
|
+
'TestClass'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when instanciated with all attributes' do
|
17
|
+
let(:attributes) do
|
18
|
+
{
|
19
|
+
:firstname => 'Markus',
|
20
|
+
:lastname => 'Schirp'
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
its(:firstname) { should eql('Markus') }
|
25
|
+
its(:lastname) { should eql('Schirp') }
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'with instanciated with extra attributes' do
|
29
|
+
let(:attributes) do
|
30
|
+
{
|
31
|
+
:firstname => 'Markus',
|
32
|
+
:lastname => 'Schirp',
|
33
|
+
:extra => 'Foo'
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should raise error' do
|
38
|
+
expect { subject }.to raise_error(
|
39
|
+
Anima::Error::Unknown,
|
40
|
+
'Unknown attribute(s) [:extra] for TestClass'
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when instanciated with missing attribute' do
|
46
|
+
|
47
|
+
let(:attributes) { {} }
|
48
|
+
|
49
|
+
it 'should raise error' do
|
50
|
+
expect { subject }.to raise_error(
|
51
|
+
Anima::Error::Missing,
|
52
|
+
'Missing attribute(s) :firstname for TestClass'
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
shared_examples_for 'an #each method' do
|
4
|
+
it_should_behave_like 'a command method'
|
5
|
+
|
6
|
+
context 'with no block' do
|
7
|
+
subject { object.each }
|
8
|
+
|
9
|
+
it { should be_instance_of(to_enum.class) }
|
10
|
+
|
11
|
+
it 'yields the expected values' do
|
12
|
+
subject.to_a.should eql(object.to_a)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
shared_examples_for 'a hash method' do
|
4
|
+
it_should_behave_like 'an idempotent method'
|
5
|
+
|
6
|
+
specification = proc do
|
7
|
+
should be_instance_of(Fixnum)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'is a fixnum' do
|
11
|
+
instance_eval(&specification)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'memoizes the hash code' do
|
15
|
+
subject.should eql(object.memoized(:hash))
|
16
|
+
end
|
17
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Anima::Attribute, '#define_reader' do
|
4
|
+
let(:object) { described_class.new(:foo) }
|
5
|
+
|
6
|
+
subject { object.define_reader(target_class) }
|
7
|
+
|
8
|
+
let(:target_class) do
|
9
|
+
Class.new do
|
10
|
+
def initialize(foo)
|
11
|
+
@foo = foo
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:value) { mock('Value') }
|
17
|
+
|
18
|
+
it 'should create a reader' do
|
19
|
+
instance = target_class.new(value)
|
20
|
+
lambda { subject }.should change { instance.respond_to?(:foo) }.from(false).to(true)
|
21
|
+
end
|
22
|
+
|
23
|
+
it_should_behave_like 'a command method'
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Anima::Attribute, '#get' do
|
4
|
+
let(:object) { described_class.new(:foo) }
|
5
|
+
|
6
|
+
subject { object.get(target) }
|
7
|
+
|
8
|
+
let(:target_class) do
|
9
|
+
Class.new do
|
10
|
+
attr_reader :foo
|
11
|
+
|
12
|
+
def initialize(foo)
|
13
|
+
@foo = foo
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:target) { target_class.new(value) }
|
19
|
+
|
20
|
+
let(:value) { mock('Value') }
|
21
|
+
|
22
|
+
it 'should return value' do
|
23
|
+
should be(value)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Anima::Attribute, '#load' do
|
4
|
+
let(:object) { described_class.new(:foo) }
|
5
|
+
|
6
|
+
subject { object.load(target, attribute_hash) }
|
7
|
+
|
8
|
+
let(:target) { Object.new }
|
9
|
+
|
10
|
+
let(:value) { mock('Value') }
|
11
|
+
|
12
|
+
context 'when attribute hash contains key' do
|
13
|
+
let(:attribute_hash) { { :foo => value } }
|
14
|
+
|
15
|
+
it 'should set value as instance variable' do
|
16
|
+
subject
|
17
|
+
target.instance_variable_get(:@foo).should be(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
it_should_behave_like 'a command method'
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when attribute hash does not contain key' do
|
25
|
+
let(:attribute_hash) { {} }
|
26
|
+
|
27
|
+
it 'should raise error' do
|
28
|
+
expect { subject }.to raise_error(Anima::Error::Missing, Anima::Error::Missing.new(target.class, :foo).message)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Anima::Attribute, '#set' do
|
4
|
+
let(:object) { described_class.new(:foo) }
|
5
|
+
|
6
|
+
subject { object.set(target, value) }
|
7
|
+
|
8
|
+
let(:target) { Object.new }
|
9
|
+
|
10
|
+
let(:value) { mock('Value') }
|
11
|
+
|
12
|
+
it_should_behave_like 'a command method'
|
13
|
+
|
14
|
+
it 'should set value as instance variable' do
|
15
|
+
subject
|
16
|
+
target.instance_variable_get(:@foo).should be(value)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Anima, '#attributes_hash' do
|
4
|
+
let(:object) { described_class.new(:foo) }
|
5
|
+
|
6
|
+
let(:value) { mock('Value') }
|
7
|
+
|
8
|
+
let(:instance) { mock(:foo => value) }
|
9
|
+
|
10
|
+
subject { object.attributes_hash(instance) }
|
11
|
+
|
12
|
+
it { should eql(:foo => value) }
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Anima::Error, '#message' do
|
4
|
+
let(:object) { described_class.new(klass, name) }
|
5
|
+
|
6
|
+
subject { object.message }
|
7
|
+
|
8
|
+
let(:klass) { mock(:name => 'THE-CLASS-NAME') }
|
9
|
+
let(:name) { 'THE-ATTRIBUTE-NAME' }
|
10
|
+
|
11
|
+
it 'should return the message string' do
|
12
|
+
should eql('Error attribute(s) "THE-ATTRIBUTE-NAME" for THE-CLASS-NAME')
|
13
|
+
end
|
14
|
+
|
15
|
+
it_should_behave_like 'an idempotent method'
|
16
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Anima, '#included' do
|
4
|
+
let(:object) { described_class.new(:foo) }
|
5
|
+
|
6
|
+
let(:target) do
|
7
|
+
object = self.object
|
8
|
+
Class.new do
|
9
|
+
include object
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:value) { mock('Value') }
|
14
|
+
|
15
|
+
let(:instance) { target.new(:foo => value) }
|
16
|
+
let(:instance_b) { target.new(:foo => value) }
|
17
|
+
let(:instance_c) { target.new(:foo => mock('Bar')) }
|
18
|
+
|
19
|
+
context 'on instance' do
|
20
|
+
subject { instance }
|
21
|
+
|
22
|
+
its(:foo) { should be(value) }
|
23
|
+
|
24
|
+
it { should eql(instance_b) }
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'on singleton' do
|
28
|
+
subject { target }
|
29
|
+
|
30
|
+
it 'should define attribute hash reader' do
|
31
|
+
target.attribute_hash(instance).should eql(:foo => value)
|
32
|
+
end
|
33
|
+
|
34
|
+
its(:anima) { should be(object) }
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Anima, '#initialize_instance' do
|
4
|
+
let(:object) { Anima.new(:foo, :bar) }
|
5
|
+
|
6
|
+
let(:target) { Object.new }
|
7
|
+
|
8
|
+
let(:foo) { mock('Foo') }
|
9
|
+
let(:bar) { mock('Bar') }
|
10
|
+
|
11
|
+
subject { object.initialize_instance(target, attribute_hash) }
|
12
|
+
|
13
|
+
context 'when all keys are present in attribute hash' do
|
14
|
+
let(:attribute_hash) { { :foo => foo, :bar => bar } }
|
15
|
+
|
16
|
+
it 'should initialize target instance variables' do
|
17
|
+
subject
|
18
|
+
target.instance_variables.map(&:to_sym).to_set.should eql([:@foo, :@bar].to_set)
|
19
|
+
target.instance_variable_get(:@foo).should be(foo)
|
20
|
+
target.instance_variable_get(:@bar).should be(bar)
|
21
|
+
end
|
22
|
+
|
23
|
+
it_should_behave_like 'a command method'
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'when extra key is missing in attribute hash' do
|
27
|
+
let(:attribute_hash) { { :foo => foo, :bar => bar, :baz => mock('Baz') } }
|
28
|
+
|
29
|
+
it 'should raise error' do
|
30
|
+
expect { subject }.to raise_error(Anima::Error::Unknown, Anima::Error::Unknown.new(target.class, [:baz]).message)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'when a key is missing in attribute hash' do
|
35
|
+
let(:attribute_hash) { { :bar => bar } }
|
36
|
+
|
37
|
+
it 'should raise error' do
|
38
|
+
expect { subject }.to raise_error(Anima::Error::Missing, Anima::Error::Missing.new(target.class, :foo).message)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: anima
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Markus Schirp
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: backports
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.6.4
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.6.4
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: adamantium
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.0.3
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.0.3
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: equalizer
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.0.1
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.0.1
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: abstract_type
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.0.2
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.0.2
|
78
|
+
description:
|
79
|
+
email: mbj@seonic.net
|
80
|
+
executables: []
|
81
|
+
extensions: []
|
82
|
+
extra_rdoc_files:
|
83
|
+
- README.md
|
84
|
+
files:
|
85
|
+
- .gitignore
|
86
|
+
- .rspec
|
87
|
+
- .travis.yml
|
88
|
+
- Changelog.md
|
89
|
+
- Gemfile
|
90
|
+
- Gemfile.devtools
|
91
|
+
- Guardfile
|
92
|
+
- README.md
|
93
|
+
- Rakefile
|
94
|
+
- TODO
|
95
|
+
- anima.gemspec
|
96
|
+
- config/flay.yml
|
97
|
+
- config/flog.yml
|
98
|
+
- config/mutant.yml
|
99
|
+
- config/roodi.yml
|
100
|
+
- config/site.reek
|
101
|
+
- config/yardstick.yml
|
102
|
+
- lib/anima.rb
|
103
|
+
- lib/anima/attribute.rb
|
104
|
+
- lib/anima/error.rb
|
105
|
+
- spec/integration/simple_spec.rb
|
106
|
+
- spec/shared/command_method_behavior.rb
|
107
|
+
- spec/shared/each_method_behaviour.rb
|
108
|
+
- spec/shared/hash_method_behavior.rb
|
109
|
+
- spec/shared/idempotent_method_behavior.rb
|
110
|
+
- spec/shared/invertible_method_behaviour.rb
|
111
|
+
- spec/spec_helper.rb
|
112
|
+
- spec/unit/anima/attribute/define_reader_spec.rb
|
113
|
+
- spec/unit/anima/attribute/get_spec.rb
|
114
|
+
- spec/unit/anima/attribute/instance_variable_name_spec.rb
|
115
|
+
- spec/unit/anima/attribute/load_spec.rb
|
116
|
+
- spec/unit/anima/attribute/set_spec.rb
|
117
|
+
- spec/unit/anima/attributes_hash_spec.rb
|
118
|
+
- spec/unit/anima/error/message_spec.rb
|
119
|
+
- spec/unit/anima/included_spec.rb
|
120
|
+
- spec/unit/anima/initialize_instance_spec.rb
|
121
|
+
homepage: http://github.com/mbj/anima
|
122
|
+
licenses: []
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options: []
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ! '>='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
none: false
|
135
|
+
requirements:
|
136
|
+
- - ! '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
requirements: []
|
140
|
+
rubyforge_project:
|
141
|
+
rubygems_version: 1.8.24
|
142
|
+
signing_key:
|
143
|
+
specification_version: 3
|
144
|
+
summary: Attributes for Plain Old Ruby Objects Experiment
|
145
|
+
test_files:
|
146
|
+
- spec/integration/simple_spec.rb
|
147
|
+
- spec/shared/command_method_behavior.rb
|
148
|
+
- spec/shared/each_method_behaviour.rb
|
149
|
+
- spec/shared/hash_method_behavior.rb
|
150
|
+
- spec/shared/idempotent_method_behavior.rb
|
151
|
+
- spec/shared/invertible_method_behaviour.rb
|
152
|
+
- spec/spec_helper.rb
|
153
|
+
- spec/unit/anima/attribute/define_reader_spec.rb
|
154
|
+
- spec/unit/anima/attribute/get_spec.rb
|
155
|
+
- spec/unit/anima/attribute/instance_variable_name_spec.rb
|
156
|
+
- spec/unit/anima/attribute/load_spec.rb
|
157
|
+
- spec/unit/anima/attribute/set_spec.rb
|
158
|
+
- spec/unit/anima/attributes_hash_spec.rb
|
159
|
+
- spec/unit/anima/error/message_spec.rb
|
160
|
+
- spec/unit/anima/included_spec.rb
|
161
|
+
- spec/unit/anima/initialize_instance_spec.rb
|
162
|
+
has_rdoc:
|