bound 0.0.9 → 0.1.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
  SHA1:
3
- metadata.gz: 46590308f9cc175507cdd528e744b43d72013d82
4
- data.tar.gz: b98915c9a3be4b1a1fe2895862ae4987c83c2fcf
3
+ metadata.gz: 73b43370b97701af8bc883f5d99ce0ccea3b69ff
4
+ data.tar.gz: 89d1eb9d1131a6162d3dcf6926839051752a654c
5
5
  SHA512:
6
- metadata.gz: 1d58eb39f202864c80d0e0edf7b32ba15b1d45cdb7c3ca6f61a2f880b4b21890677fc21e98b72edfedd1ba8ff703690b252374dfd7ef891f8d2350bc0f21358b
7
- data.tar.gz: f934f47ce58bad2dd8cddea11877fba384a24112e152bab5b447788707ee66d9d8efb64b6bcb9a2c637cc59f8d8c349e8c07def4bb4bb68d7cd3095a6fb1f9b8
6
+ metadata.gz: 44ca00d9fe5935c8c38050b8f6486e31e662593633214b935949d322b5d2ca21e9095c2a571defb2574b516d98f80b700f14ac0c4827355993444b9acb501f31
7
+ data.tar.gz: 325cd7b686ec4f7e65821c087a7a8229a02e82a22518b6ac21d5db2813213030eced4b8d13f09ded4635aeea2b63107656f069183ce6860baeb263a7ca31fe30
@@ -20,4 +20,6 @@ Gem::Specification.new do |spec|
20
20
  spec.add_development_dependency "bundler", "~> 1.3"
21
21
  spec.add_development_dependency "rake"
22
22
  spec.add_development_dependency "minitest", "~> 5.0.7"
23
+
24
+ spec.add_development_dependency "simplecov"
23
25
  end
@@ -13,6 +13,16 @@ class Bound
13
13
  new_bound_class.optional(*args)
14
14
  end
15
15
 
16
+ def self.required(*args)
17
+ bound = new_bound_class
18
+
19
+ if args.last.kind_of? Hash
20
+ bound.nested(args.pop)
21
+ end
22
+
23
+ bound.set_attributes(*args)
24
+ end
25
+
16
26
  private
17
27
 
18
28
  def self.new_bound_class
@@ -22,13 +32,97 @@ class Bound
22
32
  end
23
33
 
24
34
  class BoundClass
35
+ class Attribute
36
+ attr_reader :name, :value
37
+ attr_accessor :nested_class
38
+
39
+ def initialize(name)
40
+ @name = name
41
+ end
42
+
43
+ def assign(value)
44
+ @assigned = true
45
+ if nested_class
46
+ @value = assign_nested(value)
47
+ else
48
+ @value = value
49
+ end
50
+ end
51
+
52
+ def assign_nested(value)
53
+ nested_attribute = NestedAttribute.new(nested_class)
54
+ nested_attribute.resolve(value)
55
+ end
56
+
57
+ def call_on(object)
58
+ object.public_send @name
59
+ end
60
+
61
+ def valid?
62
+ !required? || is_assigned?
63
+ end
64
+
65
+ def required?
66
+ false
67
+ end
68
+
69
+ def is_assigned?
70
+ !!@assigned
71
+ end
72
+
73
+ def inspect
74
+ "#{@name}=#{@value.inspect}"
75
+ end
76
+ end
77
+
78
+ class RequiredAttribute < Attribute
79
+ def required?; true; end
80
+ end
81
+
82
+ class NestedAttribute
83
+ def initialize(bound_definition)
84
+ if bound_definition.kind_of?(Array)
85
+ @assigner = ArrayAssigner.new(bound_definition)
86
+ else
87
+ @assigner = ValueAssigner.new(bound_definition)
88
+ end
89
+ end
90
+
91
+ def resolve(bound_arguments)
92
+ @assigner.resolve(bound_arguments)
93
+ end
94
+
95
+ class ArrayAssigner
96
+ def initialize(definitions)
97
+ @bound_class = definitions.first
98
+ end
99
+
100
+ def resolve(arguments_list)
101
+ raise ArgumentError.new("Expected #{arguments_list.inspect} to be an array") unless arguments_list.kind_of? Array
102
+ arguments_list.map do |arguments|
103
+ @bound_class.new(arguments)
104
+ end
105
+ end
106
+ end
107
+
108
+ class ValueAssigner
109
+ def initialize(definition)
110
+ @bound_class = definition
111
+ end
112
+
113
+ def resolve(arguments)
114
+ @bound_class.new(arguments)
115
+ end
116
+ end
117
+ end
118
+
119
+
25
120
  class << self
