praxis-blueprints 3.0 → 3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +5 -1
- data/lib/praxis-blueprints/blueprint.rb +3 -1
- data/lib/praxis-blueprints/field_expander.rb +36 -18
- data/lib/praxis-blueprints/renderer.rb +17 -0
- data/lib/praxis-blueprints/version.rb +1 -1
- data/praxis-blueprints.gemspec +2 -1
- data/spec/praxis-blueprints/blueprint_spec.rb +19 -0
- data/spec/praxis-blueprints/field_expander_spec.rb +18 -2
- data/spec/praxis-blueprints/renderer_spec.rb +11 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/spec_blueprints.rb +8 -0
- metadata +18 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87521372774c1c5885e1524ba24c931b6c37cdf2
|
4
|
+
data.tar.gz: edcd9ea3c1ea8ca0a874bc8d13f5caa6c050e10d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4133d88711c6b0d46055c61b483525047463e16e2dc17baa12cd5ac99494e9966e46c828c125826f90afaf4933c4c6d0c12a83dad1036c597741ae8b67da0545
|
7
|
+
data.tar.gz: a4478e4777276bd98aab92494cf7f38b249451d88047457ad90afafaf7c5f704ab7c2a45858f140158817705a14c57e66018adc47d9577fa32ea1baaaa8fe560
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,17 @@
|
|
2
2
|
|
3
3
|
## next
|
4
4
|
|
5
|
+
## 3.1
|
6
|
+
|
7
|
+
* Reworked FieldExpander's behavior with circular references.
|
8
|
+
* Removed `Praxis::FieldExpander::CircularExpansionError`
|
9
|
+
* `FieldExpander#expand` now returns self-referential hashes for circular
|
10
|
+
expansions
|
11
|
+
* `Renderer#render` catches stack overflows and returns a
|
12
|
+
`Praxis::Rendering::CircularRenderingError`, which includes a portions
|
13
|
+
from the start and end of the context at the time of the exception.
|
14
|
+
* Report `anonymous` in Blueprints following `Attributor` semantics.
|
15
|
+
|
5
16
|
## 3.0
|
6
17
|
|
7
18
|
* Added `FieldExpander`, a new class that recursively expands a tree of
|
data/README.md
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
-
# Praxis Blueprints [![TravisCI][travis-img-url]][travis-ci-url]
|
1
|
+
# Praxis Blueprints [![TravisCI][travis-img-url]][travis-ci-url] [![Coverage Status][coveralls-img-url]][coveralls-url] [![Dependency Status][gemnasium-img-url]][gemnasium-url]
|
2
2
|
|
3
3
|
[travis-img-url]:https://travis-ci.org/rightscale/praxis-blueprints.svg?branch=master
|
4
4
|
[travis-ci-url]:https://travis-ci.org/rightscale/praxis-blueprints
|
5
|
+
[coveralls-img-url]:https://coveralls.io/repos/rightscale/praxis-blueprints/badge.svg?branch=master&service=github
|
6
|
+
[coveralls-url]:https://coveralls.io/github/rightscale/praxis-blueprints?branch=master
|
7
|
+
[gemnasium-img-url]:https://gemnasium.com/rightscale/praxis-blueprints.svg
|
8
|
+
[gemnasium-url]:https://gemnasium.com/rightscale/praxis-blueprints
|
5
9
|
|
6
10
|
|
7
11
|
Praxis Blueprints is a library that allows for defining a reusable class structures that has a set of typed attributes and a set of views with which to render them. Instantiations of Blueprints resemble ruby Structs which respond to methods of the attribute names. Rendering is format-agnostic in that
|
@@ -73,6 +73,8 @@ module Praxis
|
|
73
73
|
end
|
74
74
|
|
75
75
|
description = self.attribute.type.describe(shallow,example: example, **opts).merge!(id: self.id, name: type_name)
|
76
|
+
description.delete :anonymous # discard the Struct's view of anonymity, and use the Blueprint's one
|
77
|
+
description[:anonymous] = @_anonymous unless @_anonymous.nil?
|
76
78
|
|
77
79
|
unless shallow
|
78
80
|
description[:views] = self.views.each_with_object({}) do |(view_name, view), hash|
|
@@ -242,6 +244,7 @@ module Praxis
|
|
242
244
|
def self.define_attribute!
|
243
245
|
@attribute = Attributor::Attribute.new(Attributor::Struct, @options, &@block)
|
244
246
|
@block = nil
|
247
|
+
@attribute.type.anonymous_type true
|
245
248
|
self.const_set(:Struct, @attribute.type)
|
246
249
|
end
|
247
250
|
|
@@ -315,7 +318,6 @@ module Praxis
|
|
315
318
|
view_name = :default
|
316
319
|
end
|
317
320
|
|
318
|
-
|
319
321
|
if view_name
|
320
322
|
unless (view = self.class.views[view_name])
|
321
323
|
raise "view with name '#{view_name.inspect}' is not defined in #{self.class}"
|
@@ -1,13 +1,5 @@
|
|
1
1
|
module Praxis
|
2
2
|
class FieldExpander
|
3
|
-
class CircularExpansionError < StandardError
|
4
|
-
attr_reader :stack
|
5
|
-
def initialize(message, stack=[])
|
6
|
-
super(message)
|
7
|
-
@stack = stack
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
3
|
def self.expand(object, fields=true)
|
12
4
|
self.new.expand(object,fields)
|
13
5
|
end
|
@@ -26,21 +18,26 @@ module Praxis
|
|
26
18
|
|
27
19
|
def expand(object, fields=true)
|
28
20
|
if stack[object].include? fields
|
29
|
-
|
21
|
+
if history[object].include? fields
|
22
|
+
return history[object][fields]
|
23
|
+
end
|
24
|
+
# We should probably never get here, since we should have a record
|
25
|
+
# of the history of an expansion if we're trying to redo it,
|
26
|
+
# but we should also be conservative and raise here just in case.
|
27
|
+
raise "Circular expansion detected for object #{object.inspect} with fields #{fields.inspect}"
|
30
28
|
else
|
31
29
|
stack[object] << fields
|
32
30
|
end
|
33
31
|
|
34
|
-
if object.kind_of?(Praxis::View)
|
32
|
+
result = if object.kind_of?(Praxis::View)
|
35
33
|
self.expand_view(object, fields)
|
36
34
|
elsif object.kind_of? Attributor::Attribute
|
37
35
|
self.expand_type(object.type, fields)
|
38
36
|
else
|
39
37
|
self.expand_type(object,fields)
|
40
38
|
end
|
41
|
-
|
42
|
-
|
43
|
-
raise
|
39
|
+
|
40
|
+
result
|
44
41
|
ensure
|
45
42
|
stack[object].delete fields
|
46
43
|
end
|
@@ -67,20 +64,39 @@ module Praxis
|
|
67
64
|
|
68
65
|
|
69
66
|
def expand_view(object,fields=true)
|
67
|
+
history[object][fields] = if object.kind_of?(Praxis::CollectionView)
|
68
|
+
[]
|
69
|
+
else
|
70
|
+
{}
|
71
|
+
end
|
72
|
+
|
70
73
|
result = expand_fields(object.contents, fields) do |dumpable, sub_fields|
|
71
74
|
self.expand(dumpable, sub_fields)
|
72
75
|
end
|
73
76
|
|
74
|
-
|
75
|
-
|
77
|
+
if object.kind_of?(Praxis::CollectionView)
|
78
|
+
history[object][fields] << result
|
79
|
+
else
|
80
|
+
history[object][fields].merge!(result)
|
81
|
+
end
|
82
|
+
history[object][fields]
|
76
83
|
end
|
77
84
|
|
78
85
|
|
79
86
|
def expand_type(object,fields=true)
|
80
87
|
unless object.respond_to?(:attributes)
|
81
88
|
if object.respond_to?(:member_attribute)
|
82
|
-
|
83
|
-
|
89
|
+
if history[object].include? fields
|
90
|
+
return history[object][fields]
|
91
|
+
end
|
92
|
+
history[object][fields] = []
|
93
|
+
|
94
|
+
new_fields = fields.kind_of?(Array) ? fields[0] : fields
|
95
|
+
|
96
|
+
result = [self.expand(object.member_attribute.type, new_fields)]
|
97
|
+
history[object][fields].push(*result)
|
98
|
+
|
99
|
+
return result
|
84
100
|
else
|
85
101
|
return true
|
86
102
|
end
|
@@ -95,9 +111,11 @@ module Praxis
|
|
95
111
|
return history[object][fields]
|
96
112
|
end
|
97
113
|
|
98
|
-
history[object][fields] =
|
114
|
+
history[object][fields] = {}
|
115
|
+
result = expand_fields(object.attributes, fields) do |dumpable, sub_fields|
|
99
116
|
self.expand(dumpable.type, sub_fields)
|
100
117
|
end
|
118
|
+
history[object][fields].merge!(result)
|
101
119
|
end
|
102
120
|
|
103
121
|
end
|
@@ -3,6 +3,21 @@ module Praxis
|
|
3
3
|
attr_reader :include_nil
|
4
4
|
attr_reader :cache
|
5
5
|
|
6
|
+
class CircularRenderingError < StandardError
|
7
|
+
attr_reader :object
|
8
|
+
attr_reader :context
|
9
|
+
|
10
|
+
def initialize(object,context)
|
11
|
+
@object = object
|
12
|
+
@context = context
|
13
|
+
|
14
|
+
first = Attributor.humanize_context(context[0..10])
|
15
|
+
last = Attributor.humanize_context(context[-5..-1])
|
16
|
+
pretty_context = "#{first}...#{last}"
|
17
|
+
super("SystemStackError in rendering #{object.class} with context: #{pretty_context}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
6
21
|
def initialize(include_nil: false)
|
7
22
|
@cache = Hash.new do |hash,key|
|
8
23
|
hash[key] = Hash.new
|
@@ -35,6 +50,8 @@ module Praxis
|
|
35
50
|
else
|
36
51
|
_render(object,fields, view, context: context)
|
37
52
|
end
|
53
|
+
rescue SystemStackError
|
54
|
+
raise CircularRenderingError.new(object, context)
|
38
55
|
end
|
39
56
|
|
40
57
|
def _render(object, fields, view=nil, context: Attributor::DEFAULT_ROOT_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>, [">= 4.
|
23
|
+
spec.add_runtime_dependency(%q<attributor>, [">= 4.2"])
|
24
24
|
spec.add_runtime_dependency(%q<activesupport>, [">= 3"])
|
25
25
|
|
26
26
|
spec.add_development_dependency "bundler", "~> 1.6"
|
@@ -35,4 +35,5 @@ it results in a structured hash instead of an encoded string. Blueprints can aut
|
|
35
35
|
spec.add_development_dependency(%q<pry-byebug>, ["~> 1"])
|
36
36
|
spec.add_development_dependency(%q<pry-stack_explorer>, ["~> 0"])
|
37
37
|
spec.add_development_dependency(%q<fuubar>, ["~> 1"])
|
38
|
+
spec.add_development_dependency(%q<coveralls>)
|
38
39
|
end
|
@@ -186,17 +186,36 @@ describe Praxis::Blueprint do
|
|
186
186
|
its([:name]){ should eq(blueprint_class.name)}
|
187
187
|
its([:id]){ should eq(blueprint_class.id)}
|
188
188
|
its([:views]){ should be_kind_of(Hash)}
|
189
|
+
its(:keys){ should_not include(:anonymous) }
|
189
190
|
it 'should contain the an entry for each view' do
|
190
191
|
subject[:views].keys.should include(:default, :current, :extended, :master)
|
191
192
|
end
|
192
193
|
end
|
193
194
|
|
195
|
+
|
194
196
|
context 'for shallow descriptions' do
|
195
197
|
let(:shallow) { true }
|
196
198
|
|
197
199
|
it 'should not include views' do
|
198
200
|
blueprint_class.describe(true).key?(:views).should be(false)
|
199
201
|
end
|
202
|
+
context 'for anonymous blueprints' do
|
203
|
+
let(:blueprint_class) do
|
204
|
+
klass = Class.new(Praxis::Blueprint) do
|
205
|
+
anonymous_type
|
206
|
+
attributes do
|
207
|
+
attribute :name, String
|
208
|
+
end
|
209
|
+
end
|
210
|
+
klass.finalize!
|
211
|
+
klass
|
212
|
+
end
|
213
|
+
it 'reports their anonymous-ness' do
|
214
|
+
description = blueprint_class.describe(true)
|
215
|
+
expect( description ).to have_key(:anonymous)
|
216
|
+
expect( description[:anonymous] ).to be(true)
|
217
|
+
end
|
218
|
+
end
|
200
219
|
end
|
201
220
|
|
202
221
|
context 'with an example' do
|
@@ -125,9 +125,25 @@ describe Praxis::FieldExpander do
|
|
125
125
|
end
|
126
126
|
|
127
127
|
context 'circular expansions' do
|
128
|
-
it '
|
129
|
-
|
128
|
+
it 'preserve field object identity for circular references' do
|
129
|
+
result = field_expander.expand(Address,true)
|
130
|
+
result.should be result[:resident][:address]
|
130
131
|
end
|
132
|
+
|
133
|
+
context 'with collections of Blueprints' do
|
134
|
+
it 'still preserves object identity' do
|
135
|
+
result = field_expander.expand(Person, prior_addresses: true)[:prior_addresses][0]
|
136
|
+
result.should be result[:resident][:prior_addresses][0]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context 'for views' do
|
141
|
+
it 'still preserves object identity' do
|
142
|
+
result = field_expander.expand(Person.views[:circular], true)
|
143
|
+
result[:address][:resident].should be result
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
131
147
|
end
|
132
148
|
|
133
149
|
it 'optimizes duplicate field expansions' do
|
@@ -139,4 +139,15 @@ describe Praxis::Renderer do
|
|
139
139
|
end
|
140
140
|
|
141
141
|
end
|
142
|
+
|
143
|
+
context 'circular rendering' do
|
144
|
+
it do
|
145
|
+
field_expander = Praxis::FieldExpander.new
|
146
|
+
fields = field_expander.expand(Person,true)
|
147
|
+
|
148
|
+
person.object.address.object.resident = person
|
149
|
+
expect { renderer.render(person, fields) }.to raise_error(Praxis::Renderer::CircularRenderingError)
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
142
153
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -29,6 +29,10 @@ class Person < Praxis::Blueprint
|
|
29
29
|
attribute :prior_addresses
|
30
30
|
end
|
31
31
|
|
32
|
+
view :circular do
|
33
|
+
attribute :address, view: :circular
|
34
|
+
end
|
35
|
+
|
32
36
|
view :current do
|
33
37
|
attribute :name
|
34
38
|
attribute :full_name
|
@@ -63,8 +67,12 @@ class Address < Praxis::Blueprint
|
|
63
67
|
view :default do
|
64
68
|
attribute :street
|
65
69
|
attribute :state
|
70
|
+
|
66
71
|
end
|
67
72
|
|
73
|
+
view :circular do
|
74
|
+
attribute :resident, view: :circular
|
75
|
+
end
|
68
76
|
view :state do
|
69
77
|
attribute :state
|
70
78
|
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.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-10-
|
12
|
+
date: 2015-10-29 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.
|
34
|
+
version: '4.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.
|
41
|
+
version: '4.2'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: activesupport
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -207,6 +207,20 @@ dependencies:
|
|
207
207
|
- - "~>"
|
208
208
|
- !ruby/object:Gem::Version
|
209
209
|
version: '1'
|
210
|
+
- !ruby/object:Gem::Dependency
|
211
|
+
name: coveralls
|
212
|
+
requirement: !ruby/object:Gem::Requirement
|
213
|
+
requirements:
|
214
|
+
- - ">="
|
215
|
+
- !ruby/object:Gem::Version
|
216
|
+
version: '0'
|
217
|
+
type: :development
|
218
|
+
prerelease: false
|
219
|
+
version_requirements: !ruby/object:Gem::Requirement
|
220
|
+
requirements:
|
221
|
+
- - ">="
|
222
|
+
- !ruby/object:Gem::Version
|
223
|
+
version: '0'
|
210
224
|
description: |-
|
211
225
|
Praxis Blueprints is a library that allows for defining a reusable class structures that has a set of typed attributes and a set of views with which to render them. Instantiations of Blueprints resemble ruby Structs which respond to methods of the attribute names. Rendering is format-agnostic in that
|
212
226
|
it results in a structured hash instead of an encoded string. Blueprints can automatically generate object structures that follow the attribute definitions.
|