praxis-blueprints 3.0 → 3.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,38 +1,51 @@
1
+ # frozen_string_literal: true
1
2
  lib = File.expand_path('../lib', __FILE__)
2
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
4
  require 'praxis-blueprints/version'
4
5
 
5
6
  Gem::Specification.new do |spec|
6
- spec.name = "praxis-blueprints"
7
+ spec.name = 'praxis-blueprints'
7
8
  spec.version = Praxis::BLUEPRINTS_VERSION
8
- spec.authors = ["Josep M. Blanquer","Dane Jensen"]
9
- spec.summary = %q{Attributes, views, rendering and example generation for common Blueprint Structures.}
10
- spec.description = "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
11
- it results in a structured hash instead of an encoded string. Blueprints can automatically generate object structures that follow the attribute definitions."
12
- spec.email = ["blanquer@gmail.com","dane.jensen@gmail.com"]
9
+ spec.authors = ['Josep M. Blanquer', 'Dane Jensen']
10
+ spec.summary = 'Attributes, views, rendering and example generation for common Blueprint Structures.'
11
+ spec.description = <<-EOF
12
+ Praxis Blueprints is a library that allows for defining a reusable class
13
+ structures that has a set of typed attributes and a set of views with which
14
+ to render them. Instantiations of Blueprints resemble ruby Structs which
15
+ respond to methods of the attribute names. Rendering is format-agnostic in
16
+ that it results in a structured hash instead of an encoded string.
17
+ Blueprints can automatically generate object structures that follow the
18
+ attribute definitions.
19
+ EOF
20
+ spec.email = ['blanquer@gmail.com', 'dane.jensen@gmail.com']
13
21
 
14
- spec.homepage = "https://github.com/rightscale/praxis-blueprints"
15
- spec.license = "MIT"
16
- spec.required_ruby_version = ">=2.1"
22
+ spec.homepage = 'https://github.com/rightscale/praxis-blueprints'
23
+ spec.license = 'MIT'
24
+ spec.required_ruby_version = '>=2.1'
17
25
 
18
26
  spec.files = `git ls-files -z`.split("\x0")
19
27
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
- spec.require_paths = ["lib"]
28
+ spec.require_paths = ['lib']
21
29
 
22
- spec.add_runtime_dependency(%q<randexp>, ["~> 0"])
23
- spec.add_runtime_dependency(%q<attributor>, [">= 4.1"])
24
- spec.add_runtime_dependency(%q<activesupport>, [">= 3"])
30
+ spec.add_runtime_dependency('randexp', ['~> 0'])
31
+ spec.add_runtime_dependency('attributor', ['>= 5.5'])
32
+ spec.add_runtime_dependency('activesupport', ['>= 3'])
25
33
 
26
- spec.add_development_dependency "bundler", "~> 1.6"
27
- spec.add_development_dependency "rake", "~> 0"
34
+ spec.add_development_dependency 'bundler'
35
+ spec.add_development_dependency 'rake'
28
36
 
29
- spec.add_development_dependency(%q<redcarpet>, ["< 3.0"])
30
- spec.add_development_dependency(%q<yard>, ["~> 0.8.7"])
31
- spec.add_development_dependency(%q<guard>, ["~> 2"])
32
- spec.add_development_dependency(%q<guard-rspec>, [">= 0"])
33
- spec.add_development_dependency(%q<rspec>, ["< 2.99"])
34
- spec.add_development_dependency(%q<pry>, ["~> 0"])
35
- spec.add_development_dependency(%q<pry-byebug>, ["~> 1"])
36
- spec.add_development_dependency(%q<pry-stack_explorer>, ["~> 0"])
37
- spec.add_development_dependency(%q<fuubar>, ["~> 1"])
37
+ spec.add_development_dependency('redcarpet', ['< 3.0'])
38
+ spec.add_development_dependency('yard')
39
+ spec.add_development_dependency('guard', ['~> 2'])
40
+ spec.add_development_dependency('guard-rspec', ['>= 0'])
41
+ spec.add_development_dependency('rspec')
42
+ spec.add_development_dependency('rspec-its')
43
+ spec.add_development_dependency('rspec-collection_matchers')
44
+ spec.add_development_dependency('pry')
45
+ spec.add_development_dependency('pry-byebug')
46
+ spec.add_development_dependency('pry-stack_explorer')
47
+ spec.add_development_dependency('fuubar')
48
+ spec.add_development_dependency('coveralls')
49
+ spec.add_development_dependency 'rubocop'
50
+ spec.add_development_dependency 'guard-rubocop'
38
51
  end