26
- attr_accessor :attributes, :optional_attributes, :nested_attributes
121
+ attr_accessor :attrs, :nested_attr_classes
27
122
 
28
123
  def initialize_values
29
- self.attributes = []
30
- self.optional_attributes = []
31
- self.nested_attributes = []
124
+ self.attrs = {}
125
+ self.nested_attr_classes = {}
32
126
  end
33
127
 
34
128
  def set_attributes(*attributes)
@@ -36,8 +130,11 @@ class Bound
36
130
  raise ArgumentError.new("Invalid list of attributes: #{attributes.inspect}")
37
131
  end
38
132
 
39
- self.attributes += attributes
40
- attr_accessor *attributes
133
+ attributes.each do |attribute|
134
+ self.attrs[attribute] = RequiredAttribute
135
+ end
136
+
137
+ define_attribute_accessors attributes
41
138
 
42
139
  self
43
140
  end
@@ -47,104 +144,108 @@ class Bound
47
144
  raise ArgumentError.new("Invalid list of optional attributes: #{optionals.inspect}")
48
145
  end
49
146
 
50
- self.optional_attributes += optionals
51
- attr_accessor *optionals
147
+ optionals.each do |attribute|
148
+ self.attrs[attribute] = Attribute
149
+ end
150
+
151
+ define_attribute_accessors optionals
52
152
 
53
153
  self
54
154
  end
55
155
 
56
156
  def nested(nested_attributes)
57
157
  attributes = nested_attributes.keys
58
- self.nested_attributes += attributes
59
- self.attributes += attributes
60
- attr_reader *attributes
61
158
 
62
159
  attributes.each do |attribute|
63
- define_method :"#{attribute}=" do |initial_values|
64
- nested_target = nested_attributes[attribute]
65
- value = extract_values_for_nested_attribute(nested_target, initial_values)
66
-
67
- instance_variable_set :"@#{attribute}", value
68
- end
160
+ self.attrs[attribute] = RequiredAttribute
161
+ self.nested_attr_classes[attribute] = nested_attributes[attribute]
69
162
  end
70
163
 
164
+ define_attribute_accessors attributes
165
+
71
166
  self
72
167
  end
73
168
 
74
169
  alias :build :new
170
+
171
+ private
172
+
173
+ def define_attribute_accessors(attributes)
174
+ define_attribute_readers attributes
175
+ define_attribute_writers attributes
176
+ end
177
+
178
+ def define_attribute_readers(attributes)
179
+ attributes.each do |attribute|
180
+ define_method attribute do
181
+ get_attribute(attribute).value
182
+ end
183
+ end
184
+ end
185
+
186
+ def define_attribute_writers(attributes)
187
+ attributes.each do |attribute|
188
+ define_method :"#{attribute}=" do |value|
189
+ get_attribute(attribute).assign value
190
+ end
191
+ end
192
+ end
75
193
  end
76
194
 
77
195
  def initialize(hash_or_object = {})
78
- build_hash(hash_or_object)
196
+ seed hash_or_object
79
197
  validate!
80
- seed
81
- end
82
-
83
- def __attributes__
84
- self.class.attributes + self.class.optional_attributes
85
198
  end
86
199
 
200
+
87
201
  def method_missing(meth, *args, &blk)
88
202
  attribute = meth.to_s.gsub(/=$/, '')
89
203
  raise ArgumentError.new("Unknown attribute: #{attribute}")
90
204
  end
91
205
 
92
- def inspect
93
- class_name = self.class.name
94
- id = '%0#16x' % (object_id << 1)
95
- values = (self.class.attributes + self.class.optional_attributes).map do |attr|
96
- "#{attr}=#{public_send(attr).inspect}"
206
+ def get_attributes
207
+ self.class.attrs.keys.map do |attribute_name|
208
+ get_attribute(attribute_name)
97
209
  end
210
+ end
98
211
 
