praxis-blueprints 2.2 → 3.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 +1 -0
- data/CHANGELOG.md +39 -0
- data/Gemfile +1 -2
- data/lib/praxis-blueprints.rb +3 -0
- data/lib/praxis-blueprints/blueprint.rb +61 -55
- data/lib/praxis-blueprints/collection_view.rb +12 -15
- data/lib/praxis-blueprints/field_expander.rb +104 -0
- data/lib/praxis-blueprints/renderer.rb +71 -0
- data/lib/praxis-blueprints/version.rb +1 -1
- data/lib/praxis-blueprints/view.rb +44 -63
- data/praxis-blueprints.gemspec +2 -2
- data/spec/praxis-blueprints/blueprint_spec.rb +155 -205
- data/spec/praxis-blueprints/collection_view_spec.rb +38 -24
- data/spec/praxis-blueprints/field_expander_spec.rb +169 -0
- data/spec/praxis-blueprints/renderer_spec.rb +142 -0
- data/spec/praxis-blueprints/view_spec.rb +45 -315
- data/spec/support/spec_blueprints.rb +8 -8
- metadata +10 -4
@@ -2,50 +2,64 @@ require_relative '../spec_helper'
|
|
2
2
|
|
3
3
|
describe Praxis::CollectionView do
|
4
4
|
|
5
|
-
let(:
|
5
|
+
let(:root_context) { ['people'] }
|
6
6
|
|
7
|
-
let(:people)
|
7
|
+
let(:people) do
|
8
|
+
3.times.collect do |i|
|
9
|
+
context = ["people", "at(#{i})"]
|
10
|
+
Person.example(context)
|
11
|
+
end
|
12
|
+
end
|
8
13
|
|
9
14
|
|
10
|
-
let(:
|
11
|
-
|
15
|
+
let(:contents_definition) do
|
16
|
+
proc do
|
12
17
|
attribute :name
|
13
18
|
attribute :address, view: :state
|
14
19
|
end
|
15
20
|
end
|
16
21
|
|
22
|
+
let(:member_view) do
|
23
|
+
Praxis::View.new(:tiny, Person, &contents_definition)
|
24
|
+
end
|
25
|
+
|
17
26
|
let(:collection_view) do
|
18
|
-
Praxis::CollectionView.new(:collection_view,
|
27
|
+
Praxis::CollectionView.new(:collection_view, Person, member_view)
|
19
28
|
end
|
20
29
|
|
21
|
-
|
30
|
+
context 'creating from a member view' do
|
22
31
|
|
23
|
-
|
24
|
-
|
25
|
-
people.each_with_index do |person, i|
|
26
|
-
subcontext = root_context + ["at(#{i})"]
|
27
|
-
expect(member_view).to(
|
28
|
-
receive(:dump).
|
29
|
-
with(person, context: subcontext).
|
30
|
-
and_call_original)
|
31
|
-
end
|
32
|
+
it 'gets the proper contents' do
|
33
|
+
collection_view.contents.should eq member_view.contents
|
32
34
|
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'creating with a set of attributes defined in a block' do
|
38
|
+
let(:collection_view) do
|
39
|
+
Praxis::CollectionView.new(:collection_view, Person, &contents_definition)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'gets the proper contents' do
|
43
|
+
collection_view.contents.should eq member_view.contents
|
44
|
+
end
|
45
|
+
end
|
33
46
|
|
34
|
-
|
47
|
+
context '#render' do
|
48
|
+
subject(:output) { collection_view.render(people, context: root_context) }
|
35
49
|
|
36
50
|
it { should be_kind_of(Array) }
|
51
|
+
it { should eq people.collect {|person| member_view.render(person)} }
|
37
52
|
end
|
38
53
|
|
39
54
|
context '#example' do
|
40
55
|
it 'generates an example from the schema and renders it' do
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
collection_view.example(root_context)
|
56
|
+
# because we set the context throughout, we know +people+ will
|
57
|
+
# will generate with identical contents across all runs.
|
58
|
+
expected = people.collect do |person|
|
59
|
+
{ name: person.name, address: person.address.render(view: :state) }
|
60
|
+
end
|
61
|
+
|
62
|
+
collection_view.example(root_context).should eq expected
|
49
63
|
end
|
50
64
|
end
|
51
65
|
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Praxis::FieldExpander do
|
4
|
+
|
5
|
+
let(:field_expander) { Praxis::FieldExpander.new }
|
6
|
+
|
7
|
+
let(:view) do
|
8
|
+
Praxis::View.new(:testing, Person) do
|
9
|
+
attribute :name
|
10
|
+
attribute :full_name
|
11
|
+
attribute :parents do
|
12
|
+
attribute :father
|
13
|
+
attribute :mother
|
14
|
+
end
|
15
|
+
attribute :address, view: :extended
|
16
|
+
attribute :prior_addresses, view: :state
|
17
|
+
attribute :tags
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:full_expected) do
|
22
|
+
{
|
23
|
+
name: true,
|
24
|
+
full_name: {first: true, last: true},
|
25
|
+
parents: {mother: true, father: true},
|
26
|
+
address: {
|
27
|
+
state: true,
|
28
|
+
street: true,
|
29
|
+
resident: {
|
30
|
+
name: true,
|
31
|
+
full_name: {first: true, last: true},
|
32
|
+
address: {street:true, state:true},
|
33
|
+
prior_addresses: [{street:true, state:true}]
|
34
|
+
}
|
35
|
+
},
|
36
|
+
prior_addresses: [{state: true}],
|
37
|
+
tags: [true]
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'expanding a view' do
|
42
|
+
it 'expands all fields on the view, subviews, and related attributes' do
|
43
|
+
field_expander.expand(view,true).should eq(full_expected)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'expands for a subset of the direct fields' do
|
47
|
+
field_expander.expand(view,name: true).should eq({name:true})
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'expands for a subview' do
|
51
|
+
field_expander.expand(view,parents: true).should eq({parents:{mother: true, father: true}})
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'expands for a related attribute' do
|
55
|
+
field_expander.expand(view,address: true).should eq({address: full_expected[:address]})
|
56
|
+
end
|
57
|
+
|
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]}})
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'expands for a subset of a subview' do
|
63
|
+
field_expander.expand(view,parents: {mother: true}).should eq({parents:{mother: true}})
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'ignores fields not defined in the view' do
|
67
|
+
field_expander.expand(view,name: true, age: true).should eq({name:true})
|
68
|
+
end
|
69
|
+
|
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}})
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'wraps expanded collections in arrays' do
|
75
|
+
field_expander.expand(view,prior_addresses: {state: true}).should eq({prior_addresses: [{state: true}]})
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'wraps expanded collections in arrays' do
|
79
|
+
field_expander.expand(view, prior_addresses: true).should eq({prior_addresses: [{state: true}]})
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'expands for an Attributor::Model' do
|
84
|
+
field_expander.expand(FullName).should eq({first: true, last: true})
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
it 'expands for a Blueprint' do
|
89
|
+
field_expander.expand(Person, parents: true).should eq({parents:{father: true, mother: true}})
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'expands for an Attributor::Collection of an Attrbutor::Model' do
|
93
|
+
expected = [{first: true, last: true}]
|
94
|
+
field_expander.expand(Attributor::Collection.of(FullName)).should eq expected
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'expands for an Attributor::Collection of a Blueprint' do
|
98
|
+
expected = [{name: true, resident: {full_name: {first: true, last: true}}}]
|
99
|
+
|
100
|
+
field_expander.expand(Attributor::Collection.of(Address), name: true, resident:{full_name: true}).should eq expected
|
101
|
+
end
|
102
|
+
|
103
|
+
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
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'expands for an Attributor::Collection of a primitive type' do
|
109
|
+
field_expander.expand(Attributor::Collection.of(String)).should eq([true])
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'expands for for a primitive type' do
|
113
|
+
field_expander.expand(String).should eq(true)
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'expanding a two-dimensional collection' do
|
117
|
+
let(:matrix_type) do
|
118
|
+
Attributor::Collection.of(Attributor::Collection.of(FullName))
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'expands the fields with proper nesting' do
|
122
|
+
field_expander.expand(matrix_type).should eq([[first: true, last: true]])
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'circular expansions' do
|
128
|
+
it 'throws a CircularExpansionError' do
|
129
|
+
expect { field_expander.expand(Address,true) }.to raise_error(Praxis::FieldExpander::CircularExpansionError)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'optimizes duplicate field expansions' do
|
134
|
+
expect(field_expander.expand(FullName,true)).to be(field_expander.expand(FullName,true))
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'expanding hash attributes' do
|
138
|
+
let(:type) do
|
139
|
+
Class.new(Attributor::Model) do
|
140
|
+
attributes do
|
141
|
+
attribute :name, String
|
142
|
+
attribute :simple_hash, Hash
|
143
|
+
attribute :keyed_hash, Hash do
|
144
|
+
key :foo, String
|
145
|
+
key :bar, String
|
146
|
+
end
|
147
|
+
attribute :some_struct do
|
148
|
+
attribute :something, String
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'expands' do
|
155
|
+
expected = {
|
156
|
+
name: true,
|
157
|
+
simple_hash: true,
|
158
|
+
keyed_hash: {foo: true, bar: true},
|
159
|
+
some_struct: {something: true}
|
160
|
+
}
|
161
|
+
field_expander.expand(type, true).should eq(expected)
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Praxis::Renderer do
|
4
|
+
|
5
|
+
let(:address) { Address.example }
|
6
|
+
let(:prior_addresses) { 2.times.collect { Address.example } }
|
7
|
+
let(:person) do
|
8
|
+
Person.example(
|
9
|
+
address: address,
|
10
|
+
email: nil,
|
11
|
+
prior_addresses: prior_addresses,
|
12
|
+
alive: false,
|
13
|
+
work_address: nil
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
let(:fields) do
|
19
|
+
{
|
20
|
+
name:true,
|
21
|
+
email:true,
|
22
|
+
full_name: {first:true, last:true},
|
23
|
+
address: {
|
24
|
+
state:true,
|
25
|
+
street:true,
|
26
|
+
resident: {name:true}
|
27
|
+
},
|
28
|
+
prior_addresses: [{name: true}],
|
29
|
+
work_address: true,
|
30
|
+
alive: true
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
let(:renderer) { Praxis::Renderer.new }
|
35
|
+
|
36
|
+
subject(:output) { renderer.render(person, fields) }
|
37
|
+
|
38
|
+
it 'renders existing attributes' do
|
39
|
+
output.keys.should match_array([:name, :full_name, :alive, :address, :prior_addresses])
|
40
|
+
|
41
|
+
output[:name].should eq person.name
|
42
|
+
output[:full_name].should eq({first: person.full_name.first, last: person.full_name.last})
|
43
|
+
output[:alive].should be false
|
44
|
+
|
45
|
+
output[:address].should eq({
|
46
|
+
state: person.address.state,
|
47
|
+
street: person.address.street,
|
48
|
+
resident: {name: person.address.resident.name}
|
49
|
+
})
|
50
|
+
|
51
|
+
expected_prior_addresses = prior_addresses.collect { |addr| {name: addr.name} }
|
52
|
+
output[:prior_addresses].should match_array(expected_prior_addresses)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'does not render attributes with nil values' do
|
56
|
+
output.should_not have_key(:email)
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
it 'sends the correct ActiveSupport::Notification' do
|
61
|
+
fields = {
|
62
|
+
name:true,
|
63
|
+
email:true
|
64
|
+
}
|
65
|
+
|
66
|
+
notification_payload = {
|
67
|
+
blueprint: person,
|
68
|
+
view: nil,
|
69
|
+
fields: fields
|
70
|
+
}
|
71
|
+
|
72
|
+
ActiveSupport::Notifications.should_receive(:instrument).
|
73
|
+
with('praxis.blueprint.render',notification_payload).
|
74
|
+
and_call_original
|
75
|
+
|
76
|
+
renderer.render(person, fields)
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
context 'with include_nil: true' do
|
81
|
+
let(:renderer) { Praxis::Renderer.new(include_nil: true) }
|
82
|
+
|
83
|
+
it 'renders attributes with nil values' do
|
84
|
+
output.should have_key :email
|
85
|
+
output[:email].should be nil
|
86
|
+
|
87
|
+
output.should have_key :work_address
|
88
|
+
output[:work_address].should be nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context '#render_collection' do
|
93
|
+
let(:people) { 10.times.collect { Person.example(address: address, email: nil) } }
|
94
|
+
subject(:output) { renderer.render_collection(people, fields) }
|
95
|
+
|
96
|
+
it { should have(10).items }
|
97
|
+
|
98
|
+
it 'renders the collection' do
|
99
|
+
output.first.should eq(renderer.render(people.first,fields))
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'rendering a two-dimmensional collection' do
|
105
|
+
let(:names) { 9.times.collect { |i| Address.example(i.to_s, name: i.to_s) } }
|
106
|
+
let(:matrix_type) do
|
107
|
+
Attributor::Collection.of(Attributor::Collection.of(Address))
|
108
|
+
end
|
109
|
+
|
110
|
+
let(:matrix) { matrix_type.load(names.each_slice(3).collect { |slice| slice }) }
|
111
|
+
|
112
|
+
let(:fields) { [[{name: true}]] }
|
113
|
+
|
114
|
+
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))
|
117
|
+
end
|
118
|
+
|
119
|
+
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))
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'rendering stuff that breaks badly' do
|
126
|
+
it 'does not break badly' do
|
127
|
+
renderer.render(person, tags: [true])
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'caching rendered objects' do
|
132
|
+
let(:fields) { Praxis::FieldExpander.expand(Person, full_name: true) }
|
133
|
+
it 'caches and returns identical results for the same field objects' do
|
134
|
+
expect(person).to receive(:full_name).once.and_call_original
|
135
|
+
|
136
|
+
render_1 = renderer.render(person, fields)
|
137
|
+
render_2 = renderer.render(person, fields)
|
138
|
+
expect(render_1).to be(render_2)
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
@@ -6,347 +6,76 @@ describe Praxis::View do
|
|
6
6
|
let(:address) { person.address }
|
7
7
|
|
8
8
|
let(:view) do
|
9
|
-
Praxis::View.new(:
|
9
|
+
Praxis::View.new(:testing, Person) do
|
10
10
|
attribute :name
|
11
|
-
attribute :
|
12
|
-
attribute :
|
11
|
+
attribute :email
|
12
|
+
attribute :full_name
|
13
|
+
attribute :parents do
|
14
|
+
attribute :father
|
15
|
+
attribute :mother
|
16
|
+
end
|
17
|
+
attribute :address, view: :extended
|
18
|
+
attribute :prior_addresses, view: :state
|
19
|
+
attribute :work_address
|
13
20
|
end
|
14
21
|
end
|
15
|
-
let(:dumping_options){ {} }
|
16
|
-
subject(:output) { view.to_hash(person, dumping_options ) }
|
17
|
-
|
18
22
|
|
19
23
|
it 'can generate examples' do
|
20
24
|
view.example.should have_key(:name)
|
21
25
|
end
|
22
26
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
attribute :email
|
28
|
-
attribute :age
|
29
|
-
attribute :address
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
let(:data) { {name: 'Bob', email: nil, address: nil } }
|
34
|
-
|
35
|
-
let(:person) { Person.load(data) }
|
36
|
-
|
37
|
-
context 'with default rendering options' do
|
38
|
-
it 'attributor works right' do
|
39
|
-
person.object.key?(:name).should be(true)
|
40
|
-
person.object.key?(:email).should be(true)
|
41
|
-
person.object.key?(:age).should be(false)
|
42
|
-
person.object.key?(:address).should be(true)
|
43
|
-
|
44
|
-
person.name.should eq('Bob')
|
45
|
-
person.email.should eq(nil)
|
46
|
-
person.age.should eq(nil)
|
47
|
-
person.address.should eq(nil)
|
48
|
-
end
|
49
|
-
|
50
|
-
it 'renders existing, non-nil, attributes' do
|
51
|
-
output.key?(:name).should be(true)
|
52
|
-
output.key?(:email).should_not be(true)
|
53
|
-
output.key?(:age).should_not be(true)
|
54
|
-
output.key?(:address).should_not be(true)
|
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
|
70
|
-
end
|
71
|
-
|
72
|
-
|
73
|
-
context 'with include_nil: true' do
|
74
|
-
let(:view) do
|
75
|
-
Praxis::View.new(:info, Person, include_nil: true) do
|
76
|
-
attribute :name
|
77
|
-
attribute :email
|
78
|
-
attribute :age
|
79
|
-
attribute :address
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
it 'includes attributes with nil values' do
|
84
|
-
output.key?(:email).should be(true)
|
85
|
-
output[:email].should be(nil)
|
86
|
-
|
87
|
-
output.key?(:address).should be(true)
|
88
|
-
output[:address].should be(nil)
|
89
|
-
|
90
|
-
output.key?(:age).should be(true)
|
91
|
-
output[:age].should be(nil)
|
92
|
-
end
|
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
|
102
|
-
end
|
103
|
-
|
104
|
-
end
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
context 'direct attributes' do
|
109
|
-
|
110
|
-
let(:person) { Person.load(person_data) }
|
111
|
-
context 'with undisputably existing values' do
|
112
|
-
|
113
|
-
let(:person_data) { {name:'somename', alive:true} }
|
114
|
-
|
115
|
-
let(:expected_output) do
|
116
|
-
{
|
117
|
-
:name => 'somename',
|
118
|
-
:alive => true
|
119
|
-
}
|
120
|
-
end
|
121
|
-
it 'should show up' do
|
122
|
-
subject.should == expected_output
|
123
|
-
end
|
124
|
-
end
|
125
|
-
context 'with nil values' do
|
126
|
-
let(:person_data) { {name:'alive_is_nil', alive: nil} }
|
127
|
-
let(:expected_output) do
|
128
|
-
{
|
129
|
-
name: 'alive_is_nil',
|
130
|
-
alive: true
|
131
|
-
}
|
132
|
-
end
|
133
|
-
it 'are skipped completely' do
|
134
|
-
subject.should == expected_output
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
context 'with false values' do
|
139
|
-
let(:person_data) { {name:'alive_is_false', alive:false} }
|
140
|
-
let(:expected_output) do
|
141
|
-
{
|
142
|
-
:name => 'alive_is_false',
|
143
|
-
:alive => false
|
144
|
-
}
|
145
|
-
end
|
146
|
-
it 'should still show up, since "false" is really a valid value' do
|
147
|
-
subject.should == expected_output
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
27
|
+
it 'delegates to Renderer with its expanded_fields' do
|
28
|
+
renderer = Praxis::Renderer.new
|
29
|
+
renderer.should_receive(:render).with(person, view.expanded_fields, context: 'foo')
|
30
|
+
view.render(person, context: 'foo', renderer: renderer)
|
151
31
|
end
|
152
32
|
|
153
|
-
context '
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
attribute :parents
|
160
|
-
end
|
33
|
+
context 'defining views' do
|
34
|
+
subject(:contents) { view.contents }
|
35
|
+
its(:keys) { should match_array([:name, :email, :full_name, :parents, :address, :prior_addresses, :work_address]) }
|
36
|
+
it 'saves attributes defined on the Blueprint' do
|
37
|
+
[:name, :email, :full_name ].each do |attr|
|
38
|
+
contents[attr].should be Person.attributes[attr]
|
161
39
|
end
|
162
|
-
|
163
|
-
let(:expected_output) do
|
164
|
-
{
|
165
|
-
:name => person.name,
|
166
|
-
:parents => {
|
167
|
-
:father => person.parents.father,
|
168
|
-
:mother => person.parents.mother
|
169
|
-
}
|
170
|
-
}
|
171
|
-
end
|
172
|
-
|
173
|
-
it { should eq expected_output }
|
174
|
-
|
175
40
|
end
|
176
41
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
attribute :name
|
181
|
-
attribute :parents do
|
182
|
-
attribute :father
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
let(:expected_output) do
|
187
|
-
{
|
188
|
-
:name => person.name,
|
189
|
-
:parents => {
|
190
|
-
:father => person.parents.father
|
191
|
-
}
|
192
|
-
}
|
193
|
-
end
|
194
|
-
|
195
|
-
it { should eq expected_output }
|
42
|
+
it 'saves views for attributes for Blueprints' do
|
43
|
+
contents[:address].should be Address.views[:extended]
|
44
|
+
contents[:work_address].should be Address.views[:default]
|
196
45
|
end
|
197
46
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
context 'using a related object as an attribute' do
|
202
|
-
|
203
|
-
context 'using default view' do
|
204
|
-
let(:view) do
|
205
|
-
Praxis::View.new(:default, Person) do
|
206
|
-
attribute :name
|
207
|
-
attribute :address
|
208
|
-
end
|
209
|
-
end
|
210
|
-
let(:expected_output) do
|
211
|
-
{
|
212
|
-
:name => person.name,
|
213
|
-
:address => {
|
214
|
-
:street => address.street,
|
215
|
-
:state => address.state
|
216
|
-
}
|
217
|
-
}
|
218
|
-
end
|
219
|
-
|
220
|
-
|
221
|
-
it { should eq expected_output }
|
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
|
-
|
47
|
+
it 'does something with collections of Blueprints' do
|
48
|
+
contents[:prior_addresses].should be_kind_of(Praxis::CollectionView)
|
49
|
+
contents[:prior_addresses].contents.should eq Address.views[:state].contents
|
238
50
|
end
|
239
51
|
|
240
52
|
|
241
|
-
context '
|
242
|
-
|
243
|
-
Praxis::View
|
244
|
-
attribute :name
|
245
|
-
attribute :address, :view => :state
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
let(:expected_output) do
|
252
|
-
{
|
253
|
-
:name => person.name,
|
254
|
-
:address => {
|
255
|
-
:state => address.state
|
256
|
-
}
|
257
|
-
}
|
53
|
+
context 'creating subviews' do
|
54
|
+
it 'creates subviews when a block is used' do
|
55
|
+
contents[:parents].should be_kind_of(Praxis::View)
|
258
56
|
end
|
259
57
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
attribute :address do
|
269
|
-
attribute :state
|
58
|
+
context 'for collections' do
|
59
|
+
let(:view) do
|
60
|
+
Praxis::View.new(:testing, Person) do
|
61
|
+
attribute :name
|
62
|
+
attribute :prior_addresses do
|
63
|
+
attribute :name
|
64
|
+
attribute :street
|
65
|
+
end
|
270
66
|
end
|
271
67
|
end
|
272
|
-
end
|
273
|
-
|
274
|
-
let(:expected_output) do
|
275
|
-
{
|
276
|
-
:name => person.name,
|
277
|
-
:address => {
|
278
|
-
:state => address.state
|
279
|
-
}
|
280
|
-
}
|
281
|
-
end
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
it { should eq expected_output }
|
286
|
-
end
|
287
|
-
|
288
|
-
context 'when the related object is nil (does not respond to the related method)' do
|
289
|
-
let(:person) { Person.load(name: 'Bob') }
|
290
|
-
|
291
|
-
let(:view) do
|
292
|
-
Praxis::View.new(:default, Person) do
|
293
|
-
attribute :name
|
294
|
-
attribute :address
|
295
|
-
end
|
296
|
-
end
|
297
|
-
let(:expected_output) do
|
298
|
-
{
|
299
|
-
:name => person.name
|
300
|
-
}
|
301
|
-
end
|
302
|
-
|
303
|
-
it { should eq expected_output }
|
304
|
-
end
|
305
68
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
context 'using a related collection as an attribute' do
|
310
|
-
context 'with the default view' do
|
311
|
-
let(:view) do
|
312
|
-
Praxis::View.new(:default, Person) do
|
313
|
-
attribute :name
|
314
|
-
attribute :prior_addresses
|
315
|
-
end
|
316
|
-
end
|
317
|
-
|
318
|
-
let(:expected_output) do
|
319
|
-
{
|
320
|
-
:name => person.name,
|
321
|
-
:prior_addresses => person.prior_addresses.collect { |a| a.to_hash(view: :default)}
|
322
|
-
}
|
323
|
-
end
|
324
|
-
|
325
|
-
it { should eq expected_output }
|
326
|
-
end
|
327
|
-
|
328
|
-
|
329
|
-
context 'with a specified view' do
|
330
|
-
let(:view) do
|
331
|
-
Praxis::View.new(:default, Person) do
|
332
|
-
attribute :name
|
333
|
-
attribute :prior_addresses, :view => :state
|
69
|
+
it 'creates sub-CollectionViews from a block' do
|
70
|
+
contents[:prior_addresses].should be_kind_of(Praxis::CollectionView)
|
71
|
+
contents[:prior_addresses].contents.keys.should match_array([:name, :street])
|
334
72
|
end
|
335
73
|
end
|
336
74
|
|
337
|
-
let(:expected_output) do
|
338
|
-
{
|
339
|
-
:name => person.name,
|
340
|
-
:prior_addresses => person.prior_addresses.collect { |a| a.to_hash(view: :state)}
|
341
|
-
}
|
342
|
-
end
|
343
|
-
|
344
|
-
it { should eq expected_output }
|
345
75
|
end
|
346
76
|
|
347
77
|
end
|
348
78
|
|
349
|
-
|
350
79
|
context '#describe' do
|
351
80
|
subject(:description) { view.describe}
|
352
81
|
its(:keys){ should =~ [:attributes, :type] }
|
@@ -354,14 +83,15 @@ describe Praxis::View do
|
|
354
83
|
context 'returns attributes' do
|
355
84
|
subject { description[:attributes] }
|
356
85
|
|
357
|
-
its(:keys){ should
|
86
|
+
its(:keys){ should match_array view.contents.keys }
|
358
87
|
|
359
88
|
it 'should return empty hashes for attributes with no specially defined view' do
|
360
|
-
subject[:name].should
|
361
|
-
subject[:
|
89
|
+
subject[:name].should eq({})
|
90
|
+
subject[:email].should eq({})
|
362
91
|
end
|
363
92
|
it 'should return the view name if specified' do
|
364
|
-
subject[:address].should
|
93
|
+
subject[:address].should eq({view: :extended})
|
94
|
+
subject[:prior_addresses].should eq({view: :state})
|
365
95
|
end
|
366
96
|
end
|
367
97
|
end
|