grape-entity 0.5.0 → 0.5.1

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: ae09d2c2eb04cf66e6c186d175814bbd656d745c
4
- data.tar.gz: cb06bd7e21f3b873631a59bcd736ff4c12f981af
3
+ metadata.gz: 6316984d3c73b100e20294679aa32bf4a9fccffd
4
+ data.tar.gz: 8f687c1d19b89540afb58a9a5c83b4b4a51e0fb1
5
5
  SHA512:
6
- metadata.gz: bd4ec8cf775bf7e9ea5db80cb14ef4eec682aafb29610cf0581530f372a1929926ba8abb6e4f8e8aa9b8a5b97a0bc224548346a9e05158f75ae250d24eaa08f8
7
- data.tar.gz: f626ea6a74c2bf15945a3c0a3c29b8609939227bcb228361eedeb9dd247fc33ab7d2df2bb7c768b1a2073cc640ddfd39db51b908ff1d6a73f4a64602b66260fa
6
+ metadata.gz: 0639798ea31a230451bc8480cf6bd97818d703d3b978c204d519237ef9accf2aff1834335d749a018a9f476c927355b5e804043680b2237aa11495cdead86bcf
7
+ data.tar.gz: daff836f74b47ad4b321b647387100bced7b484d734dfa7c2627f5b1d487b4307ed5cdbf4cd0c02018e2b3be3decea9d66d2dd8c08585c2f5bc1355e42aee71d
@@ -6,14 +6,17 @@ cache: bundler
6
6
 
7
7
  rvm:
8
8
  - ruby-head
9
- - 2.1.2
9
+ - 2.3.0
10
+ - 2.2
11
+ - 2.1
10
12
  - 2.0.0
11
13
  - 1.9.3
12
14
  - jruby-19mode
13
15
  - jruby-head
14
- - rbx-2.2.10
16
+ - rbx-2
15
17
 
16
18
  matrix:
17
19
  allow_failures:
18
20
  - rvm: ruby-head
19
21
  - rvm: jruby-head
22
+ - rvm: rbx-2
@@ -1,3 +1,10 @@
1
+ 0.5.1 (2016-4-4)
2
+ ================
3
+
4
+ * [#202](https://github.com/ruby-grape/grape-entity/pull/202): Fix: Reset `@using_class` memoization on `.setup` - [@rngtng](https://github.com/rngtng).
5
+ * [#203](https://github.com/ruby-grape/grape-entity/pull/203): `Grape::Entity::Exposure::NestingExposure::NestedExposures.delete_if` always returns exposures - [@rngtng](https://github.com/rngtng).
6
+ * [#204](https://github.com/ruby-grape/grape-entity/pull/204), [#138](https://github.com/ruby-grape/grape-entity/issues/138): Added ability to merge fields into hashes/root (`:merge` option for `.expose`) - [@avyy](https://github.com/avyy).
7
+
1
8
  0.5.0 (2015-12-07)
2
9
  ==================
3
10
 
data/README.md CHANGED
@@ -21,9 +21,10 @@ module API
21
21
  expose :text, documentation: { type: "String", desc: "Status update text." }
22
22
  expose :ip, if: { type: :full }
23
23
  expose :user_type, :user_id, if: lambda { |status, options| status.user.public? }
24
+ expose :location, merge: true
24
25
  expose :contact_info do
25
26
  expose :phone
26
- expose :address, using: API::Entities::Address
27
+ expose :address, merge: true, using: API::Entities::Address
27
28
  end
28
29
  expose :digest do |status, options|
29
30
  Digest::MD5.hexdigest status.txt
@@ -153,6 +154,40 @@ As example:
153
154
 
154
155
  ```
155
156
 
157
+ #### Merge Fields
158
+
159
+ Use `:merge` option to merge fields into the hash or into the root:
160
+
161
+ ```ruby
162
+ expose :contact_info do
163
+ expose :phone
164
+ expose :address, merge: true, using: API::Entities::Address
165
+ end
166
+
167
+ expose :status, merge: true
168
+ ```
169
+
170
+ This will return something like:
171
+
172
+ ```ruby
173
+ { contact_info: { phone: "88002000700", city: 'City 17', address_line: 'Block C' }, text: 'HL3', likes: 19 }
174
+ ```
175
+
176
+ It also works with collections:
177
+
178
+ ```ruby
179
+ expose :profiles do
180
+ expose :users, merge: true, using: API::Entities::User
181
+ expose :admins, merge: true, using: API::Entities::Admin
182
+ end
183
+ ```
184
+
185
+ Provide lambda to solve collisions:
186
+
187
+ ```ruby
188
+ expose :status, merge: ->(key, old_val, new_val) { old_val + new_val if old_val && new_val }
189
+ ```
190
+
156
191
  #### Runtime Exposure
157
192
 
158
193
  Use a block or a `Proc` to evaluate exposure at runtime. The supplied block or
@@ -0,0 +1,8 @@
1
+ Upgrading Grape Entity
2
+ ===============
3
+
4
+ ### Upgrading to >= 0.5.1
5
+
6
+ * `Grape::Entity::Exposure::NestingExposure::NestedExposures.delete_if` always
7
+ returns exposures, regardless of delete result (used to be
8
+ `nil` in negative case), see [#203](https://github.com/ruby-grape/grape-entity/pull/203).
@@ -146,6 +146,7 @@ module Grape
146
146
  # block to the expose call to achieve the same effect.
147
147
  # @option options :documentation Define documenation for an exposed
148
148
  # field, typically the value is a hash with two fields, type and desc.
149
+ # @option options :merge This option allows you to merge an exposed field to the root
149
150
  def self.expose(*args, &block)
150
151
  options = merge_options(args.last.is_a?(Hash) ? args.pop : {})
151
152
 
@@ -498,7 +499,7 @@ module Grape
498
499
 
499
500
  # All supported options.
500
501
  OPTIONS = [
501
- :rewrite, :as, :if, :unless, :using, :with, :proc, :documentation, :format_with, :safe, :attr_path, :if_extras, :unless_extras
502
+ :rewrite, :as, :if, :unless, :using, :with, :proc, :documentation, :format_with, :safe, :attr_path, :if_extras, :unless_extras, :merge
502
503
  ].to_set.freeze
503
504
 
504
505
  # Merges the given options with current block options.
@@ -2,7 +2,7 @@ module Grape
2
2
  class Entity
3
3
  module Exposure
4
4
  class Base
5
- attr_reader :attribute, :key, :is_safe, :documentation, :conditions
5
+ attr_reader :attribute, :key, :is_safe, :documentation, :conditions, :for_merge
6
6
 
7
7
  def self.new(attribute, options, conditions, *args, &block)
8
8
  super(attribute, options, conditions).tap { |e| e.setup(*args, &block) }
@@ -13,6 +13,7 @@ module Grape
13
13
  @options = options
14
14
  @key = (options[:as] || attribute).try(:to_sym)
15
15
  @is_safe = options[:safe]
16
+ @for_merge = options[:merge]
16
17
  @attr_path_proc = options[:attr_path]
17
18
  @documentation = options[:documentation]
18
19
  @conditions = conditions
@@ -30,11 +30,12 @@ module Grape
30
30
 
31
31
  def value(entity, options)
32
32
  new_options = nesting_options_for(options)
33
+ output = OutputBuilder.new
33
34
 
34
- normalized_exposures(entity, new_options).each_with_object({}) do |exposure, output|
35
+ normalized_exposures(entity, new_options).each_with_object(output) do |exposure, out|
35
36
  exposure.with_attr_path(entity, new_options) do
36
37
  result = exposure.value(entity, new_options)
37
- output[exposure.key] = result
38
+ out.add(exposure, result)
38
39
  end
39
40
  end
40
41
  end
@@ -53,11 +54,12 @@ module Grape
53
54
 
54
55
  def serializable_value(entity, options)
55
56
  new_options = nesting_options_for(options)
57
+ output = OutputBuilder.new
56
58
 
57
- normalized_exposures(entity, new_options).each_with_object({}) do |exposure, output|
59
+ normalized_exposures(entity, new_options).each_with_object(output) do |exposure, out|
58
60
  exposure.with_attr_path(entity, new_options) do
59
61
  result = exposure.serializable_value(entity, new_options)
60
- output[exposure.key] = result
62
+ out.add(exposure, result)
61
63
  end
62
64
  end
63
65
  end
@@ -126,3 +128,4 @@ module Grape
126
128
  end
127
129
 
128
130
  require 'grape_entity/exposure/nesting_exposure/nested_exposures'
131
+ require 'grape_entity/exposure/nesting_exposure/output_builder'
@@ -21,6 +21,7 @@ module Grape
21
21
  def delete_by(*attributes)
22
22
  reset_memoization!
23
23
  @exposures.reject! { |e| attributes.include? e.attribute }
24
+ @exposures
24
25
  end
25
26
 
26
27
  def clear
@@ -0,0 +1,58 @@
1
+ module Grape
2
+ class Entity
3
+ module Exposure
4
+ class NestingExposure
5
+ class OutputBuilder < SimpleDelegator
6
+ def initialize
7
+ @output_hash = {}
8
+ @output_collection = []
9
+ end
10
+
11
+ def add(exposure, result)
12
+ # Save a result array in collections' array if it should be merged
13
+ if result.is_a?(Array) && exposure.for_merge
14
+ @output_collection << result
15
+ else
16
+
17
+ # If we have an array which should not be merged - save it with a key as a hash
18
+ # If we have hash which should be merged - save it without a key (merge)
19
+ if exposure.for_merge
20
+ @output_hash.merge! result, &merge_strategy(exposure.for_merge)
21
+ else
22
+ @output_hash[exposure.key] = result
23
+ end
24
+
25
+ end
26
+ end
27
+
28
+ def __getobj__
29
+ output
30
+ end
31
+
32
+ private
33
+
34
+ # If output_collection contains at least one element we have to represent the output as a collection
35
+ def output
36
+ if @output_collection.empty?
37
+ output = @output_hash
38
+ else
39
+ output = @output_collection
40
+ output << @output_hash unless @output_hash.empty?
41
+ output.flatten!
42
+ end
43
+ output
44
+ end
45
+
46
+ # In case if we want to solve collisions providing lambda to :merge option
47
+ def merge_strategy(for_merge)
48
+ if for_merge.respond_to? :call
49
+ for_merge
50
+ else
51
+ -> {}
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -5,6 +5,7 @@ module Grape
5
5
  attr_reader :using_class_name, :subexposure
6
6
 
7
7
  def setup(using_class_name, subexposure)
8
+ @using_class = nil
8
9
  @using_class_name = using_class_name
9
10
  @subexposure = subexposure
10
11
  end
@@ -1,3 +1,3 @@
1
1
  module GrapeEntity
2
- VERSION = '0.5.0'
2
+ VERSION = '0.5.1'
3
3
  end
@@ -35,6 +35,23 @@ describe Grape::Entity do
35
35
  end
36
36
  end
37
37
 
38
+ context 'with a :merge option' do
39
+ let(:nested_hash) do
40
+ { something: { like_nested_hash: true }, special: { like_nested_hash: '12' } }
41
+ end
42
+
43
+ it 'merges an exposure to the root' do
44
+ subject.expose(:something, merge: true)
45
+ expect(subject.represent(nested_hash).serializable_hash).to eq(nested_hash[:something])
46
+ end
47
+
48
+ it 'allows to solve collisions providing a lambda to a :merge option' do
49
+ subject.expose(:something, merge: true)
50
+ subject.expose(:special, merge: ->(_, v1, v2) { v1 && v2 ? 'brand new val' : v2 })
51
+ expect(subject.represent(nested_hash).serializable_hash).to eq(like_nested_hash: 'brand new val')
52
+ end
53
+ end
54
+
38
55
  context 'with a block' do
39
56
  it 'errors out if called with multiple attributes' do
40
57
  expect { subject.expose(:name, :email) { true } }.to raise_error ArgumentError
@@ -243,6 +260,30 @@ describe Grape::Entity do
243
260
  }
244
261
  )
245
262
  end
263
+ it 'merges attriutes if :merge option is passed' do
264
+ user_entity = Class.new(Grape::Entity)
265
+ admin_entity = Class.new(Grape::Entity)
266
+ user_entity.expose(:id, :name)
267
+ admin_entity.expose(:id, :name)
268
+
269
+ subject.expose(:profiles) do
270
+ subject.expose(:users, merge: true, using: user_entity)
271
+ subject.expose(:admins, merge: true, using: admin_entity)
272
+ end
273
+
274
+ subject.expose :awesome do
275
+ subject.expose(:nested, merge: true) { |_| { just_a_key: 'value' } }
276
+ subject.expose(:another_nested, merge: true) { |_| { just_another_key: 'value' } }
277
+ end
278
+
279
+ additional_hash = { users: [{ id: 1, name: 'John' }, { id: 2, name: 'Jay' }],
280
+ admins: [{ id: 3, name: 'Jack' }, { id: 4, name: 'James' }]
281
+ }
282
+ expect(subject.represent(additional_hash).serializable_hash).to eq(
283
+ profiles: additional_hash[:users] + additional_hash[:admins],
284
+ awesome: { just_a_key: 'value', just_another_key: 'value' }
285
+ )
286
+ end
246
287
  end
247
288
  end
248
289
 
@@ -863,7 +904,7 @@ describe Grape::Entity do
863
904
  subject.expose :my_items
864
905
 
865
906
  representation = subject.represent(4.times.map { Object.new }, serializable: true)
866
- expect(representation).to be_kind_of(Hash)
907
+ expect(representation).to be_kind_of(Grape::Entity::Exposure::NestingExposure::OutputBuilder)
867
908
  expect(representation).to have_key :my_items
868
909
  expect(representation[:my_items]).to be_kind_of Array
869
910
  expect(representation[:my_items].size).to be 4
@@ -1092,7 +1133,7 @@ describe Grape::Entity do
1092
1133
  context 'without safe option' do
1093
1134
  it 'throws an exception when an attribute is not found on the object' do
1094
1135
  fresh_class.expose :name, :nonexistent_attribute
1095
- expect { fresh_class.new(model).serializable_hash }.to raise_error
1136
+ expect { fresh_class.new(model).serializable_hash }.to raise_error NoMethodError
1096
1137
  end
1097
1138
 
1098
1139
  it "exposes attributes that don't exist on the object only when they are generated by a block" do
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Grape::Entity::Exposure::NestingExposure::NestedExposures do
4
- subject { described_class.new([]) }
4
+ subject(:nested_exposures) { described_class.new([]) }
5
5
 
6
6
  describe '#deep_complex_nesting?' do
7
7
  it 'is reset when additional exposure is added' do
@@ -31,4 +31,26 @@ describe Grape::Entity::Exposure::NestingExposure::NestedExposures do
31
31
  expect(subject.instance_variable_get(:@deep_complex_nesting)).to be_nil
32
32
  end
33
33
  end
34
+
35
+ describe '.delete_by' do
36
+ subject { nested_exposures.delete_by(*attributes) }
37
+
38
+ let(:attributes) { [:id] }
39
+
40
+ before do
41
+ nested_exposures << Grape::Entity::Exposure.new(:id, {})
42
+ end
43
+
44
+ it 'deletes matching exposure' do
45
+ is_expected.to eq []
46
+ end
47
+
48
+ context "when given attribute doesn't exists" do
49
+ let(:attributes) { [:foo] }
50
+
51
+ it 'deletes matching exposure' do
52
+ is_expected.to eq(nested_exposures)
53
+ end
54
+ end
55
+ end
34
56
  end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Entity::Exposure::RepresentExposure do
4
+ subject(:exposure) { described_class.new(:foo, {}, {}, double, double) }
5
+
6
+ describe '#setup' do
7
+ subject { exposure.setup(using_class_name, subexposure) }
8
+
9
+ let(:using_class_name) { double(:using_class_name) }
10
+ let(:subexposure) { double(:subexposure) }
11
+
12
+ it 'sets using_class_name' do
13
+ expect { subject }.to change { exposure.using_class_name }.to(using_class_name)
14
+ end
15
+
16
+ it 'sets subexposure' do
17
+ expect { subject }.to change { exposure.subexposure }.to(subexposure)
18
+ end
19
+
20
+ context 'when using_class is set' do
21
+ before do
22
+ exposure.using_class
23
+ end
24
+
25
+ it 'resets using_class' do
26
+ expect { subject }.to change { exposure.using_class }
27
+ end
28
+ end
29
+ end
30
+ end
metadata CHANGED
@@ -1,111 +1,111 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grape-entity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-07 00:00:00.000000000 Z
11
+ date: 2016-04-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: multi_json
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: 1.3.2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 1.3.2
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: maruku
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: yard
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - '>='
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - '>='
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rspec
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ~>
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
89
  version: '2.9'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ~>
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '2.9'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: bundler
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - '>='
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
103
  version: '0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - '>='
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  description: Extracted from Grape, A Ruby framework for rapid API development with
@@ -116,12 +116,12 @@ executables: []
116
116
  extensions: []
117
117
  extra_rdoc_files: []
118
118
  files:
119
- - .gitignore
120
- - .rspec
121
- - .rubocop.yml
122
- - .rubocop_todo.yml
123
- - .travis.yml
124
- - .yardopts
119
+ - ".gitignore"
120
+ - ".rspec"
121
+ - ".rubocop.yml"
122
+ - ".rubocop_todo.yml"
123
+ - ".travis.yml"
124
+ - ".yardopts"
125
125
  - CHANGELOG.md
126
126
  - CONTRIBUTING.md
127
127
  - Gemfile
@@ -130,6 +130,7 @@ files:
130
130
  - README.md
131
131
  - RELEASING.md
132
132
  - Rakefile
133
+ - UPGRADING.md
133
134
  - bench/serializing.rb
134
135
  - grape-entity.gemspec
135
136
  - lib/grape-entity.rb
@@ -154,11 +155,13 @@ files:
154
155
  - lib/grape_entity/exposure/formatter_exposure.rb
155
156
  - lib/grape_entity/exposure/nesting_exposure.rb
156
157
  - lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb
158
+ - lib/grape_entity/exposure/nesting_exposure/output_builder.rb
157
159
  - lib/grape_entity/exposure/represent_exposure.rb
158
160
  - lib/grape_entity/options.rb
159
161
  - lib/grape_entity/version.rb
160
162
  - spec/grape_entity/entity_spec.rb
161
163
  - spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb
164
+ - spec/grape_entity/exposure/represent_exposure_spec.rb
162
165
  - spec/grape_entity/exposure_spec.rb
163
166
  - spec/spec_helper.rb
164
167
  homepage: https://github.com/ruby-grape/grape-entity
@@ -171,23 +174,24 @@ require_paths:
171
174
  - lib
172
175
  required_ruby_version: !ruby/object:Gem::Requirement
173
176
  requirements:
174
- - - '>='
177
+ - - ">="
175
178
  - !ruby/object:Gem::Version
176
179
  version: '0'
177
180
  required_rubygems_version: !ruby/object:Gem::Requirement
178
181
  requirements:
179
- - - '>='
182
+ - - ">="
180
183
  - !ruby/object:Gem::Version
181
184
  version: '0'
182
185
  requirements: []
183
186
  rubyforge_project: grape-entity
184
- rubygems_version: 2.4.5
187
+ rubygems_version: 2.4.8
185
188
  signing_key:
186
189
  specification_version: 4
187
190
  summary: A simple facade for managing the relationship between your model and API.
188
191
  test_files:
189
192
  - spec/grape_entity/entity_spec.rb
190
193
  - spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb
194
+ - spec/grape_entity/exposure/represent_exposure_spec.rb
191
195
  - spec/grape_entity/exposure_spec.rb
192
196
  - spec/spec_helper.rb
193
197
  has_rdoc: