bound 0.0.9 → 0.1.0

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 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