99
- (["#<#{class_name}:#{id}"] + values + [">"]).join(" ")
212
+ def __attributes__
213
+ puts "DEPRECATED: use get_attributes"
214
+ get_attributes.map(&:name)
100
215
  end
101
216
 
102
217
  private
103
218
 
104
- def build_nested_value(bound_class, init)
105
- bound_class.new(init)
106
- end
219
+ def get_attribute(attribute_name)
220
+ attribute_class = self.class.attrs[attribute_name]
221
+ nested_class = self.class.nested_attr_classes[attribute_name]
107
222
 
108
- def extract_values_for_nested_attribute(nested_target, initial_values)
109
- if nested_target.kind_of? Array
110
- raise ArgumentError.new("Expected #{initial_values.inspect} to be an array") unless initial_values.kind_of? Array
223
+ var = :"@#{attribute_name}"
224
+ attribute = instance_variable_get(var)
111
225
 
112
- initial_values.map do |initial_value|
113
- build_nested_value(nested_target.first, initial_value)
114
- end
115
- else
116
- build_nested_value(nested_target, initial_values)
226
+ unless attribute
227
+ attribute = instance_variable_set(var, attribute_class.new(attribute_name))
228
+ attribute.nested_class = nested_class if nested_class
117
229
  end
230
+
231
+ attribute
118
232
  end
119
233
 
120
234
  def validate!
121
- self.class.attributes.each do |attribute|
122
- raise ArgumentError.new("Missing attribute: #{attribute}") unless @hash.key?(attribute)
235
+ get_attributes.each do |attribute|
236
+ raise ArgumentError.new("Missing attribute: #{attribute.name}") unless attribute.valid?
123
237
  end
124
238
  end
125
239
 
126
- def seed
127
- HashSeeder.new(self).seed(@hash)
128
- end
129
-
130
- def build_hash(hash_or_object)
240
+ def seed(hash_or_object)
131
241
  case hash_or_object
132
242
  when Hash
133
- @hash = hash_or_object
243
+ seeder = HashSeeder.new(self)
134
244
  else
135
- @hash = {}
136
- insert_into_hash(self.class.attributes, hash_or_object)
137
- insert_into_hash(self.class.optional_attributes, hash_or_object)
245
+ seeder = ObjectSeeder.new(self)
138
246
  end
139
- end
140
247
 
141
- def insert_into_hash(attributes, object)
142
- attributes.each_with_object(@hash) do |attr, h|
143
- begin
144
- h[attr] = object.public_send(attr)
145
- rescue NoMethodError
146
- end
147
- end
248
+ seeder.seed(hash_or_object)
148
249
  end
149
250
  end
150
251
 
@@ -159,4 +260,23 @@ class Bound
159
260
  end
160
261
  end
161
262
  end
263
+
264
+ class ObjectSeeder
265
+ def initialize(receiver)
266
+ @receiver = receiver
267
+ end
268
+
269
+ def seed(object)
270
+ @receiver.get_attributes.each do |attribute|
271
+ begin
272
+ value = attribute.call_on(object)
273
+ rescue NoMethodError => e
274
+ value = nil
275
+ raise ArgumentError, "missing #{attribute.name}" if attribute.required?
276
+ end
277
+
278
+ @receiver.public_send "#{attribute.name}=", value
279
+ end
280
+ end
281
+ end
162
282
  end
@@ -1,3 +1,3 @@
1
1
  class Bound
2
- VERSION = "0.0.9"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -1,14 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Bound do
4
- User = Bound.new(:name, :age)
4
+ User = Bound.required(:name, :age)
5
5
 
6
6
  let(:object) { HashObject.new(hash) }
7
7
  let(:hash) { {:name => 'foo', :age => 23} }
8
8
 
9
9
  it 'sets all attributes' do
10
10
  [hash, object].each do |subject|
11
- user = User.build(subject)
11
+ user = User.new(subject)
12
12
 
13
13
  assert_equal hash[:name], user.name
14
14
  assert_equal hash[:age], user.age
@@ -29,7 +29,7 @@ describe Bound do
29
29
 
30
30
  [hash, object].each do |subject|
31
31
  exception = assert_raises ArgumentError, subject.inspect do
32
- User.build(subject)
32
+ User.new(subject)
33
33
  end
34
34
 
35
35
  assert_match(/missing.+age/i, exception.message)
@@ -40,7 +40,7 @@ describe Bound do
40
40
  hash[:age] = nil