@@ -1,10 +1,10 @@
1
+ # frozen_string_literal: true
1
2
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
3
 
3
4
  describe Praxis::Blueprint do
4
-
5
5
  subject(:blueprint_class) { Person }
6
6
 
7
- its(:family){ should eq('hash') }
7
+ its(:family) { should eq('hash') }
8
8
 
9
9
  context 'deterministic examples' do
10
10
  it 'works' do
@@ -52,7 +52,6 @@ describe Praxis::Blueprint do
52
52
  its(:finalized?) { should be(true) }
53
53
  end
54
54
 
55
-
56
55
  context '.finalize on that subclass' do
57
56
  before do
58
57
  blueprint_class.should_receive(:_finalize!).and_call_original
@@ -60,9 +59,7 @@ describe Praxis::Blueprint do
60
59
  end
61
60
 
62
61
  its(:finalized?) { should be(true) }
63
-
64
62
  end
65
-
66
63
  end
67
64
 
68
65
  context 'creating a base abstract Blueprint class without attributes' do
@@ -76,7 +73,6 @@ describe Praxis::Blueprint do
76
73
  blueprint_class.finalize!
77
74
  blueprint_class.finalized?.should be(true)
78
75
  end
79
-
80
76
  end
81
77
 
82
78
  it 'has an inner Struct class for the attributes' do
@@ -88,7 +84,6 @@ describe Praxis::Blueprint do
88
84
  it 'sorta has view objects' do
89
85
  blueprint_class.views.should have_key(:default)
90
86
  end
91
-
92
87
  end
93
88
 
94
89
  context 'an instance' do
@@ -107,30 +102,34 @@ describe Praxis::Blueprint do
107
102
 
108
103
  context 'validation' do
109
104
  subject(:errors) { blueprint_class.validate(blueprint_instance) }
110
- pending do
111
- it { should be_empty }
112
- end
105
+ it { should be_empty }
113
106
  end
114
107
  end
115
108
 
116
-
117
109
  context 'from Blueprint.example' do
118
- subject(:blueprint_instance) { blueprint_class.example }
110
+ subject(:blueprint_instance) do
111
+ blueprint_class.example('ExamplePerson',
112
+ address: nil,
113
+ prior_addresses: [],
114
+ work_address: nil,
115
+ myself: nil,
116
+ friends: []
117
+ )
118
+ end
119
119
  it_behaves_like 'a blueprint instance'
120
120
  end
121
121
 
122
122
  context 'wrapping an object' do
123
-
124
123
  let(:data) do
125
124
  {
126
125
  name: 'Bob',
127
126
  full_name: FullName.example,
128
- address: Address.example,
129
- email: "bob@example.com",
127
+ address: nil,
128
+ email: 'bob@example.com',
130
129
  aliases: [],
131
130
  prior_addresses: [],
132
- parents: { father: Randgen.first_name, mother: Randgen.first_name},
133
- href: "www.example.com",
131
+ parents: { father: Randgen.first_name, mother: Randgen.first_name },
132
+ href: 'www.example.com',
134
133
  alive: true
135
134
  }
136
135
  end
@@ -139,7 +138,6 @@ describe Praxis::Blueprint do
139
138
 
140
139
  subject(:blueprint_instance) { blueprint_class.new(resource) }
141
140
 
142
-
143
141
  it_behaves_like 'a blueprint instance'
144
142
 
145
143
  context 'creating additional blueprint instances from that object' do
@@ -148,7 +146,7 @@ describe Praxis::Blueprint do
148
146
  context 'with caching enabled' do
149
147
  around do |example|
150
148
  Praxis::Blueprint.caching_enabled = true
151
- Praxis::Blueprint.cache = Hash.new { |h,k| h[k] = Hash.new }
149
+ Praxis::Blueprint.cache = Hash.new { |h, k| h[k] = {} }
152
150
  example.run
153
151
 
154
152
  Praxis::Blueprint.caching_enabled = false
@@ -165,27 +163,31 @@ describe Praxis::Blueprint do
165
163
  context 'with caching disabled' do
166
164
  it { should_not be blueprint_instance }
167
165
  end
168
-
169
166
  end
170
-
171
167
  end
172
-
173
168
  end
174
169
 
175
170
  context '.describe' do
176
- let(:shallow ) { false }
171
+ let(:shallow) { false }
177
172
  let(:example_object) { nil }
178
173
 
179
174
  before do
180
- expect(blueprint_class.attribute.type).to receive(:describe).with(shallow, example: example_object).and_call_original #.and_return(type_describe)
175
+ expect(blueprint_class.attribute.type).to receive(:describe).with(shallow, example: example_object).ordered.and_call_original
181
176
  end
182
177
 
183
178
  context 'for non-shallow descriptions' do
184
- subject(:output){ blueprint_class.describe }
179
+ before do
180
+ # Describing a Person also describes the :myself and :friends attributes. They are both a Person and a Coll of Person.
181
+ # This means that Person type `describe` is called two more times, thes times with shallow=true
182
+ expect(blueprint_class.attribute.type).to receive(:describe).with(true, example: example_object).twice.and_call_original
183
+ end
184
+
185
+ subject(:output) { blueprint_class.describe }
185
186
 
186
- its([:name]){ should eq(blueprint_class.name)}
187
- its([:id]){ should eq(blueprint_class.id)}
188
- its([:views]){ should be_kind_of(Hash)}
187
+ its([:name]) { should eq(blueprint_class.name) }
188
+ its([:id]) { should eq(blueprint_class.id) }
189
+ its([:views]) { should be_kind_of(Hash) }
190
+ its(:keys) { should_not include(:anonymous) }
189
191
  it 'should contain the an entry for each view' do
190
192
  subject[:views].keys.should include(:default, :current, :extended, :master)
191
193
  end
@@ -197,6 +199,23 @@ describe Praxis::Blueprint do
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
@@ -205,13 +224,21 @@ describe Praxis::Blueprint do
205
224
  let(:shallow) { false }
206
225
 
207
226
  subject(:output) { blueprint_class.describe(false, example: example) }
227
+ before do
228
+ # Describing a Person also describes the :myself and :friends attributes. They are both a Person and a Coll of Person.
229
+ # This means that Person type `describe` is called two more times, thes times with shallow=true
230
+ expect(blueprint_class.attribute.type).to receive(:describe)
231
+ .with(true, example: an_instance_of(blueprint_class.attribute.type)).twice.and_call_original
232
+ end
208
233
 
209
234
  it 'outputs examples for leaf values using the provided example' do
210
235
  output[:attributes][:name][:example].should eq example.name
211
236
  output[:attributes][:age][:example].should eq example.age
212
237
 
238
+ output[:attributes][:aliases].should have_key(:example)
239
+ output[:attributes][:aliases][:example].should eq example.aliases.dump
240
+
213
241
  output[:attributes][:full_name].should_not have_key(:example)
214
- output[:attributes][:aliases].should_not have_key(:example)
215
242
 
216
243
  parents_attributes = output[:attributes][:parents][:type][:attributes]
217
244
  parents_attributes[:father][:example].should eq example.parents.father
@@ -221,7 +248,7 @@ describe Praxis::Blueprint do
221
248
  end
222
249
 
223
250
  context '.validate' do
224
- let(:hash) { {name: 'bob'} }
251
+ let(:hash) { { name: 'bob' } }
225
252
  let(:person) { Person.load(hash) }
226
253
  subject(:errors) { person.validate }
227
254
 
@@ -230,7 +257,7 @@ describe Praxis::Blueprint do
230
257
  end
231
258
 
232
259
  context 'with invalid sub-attribute' do
233
- let(:hash) { {name: 'bob', address: {state: "ME"}} }
260
+ let(:hash) { { name: 'bob', address: { state: 'ME' } } }
234
261
 
235
262
  it { should have(1).item }
236
263
  its(:first) { should =~ /Attribute \$.address.state/ }
@@ -238,9 +265,9 @@ describe Praxis::Blueprint do
238
265
 
