praxis-blueprints 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cc026836ebcbda29b7cba8d4a0bfae53b450969b
4
- data.tar.gz: 42b68fb6981428da9acaf054507b16ce00606633
3
+ metadata.gz: 48fb0aab37d8848325be498e9e89221ce803cdfe
4
+ data.tar.gz: 3bb509c2daadb670527d0cf43e9b919573273059
5
5
  SHA512:
6
- metadata.gz: 11162b7589b516b84585df4d074123065ba5017ee712993c9bea8c17e2d19d598059a00c54a6dd01e7c0eab4f097041d9753f6da37d96368986d72c346431c26
7
- data.tar.gz: 96cb3ccf1238acec2e3b0a41456fe9cd0e965e4103000920d1b2486b8aa762bfd3cf6058678c8058eaa9e488eb0be39208d29c17e8a670737469225b9f21e47e
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 nil, self
114
+ when self
114
115
  value
115
- when Hash, String
116
+ when nil, Hash, String
116
117
  # Need to parse/deserialize first
117
- self.new(self.attribute.load(value,context, **options))
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.render(view, context: context)
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
- return @rendered_views[view_name] if @rendered_views.has_key? view_name
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
- if @decorators && @decorators.respond_to?(name)
290
- @decorators.send(name)
291
- else
292
- value = @object.__send__(name)
293
- return value if value.nil? || value.kind_of?(attribute.type)
294
- attribute.load(value)
295
- end
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,3 +1,3 @@
1
1
  module Praxis
2
- BLUEPRINTS_VERSION = "1.2.0"
2
+ BLUEPRINTS_VERSION = "1.3.0"
3
3
  end
@@ -1,16 +1,24 @@
1
1
  module Praxis
2
2
 
3
3
  class View
4
- attr_reader :schema, :contents, :name
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
- next unless object.respond_to?(name)
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
- next if value.nil?
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)
@@ -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(:resource) do
121
- double("resource",
122
- name: 'Bob',
123
- full_name: FullName.example,
124
- address: Address.example,
125
- email: "bob@example.com",
126
- aliases: [],
127
- prior_addresses: [],
128
- parents: double('parents', father: /[:first_name:]/.gen, mother: /[:first_name:]/.gen),
129
- href: "www.example.com",
130
- alive: true)
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
- it 'reports a dump error with the appropriate context' do
309
- person.address.should_receive(:state).and_raise("Kaboom")
310
- expect {
311
- person.render(view_name, context: ['special_root'])
312
- }.to raise_error(/Error while dumping attribute state of type Address for context special_root.address. Reason: .*Kaboom/)
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
- context 'with sub-attribute that is an Attributor::Model' do
318
- it { should have_key(:full_name) }
319
- it 'renders the model correctly' do
320
- output[:full_name].should be_kind_of(Hash)
321
- output[:full_name].should have_key(:first)
322
- output[:full_name].should have_key(:last)
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
- end
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
- let(:person) { OpenStruct.new(:name=>'somename', :alive=>true)}
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(:person) { OpenStruct.new(:name=>'alive_is_nil', :alive=>nil)}
106
+ let(:person_data) { {name:'alive_is_nil', alive: nil} }
38
107
  let(:expected_output) do
39
108
  {
40
- :name => 'alive_is_nil'
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(:person) { OpenStruct.new(:name=>'alive_is_false', :alive=>false)}
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(:resource) { OpenStruct.new(name: "Bob") }
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.2.0
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-02-11 00:00:00.000000000 Z
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