praxis-blueprints 1.3.1 → 2.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 +4 -4
- data/.travis.yml +2 -1
- data/CHANGELOG.md +10 -2
- data/lib/praxis-blueprints/blueprint.rb +42 -11
- data/lib/praxis-blueprints/version.rb +1 -1
- data/lib/praxis-blueprints/view.rb +22 -5
- data/praxis-blueprints.gemspec +1 -1
- data/spec/praxis-blueprints/blueprint_spec.rb +71 -3
- data/spec/praxis-blueprints/view_spec.rb +42 -7
- 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: 7c736a4636406c8778b15c49639322310a4d27d7
|
4
|
+
data.tar.gz: 58ffdbb7c7b247dc6db88506c7fd22991f99585b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc6e31dcdca27eda3345a5dc9c0d01b7f02263e10512e3141a7ce7b728581ee2d4323d913ae7618228a2ee809591c0952ce3ab4993d43745a4b4af451e053ab9
|
7
|
+
data.tar.gz: b62ec6a3ac621567d1a52e9dde4b44a6a686bca2fca02eda414a753e4e3e7674b6875a02569aebe72241bf7384e10a8c6891f1e08a5a1eac621862f48afd814f
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
# praxis-blueprints changelog
|
2
2
|
|
3
|
-
##
|
4
|
-
|
3
|
+
## 2.0
|
4
|
+
|
5
|
+
* Fix `Blueprint.new` handling of caching when the wrapped object responds to `identity_map`, but does not have one set.
|
6
|
+
* Undefine JRuby package helper methods in `Model` (org, java...)
|
7
|
+
* Added support for rendering custom views
|
8
|
+
* Internally, a `View` object can now be dumped passing a `:fields` option (which is a hash, that can recursively will define which sub-attributes to render along the way). See [this spec](https://github.com/rightscale/praxis-blueprints/blob/master/spec/praxis-blueprints/blueprint_spec.rb) for an example.
|
9
|
+
* `Blueprints` will also accept the `:fields` option (with the same hash syntax), but it will also accept an array to imply the list of top-level attributes to render (when recursion is not necessary)
|
10
|
+
* Caching of rendered blueprints will continue to work if the view is re-rendered with equivalent `:fields`
|
11
|
+
* Deprecate `Blueprint.render(view_name,...) positional param
|
12
|
+
* Please use :view named parameter instead. I.e., `render(:default, context: ...)` => `render(view: :default, context: ...)`
|
5
13
|
|
6
14
|
## 1.3.1
|
7
15
|
|
@@ -9,6 +9,13 @@ module Praxis
|
|
9
9
|
include Attributor::Type
|
10
10
|
extend Finalizable
|
11
11
|
|
12
|
+
if RUBY_ENGINE =~ /^jruby/
|
13
|
+
# We are "forced" to require it here (in case hasn't been yet) to make sure the added methods have been applied
|
14
|
+
require 'java'
|
15
|
+
# Only to then delete them, to make sure we don't have them clashing with any attributes
|
16
|
+
undef java, javax, org, com
|
17
|
+
end
|
18
|
+
|
12
19
|
@@caching_enabled = false
|
13
20
|
|
14
21
|
CIRCULAR_REFERENCE_MARKER = '...'.freeze
|
@@ -35,7 +42,7 @@ module Praxis
|
|
35
42
|
if @@caching_enabled && decorators.nil?
|
36
43
|
key = object
|
37
44
|
|
38
|
-
cache = if object.respond_to?(:identity_map)
|
45
|
+
cache = if object.respond_to?(:identity_map) && object.identity_map
|
39
46
|
object.identity_map.blueprint_cache[self]
|
40
47
|
else
|
41
48
|
self.cache
|
@@ -57,10 +64,10 @@ module Praxis
|
|
57
64
|
'hash'
|
58
65
|
end
|
59
66
|
|
60
|
-
def self.describe(shallow=false)
|
67
|
+
def self.describe(shallow=false,**opts)
|
61
68
|
type_name = self.ancestors.find { |k| k.name && !k.name.empty? }.name
|
62
69
|
|
63
|
-
description = self.attribute.type.describe(shallow).merge!(id: self.id, name: type_name)
|
70
|
+
description = self.attribute.type.describe(shallow,**opts).merge!(id: self.id, name: type_name)
|
64
71
|
|
65
72
|
unless shallow
|
66
73
|
description[:views] = self.views.each_with_object({}) do |(view_name, view), hash|
|
@@ -190,7 +197,12 @@ module Praxis
|
|
190
197
|
object = self.load(object, context, **opts)
|
191
198
|
return nil if object.nil?
|
192
199
|
|
193
|
-
object.render(view, context: context)
|
200
|
+
object.render(view: view, context: context, **opts)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Allow render on the class too, for completeness and consistency
|
204
|
+
def self.render(object, **opts)
|
205
|
+
self.dump(object, **opts)
|
194
206
|
end
|
195
207
|
|
196
208
|
# Internal finalize! logic
|
@@ -271,24 +283,43 @@ module Praxis
|
|
271
283
|
|
272
284
|
|
273
285
|
# Render the wrapped data with the given view
|
274
|
-
def render(view_name
|
286
|
+
def render(view_name=nil, context: Attributor::DEFAULT_ROOT_CONTEXT,**opts)
|
287
|
+
if view_name != nil
|
288
|
+
warn "DEPRECATED: please do not pass the view name as the first parameter in Blueprint.render, pass through the view: named param instead."
|
289
|
+
else
|
290
|
+
view_name = :default # Backwards compatibility with the default param value
|
291
|
+
end
|
292
|
+
|
293
|
+
# Allow the opts to specify the view name for consistency with dump (overriding the deprecated named param)
|
294
|
+
view_name = opts[:view] if opts[:view]
|
275
295
|
unless (view = self.class.views[view_name])
|
276
296
|
raise "view with name '#{view_name.inspect}' is not defined in #{self.class}"
|
277
297
|
end
|
278
298
|
|
279
|
-
|
280
|
-
|
281
|
-
|
299
|
+
rendered_key = if fields = opts[:fields]
|
300
|
+
if fields.is_a? Array
|
301
|
+
# Accept a simple array of fields, and transform it to a 1-level hash with nil values
|
302
|
+
opts[:fields] = opts[:fields].each_with_object({}) {|field, hash| hash[field] = nil }
|
303
|
+
end
|
304
|
+
# Rendered key needs to be different if only some fields were output
|
305
|
+
"%s:#%s" % [view_name, opts[:fields].hash.to_s]
|
306
|
+
else
|
307
|
+
view_name
|
308
|
+
end
|
309
|
+
|
310
|
+
return @rendered_views[rendered_key] if @rendered_views.has_key? rendered_key
|
311
|
+
return CIRCULAR_REFERENCE_MARKER if @active_renders.include?(rendered_key)
|
312
|
+
@active_renders << rendered_key
|
282
313
|
|
283
|
-
@rendered_views[
|
314
|
+
@rendered_views[rendered_key] = view.dump(self, context: context,**opts)
|
284
315
|
ensure
|
285
|
-
@active_renders.delete
|
316
|
+
@active_renders.delete rendered_key
|
286
317
|
end
|
287
318
|
alias_method :to_hash, :render
|
288
319
|
|
289
320
|
|
290
321
|
def dump(view: :default, context: Attributor::DEFAULT_ROOT_CONTEXT)
|
291
|
-
self.render(view, context: context)
|
322
|
+
self.render(view: view, context: context)
|
292
323
|
end
|
293
324
|
|
294
325
|
|
@@ -31,8 +31,14 @@ module Praxis
|
|
31
31
|
|
32
32
|
|
33
33
|
def dump(object, context: Attributor::DEFAULT_ROOT_CONTEXT,**opts)
|
34
|
-
|
35
|
-
|
34
|
+
fields = opts[:fields]
|
35
|
+
# Restrict which attributes to output if we receive a fields parameter
|
36
|
+
# Note: should we complain if any of the names in "fields" do not match an existing attribute name?
|
37
|
+
attributes_to_render = self.contents.keys
|
38
|
+
attributes_to_render &= fields.keys if fields
|
39
|
+
|
40
|
+
attributes_to_render.each_with_object({}) do |name, hash|
|
41
|
+
dumpable, dumpable_opts = self.contents[name]
|
36
42
|
unless object.respond_to?(name)
|
37
43
|
warn "#{object} does not respond to #{name} during rendering???"
|
38
44
|
next
|
@@ -51,12 +57,16 @@ module Praxis
|
|
51
57
|
# FIXME: this is such an ugly way to do this. Need attributor#67.
|
52
58
|
if dumpable.kind_of?(View) || dumpable.kind_of?(CollectionView)
|
53
59
|
new_context = context + [name]
|
54
|
-
|
60
|
+
|
61
|
+
sub_opts = add_subfield_options( fields, name, dumpable_opts )
|
62
|
+
hash[name] = dumpable.dump(value, context: new_context ,**sub_opts)
|
55
63
|
else
|
64
|
+
|
56
65
|
type = dumpable.type
|
57
66
|
if type.respond_to?(:attributes) || type.respond_to?(:member_attribute)
|
58
67
|
new_context = context + [name]
|
59
|
-
|
68
|
+
sub_opts = add_subfield_options( fields, name, dumpable_opts )
|
69
|
+
hash[name] = dumpable.dump(value, context: new_context ,**sub_opts)
|
60
70
|
else
|
61
71
|
hash[name] = value
|
62
72
|
end
|
@@ -65,7 +75,6 @@ module Praxis
|
|
65
75
|
end
|
66
76
|
alias_method :to_hash, :dump
|
67
77
|
|
68
|
-
|
69
78
|
def attribute(name, opts={}, &block)
|
70
79
|
raise AttributorException, "Attribute names must be symbols, got: #{name.inspect}" unless name.kind_of? ::Symbol
|
71
80
|
|
@@ -104,5 +113,13 @@ module Praxis
|
|
104
113
|
{ attributes: view_attributes, type: :standard }
|
105
114
|
end
|
106
115
|
|
116
|
+
private
|
117
|
+
def add_subfield_options( fields, name, existing_options)
|
118
|
+
sub_opts = if fields && fields[name]
|
119
|
+
{fields: fields[name] }
|
120
|
+
else
|
121
|
+
{}
|
122
|
+
end.merge(existing_options || {})
|
123
|
+
end
|
107
124
|
end
|
108
125
|
end
|
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>, ["~> 3.0"])
|
24
24
|
spec.add_runtime_dependency(%q<activesupport>, [">= 3"])
|
25
25
|
|
26
26
|
spec.add_development_dependency "bundler", "~> 1.6"
|
@@ -95,7 +95,7 @@ describe Praxis::Blueprint do
|
|
95
95
|
|
96
96
|
context '#render' do
|
97
97
|
let(:view) { :default }
|
98
|
-
subject(:output) { blueprint_instance.render(view) }
|
98
|
+
subject(:output) { blueprint_instance.render(view: view) }
|
99
99
|
|
100
100
|
it { should have_key(:name) }
|
101
101
|
it 'has the right values' do
|
@@ -318,10 +318,61 @@ describe Praxis::Blueprint do
|
|
318
318
|
end
|
319
319
|
end
|
320
320
|
|
321
|
+
context '.render' do
|
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
|
329
|
+
|
321
330
|
context '#render' do
|
322
331
|
let(:person) { Person.example }
|
323
332
|
let(:view_name) { :default }
|
324
|
-
|
333
|
+
let(:render_opts) { {} }
|
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
|
343
|
+
|
344
|
+
it 'and does not re-render a view if one is already cached' do
|
345
|
+
rendered1 = person.render(view: view_name)
|
346
|
+
rendered2 = person.render(view: view_name)
|
347
|
+
rendered1.should be(rendered2)
|
348
|
+
end
|
349
|
+
|
350
|
+
context 'even when :fields are specified' do
|
351
|
+
let(:render_opts) { {fields: {email: nil, age: nil, address: {street: nil, state: nil}}} }
|
352
|
+
|
353
|
+
it 'caches the output in a different key than just the view_name' do
|
354
|
+
plain_view_render = person.render(view: view_name)
|
355
|
+
fields_render = person.render(view: view_name, **render_opts)
|
356
|
+
plain_view_render.should_not be(fields_render)
|
357
|
+
end
|
358
|
+
|
359
|
+
it 'it still caches the object if rendered for the same fields' do
|
360
|
+
rendered1 = person.render(view: view_name, **render_opts)
|
361
|
+
rendered2 = person.render(view: view_name, **render_opts)
|
362
|
+
rendered1.should be(rendered2)
|
363
|
+
end
|
364
|
+
|
365
|
+
it 'it still caches the object if rendered for the same fields (even from an "equivalent" hash)' do
|
366
|
+
rendered1 = person.render(view: view_name, **render_opts)
|
367
|
+
|
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
|
+
|
371
|
+
rendered1.should be(rendered2)
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
end
|
325
376
|
|
326
377
|
context 'with a sub-attribute that is a blueprint' do
|
327
378
|
|
@@ -335,7 +386,7 @@ describe Praxis::Blueprint do
|
|
335
386
|
it 'reports a dump error with the appropriate context' do
|
336
387
|
person.address.should_receive(:state).and_raise("Kaboom")
|
337
388
|
expect {
|
338
|
-
person.render(view_name, context: ['special_root'])
|
389
|
+
person.render(view: view_name, context: ['special_root'])
|
339
390
|
}.to raise_error(/Error while dumping attribute state of type Address for context special_root.address. Reason: .*Kaboom/)
|
340
391
|
end
|
341
392
|
end
|
@@ -350,6 +401,23 @@ describe Praxis::Blueprint do
|
|
350
401
|
end
|
351
402
|
end
|
352
403
|
|
404
|
+
context 'using the `fields` option' do
|
405
|
+
context 'as a hash' do
|
406
|
+
subject(:output) { person.render(view: view_name, fields: {address: { state: nil} } ) }
|
407
|
+
it 'should only have the address rendered' do
|
408
|
+
output.keys.should == [:address]
|
409
|
+
end
|
410
|
+
it 'address should only have state' do
|
411
|
+
output[:address].keys.should == [:state]
|
412
|
+
end
|
413
|
+
end
|
414
|
+
context 'as a simple array' do
|
415
|
+
subject(:output) { person.render(view: view_name, fields: [:address] ) }
|
416
|
+
it 'accepts it as the list of top-level attributes to be rendered' do
|
417
|
+
output.keys.should == [:address]
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
353
421
|
end
|
354
422
|
|
355
423
|
end
|
@@ -12,8 +12,8 @@ describe Praxis::View do
|
|
12
12
|
attribute :address, view: :state
|
13
13
|
end
|
14
14
|
end
|
15
|
-
|
16
|
-
subject(:output) { view.to_hash(person) }
|
15
|
+
let(:dumping_options){ {} }
|
16
|
+
subject(:output) { view.to_hash(person, dumping_options ) }
|
17
17
|
|
18
18
|
|
19
19
|
it 'can generate examples' do
|
@@ -53,8 +53,23 @@ describe Praxis::View do
|
|
53
53
|
output.key?(:age).should_not be(true)
|
54
54
|
output.key?(:address).should_not be(true)
|
55
55
|
end
|
56
|
+
|
57
|
+
context 'and custom field rendering' do
|
58
|
+
let(:data) { {name: 'Bob', email: 'bob@acme.org', age: 50 } }
|
59
|
+
|
60
|
+
context 'renders only the specified fields that have values' do
|
61
|
+
let(:dumping_options){ { fields: {name: nil, email: nil} } }
|
62
|
+
its(:keys){ should == [:name, :email] }
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'renders only the specified fields excluding nil valued ones' do
|
66
|
+
let(:dumping_options){ { fields: {name: nil, address: nil} } }
|
67
|
+
its(:keys){ should == [:name] }
|
68
|
+
end
|
69
|
+
end
|
56
70
|
end
|
57
71
|
|
72
|
+
|
58
73
|
context 'with include_nil: true' do
|
59
74
|
let(:view) do
|
60
75
|
Praxis::View.new(:info, Person, include_nil: true) do
|
@@ -65,8 +80,6 @@ describe Praxis::View do
|
|
65
80
|
end
|
66
81
|
end
|
67
82
|
|
68
|
-
subject(:output) { view.to_hash(person) }
|
69
|
-
|
70
83
|
it 'includes attributes with nil values' do
|
71
84
|
output.key?(:email).should be(true)
|
72
85
|
output[:email].should be(nil)
|
@@ -78,9 +91,16 @@ describe Praxis::View do
|
|
78
91
|
output[:age].should be(nil)
|
79
92
|
end
|
80
93
|
|
94
|
+
context 'and custom field rendering' do
|
95
|
+
let(:data) { {name: 'Bob', email: 'bob@acme.org', age: 50 } }
|
96
|
+
|
97
|
+
context 'renders only the specified fields including nil valued' do
|
98
|
+
let(:dumping_options){ { fields: {name: nil, address: nil}} }
|
99
|
+
its(:keys){ should == [:name,:address] }
|
100
|
+
end
|
101
|
+
end
|
81
102
|
end
|
82
103
|
|
83
|
-
|
84
104
|
end
|
85
105
|
|
86
106
|
|
@@ -200,6 +220,21 @@ describe Praxis::View do
|
|
200
220
|
|
201
221
|
it { should eq expected_output }
|
202
222
|
|
223
|
+
context 'using the fields option' do
|
224
|
+
let(:dumping_options){ { fields: {name: nil, address: {state: nil} } } }
|
225
|
+
|
226
|
+
let(:expected_output) do
|
227
|
+
{
|
228
|
+
:name => person.name,
|
229
|
+
:address => {
|
230
|
+
:state => address.state
|
231
|
+
}
|
232
|
+
}
|
233
|
+
end
|
234
|
+
|
235
|
+
it { should eq expected_output }
|
236
|
+
end
|
237
|
+
|
203
238
|
end
|
204
239
|
|
205
240
|
|
@@ -283,7 +318,7 @@ describe Praxis::View do
|
|
283
318
|
let(:expected_output) do
|
284
319
|
{
|
285
320
|
:name => person.name,
|
286
|
-
:prior_addresses => person.prior_addresses.collect { |a| a.to_hash(:default)}
|
321
|
+
:prior_addresses => person.prior_addresses.collect { |a| a.to_hash(view: :default)}
|
287
322
|
}
|
288
323
|
end
|
289
324
|
|
@@ -302,7 +337,7 @@ describe Praxis::View do
|
|
302
337
|
let(:expected_output) do
|
303
338
|
{
|
304
339
|
:name => person.name,
|
305
|
-
:prior_addresses => person.prior_addresses.collect { |a| a.to_hash(:state)}
|
340
|
+
:prior_addresses => person.prior_addresses.collect { |a| a.to_hash(view: :state)}
|
306
341
|
}
|
307
342
|
end
|
308
343
|
|
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:
|
4
|
+
version: '2.0'
|
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-06-22 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: '3.0'
|
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: '3.0'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: activesupport
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|