anima 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ /.rspec
2
+ /Gemfile.lock
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ bundler_args: --without guard
3
+ script: "bundle exec rake ci"
4
+ rvm:
5
+ - 1.8.7
6
+ - 1.9.2
7
+ - 1.9.3
8
+ - jruby-18mode
9
+ - jruby-19mode
10
+ - rbx-18mode
11
+ - rbx-19mode
12
+ notifications:
13
+ email:
14
+ - mbj@seonic.net
@@ -0,0 +1,3 @@
1
+ # v0.0.2 2012-12-13
2
+
3
+ First public release!
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'devtools', :git => 'https://github.com/datamapper/devtools.git'
6
+ eval File.read('Gemfile.devtools')
@@ -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
@@ -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
@@ -0,0 +1,56 @@
1
+ anima
2
+ =====
3
+
4
+ [![Build Status](https://secure.travis-ci.org/mbj/anima.png?branch=master)](http://travis-ci.org/mbj/anima)
5
+ [![Dependency Status](https://gemnasium.com/mbj/anima.png)](https://gemnasium.com/mbj/anima)
6
+ [![Code Climate](https://codeclimate.com/badge.png)](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.
@@ -0,0 +1,3 @@
1
+ require 'devtools'
2
+
3
+ Devtools.init
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ * Use module subclass trick to improve expressiveness of Attribute / AttributeSet / ClassMethod
2
+ * Add ability to freeze attribute_set via dsl
3
+ * Add full unit spec coverage
@@ -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
@@ -0,0 +1,3 @@
1
+ ---
2
+ threshold: 10
3
+ total_score: 61
@@ -0,0 +1,2 @@
1
+ ---
2
+ threshold: 15.1
@@ -0,0 +1,3 @@
1
+ ---
2
+ name: anima
3
+ namespace: Anima
@@ -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 }
@@ -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
@@ -0,0 +1,2 @@
1
+ ---
2
+ threshold: 100
@@ -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
@@ -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,7 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'a command method' do
4
+ it 'returns self' do
5
+ should equal(object)
6
+ end
7
+ 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
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an idempotent method' do
4
+ it 'is idempotent' do
5
+ should equal(instance_eval(&self.class.subject))
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an invertible method' do
4
+ it_should_behave_like 'an idempotent method'
5
+
6
+ it 'is invertible' do
7
+ subject.inverse.should equal(object)
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'anima'
4
+ require 'rspec'
5
+
6
+ # require spec support files and shared behavior
7
+ Dir[File.expand_path('../{support,shared}/**/*.rb', __FILE__)].each { |f| require(f) }
8
+
9
+
10
+ RSpec.configure do |config|
11
+ end
@@ -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,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Anima::Attribute, '#instance_variable_name' do
4
+ subject { Anima::Attribute.new(:foo).instance_variable_name }
5
+
6
+ it { should be(:@foo) }
7
+
8
+ it_should_behave_like 'an idempotent method'
9
+ 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: