grape-entity 0.5.0 → 0.5.1

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: 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: