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 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