grape-entity 0.3.0 → 0.4.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.
@@ -1,3 +1,3 @@
1
1
  module GrapeEntity
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -1,6 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Grape::Entity do
4
+
4
5
  let(:fresh_class) { Class.new(Grape::Entity) }
5
6
 
6
7
  context 'class methods' do
@@ -14,33 +15,115 @@ describe Grape::Entity do
14
15
  end
15
16
 
16
17
  it 'sets the same options for all exposures passed' do
17
- subject.expose :name, :email, :location, :foo => :bar
18
- subject.exposures.values.each{|v| v.should == {:foo => :bar}}
18
+ subject.expose :name, :email, :location, documentation: true
19
+ subject.exposures.values.each { |v| v.should == { documentation: true } }
19
20
  end
20
21
  end
21
22
 
22
23
  context 'option validation' do
23
24
  it 'makes sure that :as only works on single attribute calls' do
24
- expect{ subject.expose :name, :email, :as => :foo }.to raise_error(ArgumentError)
25
- expect{ subject.expose :name, :as => :foo }.not_to raise_error
25
+ expect { subject.expose :name, :email, as: :foo }.to raise_error ArgumentError
26
+ expect { subject.expose :name, as: :foo }.not_to raise_error
27
+ end
28
+
29
+ it 'makes sure that :format_with as a proc cannot be used with a block' do
30
+ expect { subject.expose :name, format_with: proc {} {} }.to raise_error ArgumentError
26
31
  end
27
32
 
28
- it 'makes sure that :format_with as a proc can not be used with a block' do
29
- expect { subject.expose :name, :format_with => Proc.new {} do |_| end }.to raise_error(ArgumentError)
33
+ it 'makes sure unknown options are not silently ignored' do
34
+ expect { subject.expose :name, unknown: nil }.to raise_error ArgumentError
30
35
  end
31
36
  end
32
37
 
33
38
  context 'with a block' do
34
39
  it 'errors out if called with multiple attributes' do
35
- expect{ subject.expose(:name, :email) do
36
- true
37
- end }.to raise_error(ArgumentError)
40
+ expect { subject.expose(:name, :email) { true } }.to raise_error ArgumentError
38
41
  end
39
42
 
40
- it 'sets the :proc option in the exposure options' do
41
- block = lambda{|_| true }
42
- subject.expose :name, &block
43
- subject.exposures[:name][:proc].should == block
43
+ it 'references an instance of the entity with :using option' do
44
+ module EntitySpec
45
+ class SomeObject1
46
+ attr_accessor :prop1
47
+
48
+ def initialize
49
+ @prop1 = "value1"
50
+ end
51
+ end
52
+
53
+ class BogusEntity < Grape::Entity
54
+ expose :prop1
55
+ end
56
+ end
57
+
58
+ subject.expose(:bogus, using: EntitySpec::BogusEntity) do |entity|
59
+ entity.prop1 = "MODIFIED 2"
60
+ entity
61
+ end
62
+
63
+ object = EntitySpec::SomeObject1.new
64
+ value = subject.represent(object).send(:value_for, :bogus)
65
+ value.should be_instance_of EntitySpec::BogusEntity
66
+
67
+ prop1 = value.send(:value_for, :prop1)
68
+ prop1.should == "MODIFIED 2"
69
+ end
70
+
71
+ context 'with parameters passed to the block' do
72
+ it 'sets the :proc option in the exposure options' do
73
+ block = lambda { |_| true }
74
+ subject.expose :name, using: 'Awesome', &block
75
+ subject.exposures[:name].should == { proc: block, using: 'Awesome' }
76
+ end
77
+
78
+ it 'references an instance of the entity without any options' do
79
+ subject.expose(:size) { |_| self }
80
+ subject.represent(Hash.new).send(:value_for, :size).should be_an_instance_of fresh_class
81
+ end
82
+ end
83
+
84
+ context 'with no parameters passed to the block' do
85
+ it 'adds a nested exposure' do
86
+ subject.expose :awesome do
87
+ subject.expose :nested do
88
+ subject.expose :moar_nested, as: 'weee'
89
+ end
90
+ subject.expose :another_nested, using: 'Awesome'
91
+ end
92
+
93
+ subject.exposures.should == {
94
+ awesome: {},
95
+ awesome__nested: {},
96
+ awesome__nested__moar_nested: { as: 'weee' },
97
+ awesome__another_nested: { using: 'Awesome' }
98
+ }
99
+ end
100
+
101
+ it 'represents the exposure as a hash of its nested exposures' do
102
+ subject.expose :awesome do
103
+ subject.expose(:nested) { |_| "value" }
104
+ subject.expose(:another_nested) { |_| "value" }
105
+ end
106
+
107
+ subject.represent({}).send(:value_for, :awesome).should == {
108
+ nested: "value",
109
+ another_nested: "value"
110
+ }
111
+ end
112
+
113
+ it 'is safe if its nested exposures are safe' do
114
+ subject.with_options safe: true do
115
+ subject.expose :awesome do
116
+ subject.expose(:nested) { |_| "value" }
117
+ end
118
+ subject.expose :not_awesome do
119
+ subject.expose :nested
120
+ end
121
+ end
122
+
123
+ valid_keys = subject.represent({}).valid_exposures.keys
124
+ valid_keys.include?(:awesome).should == true && \
125
+ valid_keys.include?(:not_awesome).should == false
126
+ end
44
127
  end
45
128
  end
46
129
 
@@ -73,7 +156,7 @@ describe Grape::Entity do
73
156
  end
74
157
 
75
158
  context 'register formatters' do
76
- let(:date_formatter) { lambda {|date| date.strftime('%m/%d/%Y') }}
159
+ let(:date_formatter) { lambda { |date| date.strftime('%m/%d/%Y') } }
77
160
 
78
161
  it 'registers a formatter' do
79
162
  subject.format_with :timestamp, &date_formatter
@@ -89,7 +172,7 @@ describe Grape::Entity do
89
172
  end
90
173
 
91
174
  it 'does not allow registering a formatter without a block' do
92
- expect{ subject.format_with :foo }.to raise_error(ArgumentError)
175
+ expect { subject.format_with :foo }.to raise_error ArgumentError
93
176
  end
94
177
 
95
178
  it 'formats an exposure with a registered formatter' do
@@ -97,45 +180,164 @@ describe Grape::Entity do
97
180
  date.strftime('%m/%d/%Y')
98
181
  end
99
182
 
100
- subject.expose :birthday, :format_with => :timestamp
183
+ subject.expose :birthday, format_with: :timestamp
184
+
185
+ model = { birthday: Time.gm(2012, 2, 27) }
186
+ subject.new(double(model)).as_json[:birthday].should == '02/27/2012'
187
+ end
188
+
189
+ it 'formats an exposure with a :format_with lambda that returns a value from the entity instance' do
190
+ object = Hash.new
101
191
 
102
- model = { :birthday => Time.gm(2012, 2, 27) }
103
- subject.new(mock(model)).as_json[:birthday].should == '02/27/2012'
192
+ subject.expose(:size, format_with: lambda { |value| self.object.class.to_s })
193
+ subject.represent(object).send(:value_for, :size).should == object.class.to_s
194
+ end
195
+
196
+ it 'formats an exposure with a :format_with symbol that returns a value from the entity instance' do
197
+ subject.format_with :size_formatter do |date|
198
+ self.object.class.to_s
199
+ end
200
+
201
+ object = Hash.new
202
+
203
+ subject.expose(:size, format_with: :size_formatter)
204
+ subject.represent(object).send(:value_for, :size).should == object.class.to_s
104
205
  end
105
206
  end
106
207
  end
107
208
 
108
209
  describe '.with_options' do
109
- it 'should apply the options to all exposures inside' do
210
+ it 'raises an error for unknown options' do
211
+ block = proc do
212
+ with_options(unknown: true) do
213
+ expose :awesome_thing
214
+ end
215
+ end
216
+
217
+ expect { subject.class_eval(&block) }.to raise_error ArgumentError
218
+ end
219
+
220
+ it 'applies the options to all exposures inside' do
110
221
  subject.class_eval do
111
- with_options(:if => {:awesome => true}) do
112
- expose :awesome_thing, :using => 'Awesome'
222
+ with_options(if: { awesome: true }) do
223
+ expose :awesome_thing, using: 'Awesome'
113
224
  end
114
225
  end
115
226
 
116
- subject.exposures[:awesome_thing].should == {:if => {:awesome => true}, :using => 'Awesome'}
227
+ subject.exposures[:awesome_thing].should == { if: { awesome: true }, using: 'Awesome' }
117
228
  end
118
229
 
119
- it 'should allow for nested .with_options' do
230
+ it 'allows for nested .with_options' do
120
231
  subject.class_eval do
121
- with_options(:if => {:awesome => true}) do
122
- with_options(:using => 'Something') do
232
+ with_options(if: { awesome: true }) do
233
+ with_options(using: 'Something') do
123
234
  expose :awesome_thing
124
235
  end
125
236
  end
126
237
  end
127
238
 
128
- subject.exposures[:awesome_thing].should == {:if => {:awesome => true}, :using => 'Something'}
239
+ subject.exposures[:awesome_thing].should == { if: { awesome: true }, using: 'Something' }
240
+ end
241
+
242
+ it 'overrides nested :as option' do
243
+ subject.class_eval do
244
+ with_options(as: :sweet) do
245
+ expose :awesome_thing, as: :extra_smooth
246
+ end
247
+ end
248
+
249
+ subject.exposures[:awesome_thing].should == { as: :extra_smooth }
250
+ end
251
+
252
+ it "merges nested :if option" do
253
+ match_proc = lambda { |obj, opts| true }
254
+
255
+ subject.class_eval do
256
+ # Symbol
257
+ with_options(if: :awesome) do
258
+ # Hash
259
+ with_options(if: { awesome: true }) do
260
+ # Proc
261
+ with_options(if: match_proc) do
262
+ # Hash (override existing key and merge new key)
263
+ with_options(if: { awesome: false, less_awesome: true }) do
264
+ expose :awesome_thing
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end
270
+
271
+ subject.exposures[:awesome_thing].should == {
272
+ if: { awesome: false, less_awesome: true },
273
+ if_extras: [:awesome, match_proc]
274
+ }
275
+ end
276
+
277
+ it 'merges nested :unless option' do
278
+ match_proc = lambda { |obj, opts| true }
279
+
280
+ subject.class_eval do
281
+ # Symbol
282
+ with_options(unless: :awesome) do
283
+ # Hash
284
+ with_options(unless: { awesome: true }) do
285
+ # Proc
286
+ with_options(unless: match_proc) do
287
+ # Hash (override existing key and merge new key)
288
+ with_options(unless: { awesome: false, less_awesome: true }) do
289
+ expose :awesome_thing
290
+ end
291
+ end
292
+ end
293
+ end
294
+ end
295
+
296
+ subject.exposures[:awesome_thing].should == {
297
+ unless: { awesome: false, less_awesome: true },
298
+ unless_extras: [:awesome, match_proc]
299
+ }
129
300
  end
130
301
 
131
- it 'should allow for overrides' do
302
+ it 'overrides nested :using option' do
132
303
  subject.class_eval do
133
- with_options(:if => {:awesome => true}) do
134
- expose :less_awesome_thing, :if => {:awesome => false}
304
+ with_options(using: 'Something') do
305
+ expose :awesome_thing, using: 'SomethingElse'
135
306
  end
136
307
  end
137
308
 
138
- subject.exposures[:less_awesome_thing].should == {:if => {:awesome => false}}
309
+ subject.exposures[:awesome_thing].should == { using: 'SomethingElse' }
310
+ end
311
+
312
+ it 'aliases :with option to :using option' do
313
+ subject.class_eval do
314
+ with_options(using: 'Something') do
315
+ expose :awesome_thing, with: 'SomethingElse'
316
+ end
317
+ end
318
+ subject.exposures[:awesome_thing].should == { using: 'SomethingElse' }
319
+ end
320
+
321
+ it 'overrides nested :proc option' do
322
+ match_proc = lambda { |obj, opts| 'more awesomer' }
323
+
324
+ subject.class_eval do
325
+ with_options(proc: lambda { |obj, opts| 'awesome' }) do
326
+ expose :awesome_thing, proc: match_proc
327
+ end
328
+ end
329
+
330
+ subject.exposures[:awesome_thing].should == { proc: match_proc }
331
+ end
332
+
333
+ it 'overrides nested :documentation option' do
334
+ subject.class_eval do
335
+ with_options(documentation: { desc: 'Description.' }) do
336
+ expose :awesome_thing, documentation: { desc: 'Other description.' }
337
+ end
338
+ end
339
+
340
+ subject.exposures[:awesome_thing].should == { documentation: { desc: 'Other description.' } }
139
341
  end
140
342
  end
141
343
 
@@ -149,15 +351,27 @@ describe Grape::Entity do
149
351
  end
150
352
 
151
353
  it 'returns multiple entities if called with a collection' do
152
- representation = subject.represent(4.times.map{Object.new})
354
+ representation = subject.represent(4.times.map { Object.new })
153
355
  representation.should be_kind_of Array
154
356
  representation.size.should == 4
155
- representation.reject{|r| r.kind_of?(subject)}.should be_empty
357
+ representation.reject { |r| r.kind_of?(subject) }.should be_empty
156
358
  end
157
359
 
158
- it 'adds the :collection => true option if called with a collection' do
159
- representation = subject.represent(4.times.map{Object.new})
160
- representation.each{|r| r.options[:collection].should be_true}
360
+ it 'adds the collection: true option if called with a collection' do
361
+ representation = subject.represent(4.times.map { Object.new })
362
+ representation.each { |r| r.options[:collection].should be_true }
363
+ end
364
+
365
+ it 'returns a serialized hash of a single object if serializable: true' do
366
+ subject.expose(:awesome) { |_| true }
367
+ representation = subject.represent(Object.new, serializable: true)
368
+ representation.should == { awesome: true }
369
+ end
370
+
371
+ it 'returns a serialized array of hashes of multiple objects if serializable: true' do
372
+ subject.expose(:awesome) { |_| true }
373
+ representation = subject.represent(2.times.map { Object.new }, serializable: true)
374
+ representation.should == [{ awesome: true }, { awesome: true }]
161
375
  end
162
376
  end
163
377
 
@@ -178,29 +392,29 @@ describe Grape::Entity do
178
392
 
179
393
  context 'with an array of objects' do
180
394
  it 'allows a root element name to be specified' do
181
- representation = subject.represent(4.times.map{Object.new})
395
+ representation = subject.represent(4.times.map { Object.new })
182
396
  representation.should be_kind_of Hash
183
397
  representation.should have_key 'things'
184
398
  representation['things'].should be_kind_of Array
185
399
  representation['things'].size.should == 4
186
- representation['things'].reject{|r| r.kind_of?(subject)}.should be_empty
400
+ representation['things'].reject { |r| r.kind_of?(subject) }.should be_empty
187
401
  end
188
402
  end
189
403
 
190
404
  context 'it can be overridden' do
191
405
  it 'can be disabled' do
192
- representation = subject.represent(4.times.map{Object.new}, :root=>false)
406
+ representation = subject.represent(4.times.map { Object.new }, root: false)
193
407
  representation.should be_kind_of Array
194
408
  representation.size.should == 4
195
- representation.reject{|r| r.kind_of?(subject)}.should be_empty
409
+ representation.reject { |r| r.kind_of?(subject) }.should be_empty
196
410
  end
197
411
  it 'can use a different name' do
198
- representation = subject.represent(4.times.map{Object.new}, :root=>'others')
412
+ representation = subject.represent(4.times.map { Object.new }, root: 'others')
199
413
  representation.should be_kind_of Hash
200
414
  representation.should have_key 'others'
201
415
  representation['others'].should be_kind_of Array
202
416
  representation['others'].size.should == 4
203
- representation['others'].reject{|r| r.kind_of?(subject)}.should be_empty
417
+ representation['others'].reject { |r| r.kind_of?(subject) }.should be_empty
204
418
  end
205
419
  end
206
420
  end
@@ -221,10 +435,10 @@ describe Grape::Entity do
221
435
 
222
436
  context 'with an array of objects' do
223
437
  it 'allows a root element name to be specified' do
224
- representation = subject.represent(4.times.map{Object.new})
438
+ representation = subject.represent(4.times.map { Object.new })
225
439
  representation.should be_kind_of Array
226
440
  representation.size.should == 4
227
- representation.reject{|r| r.kind_of?(subject)}.should be_empty
441
+ representation.reject { |r| r.kind_of?(subject) }.should be_empty
228
442
  end
229
443
  end
230
444
  end
@@ -242,12 +456,12 @@ describe Grape::Entity do
242
456
 
243
457
  context 'with an array of objects' do
244
458
  it 'allows a root element name to be specified' do
245
- representation = subject.represent(4.times.map{Object.new})
459
+ representation = subject.represent(4.times.map { Object.new })
246
460
  representation.should be_kind_of Hash
247
461
  representation.should have_key('things')
248
462
  representation['things'].should be_kind_of Array
249
463
  representation['things'].size.should == 4
250
- representation['things'].reject{|r| r.kind_of?(subject)}.should be_empty
464
+ representation['things'].reject { |r| r.kind_of?(subject) }.should be_empty
251
465
  end
252
466
  end
253
467
  end
@@ -255,9 +469,9 @@ describe Grape::Entity do
255
469
 
256
470
  describe '#initialize' do
257
471
  it 'takes an object and an optional options hash' do
258
- expect{ subject.new(Object.new) }.not_to raise_error
259
- expect{ subject.new }.to raise_error(ArgumentError)
260
- expect{ subject.new(Object.new, {}) }.not_to raise_error
472
+ expect { subject.new(Object.new) }.not_to raise_error
473
+ expect { subject.new }.to raise_error ArgumentError
474
+ expect { subject.new(Object.new, {}) }.not_to raise_error
261
475
  end
262
476
 
263
477
  it 'has attribute readers for the object and options' do
@@ -266,63 +480,95 @@ describe Grape::Entity do
266
480
  entity.options.should == {}
267
481
  end
268
482
  end
483
+
269
484
  end
270
485
 
271
486
  context 'instance methods' do
272
-
273
- let(:model){ mock(attributes) }
274
-
275
- let(:attributes) { {
276
- :name => 'Bob Bobson',
277
- :email => 'bob@example.com',
278
- :birthday => Time.gm(2012, 2, 27),
279
- :fantasies => ['Unicorns', 'Double Rainbows', 'Nessy'],
280
- :friends => [
281
- mock(:name => "Friend 1", :email => 'friend1@example.com', :fantasies => [], :birthday => Time.gm(2012, 2, 27), :friends => []),
282
- mock(:name => "Friend 2", :email => 'friend2@example.com', :fantasies => [], :birthday => Time.gm(2012, 2, 27), :friends => [])
283
- ]
284
- } }
285
-
286
- subject{ fresh_class.new(model) }
287
487
 
288
- describe '#serializable_hash' do
488
+ let(:model) { double(attributes) }
289
489
 
490
+ let(:attributes) {
491
+ {
492
+ name: 'Bob Bobson',
493
+ email: 'bob@example.com',
494
+ birthday: Time.gm(2012, 2, 27),
495
+ fantasies: ['Unicorns', 'Double Rainbows', 'Nessy'],
496
+ friends: [
497
+ double(name: "Friend 1", email: 'friend1@example.com', fantasies: [], birthday: Time.gm(2012, 2, 27), friends: []),
498
+ double(name: "Friend 2", email: 'friend2@example.com', fantasies: [], birthday: Time.gm(2012, 2, 27), friends: [])
499
+ ]
500
+ }
501
+ }
502
+
503
+ subject { fresh_class.new(model) }
504
+
505
+ describe '#serializable_hash' do
290
506
  it 'does not throw an exception if a nil options object is passed' do
291
- expect{ fresh_class.new(model).serializable_hash(nil) }.not_to raise_error
507
+ expect { fresh_class.new(model).serializable_hash(nil) }.not_to raise_error
292
508
  end
293
509
 
294
510
  it 'does not blow up when the model is nil' do
295
511
  fresh_class.expose :name
296
- expect{ fresh_class.new(nil).serializable_hash }.not_to raise_error
512
+ expect { fresh_class.new(nil).serializable_hash }.not_to raise_error
297
513
  end
298
514
 
299
- it 'does not throw an exception when an attribute is not found on the object' do
300
- fresh_class.expose :name, :nonexistent_attribute
301
- expect{ fresh_class.new(model).serializable_hash }.not_to raise_error
302
- end
515
+ context "with safe option" do
516
+ it 'does not throw an exception when an attribute is not found on the object' do
517
+ fresh_class.expose :name, :nonexistent_attribute, safe: true
518
+ expect { fresh_class.new(model).serializable_hash }.not_to raise_error
519
+ end
303
520
 
304
- it "does not expose attributes that don't exist on the object" do
305
- fresh_class.expose :email, :nonexistent_attribute, :name
521
+ it "does not expose attributes that don't exist on the object" do
522
+ fresh_class.expose :email, :nonexistent_attribute, :name, safe: true
306
523
 
307
- res = fresh_class.new(model).serializable_hash
308
- res.should have_key :email
309
- res.should_not have_key :nonexistent_attribute
310
- res.should have_key :name
524
+ res = fresh_class.new(model).serializable_hash
525
+ res.should have_key :email
526
+ res.should_not have_key :nonexistent_attribute
527
+ res.should have_key :name
528
+ end
529
+
530
+ it "does not expose attributes that don't exist on the object, even with criteria" do
531
+ fresh_class.expose :email
532
+ fresh_class.expose :nonexistent_attribute, safe: true, if: lambda { false }
533
+ fresh_class.expose :nonexistent_attribute2, safe: true, if: lambda { true }
534
+
535
+ res = fresh_class.new(model).serializable_hash
536
+ res.should have_key :email
537
+ res.should_not have_key :nonexistent_attribute
538
+ res.should_not have_key :nonexistent_attribute2
539
+ end
311
540
  end
312
541
 
313
- it "does not expose attributes that don't exist on the object, even with criteria" do
314
- fresh_class.expose :email
315
- fresh_class.expose :nonexistent_attribute, :if => lambda { false }
316
- fresh_class.expose :nonexistent_attribute2, :if => lambda { true }
542
+ context "without safe option" do
543
+ it 'throws an exception when an attribute is not found on the object' do
544
+ fresh_class.expose :name, :nonexistent_attribute
545
+ expect { fresh_class.new(model).serializable_hash }.to raise_error
546
+ end
317
547
 
318
- res = fresh_class.new(model).serializable_hash
319
- res.should have_key :email
320
- res.should_not have_key :nonexistent_attribute
321
- res.should_not have_key :nonexistent_attribute2
548
+ it "exposes attributes that don't exist on the object only when they are generated by a block" do
549
+ fresh_class.expose :nonexistent_attribute do |model, _|
550
+ "well, I do exist after all"
551
+ end
552
+ res = fresh_class.new(model).serializable_hash
553
+ res.should have_key :nonexistent_attribute
554
+ end
555
+
556
+ it "does not expose attributes that are generated by a block but have not passed criteria" do
557
+ fresh_class.expose :nonexistent_attribute, proc: lambda { |model, _|
558
+ "I exist, but it is not yet my time to shine"
559
+ }, if: lambda { |model, _| false }
560
+ res = fresh_class.new(model).serializable_hash
561
+ res.should_not have_key :nonexistent_attribute
562
+ end
322
563
  end
323
564
 
324
- it "exposes attributes that don't exist on the object only when they are generated by a block" do
325
- fresh_class.expose :nonexistent_attribute do |model, _|
565
+ it "exposes attributes that don't exist on the object only when they are generated by a block with options" do
566
+ module EntitySpec
567
+ class TestEntity < Grape::Entity
568
+ end
569
+ end
570
+
571
+ fresh_class.expose :nonexistent_attribute, using: EntitySpec::TestEntity do |model, _|
326
572
  "well, I do exist after all"
327
573
  end
328
574
  res = fresh_class.new(model).serializable_hash
@@ -330,71 +576,88 @@ describe Grape::Entity do
330
576
  end
331
577
 
332
578
  it "does not expose attributes that are generated by a block but have not passed criteria" do
333
- fresh_class.expose :nonexistent_attribute, :proc => lambda {|model, _|
579
+ fresh_class.expose :nonexistent_attribute, proc: lambda { |model, _|
334
580
  "I exist, but it is not yet my time to shine"
335
- }, :if => lambda { |model, _| false }
581
+ }, if: lambda { |model, _| false }
336
582
  res = fresh_class.new(model).serializable_hash
337
583
  res.should_not have_key :nonexistent_attribute
338
584
  end
339
585
 
340
586
  context '#serializable_hash' do
341
-
342
587
  module EntitySpec
343
588
  class EmbeddedExample
344
589
  def serializable_hash(opts = {})
345
- { :abc => 'def' }
590
+ { abc: 'def' }
346
591
  end
347
592
  end
593
+
594
+ class EmbeddedExampleWithHash
595
+ def name
596
+ "abc"
597
+ end
598
+
599
+ def embedded
600
+ { a: nil, b: EmbeddedExample.new }
601
+ end
602
+ end
603
+
348
604
  class EmbeddedExampleWithMany
349
605
  def name
350
606
  "abc"
351
607
  end
608
+
352
609
  def embedded
353
- [ EmbeddedExample.new, EmbeddedExample.new ]
610
+ [EmbeddedExample.new, EmbeddedExample.new]
354
611
  end
355
612
  end
613
+
356
614
  class EmbeddedExampleWithOne
357
615
  def name
358
616
  "abc"
359
617
  end
618
+
360
619
  def embedded
361
620
  EmbeddedExample.new
362
621
  end
363
622
  end
364
623
  end
365
-
624
+
366
625
  it 'serializes embedded objects which respond to #serializable_hash' do
367
626
  fresh_class.expose :name, :embedded
368
627
  presenter = fresh_class.new(EntitySpec::EmbeddedExampleWithOne.new)
369
- presenter.serializable_hash.should == {:name => "abc", :embedded => {:abc => "def"}}
628
+ presenter.serializable_hash.should == { name: "abc", embedded: { abc: "def" } }
370
629
  end
371
630
 
372
631
  it 'serializes embedded arrays of objects which respond to #serializable_hash' do
373
632
  fresh_class.expose :name, :embedded
374
633
  presenter = fresh_class.new(EntitySpec::EmbeddedExampleWithMany.new)
375
- presenter.serializable_hash.should == {:name => "abc", :embedded => [{:abc => "def"}, {:abc => "def"}]}
634
+ presenter.serializable_hash.should == { name: "abc", embedded: [{ abc: "def" }, { abc: "def" }] }
635
+ end
636
+
637
+ it 'serializes embedded hashes of objects which respond to #serializable_hash' do
638
+ fresh_class.expose :name, :embedded
639
+ presenter = fresh_class.new(EntitySpec::EmbeddedExampleWithHash.new)
640
+ presenter.serializable_hash.should == { name: "abc", embedded: { a: nil, b: { abc: "def" } } }
376
641
  end
377
-
378
642
  end
379
-
380
643
  end
381
644
 
382
645
  describe '#value_for' do
383
646
  before do
384
647
  fresh_class.class_eval do
385
648
  expose :name, :email
386
- expose :friends, :using => self
649
+ expose :friends, using: self
387
650
  expose :computed do |_, options|
388
651
  options[:awesome]
389
652
  end
390
653
 
391
- expose :birthday, :format_with => :timestamp
654
+ expose :birthday, format_with: :timestamp
392
655
 
393
656
  def timestamp(date)
394
657
  date.strftime('%m/%d/%Y')
395
658
  end
396
659
 
397
- expose :fantasies, :format_with => lambda {|f| f.reverse }
660
+ expose :fantasies, format_with: lambda { |f| f.reverse }
398
661
  end
399
662
  end
400
663
 
