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.

Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -66
  3. data/.rubocop_todo.yml +78 -17
  4. data/.travis.yml +7 -3
  5. data/Appraisals +7 -0
  6. data/CHANGELOG.md +24 -0
  7. data/CONTRIBUTING.md +7 -0
  8. data/Gemfile +1 -7
  9. data/Guardfile +1 -1
  10. data/README.md +560 -94
  11. data/RELEASING.md +1 -1
  12. data/Rakefile +10 -11
  13. data/UPGRADING.md +211 -3
  14. data/gemfiles/rails_3.gemfile +14 -0
  15. data/gemfiles/rails_4.gemfile +14 -0
  16. data/grape.gemspec +10 -9
  17. data/lib/backports/active_support/deep_dup.rb +49 -0
  18. data/lib/backports/active_support/duplicable.rb +88 -0
  19. data/lib/grape.rb +29 -2
  20. data/lib/grape/api.rb +59 -65
  21. data/lib/grape/dsl/api.rb +19 -0
  22. data/lib/grape/dsl/callbacks.rb +6 -4
  23. data/lib/grape/dsl/configuration.rb +49 -5
  24. data/lib/grape/dsl/helpers.rb +7 -8
  25. data/lib/grape/dsl/inside_route.rb +22 -10
  26. data/lib/grape/dsl/middleware.rb +5 -5
  27. data/lib/grape/dsl/parameters.rb +6 -2
  28. data/lib/grape/dsl/request_response.rb +23 -20
  29. data/lib/grape/dsl/routing.rb +52 -49
  30. data/lib/grape/dsl/settings.rb +110 -0
  31. data/lib/grape/dsl/validations.rb +14 -6
  32. data/lib/grape/endpoint.rb +104 -88
  33. data/lib/grape/exceptions/base.rb +2 -2
  34. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  35. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  36. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  37. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -1
  38. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  39. data/lib/grape/exceptions/missing_option.rb +1 -1
  40. data/lib/grape/exceptions/missing_vendor_option.rb +1 -1
  41. data/lib/grape/exceptions/unknown_options.rb +1 -1
  42. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  43. data/lib/grape/exceptions/validation.rb +1 -1
  44. data/lib/grape/exceptions/validation_errors.rb +2 -2
  45. data/lib/grape/formatter/serializable_hash.rb +1 -1
  46. data/lib/grape/formatter/xml.rb +1 -1
  47. data/lib/grape/locale/en.yml +2 -0
  48. data/lib/grape/middleware/auth/dsl.rb +26 -21
  49. data/lib/grape/middleware/auth/strategies.rb +1 -1
  50. data/lib/grape/middleware/auth/strategy_info.rb +0 -2
  51. data/lib/grape/middleware/base.rb +2 -2
  52. data/lib/grape/middleware/error.rb +1 -1
  53. data/lib/grape/middleware/formatter.rb +5 -5
  54. data/lib/grape/middleware/versioner.rb +1 -1
  55. data/lib/grape/middleware/versioner/header.rb +3 -3
  56. data/lib/grape/middleware/versioner/param.rb +2 -2
  57. data/lib/grape/middleware/versioner/path.rb +1 -1
  58. data/lib/grape/namespace.rb +1 -1
  59. data/lib/grape/path.rb +9 -3
  60. data/lib/grape/util/content_types.rb +16 -8
  61. data/lib/grape/util/inheritable_setting.rb +74 -0
  62. data/lib/grape/util/inheritable_values.rb +51 -0
  63. data/lib/grape/util/stackable_values.rb +52 -0
  64. data/lib/grape/util/strict_hash_configuration.rb +106 -0
  65. data/lib/grape/validations.rb +0 -220
  66. data/lib/grape/validations/attributes_iterator.rb +21 -0
  67. data/lib/grape/validations/params_scope.rb +176 -0
  68. data/lib/grape/validations/validators/all_or_none.rb +20 -0
  69. data/lib/grape/validations/validators/allow_blank.rb +30 -0
  70. data/lib/grape/validations/validators/at_least_one_of.rb +20 -0
  71. data/lib/grape/validations/validators/base.rb +37 -0
  72. data/lib/grape/validations/{coerce.rb → validators/coerce.rb} +3 -3
  73. data/lib/grape/validations/{default.rb → validators/default.rb} +1 -1
  74. data/lib/grape/validations/validators/exactly_one_of.rb +20 -0
  75. data/lib/grape/validations/validators/multiple_params_base.rb +26 -0
  76. data/lib/grape/validations/validators/mutual_exclusion.rb +25 -0
  77. data/lib/grape/validations/{presence.rb → validators/presence.rb} +2 -2
  78. data/lib/grape/validations/validators/regexp.rb +12 -0
  79. data/lib/grape/validations/validators/values.rb +26 -0
  80. data/lib/grape/version.rb +1 -1
  81. data/spec/grape/api_spec.rb +522 -343
  82. data/spec/grape/dsl/callbacks_spec.rb +4 -4
  83. data/spec/grape/dsl/configuration_spec.rb +48 -9
  84. data/spec/grape/dsl/helpers_spec.rb +6 -13
  85. data/spec/grape/dsl/inside_route_spec.rb +43 -4
  86. data/spec/grape/dsl/middleware_spec.rb +1 -10
  87. data/spec/grape/dsl/parameters_spec.rb +8 -1
  88. data/spec/grape/dsl/request_response_spec.rb +16 -22
  89. data/spec/grape/dsl/routing_spec.rb +21 -5
  90. data/spec/grape/dsl/settings_spec.rb +219 -0
  91. data/spec/grape/dsl/validations_spec.rb +8 -11
  92. data/spec/grape/endpoint_spec.rb +115 -86
  93. data/spec/grape/entity_spec.rb +33 -33
  94. data/spec/grape/exceptions/invalid_formatter_spec.rb +3 -5
  95. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +4 -6
  96. data/spec/grape/exceptions/missing_mime_type_spec.rb +5 -6
  97. data/spec/grape/exceptions/missing_option_spec.rb +3 -5
  98. data/spec/grape/exceptions/unknown_options_spec.rb +3 -5
  99. data/spec/grape/exceptions/unknown_validator_spec.rb +3 -5
  100. data/spec/grape/exceptions/validation_errors_spec.rb +5 -5
  101. data/spec/grape/loading_spec.rb +44 -0
  102. data/spec/grape/middleware/auth/base_spec.rb +0 -4
  103. data/spec/grape/middleware/auth/dsl_spec.rb +2 -4
  104. data/spec/grape/middleware/auth/strategies_spec.rb +5 -6
  105. data/spec/grape/middleware/exception_spec.rb +8 -10
  106. data/spec/grape/middleware/formatter_spec.rb +13 -15
  107. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +10 -10
  108. data/spec/grape/middleware/versioner/header_spec.rb +25 -25
  109. data/spec/grape/middleware/versioner/param_spec.rb +15 -17
  110. data/spec/grape/middleware/versioner/path_spec.rb +1 -2
  111. data/spec/grape/middleware/versioner_spec.rb +0 -1
  112. data/spec/grape/path_spec.rb +66 -45
  113. data/spec/grape/util/inheritable_setting_spec.rb +217 -0
  114. data/spec/grape/util/inheritable_values_spec.rb +63 -0
  115. data/spec/grape/util/stackable_values_spec.rb +115 -0
  116. data/spec/grape/util/strict_hash_configuration_spec.rb +38 -0
  117. data/spec/grape/validations/attributes_iterator_spec.rb +4 -0
  118. data/spec/grape/validations/params_scope_spec.rb +57 -0
  119. data/spec/grape/validations/validators/all_or_none_spec.rb +60 -0
  120. data/spec/grape/validations/validators/allow_blank_spec.rb +170 -0
  121. data/spec/grape/validations/{at_least_one_of_spec.rb → validators/at_least_one_of_spec.rb} +7 -3
  122. data/spec/grape/validations/{coerce_spec.rb → validators/coerce_spec.rb} +8 -11
  123. data/spec/grape/validations/{default_spec.rb → validators/default_spec.rb} +7 -9
  124. data/spec/grape/validations/{exactly_one_of_spec.rb → validators/exactly_one_of_spec.rb} +15 -11
  125. data/spec/grape/validations/{mutual_exclusion_spec.rb → validators/mutual_exclusion_spec.rb} +11 -9
  126. data/spec/grape/validations/{presence_spec.rb → validators/presence_spec.rb} +30 -30
  127. data/spec/grape/validations/{regexp_spec.rb → validators/regexp_spec.rb} +2 -4
  128. data/spec/grape/validations/{values_spec.rb → validators/values_spec.rb} +95 -23
  129. data/spec/grape/validations/{zh-CN.yml → validators/zh-CN.yml} +0 -0
  130. data/spec/grape/validations_spec.rb +335 -70
  131. data/spec/shared/versioning_examples.rb +7 -8
  132. data/spec/spec_helper.rb +2 -0
  133. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  134. data/spec/support/content_type_helpers.rb +1 -1
  135. data/spec/support/versioned_helpers.rb +3 -3
  136. metadata +80 -33
  137. data/lib/grape/util/deep_merge.rb +0 -23
  138. data/lib/grape/util/hash_stack.rb +0 -120
  139. data/lib/grape/validations/at_least_one_of.rb +0 -25
  140. data/lib/grape/validations/exactly_one_of.rb +0 -26
  141. data/lib/grape/validations/mutual_exclusion.rb +0 -25
  142. data/lib/grape/validations/regexp.rb +0 -12
  143. data/lib/grape/validations/values.rb +0 -23
  144. 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 frequenty, features not so frequently and breaking API changes rarely.
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("git init")
48
- system("git remote add origin git@github.com:intridea/grape.git")
49
- system("git pull")
50
- system("git checkout gh-pages")
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("git checkout gh-pages")
59
- system("git add .")
60
- system("git add -u")
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("git push origin gh-pages")
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 "You need to install YARD."
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.9.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 => '../'
@@ -0,0 +1,14 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source 'http://rubygems.org'
4
+
5
+ gem 'rails', '4.1.6'
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("../lib", __FILE__)
2
- require "grape/version"
1
+ $:.push File.expand_path('../lib', __FILE__)
2
+ require 'grape/version'
3
3
 
4
4
  Gem::Specification.new do |s|
5
- s.name = "grape"
5
+ s.name = 'grape'
6
6
  s.version = Grape::VERSION
7
7
  s.platform = Gem::Platform::RUBY
8
- s.authors = ["Michael Bleigh"]
9
- s.email = ["michael@intridea.com"]
10
- s.homepage = "https://github.com/intridea/grape"
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 = "MIT"
13
+ s.license = 'MIT'
14
14
 
15
- s.rubyforge_project = "grape"
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 = ["lib"]
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