praxis-blueprints 3.0 → 3.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
3
+
4
+ describe Praxis::ConfigHash do
5
+ subject(:instance) { Praxis::ConfigHash.new(hash, &block) }
6
+ let(:hash) { { one: ['existing'], two: 'dos' } }
7
+ let(:block) do
8
+ proc { 'abc' }
9
+ end
10
+
11
+ context 'initialization' do
12
+ it 'saves the passed hash' do
13
+ expect(subject.hash).to be(hash)
14
+ end
15
+ end
16
+
17
+ context '.from' do
18
+ subject(:instance) { Praxis::ConfigHash.from(hash, &block) }
19
+ it 'returns an instance' do
20
+ expect(subject).to be_kind_of(Praxis::ConfigHash)
21
+ expect(subject.hash).to be(hash)
22
+ end
23
+ end
24
+
25
+ context '#to_hash' do
26
+ let(:block) do
27
+ proc { hash['i_was'] = 'here' }
28
+ end
29
+ it 'evaluates the block and returns the resulting hash' do
30
+ expect(subject.to_hash).to eq(subject.hash)
31
+ expect(subject.hash['i_was']).to eq('here')
32
+ end
33
+ end
34
+
35
+ context '#method_missing' do
36
+ context 'when keys do not exist in the hash key' do
37
+ it 'sets a single value to the hash' do
38
+ subject.some_name 'someval'
39
+ expect(subject.hash[:some_name]).to eq('someval')
40
+ end
41
+ it 'sets a multiple values to the hash key' do
42
+ subject.some_name 'someval', 'other1', 'other2'
43
+ expect(subject.hash[:some_name]).to include('someval', 'other1', 'other2')
44
+ end
45
+ end
46
+ context 'when keys already exist in the hash key' do
47
+ it 'adds one value to the hash' do
48
+ subject.one'newval'
49
+ expect(subject.hash[:one]).to match_array(%w(existing newval))
50
+ end
51
+ it 'adds multiple values to the hash key' do
52
+ subject.one 'newval', 'other1', 'other2'
53
+ expect(subject.hash[:one]).to match_array(%w(existing newval other1 other2))
54
+ end
55
+ context 'when passing a value and a block' do
56
+ let(:my_block) { proc {} }
57
+ it 'adds the tuple to the hash key' do
58
+ subject.one 'val', &my_block
59
+ expect(subject.hash[:one]).to include(['val', my_block])
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,7 +1,7 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  describe Praxis::FieldExpander do
4
-
5
5
  let(:field_expander) { Praxis::FieldExpander.new }
6
6
 
7
7
  let(:view) do
@@ -21,96 +21,95 @@ describe Praxis::FieldExpander do
21
21
  let(:full_expected) do
22
22
  {
23
23
  name: true,
24
- full_name: {first: true, last: true},
25
- parents: {mother: true, father: true},
24
+ full_name: { first: true, last: true },
25
+ parents: { mother: true, father: true },
26
26
  address: {
27
27
  state: true,
28
28
  street: true,
29
29
  resident: {
30
30
  name: true,
31
- full_name: {first: true, last: true},
32
- address: {street:true, state:true},
33
- prior_addresses: [{street:true, state:true}]
31
+ full_name: { first: true, last: true },
32
+ address: { street: true, state: true },
33
+ prior_addresses: [{ street: true, state: true }]
34
34
  }
35
35
  },
36
- prior_addresses: [{state: true}],
36
+ prior_addresses: [{ state: true }],
37
37
  tags: [true]
38
38
  }
39
39
  end
40
40
 
41
41
  context 'expanding a view' do
42
42
  it 'expands all fields on the view, subviews, and related attributes' do
43
- field_expander.expand(view,true).should eq(full_expected)
43
+ field_expander.expand(view, true).should eq(full_expected)
44
44
  end
45
45
 
46
46
  it 'expands for a subset of the direct fields' do
47
- field_expander.expand(view,name: true).should eq({name:true})
47
+ field_expander.expand(view, name: true).should eq(name: true)
48
48
  end
49
49
 
50
50
  it 'expands for a subview' do
51
- field_expander.expand(view,parents: true).should eq({parents:{mother: true, father: true}})
51
+ field_expander.expand(view, parents: true).should eq(parents: { mother: true, father: true })
52
52
  end