@@ -404,54 +667,112 @@ describe Grape::Entity do
404
667
 
405
668
  it 'instantiates a representation if that is called for' do
406
669
  rep = subject.send(:value_for, :friends)
407
- rep.reject{|r| r.is_a?(fresh_class)}.should be_empty
670
+ rep.reject { |r| r.is_a?(fresh_class) }.should be_empty
408
671
  rep.first.serializable_hash[:name].should == 'Friend 1'
409
672
  rep.last.serializable_hash[:name].should == 'Friend 2'
410
673
  end
411
674
 
412
675
  context 'child representations' do
413
676
  it 'disables root key name for child representations' do
414
-
415
677
  module EntitySpec
416
678
  class FriendEntity < Grape::Entity
417
679
  root 'friends', 'friend'
418
680
  expose :name, :email
419
681
  end
420
682
  end
421
-
683
+
422
684
  fresh_class.class_eval do
423
- expose :friends, :using => EntitySpec::FriendEntity
685
+ expose :friends, using: EntitySpec::FriendEntity
424
686
  end
425
-
687
+
426
688
  rep = subject.send(:value_for, :friends)
427
689
  rep.should be_kind_of Array
428
- rep.reject{|r| r.is_a?(EntitySpec::FriendEntity)}.should be_empty
690
+ rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }.should be_empty
429
691
  rep.first.serializable_hash[:name].should == 'Friend 1'
430
692
  rep.last.serializable_hash[:name].should == 'Friend 2'
431
693
  end
432
694
 
695
+ it "passes through the proc which returns an array of objects with custom options(:using)" do
696
+ module EntitySpec
697
+ class FriendEntity < Grape::Entity
698
+ root 'friends', 'friend'
699
+ expose :name, :email
700
+ end
701
+ end
702
+
703
+ fresh_class.class_eval do
704
+ expose :custom_friends, using: EntitySpec::FriendEntity do |user, options|
705
+ user.friends
706
+ end
707
+ end
708
+
709
+ rep = subject.send(:value_for, :custom_friends)
710
+ rep.should be_kind_of Array
711
+ rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }.should be_empty
712
+ rep.first.serializable_hash.should == { name: 'Friend 1', email: 'friend1@example.com' }
713
+ rep.last.serializable_hash.should == { name: 'Friend 2', email: 'friend2@example.com' }
714
+ end
715
+
716
+ it "passes through the proc which returns single object with custom options(:using)" do
717
+ module EntitySpec
718
+ class FriendEntity < Grape::Entity
719
+ root 'friends', 'friend'
720
+ expose :name, :email
721
+ end
722
+ end
723
+
724
+ fresh_class.class_eval do
725
+ expose :first_friend, using: EntitySpec::FriendEntity do |user, options|
726
+ user.friends.first
727
+ end
728
+ end
729
+
730
+ rep = subject.send(:value_for, :first_friend)
731
+ rep.should be_kind_of EntitySpec::FriendEntity
732
+ rep.serializable_hash.should == { name: 'Friend 1', email: 'friend1@example.com' }
733
+ end
734
+
735
+ it "passes through the proc which returns empty with custom options(:using)" do
736
+ module EntitySpec
737
+ class FriendEntity < Grape::Entity
738
+ root 'friends', 'friend'
739
+ expose :name, :email
740
+ end
741
+ end
742
+
743
+ fresh_class.class_eval do
744
+ expose :first_friend, using: EntitySpec::FriendEntity do |user, options|
745
+
746
+ end
747
+ end
748
+
749
+ rep = subject.send(:value_for, :first_friend)
750
+ rep.should be_kind_of EntitySpec::FriendEntity
751
+ rep.serializable_hash.should be_nil
752
+ end
753
+
433
754
  it 'passes through custom options' do
434
755
  module EntitySpec
435
756
  class FriendEntity < Grape::Entity
436
757
  root 'friends', 'friend'
437
758
  expose :name
438
- expose :email, :if => { :user_type => :admin }
759
+ expose :email, if: { user_type: :admin }
439
760
  end
440
761
  end
441
-
762
+
442
763
  fresh_class.class_eval do
443
- expose :friends, :using => EntitySpec::FriendEntity
764
+ expose :friends, using: EntitySpec::FriendEntity
444
765
  end
445
-
766
+
446
767
  rep = subject.send(:value_for, :friends)
447
768
  rep.should be_kind_of Array
448
- rep.reject{|r| r.is_a?(EntitySpec::FriendEntity)}.should be_empty
769
+ rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }.should be_empty
449
770
  rep.first.serializable_hash[:email].should be_nil
450
771
  rep.last.serializable_hash[:email].should be_nil
451
772
 
452
- rep = subject.send(:value_for, :friends, { :user_type => :admin })
773
+ rep = subject.send(:value_for, :friends, user_type: :admin)
453
774
  rep.should be_kind_of Array
454
- rep.reject{|r| r.is_a?(EntitySpec::FriendEntity)}.should be_empty
775
+ rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }.should be_empty
455
776
  rep.first.serializable_hash[:email].should == 'friend1@example.com'
456
777
  rep.last.serializable_hash[:email].should == 'friend2@example.com'
457
778
  end
@@ -461,25 +782,24 @@ describe Grape::Entity do
461
782
  class FriendEntity < Grape::Entity
462
783
  root 'friends', 'friend'
463
784
  expose :name
464
- expose :email, :if => { :collection => true }
785
+ expose :email, if: { collection: true }
465
786
  end
466
787
  end
467
-
788
+
468
789
  fresh_class.class_eval do
469
- expose :friends, :using => EntitySpec::FriendEntity
790
+ expose :friends, using: EntitySpec::FriendEntity
470
791
  end
471
-
472
- rep = subject.send(:value_for, :friends, { :collection => false })
792
+
793
+ rep = subject.send(:value_for, :friends, collection: false)
473
794
  rep.should be_kind_of Array
474
- rep.reject{|r| r.is_a?(EntitySpec::FriendEntity)}.should be_empty
795
+ rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }.should be_empty
475
796
  rep.first.serializable_hash[:email].should == 'friend1@example.com'
476
797
  rep.last.serializable_hash[:email].should == 'friend2@example.com'
477
798
  end
478
-
479
799
  end
480
800
 
481
801
  it 'calls through to the proc if there is one' do
482
- subject.send(:value_for, :computed, :awesome => 123).should == 123
802
+ subject.send(:value_for, :computed, awesome: 123).should == 123
483
803
  end
484
804
 
485
805
  it 'returns a formatted value if format_with is passed' do
@@ -489,6 +809,58 @@ describe Grape::Entity do
489
809
  it 'returns a formatted value if format_with is passed a lambda' do
490
810
  subject.send(:value_for, :fantasies).should == ['Nessy', 'Double Rainbows', 'Unicorns']
491
811
  end
812
+
813
+ it "tries instance methods on the entity first" do
814
+ module EntitySpec
815
+ class DelegatingEntity < Grape::Entity
816
+ root 'friends', 'friend'
817
+ expose :name
818
+ expose :email
819
+
820
+ private
821
+
822
+ def name
823
+ "cooler name"
824
+ end
825
+ end
826
+ end
827
+
828
+ friend = double("Friend", name: "joe", email: "joe@example.com")
829
+ rep = EntitySpec::DelegatingEntity.new(friend)
830
+ rep.send(:value_for, :name).should == "cooler name"
831
+ rep.send(:value_for, :email).should == "joe@example.com"
832
+ end
833
+
834
+ context "using" do
835
+ before do
836
+ module EntitySpec
837
+ class UserEntity < Grape::Entity
838
+ expose :name, :email
839
+ end
840
+ end
841
+ end
842
+ it "string" do
843
+ fresh_class.class_eval do
844
+ expose :friends, using: "EntitySpec::UserEntity"
845
+ end
846
+
847
+ rep = subject.send(:value_for, :friends)
848
+ rep.should be_kind_of Array
849
+ rep.size.should == 2
850
+ rep.all? { |r| r.is_a?(EntitySpec::UserEntity) }.should be_true
851
+ end
852
+
853
+ it 'class' do
854
+ fresh_class.class_eval do
855
+ expose :friends, using: EntitySpec::UserEntity
856
+ end
857
+
858
+ rep = subject.send(:value_for, :friends)
859
+ rep.should be_kind_of Array
860
+ rep.size.should == 2
861
+ rep.all? { |r| r.is_a?(EntitySpec::UserEntity) }.should be_true
862
+ end
863
+ end
492
864
  end
493
865
 
494
866
  describe '#documentation' do
@@ -499,89 +871,98 @@ describe Grape::Entity do
499
871
  end
500
872
 
501
873
  it 'returns each defined documentation hash' do
502
- doc = {:type => "foo", :desc => "bar"}
503
- fresh_class.expose :name, :documentation => doc
504
- fresh_class.expose :email, :documentation => doc
874
+ doc = { type: "foo", desc: "bar" }
875
+ fresh_class.expose :name, documentation: doc
876
+ fresh_class.expose :email, documentation: doc
877
+ fresh_class.expose :birthday
878
+
879
+ subject.documentation.should == { name: doc, email: doc }
880
+ end
881
+
882
+ it 'returns each defined documentation hash with :as param considering' do
883
+ doc = { type: "foo", desc: "bar" }
884
+ fresh_class.expose :name, documentation: doc, as: :label
885
+ fresh_class.expose :email, documentation: doc
505
886
  fresh_class.expose :birthday
506
887
 
507
- subject.documentation.should == {:name => doc, :email => doc}
888
+ subject.documentation.should == { label: doc, email: doc }
508
889
  end
509
890
  end
510
891
 
511
892
  describe '#key_for' do
512
893
  it 'returns the attribute if no :as is set' do
513
894
  fresh_class.expose :name
514
- subject.send(:key_for, :name).should == :name
895
+ subject.class.send(:key_for, :name).should == :name
515
896
  end
516
897
 
517
898
  it 'returns a symbolized version of the attribute' do
518
899
  fresh_class.expose :name
519
- subject.send(:key_for, 'name').should == :name
900
+ subject.class.send(:key_for, 'name').should == :name
520
901
  end
521
902
 
522
903
  it 'returns the :as alias if one exists' do
523
- fresh_class.expose :name, :as => :nombre
524
- subject.send(:key_for, 'name').should == :nombre
904
+ fresh_class.expose :name, as: :nombre
905
+ subject.class.send(:key_for, 'name').should == :nombre
525
906
  end
526
907
  end
527
908
 
528
909
  describe '#conditions_met?' do
529
910
  it 'only passes through hash :if exposure if all attributes match' do
530
- exposure_options = {:if => {:condition1 => true, :condition2 => true}}
911
+ exposure_options = { if: { condition1: true, condition2: true } }
531
912
 
532
913
  subject.send(:conditions_met?, exposure_options, {}).should be_false
533
- subject.send(:conditions_met?, exposure_options, :condition1 => true).should be_false
534
- subject.send(:conditions_met?, exposure_options, :condition1 => true, :condition2 => true).should be_true
535
- subject.send(:conditions_met?, exposure_options, :condition1 => false, :condition2 => true).should be_false
536
- subject.send(:conditions_met?, exposure_options, :condition1 => true, :condition2 => true, :other => true).should be_true
914
+ subject.send(:conditions_met?, exposure_options, condition1: true).should be_false
915
+ subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true).should be_true
916
+ subject.send(:conditions_met?, exposure_options, condition1: false, condition2: true).should be_false
917
+ subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true, other: true).should be_true
537
918
  end
