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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9fe3a8efadb5bf8bc0014eae372157bd62020363
4
- data.tar.gz: df99c67d44c66bf7d99ab017a56c220b7ff01586
3
+ metadata.gz: 87521372774c1c5885e1524ba24c931b6c37cdf2
4
+ data.tar.gz: edcd9ea3c1ea8ca0a874bc8d13f5caa6c050e10d
5
5
  SHA512:
6
- metadata.gz: ba760805fbd070350e360638d781833bd9f66abc2dfb2f1a3f30cc5456ab8f6fbfbfdd3e0a1cb5088ba18d4c1299641f8f804e4123fcc453a91c40b4e6d49091
7
- data.tar.gz: 2588dbe95bc05c824c8dfd7bffe1ddb7973839f8cbe0dca72ff5a37744add676535c09f27f733dd40f593e57aba95d9556cee183cad0e093c20c170e152c9cbc
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
- raise CircularExpansionError, "Circular expansion detected for object #{object.inspect} with fields #{fields.inspect}"
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
- rescue CircularExpansionError => e
42
- e.stack.unshift [object,fields]
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
- return [result] if object.kind_of?(Praxis::CollectionView)
75
- result
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
- fields = fields[0] if fields.kind_of? Array
83
- return [self.expand(object.member_attribute.type, fields)]
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] = expand_fields(object.attributes, fields) do |dumpable, sub_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)
@@ -1,3 +1,3 @@
1
1
  module Praxis
2
- BLUEPRINTS_VERSION = "3.0"
2
+ BLUEPRINTS_VERSION = "3.1"
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.1"])
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 'throws a CircularExpansionError' do
129
- expect { field_expander.expand(Address,true) }.to raise_error(Praxis::FieldExpander::CircularExpansionError)
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
@@ -1,3 +1,6 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
1
4
  Encoding.default_external = Encoding::UTF_8
2
5
 
3
6
  require 'rubygems'
@@ -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.0'
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-19 00:00:00.000000000 Z
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.1'
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.1'
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.