praxis-blueprints 2.2 → 3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|