grape-entity 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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