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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 87521372774c1c5885e1524ba24c931b6c37cdf2
4
- data.tar.gz: edcd9ea3c1ea8ca0a874bc8d13f5caa6c050e10d
3
+ metadata.gz: e3679f890d11a1474437b9018c7030a909e050ca
4
+ data.tar.gz: 61094297c27f94f4add6227d637654fae03ed662
5
5
  SHA512:
6
- metadata.gz: 4133d88711c6b0d46055c61b483525047463e16e2dc17baa12cd5ac99494e9966e46c828c125826f90afaf4933c4c6d0c12a83dad1036c597741ae8b67da0545
7
- data.tar.gz: a4478e4777276bd98aab92494cf7f38b249451d88047457ad90afafaf7c5f704ab7c2a45858f140158817705a14c57e66018adc47d9577fa32ea1baaaa8fe560
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
- self.class.attributes.each_with_object(Array.new) do |(sub_attribute_name, sub_attribute), errors|
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
- @contents = member_view.contents.clone
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
- return object if fields == true
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)
@@ -1,3 +1,3 @@
1
1
  module Praxis
2
- BLUEPRINTS_VERSION = "3.1"
2
+ BLUEPRINTS_VERSION = "3.2"
3
3
  end
@@ -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>, [">= 4.2"])
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 #.and_return(type_describe)
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.1'
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: 2015-10-29 00:00:00.000000000 Z
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: '4.2'
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: '4.2'
41
+ version: 5.0.2
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: activesupport
44
44
  requirement: !ruby/object:Gem::Requirement