41
41
 
42
42
  [hash, object].each do |subject|
43
- User.build(subject)
43
+ User.new(subject)
44
44
  end
45
45
  end
46
46
 
@@ -49,37 +49,37 @@ describe Bound do
49
49
  subject = hash
50
50
 
51
51
  exception = assert_raises ArgumentError, subject.inspect do
52
- User.build(subject)
52
+ User.new(subject)
53
53
  end
54
54
 
55
55
  assert_match(/unknown.+gender/i, exception.message)
56
56
  end
57
57
 
58
58
  it 'exposes an attributes method' do
59
- user = User.build(hash)
59
+ user = User.new(hash)
60
60
 
61
- assert_equal 2, user.__attributes__.size
62
- assert_includes user.__attributes__, :name
63
- assert_includes user.__attributes__, :age
61
+ assert_equal 2, user.get_attributes.size
62
+ assert_includes user.get_attributes.map(&:name), :name
63
+ assert_includes user.get_attributes.map(&:name), :age
64
64
  end
65
65
 
66
66
  describe 'wrong initialization' do
67
67
  it 'fails if new is not called with symbols' do
68
68
  assert_raises ArgumentError do
69
- Bound.new(:events => [])
69
+ Bound.required(32, "a")
70
70
  end
71
71
  end
72
72
 
73
73
  it 'fails if optional is not called with symbols' do
74
74
  assert_raises ArgumentError do
75
- Bound.new.optional(:events => [])
75
+ Bound.required(32, "a")
76
76
  end
77
77
  end
78
78
  end
79
79
 
80
80
  describe 'inspect' do
81
81
  let(:inspection) { user.inspect }
82
- let(:user) { User.build(hash) }
82
+ let(:user) { User.new(hash) }
83
83
 
84
84
  it 'lists all attributes' do
85
85
  assert_match(/name="foo"/, inspection)
@@ -94,11 +94,11 @@ describe Bound do
94
94
  end
95
95
 
96
96
  describe 'optional attributes' do
97
- UserWithoutAge = Bound.new(:name).optional(:age)
97
+ UserWithoutAge = Bound.required(:name).optional(:age)
98
98
 
99
99
  it 'sets optional attributes' do
100
100
  [hash, object].each do |subject|
101
- user = UserWithoutAge.build(subject)
101
+ user = UserWithoutAge.new(subject)
102
102
 
103
103
  assert_equal hash[:age], user.age
104
104
  end
@@ -108,7 +108,7 @@ describe Bound do
108
108
  hash.delete :age
109
109
 
110
110
  [hash, object].each do |subject|
111
- UserWithoutAge.build(subject)
111
+ UserWithoutAge.new(subject)
112
112
  end
113
113
  end
114
114
 
@@ -116,16 +116,16 @@ describe Bound do
116
116
  hash[:age] = nil
117
117
 
118
118
  [hash, object].each do |subject|
119
- UserWithoutAge.build(subject)
119
+ UserWithoutAge.new(subject)
120
120
  end
121
121
  end
122
122
 
123
123
  it 'are also included in attributes' do
124
124
  user = UserWithoutAge.build(hash)
125
125
 
126
- assert_equal 2, user.__attributes__.size
127
- assert_includes user.__attributes__, :name
128
- assert_includes user.__attributes__, :age
126
+ assert_equal 2, user.get_attributes.size
127
+ assert_includes user.get_attributes.map(&:name), :name
128
+ assert_includes user.get_attributes.map(&:name), :age
129
129
  end
130
130
  end
131
131
 
@@ -135,23 +135,23 @@ describe Bound do
135
135
 
136
136
  it 'works without attributes' do
137
137
  [hash, object, nil].each do |subject|
138
- UserWithoutAttributes.build(subject)
138
+ UserWithoutAttributes.new(subject)
139
139
  end
140
140
  end
141
141
 
142
142
  it 'works without argument' do
143
- UserWithoutAttributes.build
143
+ UserWithoutAttributes.new
144
144
  end
145
145
  end
146
146
 
147
147
  describe 'nested attribute' do
148
- Company = Bound.new(:name).nested(:address => Bound.new(:street))
149
- EmployedUser = Bound.new(:uid).nested(:company => Company)
148
+ Company = Bound.required(:name, :address => Bound.required(:street))
149
+ EmployedUser = Bound.required(:uid, :company => Company)
150
150
  let(:hash) { {:uid => '1', :company => {:name => 'featurepoly', :address => {:street => 'Germany'}}} }
