grape-entity 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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.