grape 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -66
- data/.rubocop_todo.yml +78 -17
- data/.travis.yml +7 -3
- data/Appraisals +7 -0
- data/CHANGELOG.md +24 -0
- data/CONTRIBUTING.md +7 -0
- data/Gemfile +1 -7
- data/Guardfile +1 -1
- data/README.md +560 -94
- data/RELEASING.md +1 -1
- data/Rakefile +10 -11
- data/UPGRADING.md +211 -3
- data/gemfiles/rails_3.gemfile +14 -0
- data/gemfiles/rails_4.gemfile +14 -0
- data/grape.gemspec +10 -9
- data/lib/backports/active_support/deep_dup.rb +49 -0
- data/lib/backports/active_support/duplicable.rb +88 -0
- data/lib/grape.rb +29 -2
- data/lib/grape/api.rb +59 -65
- data/lib/grape/dsl/api.rb +19 -0
- data/lib/grape/dsl/callbacks.rb +6 -4
- data/lib/grape/dsl/configuration.rb +49 -5
- data/lib/grape/dsl/helpers.rb +7 -8
- data/lib/grape/dsl/inside_route.rb +22 -10
- data/lib/grape/dsl/middleware.rb +5 -5
- data/lib/grape/dsl/parameters.rb +6 -2
- data/lib/grape/dsl/request_response.rb +23 -20
- data/lib/grape/dsl/routing.rb +52 -49
- data/lib/grape/dsl/settings.rb +110 -0
- data/lib/grape/dsl/validations.rb +14 -6
- data/lib/grape/endpoint.rb +104 -88
- data/lib/grape/exceptions/base.rb +2 -2
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- data/lib/grape/exceptions/invalid_formatter.rb +1 -1
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -1
- data/lib/grape/exceptions/missing_mime_type.rb +1 -1
- data/lib/grape/exceptions/missing_option.rb +1 -1
- data/lib/grape/exceptions/missing_vendor_option.rb +1 -1
- data/lib/grape/exceptions/unknown_options.rb +1 -1
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/validation.rb +1 -1
- data/lib/grape/exceptions/validation_errors.rb +2 -2
- data/lib/grape/formatter/serializable_hash.rb +1 -1
- data/lib/grape/formatter/xml.rb +1 -1
- data/lib/grape/locale/en.yml +2 -0
- data/lib/grape/middleware/auth/dsl.rb +26 -21
- data/lib/grape/middleware/auth/strategies.rb +1 -1
- data/lib/grape/middleware/auth/strategy_info.rb +0 -2
- data/lib/grape/middleware/base.rb +2 -2
- data/lib/grape/middleware/error.rb +1 -1
- data/lib/grape/middleware/formatter.rb +5 -5
- data/lib/grape/middleware/versioner.rb +1 -1
- data/lib/grape/middleware/versioner/header.rb +3 -3
- data/lib/grape/middleware/versioner/param.rb +2 -2
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +1 -1
- data/lib/grape/path.rb +9 -3
- data/lib/grape/util/content_types.rb +16 -8
- data/lib/grape/util/inheritable_setting.rb +74 -0
- data/lib/grape/util/inheritable_values.rb +51 -0
- data/lib/grape/util/stackable_values.rb +52 -0
- data/lib/grape/util/strict_hash_configuration.rb +106 -0
- data/lib/grape/validations.rb +0 -220
- data/lib/grape/validations/attributes_iterator.rb +21 -0
- data/lib/grape/validations/params_scope.rb +176 -0
- data/lib/grape/validations/validators/all_or_none.rb +20 -0
- data/lib/grape/validations/validators/allow_blank.rb +30 -0
- data/lib/grape/validations/validators/at_least_one_of.rb +20 -0
- data/lib/grape/validations/validators/base.rb +37 -0
- data/lib/grape/validations/{coerce.rb → validators/coerce.rb} +3 -3
- data/lib/grape/validations/{default.rb → validators/default.rb} +1 -1
- data/lib/grape/validations/validators/exactly_one_of.rb +20 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +26 -0
- data/lib/grape/validations/validators/mutual_exclusion.rb +25 -0
- data/lib/grape/validations/{presence.rb → validators/presence.rb} +2 -2
- data/lib/grape/validations/validators/regexp.rb +12 -0
- data/lib/grape/validations/validators/values.rb +26 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +522 -343
- data/spec/grape/dsl/callbacks_spec.rb +4 -4
- data/spec/grape/dsl/configuration_spec.rb +48 -9
- data/spec/grape/dsl/helpers_spec.rb +6 -13
- data/spec/grape/dsl/inside_route_spec.rb +43 -4
- data/spec/grape/dsl/middleware_spec.rb +1 -10
- data/spec/grape/dsl/parameters_spec.rb +8 -1
- data/spec/grape/dsl/request_response_spec.rb +16 -22
- data/spec/grape/dsl/routing_spec.rb +21 -5
- data/spec/grape/dsl/settings_spec.rb +219 -0
- data/spec/grape/dsl/validations_spec.rb +8 -11
- data/spec/grape/endpoint_spec.rb +115 -86
- data/spec/grape/entity_spec.rb +33 -33
- data/spec/grape/exceptions/invalid_formatter_spec.rb +3 -5
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +4 -6
- data/spec/grape/exceptions/missing_mime_type_spec.rb +5 -6
- data/spec/grape/exceptions/missing_option_spec.rb +3 -5
- data/spec/grape/exceptions/unknown_options_spec.rb +3 -5
- data/spec/grape/exceptions/unknown_validator_spec.rb +3 -5
- data/spec/grape/exceptions/validation_errors_spec.rb +5 -5
- data/spec/grape/loading_spec.rb +44 -0
- data/spec/grape/middleware/auth/base_spec.rb +0 -4
- data/spec/grape/middleware/auth/dsl_spec.rb +2 -4
- data/spec/grape/middleware/auth/strategies_spec.rb +5 -6
- data/spec/grape/middleware/exception_spec.rb +8 -10
- data/spec/grape/middleware/formatter_spec.rb +13 -15
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +10 -10
- data/spec/grape/middleware/versioner/header_spec.rb +25 -25
- data/spec/grape/middleware/versioner/param_spec.rb +15 -17
- data/spec/grape/middleware/versioner/path_spec.rb +1 -2
- data/spec/grape/middleware/versioner_spec.rb +0 -1
- data/spec/grape/path_spec.rb +66 -45
- data/spec/grape/util/inheritable_setting_spec.rb +217 -0
- data/spec/grape/util/inheritable_values_spec.rb +63 -0
- data/spec/grape/util/stackable_values_spec.rb +115 -0
- data/spec/grape/util/strict_hash_configuration_spec.rb +38 -0
- data/spec/grape/validations/attributes_iterator_spec.rb +4 -0
- data/spec/grape/validations/params_scope_spec.rb +57 -0
- data/spec/grape/validations/validators/all_or_none_spec.rb +60 -0
- data/spec/grape/validations/validators/allow_blank_spec.rb +170 -0
- data/spec/grape/validations/{at_least_one_of_spec.rb → validators/at_least_one_of_spec.rb} +7 -3
- data/spec/grape/validations/{coerce_spec.rb → validators/coerce_spec.rb} +8 -11
- data/spec/grape/validations/{default_spec.rb → validators/default_spec.rb} +7 -9
- data/spec/grape/validations/{exactly_one_of_spec.rb → validators/exactly_one_of_spec.rb} +15 -11
- data/spec/grape/validations/{mutual_exclusion_spec.rb → validators/mutual_exclusion_spec.rb} +11 -9
- data/spec/grape/validations/{presence_spec.rb → validators/presence_spec.rb} +30 -30
- data/spec/grape/validations/{regexp_spec.rb → validators/regexp_spec.rb} +2 -4
- data/spec/grape/validations/{values_spec.rb → validators/values_spec.rb} +95 -23
- data/spec/grape/validations/{zh-CN.yml → validators/zh-CN.yml} +0 -0
- data/spec/grape/validations_spec.rb +335 -70
- data/spec/shared/versioning_examples.rb +7 -8
- data/spec/spec_helper.rb +2 -0
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- data/spec/support/content_type_helpers.rb +1 -1
- data/spec/support/versioned_helpers.rb +3 -3
- metadata +80 -33
- data/lib/grape/util/deep_merge.rb +0 -23
- data/lib/grape/util/hash_stack.rb +0 -120
- data/lib/grape/validations/at_least_one_of.rb +0 -25
- data/lib/grape/validations/exactly_one_of.rb +0 -26
- data/lib/grape/validations/mutual_exclusion.rb +0 -25
- data/lib/grape/validations/regexp.rb +0 -12
- data/lib/grape/validations/values.rb +0 -23
- data/spec/grape/util/hash_stack_spec.rb +0 -132
data/RELEASING.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Releasing Grape
|
2
2
|
===============
|
3
3
|
|
4
|
-
There're no particular rules about when to release Grape. Release bug fixes
|
4
|
+
There're no particular rules about when to release Grape. Release bug fixes frequently, features not so frequently and breaking API changes rarely.
|
5
5
|
|
6
6
|
### Release
|
7
7
|
|
data/Rakefile
CHANGED
@@ -37,17 +37,16 @@ begin
|
|
37
37
|
end
|
38
38
|
|
39
39
|
namespace :pages do
|
40
|
-
|
41
|
-
desc "Check out gh-pages."
|
40
|
+
desc 'Check out gh-pages.'
|
42
41
|
task :checkout do
|
43
42
|
dir = File.dirname(__FILE__) + '/../grape.doc'
|
44
43
|
unless Dir.exist?(dir)
|
45
44
|
Dir.mkdir(dir)
|
46
45
|
Dir.chdir(dir) do
|
47
|
-
system(
|
48
|
-
system(
|
49
|
-
system(
|
50
|
-
system(
|
46
|
+
system('git init')
|
47
|
+
system('git remote add origin git@github.com:intridea/grape.git')
|
48
|
+
system('git pull')
|
49
|
+
system('git checkout gh-pages')
|
51
50
|
end
|
52
51
|
end
|
53
52
|
end
|
@@ -55,15 +54,15 @@ begin
|
|
55
54
|
desc 'Generate and publish YARD docs to GitHub pages.'
|
56
55
|
task :publish => ['doc:pages:checkout', 'doc:pages'] do
|
57
56
|
Dir.chdir(File.dirname(__FILE__) + '/../grape.doc') do
|
58
|
-
system(
|
59
|
-
system(
|
60
|
-
system(
|
57
|
+
system('git checkout gh-pages')
|
58
|
+
system('git add .')
|
59
|
+
system('git add -u')
|
61
60
|
system("git commit -m 'Generating docs for version #{Grape::VERSION}.'")
|
62
|
-
system(
|
61
|
+
system('git push origin gh-pages')
|
63
62
|
end
|
64
63
|
end
|
65
64
|
end
|
66
65
|
end
|
67
66
|
rescue LoadError
|
68
|
-
puts
|
67
|
+
puts 'You need to install YARD.'
|
69
68
|
end
|
data/UPGRADING.md
CHANGED
@@ -1,13 +1,221 @@
|
|
1
1
|
Upgrading Grape
|
2
2
|
===============
|
3
3
|
|
4
|
-
### Upgrading to >= 0.
|
4
|
+
### Upgrading to >= 0.10.0
|
5
|
+
|
6
|
+
#### Changes to content-types
|
7
|
+
|
8
|
+
The following content-types have been removed:
|
9
|
+
|
10
|
+
* atom (application/atom+xml)
|
11
|
+
* rss (application/rss+xml)
|
12
|
+
* jsonapi (application/jsonapi)
|
13
|
+
|
14
|
+
This is because they have never been properly supported.
|
15
|
+
|
16
|
+
#### Changes to desc
|
17
|
+
|
18
|
+
New block syntax:
|
19
|
+
|
20
|
+
Former:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
desc "some descs",
|
24
|
+
detail: 'more details',
|
25
|
+
entity: API::Entities::Entity,
|
26
|
+
params: API::Entities::Status.documentation,
|
27
|
+
named: 'a name',
|
28
|
+
headers: [XAuthToken: {
|
29
|
+
description: 'Valdates your identity',
|
30
|
+
required: true
|
31
|
+
}
|
32
|
+
get nil, http_codes: [
|
33
|
+
[401, 'Unauthorized', API::Entities::BaseError],
|
34
|
+
[404, 'not found', API::Entities::Error]
|
35
|
+
] do
|
36
|
+
```
|
37
|
+
|
38
|
+
Now:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
desc "some descs" do
|
42
|
+
detail 'more details'
|
43
|
+
params API::Entities::Status.documentation
|
44
|
+
success API::Entities::Entity
|
45
|
+
failure [
|
46
|
+
[401, 'Unauthorized', API::Entities::BaseError],
|
47
|
+
[404, 'not found', API::Entities::Error]
|
48
|
+
]
|
49
|
+
named 'a name'
|
50
|
+
headers [
|
51
|
+
XAuthToken: {
|
52
|
+
description: 'Valdates your identity',
|
53
|
+
required: true
|
54
|
+
},
|
55
|
+
XOptionalHeader: {
|
56
|
+
description: 'Not really needed',
|
57
|
+
required: false
|
58
|
+
}
|
59
|
+
]
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
#### Changes to Route Options and Descriptions
|
64
|
+
|
65
|
+
A common hack to extend Grape with custom DSL methods was manipulating `@last_description`.
|
66
|
+
|
67
|
+
``` ruby
|
68
|
+
module Grape
|
69
|
+
module Extensions
|
70
|
+
module SortExtension
|
71
|
+
def sort(value)
|
72
|
+
@last_description ||= {}
|
73
|
+
@last_description[:sort] ||= {}
|
74
|
+
@last_description[:sort].merge! value
|
75
|
+
value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
Grape::API.extend self
|
80
|
+
end
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
You could access this value from within the API with `route.route_sort` or, more generally, via `env['api.endpoint'].options[:route_options][:sort]`.
|
85
|
+
|
86
|
+
This will no longer work, use the documented and supported `route_setting`.
|
87
|
+
|
88
|
+
``` ruby
|
89
|
+
module Grape
|
90
|
+
module Extensions
|
91
|
+
module SortExtension
|
92
|
+
def sort(value)
|
93
|
+
route_setting :sort, sort: value
|
94
|
+
value
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
Grape::API.extend self
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
To retrieve this value at runtime from within an API, use `env['api.endpoint'].route_setting(:sort)` and when introspecting a mounted API, use `route.route_settings[:sort]`.
|
104
|
+
|
105
|
+
#### Accessing Class Variables from Helpers
|
106
|
+
|
107
|
+
It used to be possible to fetch an API class variable from a helper function. For example:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
@@static_variable = 42
|
111
|
+
|
112
|
+
helpers do
|
113
|
+
def get_static_variable
|
114
|
+
@@static_variable
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
get do
|
119
|
+
get_static_variable
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
This will no longer work. Use a class method instead of a helper.
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
@@static_variable = 42
|
127
|
+
|
128
|
+
def self.get_static_variable
|
129
|
+
@@static_variable
|
130
|
+
end
|
131
|
+
|
132
|
+
get do
|
133
|
+
get_static_variable
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
For more information see [#836](https://github.com/intridea/grape/issues/836).
|
138
|
+
|
139
|
+
#### Changes to Custom Validators
|
140
|
+
|
141
|
+
To implement a custom validator, you need to inherit from `Grape::Validations::Base` instead of `Grape::Validations::Validator`.
|
142
|
+
|
143
|
+
For more information see [Custom Validators](https://github.com/intridea/grape#custom-validators) in the documentation.
|
144
|
+
|
145
|
+
#### Changes to Raising Grape::Exceptions::Validation
|
146
|
+
|
147
|
+
In previous versions raising `Grape::Exceptions::Validation` required a single `param`.
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
raise Grape::Exceptions::Validation, param: :id, message_key: :presence
|
151
|
+
```
|
152
|
+
|
153
|
+
The `param` argument has been deprecated and is now an array of `params`, accepting multiple values.
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
raise Grape::Exceptions::Validation, params: [:id], message_key: :presence
|
157
|
+
```
|
158
|
+
|
159
|
+
#### Changes to routes when using `format`
|
160
|
+
|
161
|
+
Routes will no longer get file-type suffixes added if you declare a single API `format`. For example,
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
class API < Grape::API
|
165
|
+
format :json
|
166
|
+
|
167
|
+
get :hello do
|
168
|
+
{ hello: 'world' }
|
169
|
+
end
|
170
|
+
end
|
171
|
+
```
|
172
|
+
|
173
|
+
Pre-0.10.0, this would respond with JSON to `/hello`, `/hello.json`, `/hello.xml`, `/hello.txt`, etc.
|
174
|
+
|
175
|
+
Now, this will only respond with JSON to `/hello`, but will be a 404 when trying to access `/hello.json`, `/hello.xml`, `/hello.txt`, etc.
|
176
|
+
|
177
|
+
If you declare further `content_type`s, this behavior will be circumvented. For example, the following API will respond with JSON to `/hello`, `/hello.json`, `/hello.xml`, `/hello.txt`, etc.
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
class API < Grape::API
|
181
|
+
format :json
|
182
|
+
content_type :json, 'application/json'
|
183
|
+
|
184
|
+
get :hello do
|
185
|
+
{ hello: 'world' }
|
186
|
+
end
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
190
|
+
See the [the updated API Formats documentation](https://github.com/intridea/grape#api-formats) and [#809](https://github.com/intridea/grape/pull/809) for more info.
|
191
|
+
|
192
|
+
#### Changes to Evaluation of Permitted Parameter Values
|
193
|
+
|
194
|
+
Permitted and default parameter values are now only evaluated lazily for each request when declared as a proc. The following code would raise an error at startup time.
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
params do
|
198
|
+
optional :v, values: -> { [:x, :y] }, default: -> { :z } }
|
199
|
+
end
|
200
|
+
```
|
201
|
+
|
202
|
+
Remove the proc to get the previous behavior.
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
params do
|
206
|
+
optional :v, values: [:x, :y], default: :z }
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
See [#801](https://github.com/intridea/grape/issues/801) for more information.
|
211
|
+
|
212
|
+
### Upgrading to >= 0.9.0
|
5
213
|
|
6
214
|
#### Changes in Authentication
|
7
215
|
|
8
216
|
The following middleware classes have been removed:
|
9
217
|
|
10
|
-
* `Grape::Middleware::Auth::Basic`
|
218
|
+
* `Grape::Middleware::Auth::Basic`
|
11
219
|
* `Grape::Middleware::Auth::Digest`
|
12
220
|
* `Grape::Middleware::Auth::OAuth2`
|
13
221
|
|
@@ -20,7 +228,7 @@ When you use theses classes directly like:
|
|
20
228
|
use Grape::Middleware::Auth::OAuth2,
|
21
229
|
token_class: 'AccessToken',
|
22
230
|
parameter: %w(access_token api_key)
|
23
|
-
|
231
|
+
|
24
232
|
```
|
25
233
|
|
26
234
|
you have to replace these classes.
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source 'http://rubygems.org'
|
4
|
+
|
5
|
+
gem 'rails', '3.2.19'
|
6
|
+
|
7
|
+
group :development, :test do
|
8
|
+
gem 'rubocop', '~> 0.28.0'
|
9
|
+
gem 'guard'
|
10
|
+
gem 'guard-rspec'
|
11
|
+
gem 'guard-rubocop'
|
12
|
+
end
|
13
|
+
|
14
|
+
gemspec :path => '../'
|
data/grape.gemspec
CHANGED
@@ -1,18 +1,18 @@
|
|
1
|
-
$:.push File.expand_path(
|
2
|
-
require
|
1
|
+
$:.push File.expand_path('../lib', __FILE__)
|
2
|
+
require 'grape/version'
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
|
-
s.name =
|
5
|
+
s.name = 'grape'
|
6
6
|
s.version = Grape::VERSION
|
7
7
|
s.platform = Gem::Platform::RUBY
|
8
|
-
s.authors = [
|
9
|
-
s.email = [
|
10
|
-
s.homepage =
|
8
|
+
s.authors = ['Michael Bleigh']
|
9
|
+
s.email = ['michael@intridea.com']
|
10
|
+
s.homepage = 'https://github.com/intridea/grape'
|
11
11
|
s.summary = %q{A simple Ruby framework for building REST-like APIs.}
|
12
12
|
s.description = %q{A Ruby framework for rapid API development with great conventions.}
|
13
|
-
s.license =
|
13
|
+
s.license = 'MIT'
|
14
14
|
|
15
|
-
s.rubyforge_project =
|
15
|
+
s.rubyforge_project = 'grape'
|
16
16
|
|
17
17
|
s.add_runtime_dependency 'rack', '>= 1.3.0'
|
18
18
|
s.add_runtime_dependency 'rack-mount'
|
@@ -34,9 +34,10 @@ Gem::Specification.new do |s|
|
|
34
34
|
s.add_development_dependency 'cookiejar'
|
35
35
|
s.add_development_dependency 'rack-contrib'
|
36
36
|
s.add_development_dependency 'mime-types'
|
37
|
+
s.add_development_dependency 'appraisal'
|
37
38
|
|
38
39
|
s.files = `git ls-files`.split("\n")
|
39
40
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
40
41
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
41
|
-
s.require_paths = [
|
42
|
+
s.require_paths = ['lib']
|
42
43
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Backport from Rails 4.x
|
2
|
+
# https://github.com/rails/rails/blob/4-0-stable/activesupport/lib/active_support/core_ext/object/deep_dup.rb
|
3
|
+
|
4
|
+
require_relative 'duplicable'
|
5
|
+
|
6
|
+
class Object
|
7
|
+
# Returns a deep copy of object if it's duplicable. If it's
|
8
|
+
# not duplicable, returns +self+.
|
9
|
+
#
|
10
|
+
# object = Object.new
|
11
|
+
# dup = object.deep_dup
|
12
|
+
# dup.instance_variable_set(:@a, 1)
|
13
|
+
#
|
14
|
+
# object.instance_variable_defined?(:@a) #=> false
|
15
|
+
# dup.instance_variable_defined?(:@a) #=> true
|
16
|
+
def deep_dup
|
17
|
+
duplicable? ? dup : self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Array
|
22
|
+
# Returns a deep copy of array.
|
23
|
+
#
|
24
|
+
# array = [1, [2, 3]]
|
25
|
+
# dup = array.deep_dup
|
26
|
+
# dup[1][2] = 4
|
27
|
+
#
|
28
|
+
# array[1][2] #=> nil
|
29
|
+
# dup[1][2] #=> 4
|
30
|
+
def deep_dup
|
31
|
+
map(&:deep_dup)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Hash
|
36
|
+
# Returns a deep copy of hash.
|
37
|
+
#
|
38
|
+
# hash = { a: { b: 'b' } }
|
39
|
+
# dup = hash.deep_dup
|
40
|
+
# dup[:a][:c] = 'c'
|
41
|
+
#
|
42
|
+
# hash[:a][:c] #=> nil
|
43
|
+
# dup[:a][:c] #=> "c"
|
44
|
+
def deep_dup
|
45
|
+
each_with_object(dup) do |(key, value), hash|
|
46
|
+
hash[key.deep_dup] = value.deep_dup
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# Backport from Rails 4.x
|
2
|
+
# https://github.com/rails/rails/blob/4-0-stable/activesupport/lib/active_support/core_ext/object/deep_dup.rb
|
3
|
+
|
4
|
+
#--
|
5
|
+
# Most objects are cloneable, but not all. For example you can't dup +nil+:
|
6
|
+
#
|
7
|
+
# nil.dup # => TypeError: can't dup NilClass
|
8
|
+
#
|
9
|
+
# Classes may signal their instances are not duplicable removing +dup+/+clone+
|
10
|
+
# or raising exceptions from them. So, to dup an arbitrary object you normally
|
11
|
+
# use an optimistic approach and are ready to catch an exception, say:
|
12
|
+
#
|
13
|
+
# arbitrary_object.dup rescue object
|
14
|
+
#
|
15
|
+
# Rails dups objects in a few critical spots where they are not that arbitrary.
|
16
|
+
# That rescue is very expensive (like 40 times slower than a predicate), and it
|
17
|
+
# is often triggered.
|
18
|
+
#
|
19
|
+
# That's why we hardcode the following cases and check duplicable? instead of
|
20
|
+
# using that rescue idiom.
|
21
|
+
#++
|
22
|
+
class Object
|
23
|
+
# Can you safely dup this object?
|
24
|
+
#
|
25
|
+
# False for +nil+, +false+, +true+, symbol, and number objects;
|
26
|
+
# true otherwise.
|
27
|
+
def duplicable?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
class NilClass
|
32
|
+
# +nil+ is not duplicable:
|
33
|
+
#
|
34
|
+
# nil.duplicable? # => false
|
35
|
+
# nil.dup # => TypeError: can't dup NilClass
|
36
|
+
def duplicable?
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
class FalseClass
|
41
|
+
# +false+ is not duplicable:
|
42
|
+
#
|
43
|
+
# false.duplicable? # => false
|
44
|
+
# false.dup # => TypeError: can't dup FalseClass
|
45
|
+
def duplicable?
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
class TrueClass
|
50
|
+
# +true+ is not duplicable:
|
51
|
+
#
|
52
|
+
# true.duplicable? # => false
|
53
|
+
# true.dup # => TypeError: can't dup TrueClass
|
54
|
+
def duplicable?
|
55
|
+
false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
class Symbol
|
59
|
+
# Symbols are not duplicable:
|
60
|
+
#
|
61
|
+
# :my_symbol.duplicable? # => false
|
62
|
+
# :my_symbol.dup # => TypeError: can't dup Symbol
|
63
|
+
def duplicable?
|
64
|
+
false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
class Numeric
|
68
|
+
# Numbers are not duplicable:
|
69
|
+
#
|
70
|
+
# 3.duplicable? # => false
|
71
|
+
# 3.dup # => TypeError: can't dup Fixnum
|
72
|
+
def duplicable?
|
73
|
+
false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
require 'bigdecimal'
|
77
|
+
# rubocop:disable Lint/HandleExceptions
|
78
|
+
class BigDecimal
|
79
|
+
begin
|
80
|
+
BigDecimal.new('4.56').dup
|
81
|
+
def duplicable?
|
82
|
+
true
|
83
|
+
end
|
84
|
+
rescue TypeError
|
85
|
+
# can't dup, so use superclass implementation
|
86
|
+
end
|
87
|
+
end
|
88
|
+
# rubocop:enable Lint/HandleExceptions
|