praxis-blueprints 1.0.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 +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
|