praxis-blueprints 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +28 -0
- data/.rspec +3 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +3 -0
- data/Guardfile +11 -0
- data/LICENSE +22 -0
- data/README.md +36 -0
- data/Rakefile +16 -0
- data/lib/praxis-blueprints/blueprint.rb +330 -0
- data/lib/praxis-blueprints/config_hash.rb +40 -0
- data/lib/praxis-blueprints/finalizable.rb +38 -0
- data/lib/praxis-blueprints/version.rb +3 -0
- data/lib/praxis-blueprints/view.rb +93 -0
- data/lib/praxis-blueprints.rb +14 -0
- data/praxis-blueprints.gemspec +39 -0
- data/spec/praxis-blueprints/blueprint_spec.rb +353 -0
- data/spec/praxis-blueprints/view_spec.rb +316 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/support/spec_blueprints.rb +76 -0
- metadata +270 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'praxis-blueprints/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "praxis-blueprints"
|
7
|
+
spec.version = Praxis::BLUEPRINTS_VERSION
|
8
|
+
spec.authors = ["Josep M. Blanquer","Dane Jensen"]
|
9
|
+
spec.date = "2014-08-15"
|
10
|
+
spec.summary = %q{Attributes, views, rendering and example generation for common Blueprint Structures.}
|
11
|
+
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
|
12
|
+
it results in a structured hash instead of an encoded string. Blueprints can automatically generate object structures that follow the attribute definitions."
|
13
|
+
spec.email = ["blanquer@gmail.com","dane.jensen@gmail.com"]
|
14
|
+
|
15
|
+
spec.homepage = "https://github.com/rightscale/praxis-blueprints"
|
16
|
+
spec.license = "MIT"
|
17
|
+
spec.required_ruby_version = ">=2.1"
|
18
|
+
|
19
|
+
spec.files = `git ls-files -z`.split("\x0")
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_runtime_dependency(%q<randexp>, ["~> 0"])
|
24
|
+
spec.add_runtime_dependency(%q<attributor>, ["~> 2"])
|
25
|
+
spec.add_runtime_dependency(%q<activesupport>, ["~> 4"])
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
28
|
+
spec.add_development_dependency "rake", "~> 0"
|
29
|
+
|
30
|
+
spec.add_development_dependency(%q<redcarpet>, ["< 3.0"])
|
31
|
+
spec.add_development_dependency(%q<yard>, ["~> 0.8.7"])
|
32
|
+
spec.add_development_dependency(%q<guard>, ["~> 2"])
|
33
|
+
spec.add_development_dependency(%q<guard-rspec>, [">= 0"])
|
34
|
+
spec.add_development_dependency(%q<rspec>, ["< 2.99"])
|
35
|
+
spec.add_development_dependency(%q<pry>, ["~> 0"])
|
36
|
+
spec.add_development_dependency(%q<pry-byebug>, ["~> 1"])
|
37
|
+
spec.add_development_dependency(%q<pry-stack_explorer>, ["~> 0"])
|
38
|
+
spec.add_development_dependency(%q<fuubar>, ["~> 1"])
|
39
|
+
end
|
@@ -0,0 +1,353 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Praxis::Blueprint do
|
4
|
+
|
5
|
+
subject(:blueprint_class) { Person }
|
6
|
+
|
7
|
+
|
8
|
+
context 'deterministic examples' do
|
9
|
+
it 'works' do
|
10
|
+
person_1 = Person.example('person 1')
|
11
|
+
person_2 = Person.example('person 1')
|
12
|
+
|
13
|
+
person_1.name.should eq(person_2.name)
|
14
|
+
person_1.address.name.should eq(person_2.address.name)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'implicit master view' do
|
19
|
+
subject(:master_view) { Person.view(:master) }
|
20
|
+
|
21
|
+
it { should_not be(nil) }
|
22
|
+
it 'contains all attributes' do
|
23
|
+
master_view.contents.keys.should =~ Person.attributes.keys
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'uses :master view for rendering blueprint sub-attributes' do
|
27
|
+
dumpable, dumpable_opts = master_view.contents[:address]
|
28
|
+
dumpable_opts[:view].should == :master
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'creating a new Blueprint class' do
|
33
|
+
subject!(:blueprint_class) do
|
34
|
+
Class.new(Praxis::Blueprint) do
|
35
|
+
attributes do
|
36
|
+
attribute :id, Integer
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
its(:finalized?) { should be(false) }
|
42
|
+
|
43
|
+
context '.finalize on Praxis::Blueprint' do
|
44
|
+
before do
|
45
|
+
blueprint_class.should_receive(:_finalize!).and_call_original
|
46
|
+
Praxis::Blueprint.finalize!
|
47
|
+
end
|
48
|
+
|
49
|
+
its(:finalized?) { should be(true) }
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
context '.finalize on that subclass' do
|
54
|
+
before do
|
55
|
+
blueprint_class.should_receive(:_finalize!).and_call_original
|
56
|
+
blueprint_class.finalize!
|
57
|
+
end
|
58
|
+
|
59
|
+
its(:finalized?) { should be(true) }
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'creating a base abstract Blueprint class without attributes' do
|
66
|
+
subject!(:blueprint_class) do
|
67
|
+
Class.new(Praxis::Blueprint)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'skips attribute definition' do
|
71
|
+
blueprint_class.should_receive(:_finalize!).and_call_original
|
72
|
+
blueprint_class.should_not_receive(:define_attribute)
|
73
|
+
blueprint_class.finalize!
|
74
|
+
blueprint_class.finalized?.should be(true)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'has an inner Struct class for the attributes' do
|
80
|
+
blueprint_class.attribute.type.should be blueprint_class::Struct
|
81
|
+
end
|
82
|
+
|
83
|
+
context '.views' do
|
84
|
+
it { blueprint_class.should respond_to(:views) }
|
85
|
+
it 'sorta has view objects' do
|
86
|
+
blueprint_class.views.should have_key(:default)
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'an instance' do
|
92
|
+
shared_examples 'a blueprint instance' do
|
93
|
+
let(:expected_name) { blueprint_instance.name }
|
94
|
+
|
95
|
+
context '#render' do
|
96
|
+
let(:view) { :default }
|
97
|
+
subject(:output) { blueprint_instance.render(view) }
|
98
|
+
|
99
|
+
it { should have_key(:name) }
|
100
|
+
it 'has the right values' do
|
101
|
+
subject[:name].should eq(expected_name)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'validation' do
|
106
|
+
subject(:errors) { blueprint_class.validate(blueprint_instance) }
|
107
|
+
pending do
|
108
|
+
it { should be_empty }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
context 'from Blueprint.example' do
|
115
|
+
subject(:blueprint_instance) { blueprint_class.example }
|
116
|
+
it_behaves_like 'a blueprint instance'
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'wrapping an object' do
|
120
|
+
|
121
|
+
let(:resource) do
|
122
|
+
double("resource",
|
123
|
+
name: 'Bob',
|
124
|
+
full_name: FullName.example,
|
125
|
+
address: Address.example,
|
126
|
+
email: "bob@example.com",
|
127
|
+
aliases: [],
|
128
|
+
prior_addresses: [],
|
129
|
+
parents: double('parents', father: /[:first_name:]/.gen, mother: /[:first_name:]/.gen),
|
130
|
+
href: "www.example.com",
|
131
|
+
alive: true)
|
132
|
+
end
|
133
|
+
|
134
|
+
subject(:blueprint_instance) { blueprint_class.new(resource) }
|
135
|
+
|
136
|
+
it_behaves_like 'a blueprint instance'
|
137
|
+
|
138
|
+
context 'creating additional blueprint instances from that object' do
|
139
|
+
subject(:additional_instance) { blueprint_class.new(resource) }
|
140
|
+
|
141
|
+
context 'with caching enabled' do
|
142
|
+
around do |example|
|
143
|
+
Praxis::Blueprint.caching_enabled = true
|
144
|
+
Praxis::Blueprint.cache = Hash.new { |h,k| h[k] = Hash.new }
|
145
|
+
example.run
|
146
|
+
|
147
|
+
Praxis::Blueprint.caching_enabled = false
|
148
|
+
Praxis::Blueprint.cache = nil
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'uses the cache to memoize instance creation' do
|
152
|
+
additional_instance.should be(additional_instance)
|
153
|
+
blueprint_class.cache.should have_key(resource)
|
154
|
+
blueprint_class.cache[resource].should be(blueprint_instance)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'with caching disabled' do
|
159
|
+
it { should_not be blueprint_instance }
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
context '.validate' do
|
170
|
+
let(:hash) { {name: 'bob'} }
|
171
|
+
let(:person) { Person.load(hash) }
|
172
|
+
subject(:errors) { person.validate }
|
173
|
+
|
174
|
+
context 'that is valid' do
|
175
|
+
it { should be_empty }
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'with invalid sub-attribute' do
|
179
|
+
let(:hash) { {name: 'bob', address: {state: "ME"}} }
|
180
|
+
|
181
|
+
it { should have(1).item }
|
182
|
+
its(:first) { should =~ /Attribute \$.address.state/ }
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'for objects of the wrong type' do
|
186
|
+
it 'raises an error' do
|
187
|
+
expect {
|
188
|
+
Person.validate(Object.new)
|
189
|
+
}.to raise_error(ArgumentError, /Error validating .* as Person for an object of type Object/)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
context '.load' do
|
196
|
+
let(:hash) do
|
197
|
+
{
|
198
|
+
:name => 'Bob',
|
199
|
+
:full_name => {:first => 'Robert', :last => 'Robertson'},
|
200
|
+
:address => {:street => 'main', :state => 'OR'}
|
201
|
+
}
|
202
|
+
end
|
203
|
+
subject(:person) { Person.load(hash) }
|
204
|
+
|
205
|
+
it { should be_kind_of(Person) }
|
206
|
+
|
207
|
+
context 'recursively loading sub-attributes' do
|
208
|
+
context 'for a Blueprint' do
|
209
|
+
subject(:address) { person.address }
|
210
|
+
it { should be_kind_of(Address) }
|
211
|
+
end
|
212
|
+
context 'for an Attributor::Model' do
|
213
|
+
subject(:full_name) { person.full_name }
|
214
|
+
it { should be_kind_of(FullName) }
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
context 'decorators' do
|
222
|
+
let(:name) { 'Soren II' }
|
223
|
+
|
224
|
+
let(:object) { Person.example.object }
|
225
|
+
subject(:person) { Person.new(object, decorators) }
|
226
|
+
|
227
|
+
|
228
|
+
context 'as a hash' do
|
229
|
+
let(:decorators) { {name: name} }
|
230
|
+
it do
|
231
|
+
pers = person
|
232
|
+
# binding.pry
|
233
|
+
pers.name.should eq('Soren II')
|
234
|
+
end
|
235
|
+
|
236
|
+
its(:name) { should be(name) }
|
237
|
+
|
238
|
+
context 'an additional instance with the equivalent hash' do
|
239
|
+
subject(:additional_person) { Person.new(object, {name: name}) }
|
240
|
+
it { should_not be person }
|
241
|
+
end
|
242
|
+
|
243
|
+
context 'an additional instance with the same hash object' do
|
244
|
+
subject(:additional_person) { Person.new(object, decorators) }
|
245
|
+
it { should_not be person }
|
246
|
+
end
|
247
|
+
|
248
|
+
context 'an instance of the same object without decorators' do
|
249
|
+
subject(:additional_person) { Person.new(object) }
|
250
|
+
it { should_not be person }
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'as an object' do
|
255
|
+
let(:decorators) { double("decorators", name: name) }
|
256
|
+
its(:name) { should be(name) }
|
257
|
+
|
258
|
+
context 'an additional instance with the same object' do
|
259
|
+
subject(:additional_person) { Person.new(object, decorators) }
|
260
|
+
it { should_not be person }
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
|
267
|
+
context 'with a provided :reference option on attributes' do
|
268
|
+
context 'that does not match the value set on the class' do
|
269
|
+
|
270
|
+
subject(:mismatched_reference) do
|
271
|
+
Class.new(Praxis::Blueprint) do
|
272
|
+
self.reference = Class.new(Praxis::Blueprint)
|
273
|
+
attributes(reference: Class.new(Praxis::Blueprint)) {}
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'should raise an error' do
|
278
|
+
expect {
|
279
|
+
mismatched_reference.attributes
|
280
|
+
}.to raise_error
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
|
287
|
+
context '.example' do
|
288
|
+
context 'with some attribute values provided' do
|
289
|
+
let(:name) { 'Sir Bobbert' }
|
290
|
+
subject(:person) { Person.example(name: name) }
|
291
|
+
its(:name) { should eq(name) }
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
context '#render' do
|
296
|
+
let(:person) { Person.example }
|
297
|
+
let(:view_name) { :default }
|
298
|
+
subject(:output) { person.render(view_name) }
|
299
|
+
|
300
|
+
context 'with a sub-attribute that is a blueprint' do
|
301
|
+
|
302
|
+
it { should have_key(:name) }
|
303
|
+
it { should have_key(:address) }
|
304
|
+
it 'renders the sub-attribute correctly' do
|
305
|
+
output[:address].should have_key(:street)
|
306
|
+
output[:address].should have_key(:state)
|
307
|
+
end
|
308
|
+
|
309
|
+
it 'reports a dump error with the appropriate context' do
|
310
|
+
person.address.should_receive(:state).and_raise("Kaboom")
|
311
|
+
expect {
|
312
|
+
person.render(view_name, context: ['special_root'])
|
313
|
+
}.to raise_error(/Error while dumping attribute state of type Address for context special_root.address .*. Reason: .*Kaboom/)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
|
318
|
+
context 'with sub-attribute that is an Attributor::Model' do
|
319
|
+
it { should have_key(:full_name) }
|
320
|
+
it 'renders the model correctly' do
|
321
|
+
output[:full_name].should be_kind_of(Hash)
|
322
|
+
output[:full_name].should have_key(:first)
|
323
|
+
output[:full_name].should have_key(:last)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
|
328
|
+
# context 'with circular references' do
|
329
|
+
# let(:view_name) { :master }
|
330
|
+
|
331
|
+
# # TODO: think about circular references without caching
|
332
|
+
# around do |example|
|
333
|
+
# Praxis::Blueprint.caching_enabled = true
|
334
|
+
# example.run
|
335
|
+
# Praxis::Blueprint.caching_enabled = false
|
336
|
+
# end
|
337
|
+
|
338
|
+
# it 'terminates' do
|
339
|
+
# expect {
|
340
|
+
# Person.example.render(:master)
|
341
|
+
# }.to_not raise_error
|
342
|
+
# end
|
343
|
+
|
344
|
+
# it 'renders Praxis::Blueprint::CIRCULAR_REFERENCE_MARKER for circular references' do
|
345
|
+
# person.address.resident.should be(person)
|
346
|
+
# output[:address][:resident].should eq(Praxis::Blueprint::CIRCULAR_REFERENCE_MARKER)
|
347
|
+
# end
|
348
|
+
|
349
|
+
# end
|
350
|
+
|
351
|
+
end
|
352
|
+
|
353
|
+
end
|
@@ -0,0 +1,316 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Praxis::View do
|
4
|
+
|
5
|
+
let(:person) { Person.example(['person']) }
|
6
|
+
let(:address) { person.address }
|
7
|
+
|
8
|
+
let(:view) do
|
9
|
+
Praxis::View.new(:tiny, Person) do
|
10
|
+
attribute :name
|
11
|
+
attribute :alive
|
12
|
+
attribute :address, view: :state
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
subject(:output) { view.to_hash(person) }
|
17
|
+
|
18
|
+
|
19
|
+
it 'can generate examples' do
|
20
|
+
view.example.should have_key(:name)
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'direct attributes' do
|
24
|
+
context 'with undisputably existing values' do
|
25
|
+
let(:person) { OpenStruct.new(:name=>'somename', :alive=>true)}
|
26
|
+
let(:expected_output) do
|
27
|
+
{
|
28
|
+
:name => 'somename',
|
29
|
+
:alive => true
|
30
|
+
}
|
31
|
+
end
|
32
|
+
it 'should show up' do
|
33
|
+
subject.should == expected_output
|
34
|
+
end
|
35
|
+
end
|
36
|
+
context 'with nil values' do
|
37
|
+
let(:person) { OpenStruct.new(:name=>'alive_is_nil', :alive=>nil)}
|
38
|
+
let(:expected_output) do
|
39
|
+
{
|
40
|
+
:name => 'alive_is_nil'
|
41
|
+
}
|
42
|
+
end
|
43
|
+
it 'are skipped completely' do
|
44
|
+
subject.should == expected_output
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'with false values' do
|
49
|
+
let(:person) { OpenStruct.new(:name=>'alive_is_false', :alive=>false)}
|
50
|
+
let(:expected_output) do
|
51
|
+
{
|
52
|
+
:name => 'alive_is_false',
|
53
|
+
:alive => false
|
54
|
+
}
|
55
|
+
end
|
56
|
+
it 'should still show up, since "false" is really a valid value' do
|
57
|
+
subject.should == expected_output
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'nested attributes' do
|
64
|
+
|
65
|
+
context 'without block' do
|
66
|
+
let(:view) do
|
67
|
+
Praxis::View.new(:parents, Person) do
|
68
|
+
attribute :name
|
69
|
+
attribute :parents
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
let(:expected_output) do
|
74
|
+
{
|
75
|
+
:name => person.name,
|
76
|
+
:parents => {
|
77
|
+
:father => person.parents.father,
|
78
|
+
:mother => person.parents.mother
|
79
|
+
}
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
it { should eq expected_output }
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'with block' do
|
88
|
+
let(:view) do
|
89
|
+
Praxis::View.new(:paternal, Person) do
|
90
|
+
attribute :name
|
91
|
+
attribute :parents do
|
92
|
+
attribute :father
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
let(:expected_output) do
|
97
|
+
{
|
98
|
+
:name => person.name,
|
99
|
+
:parents => {
|
100
|
+
:father => person.parents.father
|
101
|
+
}
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
it { should eq expected_output }
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
context 'using a related object as an attribute' do
|
112
|
+
|
113
|
+
context 'using default view' do
|
114
|
+
let(:view) do
|
115
|
+
Praxis::View.new(:default, Person) do
|
116
|
+
attribute :name
|
117
|
+
attribute :address
|
118
|
+
end
|
119
|
+
end
|
120
|
+
let(:expected_output) do
|
121
|
+
{
|
122
|
+
:name => person.name,
|
123
|
+
:address => {
|
124
|
+
:street => address.street,
|
125
|
+
:state => address.state
|
126
|
+
}
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
it { should eq expected_output }
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
context 'specifying a view' do
|
137
|
+
let(:view) do
|
138
|
+
Praxis::View.new(:default, Person) do
|
139
|
+
attribute :name
|
140
|
+
attribute :address, :view => :state
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
|
146
|
+
let(:expected_output) do
|
147
|
+
{
|
148
|
+
:name => person.name,
|
149
|
+
:address => {
|
150
|
+
:state => address.state
|
151
|
+
}
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
it { should eq expected_output }
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
context 'with some sort of "in-lined" view' do
|
160
|
+
let(:view) do
|
161
|
+
Praxis::View.new(:default, Person) do
|
162
|
+
attribute :name
|
163
|
+
attribute :address do
|
164
|
+
attribute :state
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
let(:expected_output) do
|
170
|
+
{
|
171
|
+
:name => person.name,
|
172
|
+
:address => {
|
173
|
+
:state => address.state
|
174
|
+
}
|
175
|
+
}
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
|
180
|
+
it { should eq expected_output }
|
181
|
+
end
|
182
|
+
|
183
|
+
context 'when the related object is nil (does not respond to the related method)' do
|
184
|
+
let(:resource) { OpenStruct.new(name: "Bob") }
|
185
|
+
let(:person) { Person.new(resource) }
|
186
|
+
|
187
|
+
|
188
|
+
let(:view) do
|
189
|
+
Praxis::View.new(:default, Person) do
|
190
|
+
attribute :name
|
191
|
+
attribute :address
|
192
|
+
end
|
193
|
+
end
|
194
|
+
let(:expected_output) do
|
195
|
+
{
|
196
|
+
:name => person.name
|
197
|
+
}
|
198
|
+
end
|
199
|
+
|
200
|
+
it { should eq expected_output }
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
context 'using a related collection as an attribute' do
|
207
|
+
context 'with the default view' do
|
208
|
+
let(:view) do
|
209
|
+
Praxis::View.new(:default, Person) do
|
210
|
+
attribute :name
|
211
|
+
attribute :prior_addresses
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
let(:expected_output) do
|
216
|
+
{
|
217
|
+
:name => person.name,
|
218
|
+
:prior_addresses => person.prior_addresses.collect { |a| a.to_hash(:default)}
|
219
|
+
}
|
220
|
+
end
|
221
|
+
|
222
|
+
it { should eq expected_output }
|
223
|
+
|
224
|
+
|
225
|
+
end
|
226
|
+
|
227
|
+
context 'with a specified view' do
|
228
|
+
let(:view) do
|
229
|
+
Praxis::View.new(:default, Person) do
|
230
|
+
attribute :name
|
231
|
+
attribute :prior_addresses, :view => :state
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
let(:expected_output) do
|
236
|
+
{
|
237
|
+
:name => person.name,
|
238
|
+
:prior_addresses => person.prior_addresses.collect { |a| a.to_hash(:state)}
|
239
|
+
}
|
240
|
+
end
|
241
|
+
|
242
|
+
it { should eq expected_output }
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|
246
|
+
# context 'with embedded related objects' do
|
247
|
+
# context '#embed' do
|
248
|
+
# let(:view) do
|
249
|
+
# Praxis::View.new(:default, Person) do
|
250
|
+
# attribute :name
|
251
|
+
# embed :address
|
252
|
+
# end
|
253
|
+
# end
|
254
|
+
|
255
|
+
# let(:expected_output) do
|
256
|
+
# {
|
257
|
+
# :name => person.name,
|
258
|
+
# :address => {
|
259
|
+
# :street => address.street,
|
260
|
+
# :state => address.state
|
261
|
+
# }}
|
262
|
+
# end
|
263
|
+
|
264
|
+
|
265
|
+
# before do
|
266
|
+
# address.should_receive(:to_hash).with(:default).and_call_original
|
267
|
+
# end
|
268
|
+
|
269
|
+
# it { should == expected_output }
|
270
|
+
|
271
|
+
# end
|
272
|
+
|
273
|
+
# context '#embed_collection' do
|
274
|
+
# let(:view) do
|
275
|
+
# Praxis::View.new(:aka, Person) do
|
276
|
+
# attribute :name
|
277
|
+
# embed_collection :aliases
|
278
|
+
# end
|
279
|
+
# end
|
280
|
+
|
281
|
+
# subject(:output) { view.to_hash(person) }
|
282
|
+
|
283
|
+
|
284
|
+
# let(:expected_output) do
|
285
|
+
# {
|
286
|
+
# :name => person.name,
|
287
|
+
# :aliases => person.aliases.collect { |a| a.to_hash(:default)}
|
288
|
+
# }
|
289
|
+
# end
|
290
|
+
|
291
|
+
# it { should == expected_output }
|
292
|
+
# end
|
293
|
+
# end
|
294
|
+
|
295
|
+
context '#describe' do
|
296
|
+
subject(:description) { view.describe}
|
297
|
+
its(:keys){ should == [:attributes] }
|
298
|
+
|
299
|
+
context 'returns attributes' do
|
300
|
+
subject { description[:attributes] }
|
301
|
+
|
302
|
+
its(:keys){ should == [:name,:alive,:address] }
|
303
|
+
|
304
|
+
it 'should return empty hashes for attributes with no specially defined view' do
|
305
|
+
subject[:name].should == {}
|
306
|
+
subject[:alive].should == {}
|
307
|
+
end
|
308
|
+
it 'should return the view name if specified' do
|
309
|
+
subject[:address].should == {view: :state}
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
|
315
|
+
#it 'has a spec for validating attribute names'
|
316
|
+
end
|