grape-entity 0.4.4 → 0.4.5

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: bca27e644c8d862751cbb573d3da20ec58fa9a1a
4
- data.tar.gz: 40aa499604d5a7941fc13016c58c9a2fda9de787
3
+ metadata.gz: 77619537334dd75647477a452a937b672ca248cb
4
+ data.tar.gz: f7c7659bd6f13e2f65efb8091b2ca00a37dfcd74
5
5
  SHA512:
6
- metadata.gz: b02bedc0adc756dec9fb93c0ae527eb4251e21882aedefb30f3f973fd867ee9589a61702d0464ae69366ea4fc27f5e42ce382ea9ec247b347ebeab2c3c817713
7
- data.tar.gz: c175aad11e6c88bfbd10c551a3f1cd0381753b918bc901f9786676f03558dc6e75d369e25ebc3aad45ed780ecf4f78d2a3e3627aa519e0824b29e11ec248794f
6
+ metadata.gz: c73b5b28f9cb7ebcc7ba71e264029e725720cfa79593bcb891aeb5628ca3b50a26699043d38e79f1842a0336f1a66e881d636befa6f20d74e469131f86b1ac04
7
+ data.tar.gz: 2d086dee3c598f34bedc613fe1da601e7eff2afb6f05d9cd8944c9ffac21e7f16f5e4cea2a686ecdbf1f60c0c6d9f08e918b0f247ed4b23a54df1ec13a6b9f45
data/.rubocop.yml CHANGED
@@ -1,63 +1,5 @@
1
1
  AllCops:
2
2
  Exclude:
3
- - vendor/**
3
+ - vendor/**/*
4
4
 
5
- LineLength:
6
- Enabled: false
7
-
8
- MethodLength:
9
- Enabled: false
10
-
11
- ClassLength:
12
- Enabled: false
13
-
14
- Documentation:
15
- # don't require classes to be documented
16
- Enabled: false
17
-
18
- CollectionMethods:
19
- # don't prefer map to collect, recuce to inject
20
- Enabled: false
21
-
22
- Encoding:
23
- # no need to always specify encoding
24
- Enabled: false
25
-
26
- Void:
27
- # == operator used in void context in specs
28
- Enabled: false
29
-
30
- SignalException:
31
- # prefer raise to fail
32
- EnforcedStyle: only_raise
33
-
34
- RaiseArgs:
35
- # don't care for what kind of raise
36
- Enabled: false
37
-
38
- PerlBackrefs:
39
- # TODO: regular expression matching with $1, $2, etc.
40
- Enabled: false
41
-
42
- BlockNesting:
43
- # TODO: fix too much nesting
44
- Max: 4
45
-
46
- Lambda:
47
- # TODO: replace all lambda with -> or Proc
48
- Enabled: false
49
-
50
- Blocks:
51
- # allow multi-line blocks like expect { }
52
- Enabled: false
53
-
54
- WordArray:
55
- # %w vs. [ '', ... ]
56
- Enabled: false
57
-
58
- CyclomaticComplexity:
59
- Enabled: false
60
-
61
- FileName:
62
- # allow grape-entity.rb for a require
63
- Enabled: false
5
+ inherit_from: .rubocop_todo.yml
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,64 @@
1
+ # This configuration was generated by `rubocop --auto-gen-config`
2
+ # on 2014-12-11 15:27:22 -0500 using RuboCop version 0.28.0.
3
+ # The point is for the user to remove these configuration records
4
+ # one by one as the offenses are removed from the code base.
5
+ # Note that changes in the inspected code, or installation of new
6
+ # versions of RuboCop, may require this file to be generated again.
7
+
8
+ # Offense count: 5
9
+ Metrics/AbcSize:
10
+ Max: 53
11
+
12
+ # Offense count: 1
13
+ # Configuration parameters: CountComments.
14
+ Metrics/ClassLength:
15
+ Max: 301
16
+
17
+ # Offense count: 4
18
+ Metrics/CyclomaticComplexity:
19
+ Max: 17
20
+
21
+ # Offense count: 175
22
+ # Configuration parameters: AllowURI, URISchemes.
23
+ Metrics/LineLength:
24
+ Max: 147
25
+
26
+ # Offense count: 6
27
+ # Configuration parameters: CountComments.
28
+ Metrics/MethodLength:
29
+ Max: 34
30
+
31
+ # Offense count: 4
32
+ Metrics/PerceivedComplexity:
33
+ Max: 15
34
+
35
+ # Offense count: 2
36
+ # Cop supports --auto-correct.
37
+ Style/Blocks:
38
+ Enabled: false
39
+
40
+ # Offense count: 30
41
+ Style/Documentation:
42
+ Enabled: false
43
+
44
+ # Offense count: 2
45
+ Style/EachWithObject:
46
+ Enabled: false
47
+
48
+ # Offense count: 1
49
+ # Configuration parameters: Exclude.
50
+ Style/FileName:
51
+ Enabled: false
52
+
53
+ # Offense count: 16
54
+ Style/Lambda:
55
+ Enabled: false
56
+
57
+ # Offense count: 1
58
+ # Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
59
+ Style/Next:
60
+ Enabled: false
61
+
62
+ # Offense count: 2
63
+ Style/RegexpLiteral:
64
+ MaxSlashes: 0
data/.travis.yml CHANGED
@@ -1,3 +1,5 @@
1
+ sudo: false
2
+
1
3
  language: ruby
