praxis-blueprints 3.1 → 3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/lib/praxis-blueprints/blueprint.rb +11 -2
- data/lib/praxis-blueprints/collection_view.rb +9 -1
- data/lib/praxis-blueprints/renderer.rb +14 -2
- data/lib/praxis-blueprints/version.rb +1 -1
- data/praxis-blueprints.gemspec +1 -1
- data/spec/praxis-blueprints/blueprint_spec.rb +13 -1
- data/spec/praxis-blueprints/collection_view_spec.rb +13 -0
- data/spec/praxis-blueprints/renderer_spec.rb +73 -3
- data/spec/praxis-blueprints/view_spec.rb +18 -0
- data/spec/support/spec_blueprints.rb +24 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3679f890d11a1474437b9018c7030a909e050ca
|
4
|
+
data.tar.gz: 61094297c27f94f4add6227d637654fae03ed662
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa633b4772c07f859fd4024044a618c2243b5b6d95678dd7c0757486811dc1a315eafde12319c4c4215b72571d0d38f7ccdae7d91903183aa14c1baaaa86ae58
|
7
|
+
data.tar.gz: 47e3c62fd5bdfa05c142083e7053687d57d25fd2b08b3a5b2dd913050cbaf94a9ccfb699707e1c1e43f6384ce25e4688beaed9cb81a39038080eff2155e9c979
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
## next
|
4
4
|
|
5
|
+
## 3.2
|
6
|
+
|
7
|
+
* Ensure we call `object.dump` in Renderer when fully dumping an instance (or array of instances) that have the Attributor::Dumpable module (i.e., when no subfields were selected)
|
8
|
+
* In other words, attributor types (custom or not) will need to include the Attributor::Dumpable module and properly implement the dump instance method to produce the right output with native ruby objects.
|
9
|
+
* Ensure `Blueprint` validates advanced requirements.
|
10
|
+
|
5
11
|
## 3.1
|
6
12
|
|
7
13
|
* Reworked FieldExpander's behavior with circular references.
|
@@ -193,7 +193,6 @@ module Praxis
|
|
193
193
|
|
194
194
|
|
195
195
|
def self.validate(value, context=Attributor::DEFAULT_ROOT_CONTEXT, _attribute=nil)
|
196
|
-
|
197
196
|
raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context == nil
|
198
197
|
context = [context] if context.is_a? ::String
|
199
198
|
|
@@ -341,19 +340,29 @@ module Praxis
|
|
341
340
|
def validate(context=Attributor::DEFAULT_ROOT_CONTEXT)
|
342
341
|
raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context == nil
|
343
342
|
context = [context] if context.is_a? ::String
|
343
|
+
keys_with_values = []
|
344
344
|
|
345
345
|
raise "validation conflict" if @validating
|
346
346
|
@validating = true
|
347
347
|
|
348
|
-
|
348
|
+
errors = []
|
349
|
+
self.class.attributes.each do |sub_attribute_name, sub_attribute|
|
349
350
|
sub_context = self.class.generate_subcontext(context,sub_attribute_name)
|
350
351
|
value = self.send(sub_attribute_name)
|
352
|
+
unless value.nil?
|
353
|
+
keys_with_values << sub_attribute_name
|
354
|
+
end
|
351
355
|
|
352
356
|
if value.respond_to?(:validating) # really, it's a thing with sub-attributes
|
353
357
|
next if value.validating
|
354
358
|
end
|
355
359
|
errors.push(*sub_attribute.validate(value, sub_context))
|
356
360
|
end
|
361
|
+
self.class.attribute.type.requirements.each do |req|
|
362
|
+
validation_errors = req.validate(keys_with_values, context)
|
363
|
+
errors.push(*validation_errors) unless validation_errors.empty?
|
364
|
+
end
|
365
|
+
errors
|
357
366
|
ensure
|
358
367
|
@validating = false
|
359
368
|
end
|
@@ -5,10 +5,18 @@ module Praxis
|
|
5
5
|
super(name,schema)
|
6
6
|
|
7
7
|
if member_view
|
8
|
-
@
|
8
|
+
@_lazy_view = member_view
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
+
def contents
|
13
|
+
if @_lazy_view
|
14
|
+
@contents = @_lazy_view.contents.clone
|
15
|
+
@_lazy_view = nil
|
16
|
+
end
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
12
20
|
def example(context=Attributor::DEFAULT_ROOT_CONTEXT)
|
13
21
|
collection = 3.times.collect do |i|
|
14
22
|
subcontext = context + ["at(#{i})"]
|
@@ -55,7 +55,14 @@ module Praxis
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def _render(object, fields, view=nil, context: Attributor::DEFAULT_ROOT_CONTEXT)
|
58
|
-
|
58
|
+
if fields == true
|
59
|
+
return case object
|
60
|
+
when Attributor::Dumpable
|
61
|
+
object.dump
|
62
|
+
else
|
63
|
+
object
|
64
|
+
end
|
65
|
+
end
|
59
66
|
|
60
67
|
notification_payload = {
|
61
68
|
blueprint: object,
|
@@ -74,7 +81,12 @@ module Praxis
|
|
74
81
|
next if value.nil? && !self.include_nil
|
75
82
|
|
76
83
|
if subfields == true
|
77
|
-
hash[key] = value
|
84
|
+
hash[key] = case value
|
85
|
+
when Attributor::Dumpable
|
86
|
+
value.dump
|
87
|
+
else
|
88
|
+
value
|
89
|
+
end
|
78
90
|
else
|
79
91
|
new_context = context + [key]
|
80
92
|
hash[key] = self.render(value, subfields, context: new_context)
|
data/praxis-blueprints.gemspec
CHANGED
@@ -20,7 +20,7 @@ it results in a structured hash instead of an encoded string. Blueprints can aut
|
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
22
|
spec.add_runtime_dependency(%q<randexp>, ["~> 0"])
|
23
|
-
spec.add_runtime_dependency(%q<attributor>, [">=
|
23
|
+
spec.add_runtime_dependency(%q<attributor>, [">= 5.0.2"])
|
24
24
|
spec.add_runtime_dependency(%q<activesupport>, [">= 3"])
|
25
25
|
|
26
26
|
spec.add_development_dependency "bundler", "~> 1.6"
|
@@ -177,10 +177,16 @@ describe Praxis::Blueprint do
|
|
177
177
|
let(:example_object) { nil }
|
178
178
|
|
179
179
|
before do
|
180
|
-
expect(blueprint_class.attribute.type).to receive(:describe).with(shallow, example: example_object).and_call_original
|
180
|
+
expect(blueprint_class.attribute.type).to receive(:describe).with(shallow, example: example_object).ordered.and_call_original
|
181
181
|
end
|
182
182
|
|
183
183
|
context 'for non-shallow descriptions' do
|
184
|
+
before do
|
185
|
+
# Describing a Person also describes the :myself and :friends attributes. They are both a Person and a Coll of Person.
|
186
|
+
# This means that Person type `describe` is called two more times, thes times with shallow=true
|
187
|
+
expect(blueprint_class.attribute.type).to receive(:describe).with(true, example: example_object).twice.and_call_original
|
188
|
+
end
|
189
|
+
|
184
190
|
subject(:output){ blueprint_class.describe }
|
185
191
|
|
186
192
|
its([:name]){ should eq(blueprint_class.name)}
|
@@ -224,6 +230,12 @@ describe Praxis::Blueprint do
|
|
224
230
|
let(:shallow) { false }
|
225
231
|
|
226
232
|
subject(:output) { blueprint_class.describe(false, example: example) }
|
233
|
+
before do
|
234
|
+
# Describing a Person also describes the :myself and :friends attributes. They are both a Person and a Coll of Person.
|
235
|
+
# This means that Person type `describe` is called two more times, thes times with shallow=true
|
236
|
+
expect(blueprint_class.attribute.type).to receive(:describe)
|
237
|
+
.with(true, example: an_instance_of(blueprint_class.attribute.type)).twice.and_call_original
|
238
|
+
end
|
227
239
|
|
228
240
|
it 'outputs examples for leaf values using the provided example' do
|
229
241
|
output[:attributes][:name][:example].should eq example.name
|
@@ -32,6 +32,19 @@ describe Praxis::CollectionView do
|
|
32
32
|
it 'gets the proper contents' do
|
33
33
|
collection_view.contents.should eq member_view.contents
|
34
34
|
end
|
35
|
+
|
36
|
+
context 'lazy initializes its contents' do
|
37
|
+
|
38
|
+
it 'so it will not call contents until it is first needed' do
|
39
|
+
member_view.stub(:contents){ raise 'No!' }
|
40
|
+
expect{ collection_view.name }.to_not raise_error
|
41
|
+
end
|
42
|
+
it 'when contents is needed, it will clone it from the member_view' do
|
43
|
+
# Twice is because we're callong member_view.contents for the right side of the equality
|
44
|
+
expect(member_view).to receive(:contents).twice.and_call_original
|
45
|
+
collection_view.contents.should eq member_view.contents
|
46
|
+
end
|
47
|
+
end
|
35
48
|
end
|
36
49
|
|
37
50
|
context 'creating with a set of attributes defined in a block' do
|
@@ -4,13 +4,21 @@ describe Praxis::Renderer do
|
|
4
4
|
|
5
5
|
let(:address) { Address.example }
|
6
6
|
let(:prior_addresses) { 2.times.collect { Address.example } }
|
7
|
+
let(:alias_one) { FullName.example }
|
8
|
+
let(:alias_two) { FullName.example }
|
9
|
+
let(:aliases) { [alias_one, alias_two] }
|
10
|
+
let(:metadata_hash) { { something: 'here' } }
|
11
|
+
let(:metadata) { Attributor::Hash.load( metadata_hash ) }
|
12
|
+
|
7
13
|
let(:person) do
|
8
14
|
Person.example(
|
9
15
|
address: address,
|
10
16
|
email: nil,
|
11
17
|
prior_addresses: prior_addresses,
|
12
18
|
alive: false,
|
13
|
-
work_address: nil
|
19
|
+
work_address: nil,
|
20
|
+
aliases: aliases,
|
21
|
+
metadata: metadata
|
14
22
|
)
|
15
23
|
end
|
16
24
|
|
@@ -27,7 +35,9 @@ describe Praxis::Renderer do
|
|
27
35
|
},
|
28
36
|
prior_addresses: [{name: true}],
|
29
37
|
work_address: true,
|
30
|
-
alive: true
|
38
|
+
alive: true,
|
39
|
+
metadata: true,
|
40
|
+
aliases: [true]
|
31
41
|
}
|
32
42
|
end
|
33
43
|
|
@@ -36,7 +46,7 @@ describe Praxis::Renderer do
|
|
36
46
|
subject(:output) { renderer.render(person, fields) }
|
37
47
|
|
38
48
|
it 'renders existing attributes' do
|
39
|
-
output.keys.should match_array([:name, :full_name, :alive, :address, :prior_addresses])
|
49
|
+
output.keys.should match_array([:name, :full_name, :alive, :address, :prior_addresses, :metadata, :aliases])
|
40
50
|
|
41
51
|
output[:name].should eq person.name
|
42
52
|
output[:full_name].should eq({first: person.full_name.first, last: person.full_name.last})
|
@@ -50,6 +60,23 @@ describe Praxis::Renderer do
|
|
50
60
|
|
51
61
|
expected_prior_addresses = prior_addresses.collect { |addr| {name: addr.name} }
|
52
62
|
output[:prior_addresses].should match_array(expected_prior_addresses)
|
63
|
+
|
64
|
+
expected_aliases = aliases.collect { |the_alias| the_alias.dump }
|
65
|
+
output[:aliases].should match_array( expected_aliases )
|
66
|
+
|
67
|
+
output[:metadata].should eq( metadata.dump )
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'calls dump for non-Blueprint, but still Dumpable instances' do
|
71
|
+
it 'when rendering them in full as array members' do
|
72
|
+
alias_one.should_receive(:dump).and_call_original
|
73
|
+
output[:aliases].first.should eq( first: alias_one.first, last: alias_one.last )
|
74
|
+
end
|
75
|
+
it 'when rendering them in full as leaf object' do
|
76
|
+
metadata.should_receive(:dump).and_call_original
|
77
|
+
output[:metadata].should eq( metadata_hash )
|
78
|
+
end
|
79
|
+
|
53
80
|
end
|
54
81
|
|
55
82
|
it 'does not render attributes with nil values' do
|
@@ -150,4 +177,47 @@ describe Praxis::Renderer do
|
|
150
177
|
end
|
151
178
|
|
152
179
|
end
|
180
|
+
|
181
|
+
context 'rendering hashes' do
|
182
|
+
let(:fields) do
|
183
|
+
{
|
184
|
+
id: true,
|
185
|
+
hash: true
|
186
|
+
}
|
187
|
+
end
|
188
|
+
|
189
|
+
let(:data) { {id: 10, hash: {foo: 'bar'}} }
|
190
|
+
let(:object) { SimpleHash.load(data)}
|
191
|
+
let(:renderer) { Praxis::Renderer.new }
|
192
|
+
|
193
|
+
subject(:output) { renderer.render(object, fields) }
|
194
|
+
|
195
|
+
its([:id]) { should eq data[:id] }
|
196
|
+
its([:hash]) { should eq data[:hash] }
|
197
|
+
its([:hash]) { should be_kind_of(Hash) }
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'rendering collections of hashes' do
|
201
|
+
let(:fields) do
|
202
|
+
{
|
203
|
+
id: true,
|
204
|
+
hash_collection: [true]
|
205
|
+
}
|
206
|
+
end
|
207
|
+
|
208
|
+
let(:data) { {id: 10, hash_collection: [{foo: 'bar'}]} }
|
209
|
+
let(:object) { SimpleHashCollection.load(data)}
|
210
|
+
let(:renderer) { Praxis::Renderer.new }
|
211
|
+
|
212
|
+
subject(:output) { renderer.render(object, fields) }
|
213
|
+
|
214
|
+
its([:id]) { should eq data[:id] }
|
215
|
+
its([:hash_collection]) { should eq data[:hash_collection] }
|
216
|
+
its([:hash_collection]) { should be_kind_of(Array) }
|
217
|
+
|
218
|
+
it 'renders the hashes' do
|
219
|
+
expect(output[:hash_collection].first).to be_kind_of(Hash)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
153
223
|
end
|
@@ -72,6 +72,24 @@ describe Praxis::View do
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
+
context 'that reference the enclosing view' do
|
76
|
+
let(:view) { Person.views.fetch(:self_referencing) }
|
77
|
+
context 'for non-collection attributes' do
|
78
|
+
it 'the view points exactly to parent view' do
|
79
|
+
contents[:myself].should be_kind_of(Praxis::View)
|
80
|
+
contents[:myself].should be(view)
|
81
|
+
contents[:myself].contents.should eq(view.contents)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
context 'for collection attributes' do
|
85
|
+
it 'creates the sub-CollectionViews with a member view with the same contents of the parent' do
|
86
|
+
contents[:friends].should be_kind_of(Praxis::CollectionView)
|
87
|
+
contents[:friends].contents.should eq(view.contents)
|
88
|
+
contents[:friends].contents.keys.should match_array([:myself,:friends])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
75
93
|
end
|
76
94
|
|
77
95
|
end
|
@@ -20,6 +20,9 @@ class Person < Praxis::Blueprint
|
|
20
20
|
attribute :tags, Attributor::Collection.of(String)
|
21
21
|
attribute :href, String
|
22
22
|
attribute :alive, Attributor::Boolean, default: true
|
23
|
+
attribute :myself, Person
|
24
|
+
attribute :friends, Attributor::Collection.of(Person)
|
25
|
+
attribute :metadata, Attributor::Hash
|
23
26
|
end
|
24
27
|
|
25
28
|
view :default do
|
@@ -33,6 +36,11 @@ class Person < Praxis::Blueprint
|
|
33
36
|
attribute :address, view: :circular
|
34
37
|
end
|
35
38
|
|
39
|
+
view :self_referencing do
|
40
|
+
attribute :myself, view: :self_referencing
|
41
|
+
attribute :friends, view: :self_referencing
|
42
|
+
end
|
43
|
+
|
36
44
|
view :current do
|
37
45
|
attribute :name
|
38
46
|
attribute :full_name
|
@@ -94,3 +102,19 @@ class FullName < Attributor::Model
|
|
94
102
|
end
|
95
103
|
|
96
104
|
end
|
105
|
+
|
106
|
+
|
107
|
+
class SimpleHash < Attributor::Model
|
108
|
+
attributes do
|
109
|
+
attribute :id, Integer
|
110
|
+
attribute :hash, Hash
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
class SimpleHashCollection < Attributor::Model
|
116
|
+
attributes do
|
117
|
+
attribute :id, Integer
|
118
|
+
attribute :hash_collection, Attributor::Collection.of(Hash)
|
119
|
+
end
|
120
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: praxis-blueprints
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '3.
|
4
|
+
version: '3.2'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josep M. Blanquer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2016-02-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: randexp
|
@@ -31,14 +31,14 @@ dependencies:
|
|
31
31
|
requirements:
|
32
32
|
- - ">="
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version:
|
34
|
+
version: 5.0.2
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - ">="
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version:
|
41
|
+
version: 5.0.2
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: activesupport
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|