239
266
  context 'for objects of the wrong type' do
240
267
  it 'raises an error' do
241
- expect {
268
+ expect do
242
269
  Person.validate(Object.new)
243
- }.to raise_error(ArgumentError, /Error validating .* as Person for an object of type Object/)
270
+ end.to raise_error(ArgumentError, /Error validating .* as Person for an object of type Object/)
244
271
  end
245
272
  end
246
273
  end
@@ -248,9 +275,9 @@ describe Praxis::Blueprint do
248
275
  context '.load' do
249
276
  let(:hash) do
250
277
  {
251
- :name => 'Bob',
252
- :full_name => {:first => 'Robert', :last => 'Robertson'},
253
- :address => {:street => 'main', :state => 'OR'}
278
+ name: 'Bob',
279
+ full_name: { first: 'Robert', last: 'Robertson' },
280
+ address: { street: 'main', state: 'OR' }
254
281
  }
255
282
  end
256
283
  subject(:person) { Person.load(hash) }
@@ -267,19 +294,16 @@ describe Praxis::Blueprint do
267
294
  it { should be_kind_of(FullName) }
268
295
  end
269
296
  end
270
-
271
297
  end
272
298
 
273
-
274
299
  context 'decorators' do
275
300
  let(:name) { 'Soren II' }
276
301
 
277
302
  let(:object) { Person.example.object }
278
303
  subject(:person) { Person.new(object, decorators) }
279
304
 
280
-
281
305
  context 'as a hash' do
282
- let(:decorators) { {name: name} }
306
+ let(:decorators) { { name: name } }
283
307
  it do
284
308
  person.name.should eq('Soren II')
285
309
  end
@@ -287,7 +311,7 @@ describe Praxis::Blueprint do
287
311
  its(:name) { should be(name) }
288
312
 
289
313
  context 'an additional instance with the equivalent hash' do
290
- subject(:additional_person) { Person.new(object, {name: name}) }
314
+ subject(:additional_person) { Person.new(object, name: name) }
291
315
  it { should_not be person }
292
316
  end
293
317
 
@@ -303,7 +327,7 @@ describe Praxis::Blueprint do
303
327
  end
304
328
 
305
329
  context 'as an object' do
306
- let(:decorators) { double("decorators", name: name) }
330
+ let(:decorators) { double('decorators', name: name) }
307
331
  its(:name) { should be(name) }
308
332
 
309
333
  context 'an additional instance with the same object' do
@@ -311,13 +335,10 @@ describe Praxis::Blueprint do
311
335
  it { should_not be person }
312
336
  end
313
337
  end
314
-
315
338
  end
316
339
 
317
-
318
340
  context 'with a provided :reference option on attributes' do
319
341
  context 'that does not match the value set on the class' do
320
-
321
342
  subject(:mismatched_reference) do
322
343
  Class.new(Praxis::Blueprint) do
323
344
  self.reference = Class.new(Praxis::Blueprint)
@@ -326,15 +347,13 @@ describe Praxis::Blueprint do
326
347
  end
327
348
 
328
349
  it 'should raise an error' do
329
- expect {
350
+ expect do
330
351
  mismatched_reference.attributes
331
- }.to raise_error
352
+ end.to raise_error
332
353
  end
333
-
334
354
  end
335
355
  end
336
356
 
337
-
338
357
  context '.example' do
339
358
  context 'with some attribute values provided' do
340
359
  let(:name) { 'Sir Bobbert' }
@@ -346,7 +365,6 @@ describe Praxis::Blueprint do
346
365
  context '.render' do
347
366
  let(:person) { Person.example('1') }
348
367
  it 'is an alias to dump' do
349
-
350
368
  person.object.contents
351
369
  rendered = Person.render(person, view: :default)
352
370
  dumped = Person.dump(person, view: :default)
@@ -360,10 +378,7 @@ describe Praxis::Blueprint do
360
378
  let(:render_opts) { {} }
361
379
  subject(:output) { person.render(view: view_name, **render_opts) }
362
380
 
363
-
364
-
365
381
  context 'with a sub-attribute that is a blueprint' do
366
-
367
382
  it { should have_key(:name) }
368
383
  it { should have_key(:address) }