2
4
 
3
5
  cache: bundler
@@ -5,12 +7,11 @@ cache: bundler
5
7
  rvm:
6
8
  - ruby-head
7
9
  - 2.1.2
8
- - 2.1.0
9
10
  - 2.0.0
10
11
  - 1.9.3
11
12
  - jruby-19mode
12
13
  - jruby-head
13
- - rbx-2
14
+ - rbx-2.2.10
14
15
 
15
16
  matrix:
16
17
  allow_failures:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ 0.4.5 (2015-03-10)
2
+ ==================
3
+
4
+ * [#109](https://github.com/intridea/grape-entity/pull/109): Added `unexpose` method - [@jonmchan](https://github.com/jonmchan).
5
+ * [#98](https://github.com/intridea/grape-entity/pull/98): Added nested conditionals - [@zbelzer](https://github.com/zbelzer).
6
+ * [#105](https://github.com/intridea/grape-entity/pull/105): Specify which attribute is missing in which Entity - [@jhollinger](https://github.com/jhollinger).
7
+ * [#111](https://github.com/intridea/grape-entity/pull/111): Fix: allow usage of attributes with name 'key' if `Hash` objects are used - [@croeck](https://github.com/croeck).
8
+ * [#110](https://github.com/intridea/grape-entity/pull/110): Fix: safe exposure when using `Hash` models - [@croeck](https://github.com/croeck).
9
+ * [#91](https://github.com/intridea/grape-entity/pull/91): Fix: OpenStruct serializing - [@etehtsea](https://github.com/etehtsea).
10
+
1
11
  0.4.4 (2014-08-17)
2
12
  ==================
3
13
 
@@ -7,7 +17,7 @@
7
17
  0.4.3 (2014-06-12)
8
18
  ==================
9
19
 
10
- * [#77](https://github.com/intridea/grape-entity/pull/77): Fix compatibility with Rspec 3 - [@justfalter](https://github.com/justfalter).
20
+ * [#77](https://github.com/intridea/grape-entity/pull/77): Fix: compatibility with Rspec 3 - [@justfalter](https://github.com/justfalter).
11
21
  * [#76](https://github.com/intridea/grape-entity/pull/76): Improve performance of entity serialization - [@justfalter](https://github.com/justfalter)
12
22
 
13
23
  0.4.2 (2014-04-03)
data/Gemfile CHANGED
@@ -11,12 +11,6 @@ group :development, :test do
11
11
  gem 'growl'
12
12
  gem 'json'
13
13
  gem 'rspec'
14
- gem 'rack-test', "~> 0.6.2", :require => "rack/test"
15
- gem 'rubocop', '0.21.0'
16
- end
17
-
18
- platforms :rbx do
19
- gem 'rubysl', '~> 2.0'
20
- gem 'parser', '~> 2.1'
21
- gem 'racc', '~> 1.4'
14
+ gem 'rack-test', '~> 0.6.2', require: 'rack/test'
15
+ gem 'rubocop', '0.28.0'
22
16
  end
data/Guardfile CHANGED
@@ -1,11 +1,11 @@
1
1
  # A sample Guardfile
2
2
  # More info at https://github.com/guard/guard#readme
3
3
 
4
- guard 'rspec', :version => 2 do
4
+ guard 'rspec', version: 2 do
5
5
  watch(%r{^spec/.+_spec\.rb$})
6
6
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
- watch(%r{^spec/support/shared_versioning_examples.rb$}) { |m| "spec/" }
8
- watch('spec/spec_helper.rb') { "spec/" }
7
+ watch(%r{^spec/support/shared_versioning_examples.rb$}) { |_m| 'spec/' }
8
+ watch('spec/spec_helper.rb') { 'spec/' }
9
9
  end
10
10
 
11
11
  guard 'bundler' do
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # Grape::Entity
2
2
 
3
- [![Build Status](https://travis-ci.org/intridea/grape-entity.svg?branch=master)](https://travis-ci.org/intridea/grape-entity)
3
+ [![Gem Version](http://img.shields.io/gem/v/grape-entity.svg)](http://badge.fury.io/rb/grape-entity)
4
+ [![Build Status](http://img.shields.io/travis/intridea/grape-entity.svg)](https://travis-ci.org/intridea/grape-entity)
5
+ [![Dependency Status](https://gemnasium.com/intridea/grape-entity.svg)](https://gemnasium.com/intridea/grape-entity)
6
+ [![Code Climate](https://codeclimate.com/github/intridea/grape-entity.svg)](https://codeclimate.com/github/intridea/grape-entity)
4
7
 
5
8
  ## Introduction
6
9
 
@@ -66,7 +69,7 @@ expose :user_name, :ip
66
69
  The field lookup takes several steps
67
70
 
68
71
  * first try `entity-instance.exposure`
69
- * next try `object.exposure`
72
+ * next try `object.exposure`
70
73
  * next try `object.fetch(exposure)`
71
74
  * last raise an Exception
72
75
 
@@ -81,7 +84,7 @@ expose :replies, using: API::Status, as: :replies
81
84
  Presenter classes can also be specified in string format, which helps with circular dependencies.
82
85
 
83
86
  ```ruby
84
- expose :replies, using: `API::Status`, as: :replies
87
+ expose :replies, using: "API::Status", as: :replies
85
88
  ```
86
89
 
87
90
  #### Conditional Exposure
@@ -117,6 +120,16 @@ expose :contact_info do
117
120
  end
118
121
  ```
119
122
 
123
+ You can also conditionally expose attributes in nested exposures:
124
+ ```ruby
125
+ expose :contact_info do
126
+ expose :phone
127
+ expose :address, using: API::Address
128
+ expose :email, if: lambda { |instance, options| options[:type] == :full }
129
+ end
130
+ ```
131
+
132
+
120
133
  #### Collection Exposure
121
134
 
122
135
  Use `root(plural, singular = nil)` to expose an object or a collection of objects with a root key.
@@ -127,16 +140,16 @@ expose :id, :name, ...
127
140
  ```
128
141
 
129
142
  By default every object of a collection is wrapped into an instance of your `Entity` class.
130
- You can override this behavior and wrapp the hole collection into one instance of your `Entity`
143
+ You can override this behavior and wrap the whole collection into one instance of your `Entity`
131
144
  class.
132
145
 
133
146
  As example:
134
147
 
135
148
  ```ruby
136
-
149
+
137
150
  present_collection true, :collection_name # `collection_name` is optional and defaults to `items`
138
151
  expose :collection_name, using: API:Items
139
-
152
+
140
153
 
141
154
  ```
142
155
 
@@ -186,6 +199,31 @@ private
186
199
  end
187
200
  ```
188
201
 
202
+ #### Unexpose
203
+
204
+ To undefine an exposed field, use the ```.unexpose``` method. Useful for modifying inherited entities.
205
+
206
+ ```ruby
207
+ class UserData < Grape::Entity
208
+ expose :name
209
+ expose :address1
210
+ expose :address2
211
+ expose :address_state
212
+ expose :address_city
213
+ expose :email
214
+ expose :phone
215
+ end
216
+
217
+ class MailingAddress < UserData
218
+ unexpose :email
219
+ unexpose :phone
220
+ end
221
+ ```
222
+
223
+
224
+
225
+
226
+
189
227
  #### Aliases
190
228
 
191
229
  Expose under a different name with `:as`.
@@ -346,18 +384,13 @@ Also see [Grape Entity Matchers](https://github.com/agileanimal/grape-entity-mat
346
384
 
347
385
  ## Contributing
348
386
 
349
- 1. Fork the project
350
- 2. Create your feature branch (`git checkout -b my-new-feature`)
351
- 3. Write tests. Make changes. Run `rubocop`.
352
- 3. Commit your changes (`git commit -am 'Add some feature'`)
353
- 4. Push to the branch (`git push origin my-new-feature`)
354
- 5. Create a new pull request
387
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
355
388
 
356
389
  ## License
357
390
 
358
- MIT License. See LICENSE for details.
391
+ MIT License. See [LICENSE](LICENSE) for details.
359
392
 
360
393
  ## Copyright
361
394
 
362
- Copyright (c) 2010-2013 Michael Bleigh, Intridea, Inc., and contributors.
395
+ Copyright (c) 2010-2014 Michael Bleigh, Intridea, Inc., and contributors.
363
396
 
data/RELEASING.md CHANGED
@@ -31,7 +31,7 @@ Remove the line with "Your contribution here.", since there will be no more cont
31
31
  Commit your changes.
32
32
 
33
33
  ```
34
- git add README.md CHANGELOG.md lib/grape-entity/version.rb
34
+ git add CHANGELOG.md lib/grape-entity/version.rb
35
35
  git commit -m "Preparing for release, 0.4.0."
36
36
  git push origin master
37
37
  ```
@@ -58,11 +58,13 @@ Next Release
58
58
  * Your contribution here.
59
59
  ```
60
60
 
61
+ Increment the minor version, modify [lib/grape-entity/version.rb](lib/grape-entity/version.rb).
62
+
61
63
  Comit your changes.
62
64
 
63
65
  ```
64
- git add CHANGELOG.md README.md
65
- git commit -m "Preparing for next release."
66
+ git add CHANGELOG.md lib/grape-entity/version.rb
67
+ git commit -m "Preparing for next release, 0.4.1."
66
68
  git push origin master
67
69
  ```
68
70
 
data/Rakefile CHANGED
@@ -17,6 +17,6 @@ end
17
17
  task :spec
18
18
  require 'rainbow/ext/string' unless String.respond_to?(:color)
19
19
  require 'rubocop/rake_task'
20
- Rubocop::RakeTask.new(:rubocop)
20
+ RuboCop::RakeTask.new(:rubocop)
21
21
 
22
22
  task default: [:rubocop, :spec]
data/grape-entity.gemspec CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
8
8
  s.authors = ['Michael Bleigh']
9
9
  s.email = ['michael@intridea.com']
10
10
  s.homepage = 'https://github.com/intridea/grape-entity'
11
- s.summary = %q(A simple facade for managing the relationship between your model and API.)
12
- s.description = %q(Extracted from Grape, A Ruby framework for rapid API development with great conventions.)
11
+ s.summary = 'A simple facade for managing the relationship between your model and API.'
12
+ s.description = 'Extracted from Grape, A Ruby framework for rapid API development with great conventions.'
13
13
  s.license = 'MIT'
14
14
 
15
15
  s.rubyforge_project = 'grape-entity'
@@ -127,11 +127,11 @@ module Grape
127
127
  options = merge_options(args.last.is_a?(Hash) ? args.pop : {})
128
128
 
129
129
  if args.size > 1
130
- raise ArgumentError, 'You may not use the :as option on multi-attribute exposures.' if options[:as]
131
- raise ArgumentError, 'You may not use block-setting on multi-attribute exposures.' if block_given?
130
+ fail ArgumentError, 'You may not use the :as option on multi-attribute exposures.' if options[:as]
131
+ fail ArgumentError, 'You may not use block-setting on multi-attribute exposures.' if block_given?
132
132
  end
133
133
 
134
- raise ArgumentError, 'You may not use block-setting when also using format_with' if block_given? && options[:format_with].respond_to?(:call)
134
+ fail ArgumentError, 'You may not use block-setting when also using format_with' if block_given? && options[:format_with].respond_to?(:call)
135
135
 
136
136
  options[:proc] = block if block_given? && block.parameters.any?
137
137
 
@@ -158,6 +158,10 @@ module Grape
158
158
  end
159
159
  end
160
160
 
161
+ def self.unexpose(attribute)
162
+ exposures.delete(attribute)
163
+ end
164
+
161
165
  # Set options that will be applied to any exposures declared inside the block.
162
166
  #
163
167
  # @example Multi-exposure if
@@ -270,7 +274,7 @@ module Grape
270
274
  # end
271
275
  #
272
276
  def self.format_with(name, &block)
273
- raise ArgumentError, 'You must pass a block for formatters' unless block_given?
277
+ fail ArgumentError, 'You must pass a block for formatters' unless block_given?
274
278
  formatters[name.to_sym] = block
275
279
  end
276
280
 
@@ -461,9 +465,9 @@ module Grape
461
465
  output[self.class.key_for(attribute)] =
462
466
  if partial_output.respond_to? :serializable_hash
463
467
  partial_output.serializable_hash(runtime_options)
464
- elsif partial_output.kind_of?(Array) && !partial_output.map { |o| o.respond_to? :serializable_hash }.include?(false)
465
- partial_output.map { |o| o.serializable_hash }
466
- elsif partial_output.kind_of?(Hash)
468
+ elsif partial_output.is_a?(Array) && !partial_output.map { |o| o.respond_to? :serializable_hash }.include?(false)
469
+ partial_output.map(&:serializable_hash)
470
+ elsif partial_output.is_a?(Hash)
467
471
  partial_output.each do |key, value|
468
472
  partial_output[key] = value.serializable_hash if value.respond_to? :serializable_hash
469
473
  end
@@ -535,10 +539,14 @@ module Grape
535
539
  end
536
540
 
537
541
  elsif nested_exposures.any?
538
- Hash[nested_exposures.map do |nested_attribute, _|
539
- [self.class.key_for(nested_attribute), value_for(nested_attribute, options)]
540
- end]
542
+ nested_attributes =
543
+ nested_exposures.map do |nested_attribute, nested_exposure_options|
544
+ if conditions_met?(nested_exposure_options, options)
545
+ [self.class.key_for(nested_attribute), value_for(nested_attribute, options)]
546
+ end
547
+ end
541
548
 
549
+ Hash[nested_attributes.compact]
542
550
  else
543
551
  delegate_attribute(attribute)
544
552
  end
@@ -548,13 +556,17 @@ module Grape
548
556
  name = self.class.name_for(attribute)
549
557
  if respond_to?(name, true)
550
558
  send(name)
559
+ elsif object.is_a?(Hash)
560
+ object[name]
561
+ elsif object.respond_to?(name, true)
562
+ object.send(name)
563
+ elsif object.respond_to?(:fetch, true)
564
+ object.fetch(name)
551
565
  else
552
- if object.respond_to?(name, true)
566
+ begin
553
567
  object.send(name)
554
- elsif object.respond_to?(:fetch, true)
555
- object.fetch(name)
556
- else
557
- raise ArgumentError
568
+ rescue NoMethodError
569
+ raise NoMethodError, "#{self.class.name} missing attribute `#{name}' on #{object}"
558
570
  end
559
571
  end
560
572
  end
@@ -562,9 +574,10 @@ module Grape
562
574
  def valid_exposure?(attribute, exposure_options)
563
575
  nested_exposures = self.class.nested_exposures_for(attribute)
564
576
  (nested_exposures.any? && nested_exposures.all? { |a, o| valid_exposure?(a, o) }) || \
565
- exposure_options.key?(:proc) || \
566
- !exposure_options[:safe] || \
567
- object.respond_to?(self.class.name_for(attribute))
577
+ exposure_options.key?(:proc) || \
578
+ !exposure_options[:safe] || \
579
+ object.respond_to?(self.class.name_for(attribute)) || \
580
+ object.is_a?(Hash) && object.key?(self.class.name_for(attribute))
568
581
  end
569
582
 
570
583
  def conditions_met?(exposure_options, options)
@@ -638,7 +651,7 @@ module Grape
638
651
  # @param options [Hash] Exposure options.
639
652
  def self.valid_options(options)
640
653
  options.keys.each do |key|
641
- raise ArgumentError, "#{key.inspect} is not a valid option." unless OPTIONS.include?(key)
654
+ fail ArgumentError, "#{key.inspect} is not a valid option." unless OPTIONS.include?(key)
642
655
  end
643
656
 
644
657
  options[:using] = options.delete(:with) if options.key?(:with)
@@ -1,3 +1,3 @@
1
1
  module GrapeEntity
2
- VERSION = '0.4.4'
2
+ VERSION = '0.4.5'
3
3
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
+ require 'ostruct'
2
3
 
3
4
  describe Grape::Entity do
4
-
5
5
  let(:fresh_class) { Class.new(Grape::Entity) }
6
6
 
7
7
  context 'class methods' do
@@ -110,6 +110,15 @@ describe Grape::Entity do
110
110
  )
111
111
  end
112
112
 
113
+ it 'does not represent nested exposures whose conditions are not met' do
114
+ subject.expose :awesome do
115
+ subject.expose(:condition_met, if: lambda { |_, _| true }) { |_| 'value' }
116
+ subject.expose(:condition_not_met, if: lambda { |_, _| false }) { |_| 'value' }
117
+ end
118
+
119
+ expect(subject.represent({}).send(:value_for, :awesome)).to eq(condition_met: 'value')
120
+ end
121
+
113
122
  it 'does not represent attributes, declared inside nested exposure, outside of it' do
114
123
  subject.expose :awesome do
115
124
  subject.expose(:nested) { |_| 'value' }
@@ -269,6 +278,48 @@ describe Grape::Entity do
269
278
  end
270
279
  end
271
280
 
281
+ describe '.unexpose' do
282
+ it 'is able to remove exposed attributes' do
283
+ subject.expose :name, :email
284
+ subject.unexpose :email
285
+
286
+ expect(subject.exposures).to eq(name: {})
287
+ end
288
+
289
+ context 'inherited exposures' do
290
+ it 'when called from child class, only removes from the attribute from child' do
291
+ subject.expose :name, :email
292
+ child_class = Class.new(subject)
293
+ child_class.unexpose :email
294
+
295
+ expect(child_class.exposures).to eq(name: {})
296
+ expect(subject.exposures).to eq(name: {}, email: {})
297
+ end
298
+
299
+ # the following 2 behaviors are testing because it is not most intuitive and could be confusing
300
+ context 'when called from the parent class' do
301
+ it 'remove from parent and all child classes that have not locked down their attributes with an .exposures call' do
302
+ subject.expose :name, :email
303
+ child_class = Class.new(subject)
304
+ subject.unexpose :email
305
+
306
+ expect(subject.exposures).to eq(name: {})
307
+ expect(child_class.exposures).to eq(name: {})
308
+ end
309
+
310
+ it 'remove from parent and do not remove from child classes that have locked down their attributes with an .exposures call' do
311
+ subject.expose :name, :email
312
+ child_class = Class.new(subject)
313
+ child_class.exposures
314
+ subject.unexpose :email
315
+
316
+ expect(subject.exposures).to eq(name: {})
317
+ expect(child_class.exposures).to eq(name: {}, email: {})
318
+ end
319
+ end
320
+ end
321
+ end
322
+
272
323
  describe '.with_options' do
273
324
  it 'raises an error for unknown options' do
274
325
  block = proc do
@@ -417,7 +468,7 @@ describe Grape::Entity do
417
468
  representation = subject.represent(4.times.map { Object.new })
418
469
  expect(representation).to be_kind_of Array
419
470
  expect(representation.size).to eq(4)
420
- expect(representation.reject { |r| r.kind_of?(subject) }).to be_empty
471
+ expect(representation.reject { |r| r.is_a?(subject) }).to be_empty
421
472
  end
422
473
 
423
474
  it 'adds the collection: true option if called with a collection' do
@@ -442,6 +493,19 @@ describe Grape::Entity do
442
493
  representation = subject.represent({ awesome: true }, serializable: true)
443
494
  expect(representation).to eq(awesome: true)
444
495
  end
496
+
497
+ it 'returns a serialized hash of an OpenStruct' do
498
+ subject.expose(:awesome)
499
+ representation = subject.represent(OpenStruct.new, serializable: true)
500
+ expect(representation).to eq(awesome: nil)
501
+ end
502
+
503
+ it 'raises error if field not found' do
504
+ subject.expose(:awesome)
505
+ expect do
506
+ subject.represent(Object.new, serializable: true)
507
+ end.to raise_error(NoMethodError, /missing attribute `awesome'/)
508
+ end
445
509
  end
446
510
 
447
511
  describe '.present_collection' do
@@ -491,7 +555,7 @@ describe Grape::Entity do
491
555
  expect(representation).to have_key 'things'
492
556
  expect(representation['things']).to be_kind_of Array
493
557
  expect(representation['things'].size).to eq 4
494
- expect(representation['things'].reject { |r| r.kind_of?(subject) }).to be_empty
558
+ expect(representation['things'].reject { |r| r.is_a?(subject) }).to be_empty
495
559
  end
496
560
  end
497
561
 
@@ -500,7 +564,7 @@ describe Grape::Entity do
500
564
  representation = subject.represent(4.times.map { Object.new }, root: false)
501
565
  expect(representation).to be_kind_of Array
502
566
  expect(representation.size).to eq 4
503
- expect(representation.reject { |r| r.kind_of?(subject) }).to be_empty
567
+ expect(representation.reject { |r| r.is_a?(subject) }).to be_empty
504
568
  end
505
569
  it 'can use a different name' do
506
570
  representation = subject.represent(4.times.map { Object.new }, root: 'others')
@@ -508,7 +572,7 @@ describe Grape::Entity do
508
572
  expect(representation).to have_key 'others'
509
573
  expect(representation['others']).to be_kind_of Array
510
574
  expect(representation['others'].size).to eq 4
511
- expect(representation['others'].reject { |r| r.kind_of?(subject) }).to be_empty
575
+ expect(representation['others'].reject { |r| r.is_a?(subject) }).to be_empty
512
576
  end
513
577
  end
514
578
  end
@@ -532,7 +596,7 @@ describe Grape::Entity do
532
596
  representation = subject.represent(4.times.map { Object.new })
533
597
  expect(representation).to be_kind_of Array
534
598
  expect(representation.size).to eq 4
535
- expect(representation.reject { |r| r.kind_of?(subject) }).to be_empty
599
+ expect(representation.reject { |r| r.is_a?(subject) }).to be_empty
536
600
  end
537
601
  end
538
602
  end
@@ -555,7 +619,7 @@ describe Grape::Entity do
555
619
  expect(representation).to have_key('things')
556
620
  expect(representation['things']).to be_kind_of Array
557
621
  expect(representation['things'].size).to eq 4
558
- expect(representation['things'].reject { |r| r.kind_of?(subject) }).to be_empty
622
+ expect(representation['things'].reject { |r| r.is_a?(subject) }).to be_empty
559
623
  end
560
624
  end
561
625
  end
@@ -574,25 +638,26 @@ describe Grape::Entity do
574
638
  expect(entity.options).to eq({})
575
639
  end
576
640
  end
577
-
578
641
  end
579
642
 
580
643
  context 'instance methods' do
581
-
582
644
  let(:model) { double(attributes) }
583
645
 
584
- let(:attributes) {
646
+ let(:attributes) do
585
647
  {
586
648
  name: 'Bob Bobson',
587
649
  email: 'bob@example.com',
588
650
  birthday: Time.gm(2012, 2, 27),
589
651
  fantasies: ['Unicorns', 'Double Rainbows', 'Nessy'],
652
+ characteristics: [
653
+ { key: 'hair_color', value: 'brown' }
654
+ ],
590
655
  friends: [
591
- double(name: 'Friend 1', email: 'friend1@example.com', fantasies: [], birthday: Time.gm(2012, 2, 27), friends: []),
592
- double(name: 'Friend 2', email: 'friend2@example.com', fantasies: [], birthday: Time.gm(2012, 2, 27), friends: [])
656
+ double(name: 'Friend 1', email: 'friend1@example.com', characteristics: [], fantasies: [], birthday: Time.gm(2012, 2, 27), friends: []),
657
+ double(name: 'Friend 2', email: 'friend2@example.com', characteristics: [], fantasies: [], birthday: Time.gm(2012, 2, 27), friends: [])
593
658
  ]
594
659
  }
595
- }
660
+ end
596
661
 
597
662
  subject { fresh_class.new(model) }
598
663
 
@@ -621,6 +686,13 @@ describe Grape::Entity do
621
686
  expect(res).to have_key :name
622
687
  end
623
688
 
689
+ it 'does expose attributes marked as safe if model is a hash object' do
690
+ fresh_class.expose :name, safe: true
691
+
692
+ res = fresh_class.new(name: 'myname').serializable_hash
693
+ expect(res).to have_key :name
694
+ end
695
+
624
696
  it "does not expose attributes that don't exist on the object, even with criteria" do
625
697
  fresh_class.expose :email
626
698
  fresh_class.expose :nonexistent_attribute, safe: true, if: lambda { false }
@@ -844,6 +916,25 @@ describe Grape::Entity do
844
916
  expect(rep.serializable_hash).to be_nil
845
917
  end
846
918
 
919
+ it 'passes through exposed entity with key and value attributes' do
920
+ module EntitySpec
921
+ class CharacteristicsEntity < Grape::Entity
922
+ root 'characteristics', 'characteristic'
923
+ expose :key, :value
924
+ end
925
+ end
926
+
927
+ fresh_class.class_eval do
928
+ expose :characteristics, using: EntitySpec::CharacteristicsEntity
929
+ end
930
+
931
+ rep = subject.send(:value_for, :characteristics)
932
+ expect(rep).to be_kind_of Array
933
+ expect(rep.reject { |r| r.is_a?(EntitySpec::CharacteristicsEntity) }).to be_empty
934
+ expect(rep.first.serializable_hash[:key]).to eq 'hair_color'
935
+ expect(rep.first.serializable_hash[:value]).to eq 'brown'
936
+ end
937
+
847
938
  it 'passes through custom options' do
848
939
  module EntitySpec
849
940
  class FriendEntity < Grape::Entity
data/spec/spec_helper.rb CHANGED
@@ -9,6 +9,4 @@ Bundler.require :default, :test
9
9
 
10
10
  require 'pry'
11
11
 
12
- RSpec.configure do |config|
13
- config.raise_errors_for_deprecations!
14
- end
12
+ RSpec.configure(&:raise_errors_for_deprecations!)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grape-entity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-17 00:00:00.000000000 Z
11
+ date: 2015-03-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -119,6 +119,7 @@ files:
119
119
  - .gitignore
120
120
  - .rspec
121
121
  - .rubocop.yml
122
+ - .rubocop_todo.yml
122
123
  - .travis.yml
123
124
  - .yardopts
124
125
  - CHANGELOG.md
@@ -157,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
158
  version: '0'
158
159
  requirements: []
159
160
  rubyforge_project: grape-entity
160
- rubygems_version: 2.0.14
161
+ rubygems_version: 2.4.5
161
162
  signing_key:
162
163
  specification_version: 4
163
164
  summary: A simple facade for managing the relationship between your model and API.