praxis-blueprints 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -1
- data/lib/praxis-blueprints/blueprint.rb +64 -84
- data/lib/praxis-blueprints/version.rb +1 -1
- data/lib/praxis-blueprints/view.rb +20 -5
- data/praxis-blueprints.gemspec +1 -1
- data/spec/praxis-blueprints/blueprint_spec.rb +173 -148
- data/spec/praxis-blueprints/view_spec.rb +79 -11
- data/spec/support/spec_blueprints.rb +10 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48fb0aab37d8848325be498e9e89221ce803cdfe
|
4
|
+
data.tar.gz: 3bb509c2daadb670527d0cf43e9b919573273059
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 83cfc54c0609a7586f4961ebb698b87018c3e1f0a191de3e9f0db4b2bed0eac9cea58a828dd7b2c151448ec3e5fb7f224fe76810107969685d6314d8b657f2d7
|
7
|
+
data.tar.gz: f18a5b85e4b17b104912910725a647ce9886c2cede2c617e313a1eb185de73c785af265bc2ce5ef9d805889fbdfa911d7887b335a9778dbf34ea938e3e8a95fd
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
# praxis-blueprints changelog
|
2
2
|
|
3
|
-
## next
|
3
|
+
## next
|
4
|
+
|
5
|
+
## 1.3.0
|
6
|
+
|
7
|
+
* Added `include_nil` option to `View` for refining the rendering of nil values.
|
8
|
+
* This can be set when defining the view with `Blueprint.view`, and defaults to false. For example: `view :default, include_nil: true { ... }`.
|
9
|
+
* Fixed `Blueprint.describe` to output the proper `:id`. That is: the 'id` from the Class name, rather than the internal Struct attribute.
|
10
|
+
* Fixed `Blueprint.dump(nil)` raising a `NoMethodError`
|
11
|
+
* Fixed `Blueprint.load(nil)` to properly support the `recurse` option.
|
4
12
|
|
5
13
|
## 1.2.0
|
6
14
|
|
@@ -3,6 +3,7 @@ require 'ostruct'
|
|
3
3
|
# Blueprint ==
|
4
4
|
# - part implementation definition for attributes
|
5
5
|
# - part container for views
|
6
|
+
|
6
7
|
module Praxis
|
7
8
|
class Blueprint
|
8
9
|
include Attributor::Type
|
@@ -56,14 +57,14 @@ module Praxis
|
|
56
57
|
def self.describe(shallow=false)
|
57
58
|
type_name = self.ancestors.find { |k| k.name && !k.name.empty? }.name
|
58
59
|
|
59
|
-
description = self.attribute.type.describe(shallow).merge!(name: type_name)
|
60
|
+
description = self.attribute.type.describe(shallow).merge!(id: self.id, name: type_name)
|
60
61
|
|
61
62
|
unless shallow
|
62
63
|
description[:views] = self.views.each_with_object({}) do |(view_name, view), hash|
|
63
64
|
hash[view_name] = view.describe
|
64
65
|
end
|
65
66
|
end
|
66
|
-
|
67
|
+
|
67
68
|
description
|
68
69
|
end
|
69
70
|
|
@@ -110,11 +111,14 @@ module Praxis
|
|
110
111
|
|
111
112
|
def self.load(value,context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
|
112
113
|
case value
|
113
|
-
when
|
114
|
+
when self
|
114
115
|
value
|
115
|
-
when Hash, String
|
116
|
+
when nil, Hash, String
|
116
117
|
# Need to parse/deserialize first
|
117
|
-
|
118
|
+
# or apply default/recursive loading options if necessary
|
119
|
+
if (value = self.attribute.load(value,context, **options))
|
120
|
+
self.new(value)
|
121
|
+
end
|
118
122
|
else
|
119
123
|
# Just wrap whatever value
|
120
124
|
self.new(value)
|
@@ -171,59 +175,19 @@ module Praxis
|
|
171
175
|
end
|
172
176
|
|
173
177
|
|
174
|
-
def self.view(name, &block)
|
178
|
+
def self.view(name, **options, &block)
|
175
179
|
if block_given?
|
176
|
-
return self.views[name] = View.new(name, self, &block)
|
180
|
+
return self.views[name] = View.new(name, self, **options, &block)
|
177
181
|
end
|
178
182
|
|
179
183
|
self.views[name]
|
180
184
|
end
|
181
185
|
|
182
186
|
def self.dump(object, view: :default, context: Attributor::DEFAULT_ROOT_CONTEXT, **opts)
|
183
|
-
object = self.load(object, context)
|
184
|
-
object.
|
185
|
-
end
|
186
|
-
|
187
|
-
|
188
|
-
def initialize(object, decorators=nil)
|
189
|
-
# TODO: decide what sort of type checking (if any) we want to perform here.
|
190
|
-
@object = object
|
191
|
-
@decorators = if decorators.kind_of?(Hash) && decorators.any?
|
192
|
-
OpenStruct.new(decorators)
|
193
|
-
else
|
194
|
-
decorators
|
195
|
-
end
|
196
|
-
@rendered_views = {}
|
197
|
-
@validating = false
|
198
|
-
|
199
|
-
# OPTIMIZE: revisit the circular rendering tracking.
|
200
|
-
# removing this results in a significant performance
|
201
|
-
# and memory use savings.
|
202
|
-
@active_renders = []
|
203
|
-
end
|
204
|
-
|
205
|
-
|
206
|
-
# Render the wrapped data with the given view
|
207
|
-
def render(view_name=:default, context: Attributor::DEFAULT_ROOT_CONTEXT)
|
208
|
-
unless (view = self.class.views[view_name])
|
209
|
-
raise "view with name '#{view_name.inspect}' is not defined in #{self.class}"
|
210
|
-
end
|
187
|
+
object = self.load(object, context, **opts)
|
188
|
+
return nil if object.nil?
|
211
189
|
|
212
|
-
|
213
|
-
return CIRCULAR_REFERENCE_MARKER if @active_renders.include?(view_name)
|
214
|
-
@active_renders << view_name
|
215
|
-
|
216
|
-
@rendered_views[view_name] = view.dump(self, context: context)
|
217
|
-
ensure
|
218
|
-
@active_renders.delete view_name
|
219
|
-
end
|
220
|
-
|
221
|
-
|
222
|
-
alias_method :to_hash, :render
|
223
|
-
|
224
|
-
|
225
|
-
def dump(view: :default, context: Attributor::DEFAULT_ROOT_CONTEXT)
|
226
|
-
self.render(view, context: context)
|
190
|
+
object.render(view, context: context)
|
227
191
|
end
|
228
192
|
|
229
193
|
# Internal finalize! logic
|
@@ -256,46 +220,23 @@ module Praxis
|
|
256
220
|
|
257
221
|
|
258
222
|
def self.define_reader!(name)
|
259
|
-
attribute = self.attributes[name]
|
260
|
-
if attribute.type < Praxis::Blueprint
|
261
|
-
define_blueprint_reader!(name)
|
262
|
-
else
|
263
|
-
define_direct_reader!(name)
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
def self.define_blueprint_reader!(name)
|
268
|
-
# it's faster to use define_method in this case than module_eval
|
269
|
-
# because we save the attribute lookup on every access.
|
270
|
-
attribute = self.attributes[name]
|
271
|
-
define_method(name) do
|
272
|
-
if @decorators && @decorators.respond_to?(name)
|
273
|
-
@decorators.send(name)
|
274
|
-
else
|
275
|
-
value = @object.send(name)
|
276
|
-
return value if value.nil? || value.kind_of?(attribute.type)
|
277
|
-
attribute.type.load(value)
|
278
|
-
end
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
def self.define_direct_reader!(name)
|
283
223
|
attribute = self.attributes[name]
|
284
224
|
# TODO: profile and optimize
|
285
|
-
# because we use the attribute in the reader,
|
286
|
-
# it's likely faster to use define_method here
|
225
|
+
# because we use the attribute in the reader,
|
226
|
+
# it's likely faster to use define_method here
|
287
227
|
# than module_eval, but we should make sure.
|
288
228
|
define_method(name) do
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
229
|
+
if @decorators && @decorators.respond_to?(name)
|
230
|
+
@decorators.send(name)
|
231
|
+
else
|
232
|
+
value = @object.__send__(name)
|
233
|
+
return value if value.nil? || value.kind_of?(attribute.type)
|
234
|
+
attribute.load(value)
|
235
|
+
end
|
296
236
|
end
|
297
237
|
end
|
298
238
|
|
239
|
+
|
299
240
|
def self.generate_master_view!
|
300
241
|
attributes = self.attributes
|
301
242
|
view :master do
|
@@ -308,6 +249,46 @@ module Praxis
|
|
308
249
|
end
|
309
250
|
|
310
251
|
|
252
|
+
def initialize(object, decorators=nil)
|
253
|
+
# TODO: decide what sort of type checking (if any) we want to perform here.
|
254
|
+
@object = object
|
255
|
+
@decorators = if decorators.kind_of?(Hash) && decorators.any?
|
256
|
+
OpenStruct.new(decorators)
|
257
|
+
else
|
258
|
+
decorators
|
259
|
+
end
|
260
|
+
@rendered_views = {}
|
261
|
+
@validating = false
|
262
|
+
|
263
|
+
# OPTIMIZE: revisit the circular rendering tracking.
|
264
|
+
# removing this results in a significant performance
|
265
|
+
# and memory use savings.
|
266
|
+
@active_renders = []
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
# Render the wrapped data with the given view
|
271
|
+
def render(view_name=:default, context: Attributor::DEFAULT_ROOT_CONTEXT)
|
272
|
+
unless (view = self.class.views[view_name])
|
273
|
+
raise "view with name '#{view_name.inspect}' is not defined in #{self.class}"
|
274
|
+
end
|
275
|
+
|
276
|
+
return @rendered_views[view_name] if @rendered_views.has_key? view_name
|
277
|
+
return CIRCULAR_REFERENCE_MARKER if @active_renders.include?(view_name)
|
278
|
+
@active_renders << view_name
|
279
|
+
|
280
|
+
@rendered_views[view_name] = view.dump(self, context: context)
|
281
|
+
ensure
|
282
|
+
@active_renders.delete view_name
|
283
|
+
end
|
284
|
+
alias_method :to_hash, :render
|
285
|
+
|
286
|
+
|
287
|
+
def dump(view: :default, context: Attributor::DEFAULT_ROOT_CONTEXT)
|
288
|
+
self.render(view, context: context)
|
289
|
+
end
|
290
|
+
|
291
|
+
|
311
292
|
def validate(context=Attributor::DEFAULT_ROOT_CONTEXT)
|
312
293
|
raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context == nil
|
313
294
|
context = [context] if context.is_a? ::String
|
@@ -328,7 +309,6 @@ module Praxis
|
|
328
309
|
@validating = false
|
329
310
|
end
|
330
311
|
|
331
|
-
|
332
312
|
end
|
333
313
|
|
334
314
|
end
|
@@ -1,16 +1,24 @@
|
|
1
1
|
module Praxis
|
2
2
|
|
3
3
|
class View
|
4
|
-
attr_reader :schema
|
4
|
+
attr_reader :schema
|
5
|
+
attr_reader :contents
|
6
|
+
attr_reader :name
|
7
|
+
attr_reader :options
|
5
8
|
|
9
|
+
attr_reader :include_nil
|
6
10
|
|
7
|
-
def initialize(name, schema, &block)
|
11
|
+
def initialize(name, schema, **options, &block)
|
8
12
|
@name = name
|
9
13
|
@schema = schema
|
10
14
|
@contents = ::Hash.new
|
11
15
|
@block = block
|
12
|
-
end
|
13
16
|
|
17
|
+
@include_nil = options.fetch(:include_nil, false)
|
18
|
+
|
19
|
+
@options = options
|
20
|
+
|
21
|
+
end
|
14
22
|
|
15
23
|
def contents
|
16
24
|
if @block
|
@@ -23,15 +31,22 @@ module Praxis
|
|
23
31
|
|
24
32
|
|
25
33
|
def dump(object, context: Attributor::DEFAULT_ROOT_CONTEXT,**opts)
|
34
|
+
|
26
35
|
self.contents.each_with_object({}) do |(name, (dumpable, dumpable_opts)), hash|
|
27
|
-
|
36
|
+
unless object.respond_to?(name)
|
37
|
+
warn "#{object} does not respond to #{name} during rendering???"
|
38
|
+
next
|
39
|
+
end
|
28
40
|
|
29
41
|
begin
|
30
42
|
value = object.send(name)
|
31
43
|
rescue => e
|
32
44
|
raise Attributor::DumpError, context: context, name: name, type: object.class, original_exception: e
|
33
45
|
end
|
34
|
-
|
46
|
+
|
47
|
+
if value.nil?
|
48
|
+
next unless @include_nil
|
49
|
+
end
|
35
50
|
|
36
51
|
# FIXME: this is such an ugly way to do this. Need attributor#67.
|
37
52
|
if dumpable.kind_of?(View) || dumpable.kind_of?(CollectionView)
|
data/praxis-blueprints.gemspec
CHANGED
@@ -20,7 +20,7 @@ it results in a structured hash instead of an encoded string. Blueprints can aut
|
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
22
|
spec.add_runtime_dependency(%q<randexp>, ["~> 0"])
|
23
|
-
spec.add_runtime_dependency(%q<attributor>, ["~> 2"])
|
23
|
+
spec.add_runtime_dependency(%q<attributor>, ["~> 2.6"])
|
24
24
|
spec.add_runtime_dependency(%q<activesupport>, [">= 3"])
|
25
25
|
|
26
26
|
spec.add_development_dependency "bundler", "~> 1.6"
|
@@ -117,21 +117,25 @@ describe Praxis::Blueprint do
|
|
117
117
|
|
118
118
|
context 'wrapping an object' do
|
119
119
|
|
120
|
-
let(:
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
120
|
+
let(:data) do
|
121
|
+
{
|
122
|
+
name: 'Bob',
|
123
|
+
full_name: FullName.example,
|
124
|
+
address: Address.example,
|
125
|
+
email: "bob@example.com",
|
126
|
+
aliases: [],
|
127
|
+
prior_addresses: [],
|
128
|
+
parents: { father: /[:first_name:]/.gen, mother: /[:first_name:]/.gen},
|
129
|
+
href: "www.example.com",
|
130
|
+
alive: true
|
131
|
+
}
|
131
132
|
end
|
132
133
|
|
134
|
+
let(:resource) { blueprint_class.load(data).object }
|
135
|
+
|
133
136
|
subject(:blueprint_instance) { blueprint_class.new(resource) }
|
134
137
|
|
138
|
+
|
135
139
|
it_behaves_like 'a blueprint instance'
|
136
140
|
|
137
141
|
context 'creating additional blueprint instances from that object' do
|
@@ -153,7 +157,7 @@ describe Praxis::Blueprint do
|
|
153
157
|
blueprint_class.cache[resource].should be(blueprint_instance)
|
154
158
|
end
|
155
159
|
end
|
156
|
-
|
160
|
+
|
157
161
|
context 'with caching disabled' do
|
158
162
|
it { should_not be blueprint_instance }
|
159
163
|
end
|
@@ -164,7 +168,29 @@ describe Praxis::Blueprint do
|
|
164
168
|
|
165
169
|
end
|
166
170
|
|
171
|
+
context '.describe' do
|
172
|
+
before do
|
173
|
+
expect(blueprint_class.attribute.type).to receive(:describe).and_return(type_describe)
|
174
|
+
end
|
175
|
+
let(:type_describe){ {name: "Fake", id: "From Struct attr", other: "type attribute"} }
|
176
|
+
|
177
|
+
context 'for non-shallow descriptions' do
|
178
|
+
subject(:output){ blueprint_class.describe }
|
179
|
+
its([:name]){ should eq(blueprint_class.name)}
|
180
|
+
its([:id]){ should eq(blueprint_class.id)}
|
181
|
+
its([:other]){ should eq("type attribute")}
|
182
|
+
its([:views]){ should be_kind_of(Hash)}
|
183
|
+
it 'should contain the an entry for each view' do
|
184
|
+
subject[:views].keys.should include(:default, :current, :extended, :master)
|
185
|
+
end
|
186
|
+
end
|
167
187
|
|
188
|
+
context 'for shallow descriptions' do
|
189
|
+
it 'should not include views' do
|
190
|
+
blueprint_class.describe(true).key?(:views).should be(false)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
168
194
|
context '.validate' do
|
169
195
|
let(:hash) { {name: 'bob'} }
|
170
196
|
let(:person) { Person.load(hash) }
|
@@ -179,150 +205,149 @@ describe Praxis::Blueprint do
|
|
179
205
|
|
180
206
|
it { should have(1).item }
|
181
207
|
its(:first) { should =~ /Attribute \$.address.state/ }
|
182
|
-
end
|
183
|
-
|
184
|
-
context 'for objects of the wrong type' do
|
185
|
-
it 'raises an error' do
|
186
|
-
expect {
|
187
|
-
Person.validate(Object.new)
|
188
|
-
}.to raise_error(ArgumentError, /Error validating .* as Person for an object of type Object/)
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
|
194
|
-
context '.load' do
|
195
|
-
let(:hash) do
|
196
|
-
{
|
197
|
-
:name => 'Bob',
|
198
|
-
:full_name => {:first => 'Robert', :last => 'Robertson'},
|
199
|
-
:address => {:street => 'main', :state => 'OR'}
|
200
|
-
}
|
201
|
-
end
|
202
|
-
subject(:person) { Person.load(hash) }
|
203
|
-
|
204
|
-
it { should be_kind_of(Person) }
|
205
|
-
|
206
|
-
context 'recursively loading sub-attributes' do
|
207
|
-
context 'for a Blueprint' do
|
208
|
-
subject(:address) { person.address }
|
209
|
-
it { should be_kind_of(Address) }
|
210
|
-
end
|
211
|
-
context 'for an Attributor::Model' do
|
212
|
-
subject(:full_name) { person.full_name }
|
213
|
-
it { should be_kind_of(FullName) }
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
end
|
218
|
-
|
219
|
-
|
220
|
-
context 'decorators' do
|
221
|
-
let(:name) { 'Soren II' }
|
222
|
-
|
223
|
-
let(:object) { Person.example.object }
|
224
|
-
subject(:person) { Person.new(object, decorators) }
|
225
|
-
|
226
|
-
|
227
|
-
context 'as a hash' do
|
228
|
-
let(:decorators) { {name: name} }
|
229
|
-
it do
|
230
|
-
pers = person
|
231
|
-
# binding.pry
|
232
|
-
pers.name.should eq('Soren II')
|
233
|
-
end
|
234
|
-
|
235
|
-
its(:name) { should be(name) }
|
236
|
-
|
237
|
-
context 'an additional instance with the equivalent hash' do
|
238
|
-
subject(:additional_person) { Person.new(object, {name: name}) }
|
239
|
-
it { should_not be person }
|
240
|
-
end
|
241
|
-
|
242
|
-
context 'an additional instance with the same hash object' do
|
243
|
-
subject(:additional_person) { Person.new(object, decorators) }
|
244
|
-
it { should_not be person }
|
245
|
-
end
|
246
|
-
|
247
|
-
context 'an instance of the same object without decorators' do
|
248
|
-
subject(:additional_person) { Person.new(object) }
|
249
|
-
it { should_not be person }
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
context 'as an object' do
|
254
|
-
let(:decorators) { double("decorators", name: name) }
|
255
|
-
its(:name) { should be(name) }
|
256
|
-
|
257
|
-
context 'an additional instance with the same object' do
|
258
|
-
subject(:additional_person) { Person.new(object, decorators) }
|
259
|
-
it { should_not be person }
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
end
|
264
|
-
|
265
|
-
|
266
|
-
context 'with a provided :reference option on attributes' do
|
267
|
-
context 'that does not match the value set on the class' do
|
268
|
-
|
269
|
-
subject(:mismatched_reference) do
|
270
|
-
Class.new(Praxis::Blueprint) do
|
271
|
-
self.reference = Class.new(Praxis::Blueprint)
|
272
|
-
attributes(reference: Class.new(Praxis::Blueprint)) {}
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
it 'should raise an error' do
|
277
|
-
expect {
|
278
|
-
mismatched_reference.attributes
|
279
|
-
}.to raise_error
|
280
|
-
end
|
281
|
-
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
|
286
|
-
context '.example' do
|
287
|
-
context 'with some attribute values provided' do
|
288
|
-
let(:name) { 'Sir Bobbert' }
|
289
|
-
subject(:person) { Person.example(name: name) }
|
290
|
-
its(:name) { should eq(name) }
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
context '#render' do
|
295
|
-
let(:person) { Person.example }
|
296
|
-
let(:view_name) { :default }
|
297
|
-
subject(:output) { person.render(view_name) }
|
298
|
-
|
299
|
-
context 'with a sub-attribute that is a blueprint' do
|
300
|
-
|
301
|
-
it { should have_key(:name) }
|
302
|
-
it { should have_key(:address) }
|
303
|
-
it 'renders the sub-attribute correctly' do
|
304
|
-
output[:address].should have_key(:street)
|
305
|
-
output[:address].should have_key(:state)
|
306
208
|
end
|
307
209
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
210
|
+
context 'for objects of the wrong type' do
|
211
|
+
it 'raises an error' do
|
212
|
+
expect {
|
213
|
+
Person.validate(Object.new)
|
214
|
+
}.to raise_error(ArgumentError, /Error validating .* as Person for an object of type Object/)
|
215
|
+
end
|
313
216
|
end
|
314
217
|
end
|
315
218
|
|
219
|
+
context '.load' do
|
220
|
+
let(:hash) do
|
221
|
+
{
|
222
|
+
:name => 'Bob',
|
223
|
+
:full_name => {:first => 'Robert', :last => 'Robertson'},
|
224
|
+
:address => {:street => 'main', :state => 'OR'}
|
225
|
+
}
|
226
|
+
end
|
227
|
+
subject(:person) { Person.load(hash) }
|
228
|
+
|
229
|
+
it { should be_kind_of(Person) }
|
316
230
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
231
|
+
context 'recursively loading sub-attributes' do
|
232
|
+
context 'for a Blueprint' do
|
233
|
+
subject(:address) { person.address }
|
234
|
+
it { should be_kind_of(Address) }
|
235
|
+
end
|
236
|
+
context 'for an Attributor::Model' do
|
237
|
+
subject(:full_name) { person.full_name }
|
238
|
+
it { should be_kind_of(FullName) }
|
239
|
+
end
|
323
240
|
end
|
241
|
+
|
324
242
|
end
|
325
243
|
|
326
|
-
|
244
|
+
|
245
|
+
context 'decorators' do
|
246
|
+
let(:name) { 'Soren II' }
|
247
|
+
|
248
|
+
let(:object) { Person.example.object }
|
249
|
+
subject(:person) { Person.new(object, decorators) }
|
250
|
+
|
251
|
+
|
252
|
+
context 'as a hash' do
|
253
|
+
let(:decorators) { {name: name} }
|
254
|
+
it do
|
255
|
+
pers = person
|
256
|
+
# binding.pry
|
257
|
+
pers.name.should eq('Soren II')
|
258
|
+
end
|
259
|
+
|
260
|
+
its(:name) { should be(name) }
|
261
|
+
|
262
|
+
context 'an additional instance with the equivalent hash' do
|
263
|
+
subject(:additional_person) { Person.new(object, {name: name}) }
|
264
|
+
it { should_not be person }
|
265
|
+
end
|
266
|
+
|
267
|
+
context 'an additional instance with the same hash object' do
|
268
|
+
subject(:additional_person) { Person.new(object, decorators) }
|
269
|
+
it { should_not be person }
|
270
|
+
end
|
271
|
+
|
272
|
+
context 'an instance of the same object without decorators' do
|
273
|
+
subject(:additional_person) { Person.new(object) }
|
274
|
+
it { should_not be person }
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
context 'as an object' do
|
279
|
+
let(:decorators) { double("decorators", name: name) }
|
280
|
+
its(:name) { should be(name) }
|
281
|
+
|
282
|
+
context 'an additional instance with the same object' do
|
283
|
+
subject(:additional_person) { Person.new(object, decorators) }
|
284
|
+
it { should_not be person }
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
|
290
|
+
|
291
|
+
context 'with a provided :reference option on attributes' do
|
292
|
+
context 'that does not match the value set on the class' do
|
293
|
+
|
294
|
+
subject(:mismatched_reference) do
|
295
|
+
Class.new(Praxis::Blueprint) do
|
296
|
+
self.reference = Class.new(Praxis::Blueprint)
|
297
|
+
attributes(reference: Class.new(Praxis::Blueprint)) {}
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'should raise an error' do
|
302
|
+
expect {
|
303
|
+
mismatched_reference.attributes
|
304
|
+
}.to raise_error
|
305
|
+
end
|
306
|
+
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
context '.example' do
|
312
|
+
context 'with some attribute values provided' do
|
313
|
+
let(:name) { 'Sir Bobbert' }
|
314
|
+
subject(:person) { Person.example(name: name) }
|
315
|
+
its(:name) { should eq(name) }
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
context '#render' do
|
320
|
+
let(:person) { Person.example }
|
321
|
+
let(:view_name) { :default }
|
322
|
+
subject(:output) { person.render(view_name) }
|
323
|
+
|
324
|
+
context 'with a sub-attribute that is a blueprint' do
|
325
|
+
|
326
|
+
it { should have_key(:name) }
|
327
|
+
it { should have_key(:address) }
|
328
|
+
it 'renders the sub-attribute correctly' do
|
329
|
+
output[:address].should have_key(:street)
|
330
|
+
output[:address].should have_key(:state)
|
331
|
+
end
|
332
|
+
|
333
|
+
it 'reports a dump error with the appropriate context' do
|
334
|
+
person.address.should_receive(:state).and_raise("Kaboom")
|
335
|
+
expect {
|
336
|
+
person.render(view_name, context: ['special_root'])
|
337
|
+
}.to raise_error(/Error while dumping attribute state of type Address for context special_root.address. Reason: .*Kaboom/)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
|
342
|
+
context 'with sub-attribute that is an Attributor::Model' do
|
343
|
+
it { should have_key(:full_name) }
|
344
|
+
it 'renders the model correctly' do
|
345
|
+
output[:full_name].should be_kind_of(Hash)
|
346
|
+
output[:full_name].should have_key(:first)
|
347
|
+
output[:full_name].should have_key(:last)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
end
|
327
352
|
|
328
353
|
end
|
@@ -15,14 +15,83 @@ describe Praxis::View do
|
|
15
15
|
|
16
16
|
subject(:output) { view.to_hash(person) }
|
17
17
|
|
18
|
-
|
18
|
+
|
19
19
|
it 'can generate examples' do
|
20
20
|
view.example.should have_key(:name)
|
21
21
|
end
|
22
22
|
|
23
|
+
context 'swanky rendering options' do
|
24
|
+
let(:view) do
|
25
|
+
Praxis::View.new(:info, Person) do
|
26
|
+
attribute :name
|
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
|
+
end
|
57
|
+
|
58
|
+
context 'with include_nil: true' do
|
59
|
+
let(:view) do
|
60
|
+
Praxis::View.new(:info, Person, include_nil: true) do
|
61
|
+
attribute :name
|
62
|
+
attribute :email
|
63
|
+
attribute :age
|
64
|
+
attribute :address
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
subject(:output) { view.to_hash(person) }
|
69
|
+
|
70
|
+
it 'includes attributes with nil values' do
|
71
|
+
output.key?(:email).should be(true)
|
72
|
+
output[:email].should be(nil)
|
73
|
+
|
74
|
+
output.key?(:address).should be(true)
|
75
|
+
output[:address].should be(nil)
|
76
|
+
|
77
|
+
output.key?(:age).should be(true)
|
78
|
+
output[:age].should be(nil)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
|
23
88
|
context 'direct attributes' do
|
89
|
+
|
90
|
+
let(:person) { Person.load(person_data) }
|
24
91
|
context 'with undisputably existing values' do
|
25
|
-
|
92
|
+
|
93
|
+
let(:person_data) { {name:'somename', alive:true} }
|
94
|
+
|
26
95
|
let(:expected_output) do
|
27
96
|
{
|
28
97
|
:name => 'somename',
|
@@ -34,10 +103,11 @@ describe Praxis::View do
|
|
34
103
|
end
|
35
104
|
end
|
36
105
|
context 'with nil values' do
|
37
|
-
let(:
|
106
|
+
let(:person_data) { {name:'alive_is_nil', alive: nil} }
|
38
107
|
let(:expected_output) do
|
39
108
|
{
|
40
|
-
:
|
109
|
+
name: 'alive_is_nil',
|
110
|
+
alive: true
|
41
111
|
}
|
42
112
|
end
|
43
113
|
it 'are skipped completely' do
|
@@ -46,7 +116,7 @@ describe Praxis::View do
|
|
46
116
|
end
|
47
117
|
|
48
118
|
context 'with false values' do
|
49
|
-
let(:
|
119
|
+
let(:person_data) { {name:'alive_is_false', alive:false} }
|
50
120
|
let(:expected_output) do
|
51
121
|
{
|
52
122
|
:name => 'alive_is_false',
|
@@ -181,10 +251,8 @@ describe Praxis::View do
|
|
181
251
|
end
|
182
252
|
|
183
253
|
context 'when the related object is nil (does not respond to the related method)' do
|
184
|
-
let(:
|
185
|
-
let(:person) { Person.new(resource) }
|
254
|
+
let(:person) { Person.load(name: 'Bob') }
|
186
255
|
|
187
|
-
|
188
256
|
let(:view) do
|
189
257
|
Praxis::View.new(:default, Person) do
|
190
258
|
attribute :name
|
@@ -242,7 +310,7 @@ describe Praxis::View do
|
|
242
310
|
end
|
243
311
|
|
244
312
|
end
|
245
|
-
|
313
|
+
|
246
314
|
|
247
315
|
context '#describe' do
|
248
316
|
subject(:description) { view.describe}
|
@@ -250,7 +318,7 @@ describe Praxis::View do
|
|
250
318
|
its([:type]) { should eq(:standard) }
|
251
319
|
context 'returns attributes' do
|
252
320
|
subject { description[:attributes] }
|
253
|
-
|
321
|
+
|
254
322
|
its(:keys){ should == [:name,:alive,:address] }
|
255
323
|
|
256
324
|
it 'should return empty hashes for attributes with no specially defined view' do
|
@@ -263,4 +331,4 @@ describe Praxis::View do
|
|
263
331
|
end
|
264
332
|
end
|
265
333
|
|
266
|
-
end
|
334
|
+
end
|
@@ -3,7 +3,9 @@ class Person < Praxis::Blueprint
|
|
3
3
|
attributes do
|
4
4
|
attribute :name, String, example: /[:first_name:]/
|
5
5
|
attribute :email, String, example: proc { |person| "#{person.name}@example.com" }
|
6
|
-
|
6
|
+
|
7
|
+
attribute :age, Integer
|
8
|
+
|
7
9
|
attribute :full_name, FullName
|
8
10
|
attribute :aliases, Attributor::Collection.of(FullName)
|
9
11
|
|
@@ -36,10 +38,17 @@ class Person < Praxis::Blueprint
|
|
36
38
|
view :extended do
|
37
39
|
attribute :name
|
38
40
|
attribute :full_name
|
41
|
+
attribute :age
|
39
42
|
attribute :address
|
40
43
|
attribute :alive
|
41
44
|
end
|
42
45
|
|
46
|
+
view :with_unset, include_unset: true do
|
47
|
+
attribute :name
|
48
|
+
attribute :email
|
49
|
+
attribute :age
|
50
|
+
end
|
51
|
+
|
43
52
|
end
|
44
53
|
|
45
54
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: praxis-blueprints
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josep M. Blanquer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-
|
12
|
+
date: 2015-03-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: randexp
|
@@ -31,14 +31,14 @@ dependencies:
|
|
31
31
|
requirements:
|
32
32
|
- - "~>"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '2'
|
34
|
+
version: '2.6'
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: '2'
|
41
|
+
version: '2.6'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: activesupport
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|