53
53
 
54
54
  it 'expands for a related attribute' do
55
- field_expander.expand(view,address: true).should eq({address: full_expected[:address]})
55
+ field_expander.expand(view, address: true).should eq(address: full_expected[:address])
56
56
  end
57
57
 
58
58
  it 'expands for a subset of a related attribute' do
59
- field_expander.expand(view,address: {resident: true}).should eq({address: {resident: full_expected[:address][:resident]}})
59
+ field_expander.expand(view, address: { resident: true }).should eq(address: { resident: full_expected[:address][:resident] })
60
60
  end
61
61
 
62
62
  it 'expands for a subset of a subview' do
63
- field_expander.expand(view,parents: {mother: true}).should eq({parents:{mother: true}})
63
+ field_expander.expand(view, parents: { mother: true }).should eq(parents: { mother: true })
64
64
  end
65
65
 
66
66
  it 'ignores fields not defined in the view' do
67
- field_expander.expand(view,name: true, age: true).should eq({name:true})
67
+ field_expander.expand(view, name: true, age: true).should eq(name: true)
68
68
  end
69
69
 
70
70
  it 'expands a specific subattribute of a struct' do
71
- field_expander.expand(view,full_name: {first: true}).should eq({full_name: {first: true}})
71
+ field_expander.expand(view, full_name: { first: true }).should eq(full_name: { first: true })
72
72
  end
73
73
 
74
74
  it 'wraps expanded collections in arrays' do
75
- field_expander.expand(view,prior_addresses: {state: true}).should eq({prior_addresses: [{state: true}]})
75
+ field_expander.expand(view, prior_addresses: { state: true }).should eq(prior_addresses: [{ state: true }])
76
76
  end
77
77
 
78
78
  it 'wraps expanded collections in arrays' do
79
- field_expander.expand(view, prior_addresses: true).should eq({prior_addresses: [{state: true}]})
79
+ field_expander.expand(view, prior_addresses: true).should eq(prior_addresses: [{ state: true }])
80
80
  end
81
81
  end
82
82
 
83
83
  it 'expands for an Attributor::Model' do
84
- field_expander.expand(FullName).should eq({first: true, last: true})
84
+ field_expander.expand(FullName).should eq(first: true, last: true)
85
85
  end
86
86
 
87
-
88
87
  it 'expands for a Blueprint' do
89
- field_expander.expand(Person, parents: true).should eq({parents:{father: true, mother: true}})
88
+ field_expander.expand(Person, parents: true).should eq(parents: { father: true, mother: true })
90
89
  end
91
90
 
92
91
  it 'expands for an Attributor::Collection of an Attrbutor::Model' do
93
- expected = [{first: true, last: true}]
92
+ expected = [{ first: true, last: true }]
94
93
  field_expander.expand(Attributor::Collection.of(FullName)).should eq expected
95
94
  end
96
95
 
97
96
  it 'expands for an Attributor::Collection of a Blueprint' do
98
- expected = [{name: true, resident: {full_name: {first: true, last: true}}}]
97
+ expected = [{ name: true, resident: { full_name: { first: true, last: true } } }]
99
98
 
100
- field_expander.expand(Attributor::Collection.of(Address), name: true, resident:{full_name: true}).should eq expected
99
+ field_expander.expand(Attributor::Collection.of(Address), name: true, resident: { full_name: true }).should eq expected
101
100
  end
102
101
 
103
102
  it 'also expands array-wrapped field hashes for collections' do
104
- expected = [{name: true, resident: {full_name: {first: true, last: true}}}]
105
- field_expander.expand(Attributor::Collection.of(Address), [name: true, resident:{full_name: true}]).should eq expected
103
+ expected = [{ name: true, resident: { full_name: { first: true, last: true } } }]
104
+ field_expander.expand(Attributor::Collection.of(Address), [name: true, resident: { full_name: true }]).should eq expected
106
105
  end
107
106
 
108
107
  it 'expands for an Attributor::Collection of a primitive type' do
109
- field_expander.expand(Attributor::Collection.of(String)).should eq([true])
108
+ field_expander.expand(Attributor::Collection.of(String)).should eq([true])
110
109
  end