538
919
 
539
920
  it 'looks for presence/truthiness if a symbol is passed' do
540
- exposure_options = {:if => :condition1}
921
+ exposure_options = { if: :condition1 }
541
922
 
542
923
  subject.send(:conditions_met?, exposure_options, {}).should be_false
543
- subject.send(:conditions_met?, exposure_options, {:condition1 => true}).should be_true
544
- subject.send(:conditions_met?, exposure_options, {:condition1 => false}).should be_false
545
- subject.send(:conditions_met?, exposure_options, {:condition1 => nil}).should be_false
924
+ subject.send(:conditions_met?, exposure_options, condition1: true).should be_true
925
+ subject.send(:conditions_met?, exposure_options, condition1: false).should be_false
926
+ subject.send(:conditions_met?, exposure_options, condition1: nil).should be_false
546
927
  end
547
928
 
548
929
  it 'looks for absence/falsiness if a symbol is passed' do
549
- exposure_options = {:unless => :condition1}
930
+ exposure_options = { unless: :condition1 }
550
931
 
551
932
  subject.send(:conditions_met?, exposure_options, {}).should be_true
552
- subject.send(:conditions_met?, exposure_options, {:condition1 => true}).should be_false
553
- subject.send(:conditions_met?, exposure_options, {:condition1 => false}).should be_true
554
- subject.send(:conditions_met?, exposure_options, {:condition1 => nil}).should be_true
933
+ subject.send(:conditions_met?, exposure_options, condition1: true).should be_false
934
+ subject.send(:conditions_met?, exposure_options, condition1: false).should be_true
935
+ subject.send(:conditions_met?, exposure_options, condition1: nil).should be_true
555
936
  end
556
937
 
557
938
  it 'only passes through proc :if exposure if it returns truthy value' do
558
- exposure_options = {:if => lambda{|_,opts| opts[:true]}}
939
+ exposure_options = { if: lambda { |_, opts| opts[:true] } }
559
940
 
560
- subject.send(:conditions_met?, exposure_options, :true => false).should be_false
561
- subject.send(:conditions_met?, exposure_options, :true => true).should be_true
941
+ subject.send(:conditions_met?, exposure_options, true: false).should be_false
942
+ subject.send(:conditions_met?, exposure_options, true: true).should be_true
562
943
  end
563
944
 
564
945
  it 'only passes through hash :unless exposure if any attributes do not match' do
565
- exposure_options = {:unless => {:condition1 => true, :condition2 => true}}
946
+ exposure_options = { unless: { condition1: true, condition2: true } }
566
947
 
567
948
  subject.send(:conditions_met?, exposure_options, {}).should be_true
568
- subject.send(:conditions_met?, exposure_options, :condition1 => true).should be_false
569
- subject.send(:conditions_met?, exposure_options, :condition1 => true, :condition2 => true).should be_false
570
- subject.send(:conditions_met?, exposure_options, :condition1 => false, :condition2 => true).should be_false
571
- subject.send(:conditions_met?, exposure_options, :condition1 => true, :condition2 => true, :other => true).should be_false
572
- subject.send(:conditions_met?, exposure_options, :condition1 => false, :condition2 => false).should be_true
949
+ subject.send(:conditions_met?, exposure_options, condition1: true).should be_false
950
+ subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true).should be_false
951
+ subject.send(:conditions_met?, exposure_options, condition1: false, condition2: true).should be_false
952
+ subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true, other: true).should be_false
953
+ subject.send(:conditions_met?, exposure_options, condition1: false, condition2: false).should be_true
573
954
  end
574
955
 
575
956
  it 'only passes through proc :unless exposure if it returns falsy value' do
576
- exposure_options = {:unless => lambda{|_,options| options[:true] == true}}
957
+ exposure_options = { unless: lambda { |_, options| options[:true] == true } }
577
958
 
578
- subject.send(:conditions_met?, exposure_options, :true => false).should be_true
579
- subject.send(:conditions_met?, exposure_options, :true => true).should be_false
959
+ subject.send(:conditions_met?, exposure_options, true: false).should be_true
960
+ subject.send(:conditions_met?, exposure_options, true: true).should be_false
580
961
  end
581
962
  end
582
963
 
583
964
  describe '::DSL' do
584
- subject{ Class.new }
965
+ subject { Class.new }
585
966
 
586
967
  it 'creates an Entity class when called' do
587
968
  subject.should_not be_const_defined :Entity
@@ -590,7 +971,7 @@ describe Grape::Entity do
590
971
  end
591
972
 
592
973
  context 'pre-mixed' do
593
- before{ subject.send(:include, Grape::Entity::DSL) }
974
+ before { subject.send(:include, Grape::Entity::DSL) }
594
975
 
595
976
  it 'is able to define entity traits through DSL' do
596
977
  subject.entity do
@@ -613,7 +994,7 @@ describe Grape::Entity do
613
994
  end
614
995
 
615
996
  context 'instance' do
616
- let(:instance){ subject.new }
997
+ let(:instance) { subject.new }
617
998
 
618
999
  describe '#entity' do
619
1000
  it 'is an instance of the entity class' do
@@ -624,8 +1005,8 @@ describe Grape::Entity do
624
1005
  instance.entity.object.should == instance
625
1006
  end
626
1007
 
627
- it 'should instantiate with options if provided' do
628
- instance.entity(:awesome => true).options.should == {:awesome => true}
1008
+ it 'instantiates with options if provided' do
1009
+ instance.entity(awesome: true).options.should == { awesome: true }
629
1010
  end
630
1011
  end
631
1012
  end