369
384
  it 'renders the sub-attribute correctly' do
@@ -372,14 +387,13 @@ describe Praxis::Blueprint do
372
387
  end
373
388
 
374
389
  it 'reports a dump error with the appropriate context' do
375
- person.address.should_receive(:state).and_raise("Kaboom")
376
- expect {
390
+ person.address.should_receive(:state).and_raise('Kaboom')
391
+ expect do
377
392
  person.render(view: view_name, context: ['special_root'])
378
- }.to raise_error(/Error while dumping attribute state of type Address for context special_root.address. Reason: .*Kaboom/)
393
+ end.to raise_error(/Error while dumping attribute state of type Address for context special_root.address. Reason: .*Kaboom/)
379
394
  end
380
395
  end
381
396
 
382
-
383
397
  context 'with sub-attribute that is an Attributor::Model' do
384
398
  it { should have_key(:full_name) }
385
399
  it 'renders the model correctly' do
@@ -391,7 +405,7 @@ describe Praxis::Blueprint do
391
405
 
392
406
  context 'using the `fields` option' do
393
407
  context 'as a hash' do
394
- subject(:output) { person.render(fields: {address: {state: true}}) }
408
+ subject(:output) { person.render(fields: { address: { state: true } }) }
395
409
  it 'should only have the address rendered' do
396
410
  output.keys.should eq [:address]
397
411
  end
@@ -408,4 +422,16 @@ describe Praxis::Blueprint do
408
422
  end
409
423
  end
410
424
 
425
+ context '.as_json_schema' do
426
+ it 'delegates to the attribute type' do
427
+ Person.attribute.type.should receive(:as_json_schema)
428
+ Person.as_json_schema
429
+ end
430
+ end
431
+ context '.json_schema_type' do
432
+ it 'delegates to the attribute type' do
433
+ Person.attribute.type.should receive(:json_schema_type)
434
+ Person.json_schema_type
435
+ end
436
+ end
411
437
  end
@@ -1,17 +1,16 @@
1
+ # frozen_string_literal: true
1
2
  require_relative '../spec_helper'
2
3
 
3
4
  describe Praxis::CollectionView do
4
-
5
5
  let(:root_context) { ['people'] }
6
6
 
7
7
  let(:people) do
8
- 3.times.collect do |i|
9
- context = ["people", "at(#{i})"]
8
+ Array.new(3) do |i|
9
+ context = ['people', "at(#{i})"]
10
10
  Person.example(context)
11
11
  end
12
12
  end
13
13
 
14
-
15
14
  let(:contents_definition) do
16
15
  proc do
17
16
  attribute :name
@@ -28,10 +27,21 @@ describe Praxis::CollectionView do
28
27
  end
29
28
 
30
29
  context 'creating from a member view' do
31
-
32
30
  it 'gets the proper contents' do
33
31
  collection_view.contents.should eq member_view.contents
34
32
  end
33
+
34
+ context 'lazy initializes its contents' do
35
+ it 'so it will not call contents until it is first needed' do
36
+ member_view.stub(:contents) { raise 'No!' }
37
+ expect { collection_view.name }.to_not raise_error
38
+ end
39
+ it 'when contents is needed, it will clone it from the member_view' do
40
+ # Twice is because we're callong member_view.contents for the right side of the equality
41
+ expect(member_view).to receive(:contents).twice.and_call_original
42
+ collection_view.contents.should eq member_view.contents
43
+ end
44
+ end
35
45
  end
36
46
 
37
47
  context 'creating with a set of attributes defined in a block' do
@@ -48,7 +58,7 @@ describe Praxis::CollectionView do
48
58
  subject(:output) { collection_view.render(people, context: root_context) }
49
59
 
50
60
  it { should be_kind_of(Array) }
51
- it { should eq people.collect {|person| member_view.render(person)} }
61
+ it { should eq people.collect { |person| member_view.render(person) } }
52
62
  end
53
63
 
54
64
  context '#example' do
@@ -69,5 +79,4 @@ describe Praxis::CollectionView do
69
79
  its([:attributes]) { should eq(member_view.describe[:attributes]) }
70
80
  its([:type]) { should eq(:collection) }
71
81
  end
72
-
73
82
  end