111
110
 
112
111
  it 'expands for for a primitive type' do
113
- field_expander.expand(String).should eq(true)
112
+ field_expander.expand(String).should eq(true)
114
113
  end
115
114
 
116
115
  context 'expanding a two-dimensional collection' do
@@ -121,17 +120,31 @@ describe Praxis::FieldExpander do
121
120
  it 'expands the fields with proper nesting' do
122
121
  field_expander.expand(matrix_type).should eq([[first: true, last: true]])
123
122
  end
124
-
125
123
  end
126
124
 
127
125
  context 'circular expansions' do
128
- it 'throws a CircularExpansionError' do
129
- expect { field_expander.expand(Address,true) }.to raise_error(Praxis::FieldExpander::CircularExpansionError)
126
+ it 'preserve field object identity for circular references' do
127
+ result = field_expander.expand(Address, true)
128
+ result.should be result[:resident][:address]
129
+ end
130
+
131
+ context 'with collections of Blueprints' do
132
+ it 'still preserves object identity' do
133
+ result = field_expander.expand(Person, prior_addresses: true)[:prior_addresses][0]
134
+ result.should be result[:resident][:prior_addresses][0]
135
+ end
136
+ end
137
+
138
+ context 'for views' do
139
+ it 'still preserves object identity' do
140
+ result = field_expander.expand(Person.views[:circular], true)
141
+ result[:address][:resident].should be result
142
+ end
130
143
  end
131
144
  end
132
145
 
133
146
  it 'optimizes duplicate field expansions' do
134
- expect(field_expander.expand(FullName,true)).to be(field_expander.expand(FullName,true))
147
+ expect(field_expander.expand(FullName, true)).to be(field_expander.expand(FullName, true))
135
148
  end
136
149
 
137
150
  context 'expanding hash attributes' do
@@ -155,15 +168,10 @@ describe Praxis::FieldExpander do
155
168
  expected = {
156
169
  name: true,
157
170
  simple_hash: true,
158
- keyed_hash: {foo: true, bar: true},
159
- some_struct: {something: true}
171
+ keyed_hash: { foo: true, bar: true },
172
+ some_struct: { something: true }
160
173
  }
161
174
  field_expander.expand(type, true).should eq(expected)
162
175
  end
163
-
164
-
165
-
166
176
  end
167
-
168
-
169
177
  end
@@ -1,33 +1,42 @@
1
+ # frozen_string_literal: true
1
2
  require_relative '../spec_helper'
2
3
 
3
4
  describe Praxis::Renderer do
4
-
5
5
  let(:address) { Address.example }
6
- let(:prior_addresses) { 2.times.collect { Address.example } }
6
+ let(:prior_addresses) { Array.new(2) { Address.example } }
7
+ let(:alias_one) { FullName.example }
8
+ let(:alias_two) { FullName.example }
9
+ let(:aliases) { [alias_one, alias_two] }
10
+ let(:metadata_hash) { { something: 'here' } }
11
+ let(:metadata) { Attributor::Hash.load(metadata_hash) }
12
+
7
13
  let(:person) do