151
151
 
152
152
  it 'works with nested attributes' do
153
153
  [hash, object].each do |subject|
154
- user = EmployedUser.build(subject)
154
+ user = EmployedUser.new(subject)
155
155
 
156
156
  assert_equal hash[:uid], user.uid
157
157
  assert_equal hash[:company][:name], user.company.name
@@ -161,8 +161,8 @@ describe Bound do
161
161
  end
162
162
 
163
163
  describe 'array of nested attribute' do
164
- Post = Bound.new(:title)
165
- BloggingUser = Bound.new(:name).nested(:posts => [Post])
164
+ Post = Bound.required(:title)
165
+ BloggingUser = Bound.required(:name, :posts => [Post])
166
166
  let(:hash) do
167
167
  {
168
168
  :name => 'Steve',
@@ -175,7 +175,7 @@ describe Bound do
175
175
 
176
176
  it 'works with array of nested attributes' do
177
177
  [hash, object].each do |subject|
178
- user = BloggingUser.build(subject)
178
+ user = BloggingUser.new(subject)
179
179
 
180
180
  assert_equal hash[:name], user.name
181
181
  assert_equal hash[:posts][0][:title], user.posts[0].title
@@ -188,7 +188,7 @@ describe Bound do
188
188
 
189
189
  [hash, object].each do |subject|
190
190
  exception = assert_raises ArgumentError do
191
- BloggingUser.build(subject)
191
+ BloggingUser.new(subject)
192
192
  end
193
193
 
194
194
  assert_match(/array/i, exception.message)
@@ -197,11 +197,11 @@ describe Bound do
197
197
  end
198
198
 
199
199
  it 'are also included in attributes' do
200
- user = BloggingUser.build(hash)
200
+ user = BloggingUser.new(hash)
201
201
 
202
- assert_equal 2, user.__attributes__.size
203
- assert_includes user.__attributes__, :name
204
- assert_includes user.__attributes__, :posts
202
+ assert_equal 2, user.get_attributes.size
203
+ assert_includes user.get_attributes.map(&:name), :name
204
+ assert_includes user.get_attributes.map(&:name), :posts
205
205
  end
206
206
  end
207
207
 
@@ -215,11 +215,26 @@ describe Bound do
215
215
  end
216
216
 
217
217
  describe 'allows nested as constructor' do
218
- Car = Bound.nested(:producer => Bound.new(:name))
218
+ Car = Bound.required(:producer => Bound.required(:name))
219
219
 
220
220
  it 'works' do
221
221
  assert_raises(ArgumentError) { Car.new }
222
222
  assert_equal "VW", Car.new(:producer => {:name => 'VW'}).producer.name
223
223
  end
224
224
  end
225
+
226
+ describe '__attributes__' do
227
+ DeprecatedUser = Bound.required(:name)
228
+
229
+ it 'is deprecated' do
230
+ user = DeprecatedUser.new(:name => 'foo')
231
+
232
+ deprecation_warning, _ = capture_io do
233
+ user.__attributes__
234
+ end
235
+
236
+ assert_match(/DEPRECATED/, deprecation_warning)
237
+ end
238
+ end
239
+
225
240
  end
@@ -1,3 +1,8 @@
1
+ if ENV['coverage']
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+ end
5
+
1
6
  require 'minitest/autorun'
2
7
  require 'minitest/spec'
3
8
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bound
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jakob Holderbaum
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-09-25 00:00:00.000000000 Z
12
+ date: 2013-09-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -53,6 +53,20 @@ dependencies:
53
53
  - - ~>
54
54
  - !ruby/object:Gem::Version
55
55
  version: 5.0.7
56
+ - !ruby/object:Gem::Dependency
57
+ name: simplecov
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
56
70
  description:
57
71
  email:
58
72
  - jh@neopoly.de
@@ -92,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
92
106
  version: '0'
93
107
  requirements: []
94
108
  rubyforge_project:
95
- rubygems_version: 2.0.0
109
+ rubygems_version: 2.0.6
96
110
  signing_key:
97
111
  specification_version: 4
98
112
  summary: Implements a nice helper for fast boundary definitions