praxis-blueprints 2.0.1 → 2.1
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 +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/praxis-blueprints/blueprint.rb +6 -2
- data/lib/praxis-blueprints/version.rb +1 -1
- data/praxis-blueprints.gemspec +1 -1
- data/spec/praxis-blueprints/blueprint_spec.rb +217 -192
- 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: 14169dd33330be68f4e42c2db5ed8884e243b893
|
4
|
+
data.tar.gz: c4fb566937d470785645d7899ba7351dfa174289
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23dbe2797e5ce3575608d619f56908e21fad55baa53934c2a78dccf594231def06d18b28dc24110b8f09b79a290284eae7cf68d313129925eb8ffd8b32a91c0a
|
7
|
+
data.tar.gz: 4dc85caab7023389dcda2a6974328e815850c07adfbc17ce32ffed83423c58803462bbf5cedb3e799d3fe8b72ecf7d7f46451c1d805f56686e8fa6d7baa8dbcb
|
data/CHANGELOG.md
CHANGED
@@ -64,10 +64,14 @@ module Praxis
|
|
64
64
|
'hash'
|
65
65
|
end
|
66
66
|
|
67
|
-
def self.describe(shallow=false
|
67
|
+
def self.describe(shallow=false,example: nil, **opts)
|
68
68
|
type_name = self.ancestors.find { |k| k.name && !k.name.empty? }.name
|
69
69
|
|
70
|
-
|
70
|
+
if example
|
71
|
+
example = example.object
|
72
|
+
end
|
73
|
+
|
74
|
+
description = self.attribute.type.describe(shallow,example: example, **opts).merge!(id: self.id, name: type_name)
|
71
75
|
|
72
76
|
unless shallow
|
73
77
|
description[:views] = self.views.each_with_object({}) do |(view_name, view), hash|
|
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>, [">= 4.0.1"])
|
24
24
|
spec.add_runtime_dependency(%q<activesupport>, [">= 3"])
|
25
25
|
|
26
26
|
spec.add_development_dependency "bundler", "~> 1.6"
|
@@ -171,16 +171,18 @@ describe Praxis::Blueprint do
|
|
171
171
|
end
|
172
172
|
|
173
173
|
context '.describe' do
|
174
|
+
let(:shallow ) { false }
|
175
|
+
let(:example_object) { nil }
|
176
|
+
|
174
177
|
before do
|
175
|
-
expect(blueprint_class.attribute.type).to receive(:describe).and_return(type_describe)
|
178
|
+
expect(blueprint_class.attribute.type).to receive(:describe).with(shallow, example: example_object).and_call_original #.and_return(type_describe)
|
176
179
|
end
|
177
|
-
let(:type_describe){ {name: "Fake", id: "From Struct attr", other: "type attribute"} }
|
178
180
|
|
179
181
|
context 'for non-shallow descriptions' do
|
180
182
|
subject(:output){ blueprint_class.describe }
|
183
|
+
|
181
184
|
its([:name]){ should eq(blueprint_class.name)}
|
182
185
|
its([:id]){ should eq(blueprint_class.id)}
|
183
|
-
its([:other]){ should eq("type attribute")}
|
184
186
|
its([:views]){ should be_kind_of(Hash)}
|
185
187
|
it 'should contain the an entry for each view' do
|
186
188
|
subject[:views].keys.should include(:default, :current, :extended, :master)
|
@@ -188,11 +190,34 @@ describe Praxis::Blueprint do
|
|
188
190
|
end
|
189
191
|
|
190
192
|
context 'for shallow descriptions' do
|
193
|
+
let(:shallow) { true }
|
194
|
+
|
191
195
|
it 'should not include views' do
|
192
196
|
blueprint_class.describe(true).key?(:views).should be(false)
|
193
197
|
end
|
194
198
|
end
|
199
|
+
|
200
|
+
context 'with an example' do
|
201
|
+
let(:example) { blueprint_class.example }
|
202
|
+
let(:example_object) { example.object }
|
203
|
+
let(:shallow) { false }
|
204
|
+
|
205
|
+
subject(:output) { blueprint_class.describe(false, example: example) }
|
206
|
+
|
207
|
+
it 'outputs examples for leaf values using the provided example' do
|
208
|
+
output[:attributes][:name][:example].should eq example.name
|
209
|
+
output[:attributes][:age][:example].should eq example.age
|
210
|
+
|
211
|
+
output[:attributes][:full_name].should_not have_key(:example)
|
212
|
+
output[:attributes][:aliases].should_not have_key(:example)
|
213
|
+
|
214
|
+
parents_attributes = output[:attributes][:parents][:type][:attributes]
|
215
|
+
parents_attributes[:father][:example].should eq example.parents.father
|
216
|
+
parents_attributes[:mother][:example].should eq example.parents.mother
|
217
|
+
end
|
218
|
+
end
|
195
219
|
end
|
220
|
+
|
196
221
|
context '.validate' do
|
197
222
|
let(:hash) { {name: 'bob'} }
|
198
223
|
let(:person) { Person.load(hash) }
|
@@ -207,217 +232,217 @@ describe Praxis::Blueprint do
|
|
207
232
|
|
208
233
|
it { should have(1).item }
|
209
234
|
its(:first) { should =~ /Attribute \$.address.state/ }
|
210
|
-
|
211
|
-
|
212
|
-
context 'for objects of the wrong type' do
|
213
|
-
it 'raises an error' do
|
214
|
-
expect {
|
215
|
-
Person.validate(Object.new)
|
216
|
-
}.to raise_error(ArgumentError, /Error validating .* as Person for an object of type Object/)
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
context '.load' do
|
222
|
-
let(:hash) do
|
223
|
-
{
|
224
|
-
:name => 'Bob',
|
225
|
-
:full_name => {:first => 'Robert', :last => 'Robertson'},
|
226
|
-
:address => {:street => 'main', :state => 'OR'}
|
227
|
-
}
|
228
|
-
end
|
229
|
-
subject(:person) { Person.load(hash) }
|
230
|
-
|
231
|
-
it { should be_kind_of(Person) }
|
232
|
-
|
233
|
-
context 'recursively loading sub-attributes' do
|
234
|
-
context 'for a Blueprint' do
|
235
|
-
subject(:address) { person.address }
|
236
|
-
it { should be_kind_of(Address) }
|
237
|
-
end
|
238
|
-
context 'for an Attributor::Model' do
|
239
|
-
subject(:full_name) { person.full_name }
|
240
|
-
it { should be_kind_of(FullName) }
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
end
|
245
|
-
|
246
|
-
|
247
|
-
context 'decorators' do
|
248
|
-
let(:name) { 'Soren II' }
|
249
|
-
|
250
|
-
let(:object) { Person.example.object }
|
251
|
-
subject(:person) { Person.new(object, decorators) }
|
252
|
-
|
253
|
-
|
254
|
-
context 'as a hash' do
|
255
|
-
let(:decorators) { {name: name} }
|
256
|
-
it do
|
257
|
-
pers = person
|
258
|
-
# binding.pry
|
259
|
-
pers.name.should eq('Soren II')
|
260
|
-
end
|
261
|
-
|
262
|
-
its(:name) { should be(name) }
|
263
|
-
|
264
|
-
context 'an additional instance with the equivalent hash' do
|
265
|
-
subject(:additional_person) { Person.new(object, {name: name}) }
|
266
|
-
it { should_not be person }
|
267
|
-
end
|
268
|
-
|
269
|
-
context 'an additional instance with the same hash object' do
|
270
|
-
subject(:additional_person) { Person.new(object, decorators) }
|
271
|
-
it { should_not be person }
|
272
|
-
end
|
273
|
-
|
274
|
-
context 'an instance of the same object without decorators' do
|
275
|
-
subject(:additional_person) { Person.new(object) }
|
276
|
-
it { should_not be person }
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
context 'as an object' do
|
281
|
-
let(:decorators) { double("decorators", name: name) }
|
282
|
-
its(:name) { should be(name) }
|
283
|
-
|
284
|
-
context 'an additional instance with the same object' do
|
285
|
-
subject(:additional_person) { Person.new(object, decorators) }
|
286
|
-
it { should_not be person }
|
287
|
-
end
|
288
|
-
end
|
235
|
+
end
|
289
236
|
|
290
|
-
|
237
|
+
context 'for objects of the wrong type' do
|
238
|
+
it 'raises an error' do
|
239
|
+
expect {
|
240
|
+
Person.validate(Object.new)
|
241
|
+
}.to raise_error(ArgumentError, /Error validating .* as Person for an object of type Object/)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
291
245
|
|
246
|
+
context '.load' do
|
247
|
+
let(:hash) do
|
248
|
+
{
|
249
|
+
:name => 'Bob',
|
250
|
+
:full_name => {:first => 'Robert', :last => 'Robertson'},
|
251
|
+
:address => {:street => 'main', :state => 'OR'}
|
252
|
+
}
|
253
|
+
end
|
254
|
+
subject(:person) { Person.load(hash) }
|
255
|
+
|
256
|
+
it { should be_kind_of(Person) }
|
257
|
+
|
258
|
+
context 'recursively loading sub-attributes' do
|
259
|
+
context 'for a Blueprint' do
|
260
|
+
subject(:address) { person.address }
|
261
|
+
it { should be_kind_of(Address) }
|
262
|
+
end
|
263
|
+
context 'for an Attributor::Model' do
|
264
|
+
subject(:full_name) { person.full_name }
|
265
|
+
it { should be_kind_of(FullName) }
|
266
|
+
end
|
267
|
+
end
|
292
268
|
|
293
|
-
|
294
|
-
context 'that does not match the value set on the class' do
|
269
|
+
end
|
295
270
|
|
296
|
-
subject(:mismatched_reference) do
|
297
|
-
Class.new(Praxis::Blueprint) do
|
298
|
-
self.reference = Class.new(Praxis::Blueprint)
|
299
|
-
attributes(reference: Class.new(Praxis::Blueprint)) {}
|
300
|
-
end
|
301
|
-
end
|
302
271
|
|
303
|
-
|
304
|
-
|
305
|
-
mismatched_reference.attributes
|
306
|
-
}.to raise_error
|
307
|
-
end
|
272
|
+
context 'decorators' do
|
273
|
+
let(:name) { 'Soren II' }
|
308
274
|
|
309
|
-
|
310
|
-
|
275
|
+
let(:object) { Person.example.object }
|
276
|
+
subject(:person) { Person.new(object, decorators) }
|
311
277
|
|
312
278
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
279
|
+
context 'as a hash' do
|
280
|
+
let(:decorators) { {name: name} }
|
281
|
+
it do
|
282
|
+
pers = person
|
283
|
+
# binding.pry
|
284
|
+
pers.name.should eq('Soren II')
|
285
|
+
end
|
320
286
|
|
321
|
-
|
322
|
-
let(:person) { Person.example }
|
323
|
-
it 'is an alias to dump' do
|
324
|
-
rendered = Person.render(person, view: :default)
|
325
|
-
dumped = Person.dump(person, view: :default)
|
326
|
-
expect(rendered).to eq(dumped)
|
327
|
-
end
|
328
|
-
end
|
287
|
+
its(:name) { should be(name) }
|
329
288
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
subject(:output) { person.render(view: view_name, **render_opts) }
|
335
|
-
|
336
|
-
context 'caches rendered views' do
|
337
|
-
it 'in the instance, by view name' do
|
338
|
-
person.instance_variable_get(:@rendered_views)[view_name].should be_nil
|
339
|
-
person.render(view: view_name)
|
340
|
-
cached = person.instance_variable_get(:@rendered_views)[view_name]
|
341
|
-
cached.should_not be_nil
|
342
|
-
end
|
289
|
+
context 'an additional instance with the equivalent hash' do
|
290
|
+
subject(:additional_person) { Person.new(object, {name: name}) }
|
291
|
+
it { should_not be person }
|
292
|
+
end
|
343
293
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
end
|
294
|
+
context 'an additional instance with the same hash object' do
|
295
|
+
subject(:additional_person) { Person.new(object, decorators) }
|
296
|
+
it { should_not be person }
|
297
|
+
end
|
349
298
|
|
350
|
-
|
351
|
-
|
299
|
+
context 'an instance of the same object without decorators' do
|
300
|
+
subject(:additional_person) { Person.new(object) }
|
301
|
+
it { should_not be person }
|
302
|
+
end
|
303
|
+
end
|
352
304
|
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
plain_view_render.should_not be(fields_render)
|
357
|
-
end
|
305
|
+
context 'as an object' do
|
306
|
+
let(:decorators) { double("decorators", name: name) }
|
307
|
+
its(:name) { should be(name) }
|
358
308
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
309
|
+
context 'an additional instance with the same object' do
|
310
|
+
subject(:additional_person) { Person.new(object, decorators) }
|
311
|
+
it { should_not be person }
|
312
|
+
end
|
313
|
+
end
|
364
314
|
|
365
|
-
|
366
|
-
rendered1 = person.render(view: view_name, **render_opts)
|
315
|
+
end
|
367
316
|
|
368
|
-
equivalent_render_opts = { fields: {age: nil, address: {state: nil, street: nil}, email: nil} }
|
369
|
-
rendered2 = person.render(view: view_name, **equivalent_render_opts)
|
370
317
|
|
371
|
-
|
372
|
-
|
373
|
-
end
|
318
|
+
context 'with a provided :reference option on attributes' do
|
319
|
+
context 'that does not match the value set on the class' do
|
374
320
|
|
375
|
-
|
321
|
+
subject(:mismatched_reference) do
|
322
|
+
Class.new(Praxis::Blueprint) do
|
323
|
+
self.reference = Class.new(Praxis::Blueprint)
|
324
|
+
attributes(reference: Class.new(Praxis::Blueprint)) {}
|
325
|
+
end
|
326
|
+
end
|
376
327
|
|
377
|
-
|
328
|
+
it 'should raise an error' do
|
329
|
+
expect {
|
330
|
+
mismatched_reference.attributes
|
331
|
+
}.to raise_error
|
332
|
+
end
|
378
333
|
|
379
|
-
|
380
|
-
|
381
|
-
it 'renders the sub-attribute correctly' do
|
382
|
-
output[:address].should have_key(:street)
|
383
|
-
output[:address].should have_key(:state)
|
384
|
-
end
|
334
|
+
end
|
335
|
+
end
|
385
336
|
|
386
|
-
it 'reports a dump error with the appropriate context' do
|
387
|
-
person.address.should_receive(:state).and_raise("Kaboom")
|
388
|
-
expect {
|
389
|
-
person.render(view: view_name, context: ['special_root'])
|
390
|
-
}.to raise_error(/Error while dumping attribute state of type Address for context special_root.address. Reason: .*Kaboom/)
|
391
|
-
end
|
392
|
-
end
|
393
337
|
|
338
|
+
context '.example' do
|
339
|
+
context 'with some attribute values provided' do
|
340
|
+
let(:name) { 'Sir Bobbert' }
|
341
|
+
subject(:person) { Person.example(name: name) }
|
342
|
+
its(:name) { should eq(name) }
|
343
|
+
end
|
344
|
+
end
|
394
345
|
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
346
|
+
context '.render' do
|
347
|
+
let(:person) { Person.example }
|
348
|
+
it 'is an alias to dump' do
|
349
|
+
rendered = Person.render(person, view: :default)
|
350
|
+
dumped = Person.dump(person, view: :default)
|
351
|
+
expect(rendered).to eq(dumped)
|
352
|
+
end
|
353
|
+
end
|
403
354
|
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
355
|
+
context '#render' do
|
356
|
+
let(:person) { Person.example }
|
357
|
+
let(:view_name) { :default }
|
358
|
+
let(:render_opts) { {} }
|
359
|
+
subject(:output) { person.render(view: view_name, **render_opts) }
|
360
|
+
|
361
|
+
context 'caches rendered views' do
|
362
|
+
it 'in the instance, by view name' do
|
363
|
+
person.instance_variable_get(:@rendered_views)[view_name].should be_nil
|
364
|
+
person.render(view: view_name)
|
365
|
+
cached = person.instance_variable_get(:@rendered_views)[view_name]
|
366
|
+
cached.should_not be_nil
|
367
|
+
end
|
368
|
+
|
369
|
+
it 'and does not re-render a view if one is already cached' do
|
370
|
+
rendered1 = person.render(view: view_name)
|
371
|
+
rendered2 = person.render(view: view_name)
|
372
|
+
rendered1.should be(rendered2)
|
373
|
+
end
|
374
|
+
|
375
|
+
context 'even when :fields are specified' do
|
376
|
+
let(:render_opts) { {fields: {email: nil, age: nil, address: {street: nil, state: nil}}} }
|
377
|
+
|
378
|
+
it 'caches the output in a different key than just the view_name' do
|
379
|
+
plain_view_render = person.render(view: view_name)
|
380
|
+
fields_render = person.render(view: view_name, **render_opts)
|
381
|
+
plain_view_render.should_not be(fields_render)
|
382
|
+
end
|
383
|
+
|
384
|
+
it 'it still caches the object if rendered for the same fields' do
|
385
|
+
rendered1 = person.render(view: view_name, **render_opts)
|
386
|
+
rendered2 = person.render(view: view_name, **render_opts)
|
387
|
+
rendered1.should be(rendered2)
|
388
|
+
end
|
389
|
+
|
390
|
+
it 'it still caches the object if rendered for the same fields (even from an "equivalent" hash)' do
|
391
|
+
rendered1 = person.render(view: view_name, **render_opts)
|
392
|
+
|
393
|
+
equivalent_render_opts = { fields: {age: nil, address: {state: nil, street: nil}, email: nil} }
|
394
|
+
rendered2 = person.render(view: view_name, **equivalent_render_opts)
|
395
|
+
|
396
|
+
rendered1.should be(rendered2)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
end
|
401
|
+
|
402
|
+
context 'with a sub-attribute that is a blueprint' do
|
403
|
+
|
404
|
+
it { should have_key(:name) }
|
405
|
+
it { should have_key(:address) }
|
406
|
+
it 'renders the sub-attribute correctly' do
|
407
|
+
output[:address].should have_key(:street)
|
408
|
+
output[:address].should have_key(:state)
|
409
|
+
end
|
410
|
+
|
411
|
+
it 'reports a dump error with the appropriate context' do
|
412
|
+
person.address.should_receive(:state).and_raise("Kaboom")
|
413
|
+
expect {
|
414
|
+
person.render(view: view_name, context: ['special_root'])
|
415
|
+
}.to raise_error(/Error while dumping attribute state of type Address for context special_root.address. Reason: .*Kaboom/)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
|
420
|
+
context 'with sub-attribute that is an Attributor::Model' do
|
421
|
+
it { should have_key(:full_name) }
|
422
|
+
it 'renders the model correctly' do
|
423
|
+
output[:full_name].should be_kind_of(Hash)
|
424
|
+
output[:full_name].should have_key(:first)
|
425
|
+
output[:full_name].should have_key(:last)
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
context 'using the `fields` option' do
|
430
|
+
context 'as a hash' do
|
431
|
+
subject(:output) { person.render(view: view_name, fields: {address: { state: nil} } ) }
|
432
|
+
it 'should only have the address rendered' do
|
433
|
+
output.keys.should == [:address]
|
434
|
+
end
|
435
|
+
it 'address should only have state' do
|
436
|
+
output[:address].keys.should == [:state]
|
437
|
+
end
|
438
|
+
end
|
439
|
+
context 'as a simple array' do
|
440
|
+
subject(:output) { person.render(view: view_name, fields: [:address] ) }
|
441
|
+
it 'accepts it as the list of top-level attributes to be rendered' do
|
442
|
+
output.keys.should == [:address]
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
422
447
|
|
423
|
-
end
|
448
|
+
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: 2.
|
4
|
+
version: '2.1'
|
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-
|
12
|
+
date: 2015-08-27 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: 4.0.1
|
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: 4.0.1
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: activesupport
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|