8
- Person.example(
14
+ Person.example(
9
15
  address: address,
10
16
  email: nil,
11
17
  prior_addresses: prior_addresses,
12
18
  alive: false,
13
- work_address: nil
19
+ work_address: nil,
20
+ aliases: aliases,
21
+ metadata: metadata
14
22
  )
15
23
  end
16
24
 
17
-
18
25
  let(:fields) do
19
26
  {
20
- name:true,
21
- email:true,
22
- full_name: {first:true, last:true},
27
+ name: true,
28
+ email: true,
29
+ full_name: { first: true, last: true },
23
30
  address: {
24
- state:true,
25
- street:true,
26
- resident: {name:true}
31
+ state: true,
32
+ street: true,
33
+ resident: { name: true }
27
34
  },
28
- prior_addresses: [{name: true}],
35
+ prior_addresses: [{ name: true }],
29
36
  work_address: true,
30
- alive: true
37
+ alive: true,
38
+ metadata: true,
39
+ aliases: [true]
31
40
  }
32
41
  end
33
42
 
@@ -36,31 +45,44 @@ describe Praxis::Renderer do
36
45
  subject(:output) { renderer.render(person, fields) }
37
46
 
38
47
  it 'renders existing attributes' do
39
- output.keys.should match_array([:name, :full_name, :alive, :address, :prior_addresses])
48
+ output.keys.should match_array([:name, :full_name, :alive, :address, :prior_addresses, :metadata, :aliases])
40
49
 
41
50
  output[:name].should eq person.name
42
- output[:full_name].should eq({first: person.full_name.first, last: person.full_name.last})
51
+ output[:full_name].should eq(first: person.full_name.first, last: person.full_name.last)
43
52
  output[:alive].should be false
44
53
 
45
- output[:address].should eq({
46
- state: person.address.state,
47
- street: person.address.street,
48
- resident: {name: person.address.resident.name}
49
- })
54
+ output[:address].should eq(state: person.address.state,
55
+ street: person.address.street,
56
+ resident: { name: person.address.resident.name })
50
57
 
51
- expected_prior_addresses = prior_addresses.collect { |addr| {name: addr.name} }
58
+ expected_prior_addresses = prior_addresses.collect { |addr| { name: addr.name } }
52
59
  output[:prior_addresses].should match_array(expected_prior_addresses)
60
+
61
+ expected_aliases = aliases.collect(&:dump)
62
+ output[:aliases].should match_array(expected_aliases)
63
+
64
+ output[:metadata].should eq(metadata.dump)
65
+ end
66
+
67
+ context 'calls dump for non-Blueprint, but still Dumpable instances' do
68
+ it 'when rendering them in full as array members' do
69
+ alias_one.should_receive(:dump).and_call_original
70
+ output[:aliases].first.should eq(first: alias_one.first, last: alias_one.last)
71
+ end
72
+ it 'when rendering them in full as leaf object' do
73
+ metadata.should_receive(:dump).and_call_original
74
+ output[:metadata].should eq(metadata_hash)
75
+ end
53
76
  end
54
77
 
55
78
  it 'does not render attributes with nil values' do
56
79
  output.should_not have_key(:email)
57
80
  end
58
81
 
59
-
60
82
  it 'sends the correct ActiveSupport::Notification' do
61
83
  fields = {
62
- name:true,
63
- email:true
84
+ name: true,
85
+ email: true
64
86
  }
65
87
 
66
88
  notification_payload = {
@@ -69,16 +91,16 @@ describe Praxis::Renderer do
69
91
  fields: fields
70
92
  }
71
93
 
72
- ActiveSupport::Notifications.should_receive(:instrument).
73
- with('praxis.blueprint.render',notification_payload).
74
- and_call_original
94
+ ActiveSupport::Notifications.should_receive(:instrument)
95
+ .with('praxis.blueprint.render', notification_payload)
96
+ .and_call_original
75
97
 
76
98
  renderer.render(person, fields)
77
- end
78
-
99
+ end
79
100
 
80
101
  context 'with include_nil: true' do
81
102
  let(:renderer) { Praxis::Renderer.new(include_nil: true) }
103
+ let(:address) { nil }
82
104
 
83
105
  it 'renders attributes with nil values' do
84
106
  output.should have_key :email
@@ -87,44 +109,48 @@ describe Praxis::Renderer do
87
109
  output.should have_key :work_address
88
110
  output[:work_address].should be nil
89
111
  end
112
+
113
+ it 'renders nil directly for nil subobjects' do
114
+ output.should have_key :address
115
+ output[:address].should be nil
116
+ end
90
117
  end
91
118
 
92
119
  context '#render_collection' do
93
- let(:people) { 10.times.collect { Person.example(address: address, email: nil) } }
120
+ let(:people) { Array.new(10) { Person.example(address: address, email: nil) } }
94
121
  subject(:output) { renderer.render_collection(people, fields) }
95
122
 
96
123
  it { should have(10).items }
97
124
 
98
125
  it 'renders the collection' do
99
- output.first.should eq(renderer.render(people.first,fields))
126
+ output.first.should eq(renderer.render(people.first, fields))
100
127
  end
101
-
102
128
  end
103
129
 
104
130
  context 'rendering a two-dimmensional collection' do
105
- let(:names) { 9.times.collect { |i| Address.example(i.to_s, name: i.to_s) } }
131
+ let(:names) { Array.new(9) { |i| Address.example(i.to_s, name: i.to_s) } }
106
132
  let(:matrix_type) do
107
133
  Attributor::Collection.of(Attributor::Collection.of(Address))
108
134
  end
109
135
 
110
- let(:matrix) { matrix_type.load(names.each_slice(3).collect { |slice| slice }) }
136
+ let(:matrix) { matrix_type.load(names.each_slice(3).collect { |slice| slice }) }
111
137
 
112
- let(:fields) { [[{name: true}]] }
138
+ let(:fields) { [[{ name: true }]] }
113
139
 
114
140
  it 'renders with render_collection and per-element field spec' do
115
- rendered = renderer.render_collection(matrix,fields.first)
116
- rendered.flatten.collect {|r| r[:name] }.should eq((0..8).collect(&:to_s))
141
+ rendered = renderer.render_collection(matrix, fields.first)
142
+ rendered.flatten.collect { |r| r[:name] }.should eq((0..8).collect(&:to_s))
117
143
  end
118
144
 
119
145
  it 'renders with render and proper field spec' do
120
- rendered = renderer.render(matrix,fields)
121
- rendered.flatten.collect {|r| r[:name] }.should eq((0..8).collect(&:to_s))
146
+ rendered = renderer.render(matrix, fields)
147
+ rendered.flatten.collect { |r| r[:name] }.should eq((0..8).collect(&:to_s))
122
148
  end
123
149
  end
124
150
 
125
151
  context 'rendering stuff that breaks badly' do
126
152
  it 'does not break badly' do
127
- renderer.render(person, tags: [true])
153
+ renderer.render(person, {tags: [true]})
128
154
  end
129
155
  end
130
156
 
@@ -137,6 +163,68 @@ describe Praxis::Renderer do
137
163
  render_2 = renderer.render(person, fields)
138
164
  expect(render_1).to be(render_2)
139
165
  end
166
+ end
167
+
168
+ context 'circular rendering' do
169
+ it do
170
+ field_expander = Praxis::FieldExpander.new
171
+ fields = field_expander.expand(Person, true)
172
+
173
+ person.object.address.object.resident = person
174
+ expect { renderer.render(person, fields) }.to raise_error(Praxis::Renderer::CircularRenderingError)
175
+ end
176
+ end
177
+
178
+ context 'rendering hashes' do
179
+ let(:fields) do
180
+ {
181
+ id: true,
182
+ hash: true
183
+ }
184
+ end
185
+
186
+ let(:data) { { id: 10, hash: { foo: 'bar' } } }
187
+ let(:object) { SimpleHash.load(data) }
188
+ let(:renderer) { Praxis::Renderer.new }
189
+
190
+ subject(:output) { renderer.render(object, fields) }
191
+
192
+ its([:id]) { should eq data[:id] }
193
+ its([:hash]) { should eq data[:hash] }
194
+ its([:hash]) { should be_kind_of(Hash) }
195
+ end
196
+
197
+ context 'rendering collections of hashes' do
198
+ let(:fields) do
199
+ {
200
+ id: true,
201
+ hash_collection: [true]
202
+ }
203
+ end
204
+
205
+ let(:data) { { id: 10, hash_collection: [{ foo: 'bar' }] } }
206
+ let(:object) { SimpleHashCollection.load(data) }
207
+ let(:renderer) { Praxis::Renderer.new }
208
+
209
+ subject(:output) { renderer.render(object, fields) }
210
+
211
+ its([:id]) { should eq data[:id] }
212
+ its([:hash_collection]) { should eq data[:hash_collection] }
213
+ its([:hash_collection]) { should be_kind_of(Array) }
214
+
215
+ it 'renders the hashes' do
216
+ expect(output[:hash_collection].first).to be_kind_of(Hash)
217
+ end
218
+ end
219
+
220
+ context 'rendering a Blueprint with fields true' do
221
+ let(:fields) do
222
+ {
223
+ name: true,
224
+ address: true
225
+ }
226
+ end
140
227
 
228
+ its([:address]) { should eq person.address.dump }
141
229
  end
142
230
  end