grape 0.10.1 → 0.11.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rspec +1 -1
  4. data/CHANGELOG.md +18 -0
  5. data/Gemfile +1 -1
  6. data/README.md +124 -9
  7. data/UPGRADING.md +66 -0
  8. data/gemfiles/rails_3.gemfile +1 -1
  9. data/gemfiles/rails_4.gemfile +1 -1
  10. data/lib/grape.rb +4 -0
  11. data/lib/grape/api.rb +1 -5
  12. data/lib/grape/dsl/inside_route.rb +2 -1
  13. data/lib/grape/dsl/parameters.rb +20 -9
  14. data/lib/grape/dsl/routing.rb +11 -1
  15. data/lib/grape/error_formatter/base.rb +1 -1
  16. data/lib/grape/exceptions/invalid_accept_header.rb +10 -0
  17. data/lib/grape/exceptions/invalid_message_body.rb +10 -0
  18. data/lib/grape/exceptions/missing_group_type.rb +10 -0
  19. data/lib/grape/exceptions/unsupported_group_type.rb +10 -0
  20. data/lib/grape/http/request.rb +1 -0
  21. data/lib/grape/locale/en.yml +11 -0
  22. data/lib/grape/middleware/base.rb +1 -1
  23. data/lib/grape/middleware/formatter.rb +2 -0
  24. data/lib/grape/middleware/versioner/header.rb +14 -11
  25. data/lib/grape/parser/json.rb +3 -0
  26. data/lib/grape/parser/xml.rb +3 -0
  27. data/lib/grape/validations/params_scope.rb +20 -4
  28. data/lib/grape/validations/validators/coerce.rb +4 -1
  29. data/lib/grape/validations/validators/values.rb +1 -1
  30. data/lib/grape/version.rb +1 -1
  31. data/spec/grape/api_spec.rb +3 -3
  32. data/spec/grape/dsl/parameters_spec.rb +11 -11
  33. data/spec/grape/dsl/routing_spec.rb +13 -4
  34. data/spec/grape/endpoint_spec.rb +2 -2
  35. data/spec/grape/exceptions/body_parse_errors_spec.rb +105 -0
  36. data/spec/grape/exceptions/invalid_accept_header_spec.rb +330 -0
  37. data/spec/grape/integration/rack_spec.rb +32 -0
  38. data/spec/grape/middleware/base_spec.rb +20 -0
  39. data/spec/grape/middleware/versioner/header_spec.rb +74 -96
  40. data/spec/grape/validations/params_scope_spec.rb +124 -0
  41. data/spec/grape/validations/validators/allow_blank_spec.rb +102 -0
  42. data/spec/grape/validations/validators/values_spec.rb +45 -1
  43. data/spec/grape/validations_spec.rb +54 -16
  44. data/spec/shared/versioning_examples.rb +32 -0
  45. metadata +61 -51
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e3f8c2e0db297ca831e464a1c0db49f2e5ba6f2e
4
- data.tar.gz: 34b6bcee2a26ecb8e87b207abbb59647d2376773
3
+ metadata.gz: cce57801c840dd9a64018350c6dd1eb955fc7b70
4
+ data.tar.gz: e09089d477bba3cf9ff2bd53a5f164ecffefe494
5
5
  SHA512:
6
- metadata.gz: 98bbce31a09337c9ef681b59f7a63c2bfb7cdb472d6b46275d73bd22064bcff48288283d57f5ed04ba6f662571975d3a284a91b0448a3369149729c363838fde
7
- data.tar.gz: 41d9f0ac8df0ba6c7105ab8b1e50183d569f5708bdd9469661cde03a79e6f3e604551d049e49fd4fc3eb49d49456d42523718fcd961711112c4f904389ed9b70
6
+ metadata.gz: 1a7d4e52cdc91c6ee4f623b4a14d986b60a0f574b2885392487ff22993c6465c923165e9214382e9465265f8e1240519be0b95fec0c19c7d3f4fc603827edd1c
7
+ data.tar.gz: 7ec02b2a4bcc083f6034c681cf82356d97e6d3aa65ad2d6663fe2c437c5e0da7f344371f003391ff9c838b7b190c9415e5abe986f9449670a76ab545e0e7aa5d
data/.gitignore CHANGED
@@ -30,6 +30,7 @@ pkg
30
30
  .yardoc/*
31
31
  dist
32
32
  Gemfile.lock
33
+ gemfiles/*.lock
33
34
  tmp
34
35
 
35
36
  ## Rubinius
data/.rspec CHANGED
@@ -1,2 +1,2 @@
1
1
  --color
2
- --format=progress
2
+ --format=documentation
@@ -1,3 +1,21 @@
1
+ 0.11.0 (2/23/2015)
2
+ ==================
3
+
4
+ * [#925](https://github.com/intridea/grape/pull/925): Fixed `toplevel constant DateTime referenced by Virtus::Attribute::DateTime` - [@u2](https://github.com/u2).
5
+ * [#916](https://github.com/intridea/grape/pull/916): Added `DateTime/Date/Numeric/Boolean` type support `allow_blank` - [@u2](https://github.com/u2).
6
+ * [#871](https://github.com/intridea/grape/pull/871): Fixed `Grape::Middleware::Base#response` - [@galathius](https://github.com/galathius).
7
+ * [#559](https://github.com/intridea/grape/issues/559): Added support for Rack 1.6.0, which parses requests larger than 128KB - [@myitcv](https://github.com/myitcv).
8
+ * [#876](https://github.com/intridea/grape/pull/876): Call to `declared(params)` now returns a `Hashie::Mash` - [@rodzyn](https://github.com/rodzyn).
9
+ * [#879](https://github.com/intridea/grape/pull/879): The `route_info` value is no longer included in `params` Hash - [@rodzyn](https://github.com/rodzyn).
10
+ * [#881](https://github.com/intridea/grape/issues/881): Fixed `Grape::Validations::ValuesValidator` support for `Range` type - [@ajvondrak](https://github.com/ajvondrak).
11
+ * [#901](https://github.com/intridea/grape/pull/901): Fix: callbacks defined in a version block are only called for the routes defined in that block - [@kushkella](https://github.com/kushkella).
12
+ * [#886](https://github.com/intridea/grape/pull/886): Group of parameters made to require an explicit type of Hash or Array - [@jrichter1](https://github.com/jrichter1).
13
+ * [#912](https://github.com/intridea/grape/pull/912): Extended the `:using` feature for param documentation to `optional` fields - [@croeck](https://github.com/croeck).
14
+ * [#906](https://github.com/intridea/grape/pull/906): Fix: invalid body parse errors are not rescued by handlers - [@croeck](https://github.com/croeck).
15
+ * [#913](https://github.com/intridea/grape/pull/913): Fix: Invalid accept headers are not processed by rescue handlers - [@croeck](https://github.com/croeck).
16
+ * [#913](https://github.com/intridea/grape/pull/913): Fix: Invalid accept headers cause internal processing errors (500) when http_codes are defined - [@croeck](https://github.com/croeck).
17
+ * [#917](https://github.com/intridea/grape/pull/917): Use HTTPS for rubygems.org - [@O-I](https://github.com/O-I).
18
+
1
19
  0.10.1 (12/28/2014)
2
20
  ===================
3
21
 
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'http://rubygems.org'
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  ![grape logo](grape.png)
2
2
 
3
- [![Build Status](https://travis-ci.org/intridea/grape.png?branch=master)](http://travis-ci.org/intridea/grape) [![Code Climate](https://codeclimate.com/github/intridea/grape.png)](https://codeclimate.com/github/intridea/grape) [![Inline docs](http://inch-ci.org/github/intridea/grape.png)](http://inch-ci.org/github/intridea/grape) [![Dependency Status](https://www.versioneye.com/ruby/grape/badge.png)](https://www.versioneye.com/ruby/grape)
3
+ [![Gem Version](http://img.shields.io/gem/v/grape.svg)](http://badge.fury.io/rb/grape)
4
+ [![Build Status](http://img.shields.io/travis/intridea/grape.svg)](https://travis-ci.org/intridea/grape)
5
+ [![Dependency Status](https://gemnasium.com/intridea/grape.svg)](https://gemnasium.com/intridea/grape)
6
+ [![Code Climate](https://codeclimate.com/github/intridea/grape.svg)](https://codeclimate.com/github/intridea/grape)
7
+ [![Inline docs](http://inch-ci.org/github/intridea/grape.svg)](http://inch-ci.org/github/intridea/grape)
4
8
 
5
9
  ## Table of Contents
6
10
 
@@ -86,8 +90,7 @@ content negotiation, versioning and much more.
86
90
 
87
91
  ## Stable Release
88
92
 
89
- You're reading the documentation for the stable release of Grape, which is be 0.10.1.
90
- Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
93
+ You're reading the documentation for the stable release of Grape, [0.11.0](https://github.com/intridea/grape/blob/v0.11.0/README.md).
91
94
 
92
95
  ## Project Resources
93
96
 
@@ -269,14 +272,14 @@ Modify `config/routes`:
269
272
  mount Twitter::API => '/'
270
273
  ```
271
274
 
272
- Additionally, if the version of your Rails is 4.0+ and the application uses the default model layer of ActiveRecord, you will want to use the `hashie_rails` [gem](http://rubygems.org/gems/hashie_rails). This gem disables the security feature of `strong_params` at the model layer, allowing you the use of Grape's own params validation instead.
275
+ Additionally, if the version of your Rails is 4.0+ and the application uses the default model layer of ActiveRecord, you will want to use the `hashie_rails` [gem](https://rubygems.org/gems/hashie_rails). This gem disables the security feature of `strong_params` at the model layer, allowing you the use of Grape's own params validation instead.
273
276
 
274
277
  ```ruby
275
278
  # Gemfile
276
279
  gem "hashie_rails"
277
280
  ```
278
281
 
279
- See below for additional code that enables reloading of API changes in development.
282
+ See [below](#reloading-api-changes-in-development) for additional code that enables reloading of API changes in development.
280
283
 
281
284
  ### Modules
282
285
 
@@ -479,7 +482,7 @@ post 'users/signup' do
479
482
  end
480
483
  ````
481
484
 
482
- If we do not specify any params, declared will return an empty hash.
485
+ If we do not specify any params, declared will return an empty Hashie::Mash instance.
483
486
 
484
487
  **Request**
485
488
 
@@ -532,6 +535,12 @@ curl -X POST -H "Content-Type: application/json" localhost:9292/users/signup -d
532
535
  }
533
536
  ````
534
537
 
538
+ Returned hash is a Hashie::Mash instance so you can access parameters via dot notation:
539
+
540
+ ```ruby
541
+ declared(params).user == declared(params)["user"]
542
+ ```
543
+
535
544
  #### Include missing
536
545
 
537
546
  By default `declared(params)` returns parameters that has `nil` value. If you want to return only the parameters that have any value, you can use the `include_missing` option. By default it is `true`. Let's have the following api:
@@ -739,7 +748,7 @@ end
739
748
  #### `allow_blank`
740
749
 
741
750
  Parameters can be defined as `allow_blank`, ensuring that they contain a value. By default, `requires`
742
- only validates that a parameter was sent in the request, regardless its value. With `allow_blank`,
751
+ only validates that a parameter was sent in the request, regardless its value. With `allow_blank: false`,
743
752
  empty values or whitespace only values are invalid.
744
753
 
745
754
  `allow_blank` can be combined with both `requires` and `optional`. If the parameter is required, it has to contain
@@ -769,6 +778,25 @@ params do
769
778
  end
770
779
  ```
771
780
 
781
+ Supplying a range to the `:values` option ensures that the parameter is (or parameters are) included in that range (using `Range#include?`).
782
+
783
+ ```ruby
784
+ params do
785
+ requires :latitude, type: Float, values: -90.0..+90.0
786
+ requires :longitude, type: Float, values: -180.0..+180.0
787
+ optional :letters, type: Array[String], values: 'a'..'z'
788
+ end
789
+ ```
790
+
791
+ Note that *both* range endpoints have to be a `#kind_of?` your `:type` option (if you don't supplied the `:type` option, it will be guessed to be equal to the class of the range's first endpoint). So the following is invalid:
792
+
793
+ ```ruby
794
+ params do
795
+ requires :invalid1, type: Float, values: 0..10 # 0.kind_of?(Float) => false
796
+ optional :invalid2, values: 0..10.0 # 10.0.kind_of?(0.class) => false
797
+ end
798
+ ```
799
+
772
800
  The `:values` option can also be supplied with a `Proc`, evaluated lazily with each request.
773
801
  For example, given a status model you may want to restrict by hashtags that you have
774
802
  previously defined in the `HashTag` model.
@@ -2132,6 +2160,39 @@ GET /123 # 'Fixnum'
2132
2160
  GET /foo # 400 error - 'blah is invalid'
2133
2161
  ```
2134
2162
 
2163
+ When a callback is defined within a version block, it's only called for the routes defined in that block.
2164
+
2165
+ ```ruby
2166
+ class Test < Grape::API
2167
+ resource :foo do
2168
+ version 'v1', :using => :path do
2169
+ before do
2170
+ @output ||= 'v1-'
2171
+ end
2172
+ get '/' do
2173
+ @output += 'hello'
2174
+ end
2175
+ end
2176
+
2177
+ version 'v2', :using => :path do
2178
+ before do
2179
+ @output ||= 'v2-'
2180
+ end
2181
+ get '/' do
2182
+ @output += 'hello'
2183
+ end
2184
+ end
2185
+ end
2186
+ end
2187
+ ```
2188
+
2189
+ The behaviour is then:
2190
+
2191
+ ```bash
2192
+ GET /foo/v1 # 'v1-hello'
2193
+ GET /foo/v2 # 'v2-hello'
2194
+ ```
2195
+
2135
2196
  ## Anchoring
2136
2197
 
2137
2198
  Grape by default anchors all request paths, which means that the request URL
@@ -2184,7 +2245,7 @@ class API < Grape::API
2184
2245
  end
2185
2246
  end
2186
2247
 
2187
- get :remopte_ip do
2248
+ get :remote_ip do
2188
2249
  { ip: client_ip }
2189
2250
  end
2190
2251
  end
@@ -2198,6 +2259,8 @@ You can test a Grape API with RSpec by making HTTP requests and examining the re
2198
2259
 
2199
2260
  Use `rack-test` and define your API as `app`.
2200
2261
 
2262
+ #### RSpec
2263
+
2201
2264
  ```ruby
2202
2265
  require 'spec_helper'
2203
2266
 
@@ -2227,8 +2290,36 @@ describe Twitter::API do
2227
2290
  end
2228
2291
  ```
2229
2292
 
2293
+ #### MiniTest
2294
+
2295
+ ```ruby
2296
+ require "test_helper"
2297
+
2298
+ class Twitter::APITest < MiniTest::Unit::TestCase
2299
+ include Rack::Test::Methods
2300
+
2301
+ def app
2302
+ Twitter::API
2303
+ end
2304
+
2305
+ def test_get_api_statuses_public_timeline_returns_an_empty_array_of_statuses
2306
+ get "/api/statuses/public_timeline"
2307
+ assert last_response.ok?
2308
+ assert_equal JSON.parse(last_response.body), []
2309
+ end
2310
+
2311
+ def test_get_api_statuses_id_returns_a_status_by_id
2312
+ status = Status.create!
2313
+ get "/api/statuses/#{status.id}"
2314
+ assert_equal last_response.body, status.to_json
2315
+ end
2316
+ end
2317
+ ```
2318
+
2230
2319
  ### Writing Tests with Rails
2231
2320
 
2321
+ #### RSpec
2322
+
2232
2323
  ```ruby
2233
2324
  describe Twitter::API do
2234
2325
  describe "GET /api/statuses/public_timeline" do
@@ -2257,6 +2348,30 @@ RSpec.configure do |config|
2257
2348
  end
2258
2349
  ```
2259
2350
 
2351
+ #### MiniTest
2352
+
2353
+ ```ruby
2354
+ class Twitter::APITest < ActiveSupport::TestCase
2355
+ include Rack::Test::Methods
2356
+
2357
+ def app
2358
+ Rails.application
2359
+ end
2360
+
2361
+ test "GET /api/statuses/public_timeline returns an empty array of statuses" do
2362
+ get "/api/statuses/public_timeline"
2363
+ assert last_response.ok?
2364
+ assert_equal JSON.parse(last_response.body), []
2365
+ end
2366
+
2367
+ test "GET /api/statuses/:id returns a status by id" do
2368
+ status = Status.create!
2369
+ get "/api/statuses/#{status.id}"
2370
+ assert_equal last_response.body, status.to_json
2371
+ end
2372
+ end
2373
+ ```
2374
+
2260
2375
  ### Stubbing Helpers
2261
2376
 
2262
2377
  Because helpers are mixed in based on the context when an endpoint is defined, it can
@@ -2268,7 +2383,7 @@ request.
2268
2383
  describe 'an endpoint that needs helpers stubbed' do
2269
2384
  before do
2270
2385
  Grape::Endpoint.before_each do |endpoint|
2271
- endpoint.stub(:helper_name).and_return('desired_value')
2386
+ allow(endpoint).to receive(:helper_name).and_return('desired_value')
2272
2387
  end
2273
2388
  end
2274
2389
 
@@ -1,6 +1,39 @@
1
1
  Upgrading Grape
2
2
  ===============
3
3
 
4
+ ### Upgrading to >= 0.11.0
5
+
6
+ #### Added Rack 1.6.0 Support
7
+
8
+ Grape now supports, but doesn't require Rack 1.6.0. If you encounter an issue with parsing requests larger than 128KB, explictly require Rack 1.6.0 in your Gemfile.
9
+
10
+ ```ruby
11
+ gem 'rack', '~> 1.6.0'
12
+ ```
13
+
14
+ See [#559](https://github.com/intridea/grape/issues/559) for more information.
15
+
16
+ #### Removed route_info
17
+
18
+ Key route_info is excluded from params.
19
+
20
+ See [#879](https://github.com/intridea/grape/pull/879) for more information.
21
+
22
+
23
+ #### Fix callbacks within a version block
24
+
25
+ Callbacks defined in a version block are only called for the routes defined in that block. This was a regression introduced in Grape 0.10.0, and is fixed in this version.
26
+
27
+ See [#901](https://github.com/intridea/grape/pull/901) for more information.
28
+
29
+
30
+ #### Make type of group of parameters required
31
+
32
+ Groups of parameters now require their type to be set explicitly as Array or Hash.
33
+ Not setting the type now results in MissingGroupTypeError, unsupported type will raise UnsupportedTypeError.
34
+
35
+ See [#886](https://github.com/intridea/grape/pull/886) for more information.
36
+
4
37
  ### Upgrading to >= 0.10.1
5
38
 
6
39
  #### Changes to `declared(params, include_missing: false)`
@@ -217,6 +250,39 @@ end
217
250
 
218
251
  See [#801](https://github.com/intridea/grape/issues/801) for more information.
219
252
 
253
+ #### Changes to version
254
+
255
+ If version is used with a block, the callbacks defined within that version block are not scoped to that individual block. In other words, the callback would be inherited by all versions blocks that follow the first one e.g
256
+
257
+ ```ruby
258
+ class API < Grape::API
259
+ resource :foo do
260
+ version 'v1', :using => :path do
261
+ before do
262
+ @output ||= 'hello1'
263
+ end
264
+ get '/' do
265
+ @output += '-v1'
266
+ end
267
+ end
268
+
269
+ version 'v2', :using => :path do
270
+ before do
271
+ @output ||= 'hello2'
272
+ end
273
+ get '/:id' do
274
+ @output += '-v2'
275
+ end
276
+ end
277
+ end
278
+ end
279
+ ```
280
+
281
+ when making a API call `GET /foo/v2/1`, the API would set instance variable `@output` to `hello1-v2`
282
+
283
+ See [#898](https://github.com/intridea/grape/issues/898) for more information.
284
+
285
+
220
286
  ### Upgrading to >= 0.9.0
221
287
 
222
288
  #### Changes in Authentication
@@ -1,6 +1,6 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source 'http://rubygems.org'
3
+ source 'https://rubygems.org'
4
4
 
5
5
  gem 'rails', '3.2.19'
6
6
 
@@ -1,6 +1,6 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source 'http://rubygems.org'
3
+ source 'https://rubygems.org'
4
4
 
5
5
  gem 'rails', '4.1.6'
6
6
 
@@ -55,6 +55,10 @@ module Grape
55
55
  autoload :UnknownOptions, 'grape/exceptions/unknown_options'
56
56
  autoload :InvalidWithOptionForRepresent, 'grape/exceptions/invalid_with_option_for_represent'
57
57
  autoload :IncompatibleOptionValues, 'grape/exceptions/incompatible_option_values'
58
+ autoload :MissingGroupTypeError, 'grape/exceptions/missing_group_type'
59
+ autoload :UnsupportedGroupTypeError, 'grape/exceptions/unsupported_group_type'
60
+ autoload :InvalidMessageBody, 'grape/exceptions/invalid_message_body'
61
+ autoload :InvalidAcceptHeader, 'grape/exceptions/invalid_accept_header'
58
62
  end
59
63
 
60
64
  module ErrorFormatter
@@ -53,11 +53,7 @@ module Grape
53
53
  protected
54
54
 
55
55
  def prepare_routes
56
- routes = []
57
- endpoints.each do |endpoint|
58
- routes.concat(endpoint.routes)
59
- end
60
- routes
56
+ endpoints.map(&:routes).flatten
61
57
  end
62
58
 
63
59
  # Execute first the provided block, then each of the
@@ -18,6 +18,7 @@ module Grape
18
18
  def declared(params, options = {}, declared_params = nil)
19
19
  options[:include_missing] = true unless options.key?(:include_missing)
20
20
  options[:include_parent_namespaces] = true unless options.key?(:include_parent_namespaces)
21
+
21
22
  if declared_params.nil?
22
23
  declared_params = (!options[:include_parent_namespaces] ? route_setting(:declared_params) :
23
24
  (route_setting(:saved_declared_params) || [])).flatten(1) || []
@@ -32,7 +33,7 @@ module Grape
32
33
  declared(param || {}, options, declared_params)
33
34
  end
34
35
  else
35
- declared_params.inject({}) do |hash, key|
36
+ declared_params.inject(Hashie::Mash.new) do |hash, key|
36
37
  key = { key => nil } unless key.is_a? Hash
37
38
 
38
39
  key.each_pair do |parent, children|
@@ -21,9 +21,10 @@ module Grape
21
21
  def requires(*attrs, &block)
22
22
  orig_attrs = attrs.clone
23
23
 
24
- opts = attrs.last.is_a?(Hash) ? attrs.pop : nil
24
+ opts = attrs.last.is_a?(Hash) ? attrs.pop : {}
25
+ opts.merge!(presence: true)
25
26
 
26
- if opts && opts[:using]
27
+ if opts[:using]
27
28
  require_required_and_optional_fields(attrs.first, opts)
28
29
  else
29
30
  validate_attributes(attrs, opts, &block)
@@ -34,15 +35,25 @@ module Grape
34
35
  end
35
36
 
36
37
  def optional(*attrs, &block)
37
- orig_attrs = attrs
38
+ orig_attrs = attrs.clone
39
+
40
+ opts = attrs.last.is_a?(Hash) ? attrs.pop : {}
41
+ type = opts[:type]
42
+
43
+ # check type for optional parameter group
44
+ if attrs && block_given?
45
+ fail Grape::Exceptions::MissingGroupTypeError.new if type.nil?
46
+ fail Grape::Exceptions::UnsupportedGroupTypeError.new unless [Array, Hash].include?(type)
47
+ end
38
48
 
39
- validations = {}
40
- validations.merge!(attrs.pop) if attrs.last.is_a?(Hash)
41
- validations[:type] ||= Array if block_given?
42
- validates(attrs, validations)
49
+ if opts[:using]
50
+ require_optional_fields(attrs.first, opts)
51
+ else
52
+ validate_attributes(attrs, opts, &block)
43
53
 
44
- block_given? ? new_scope(orig_attrs, true, &block) :
45
- push_declared_params(attrs)
54
+ block_given? ? new_scope(orig_attrs, true, &block) :
55
+ push_declared_params(attrs)
56
+ end
46
57
  end
47
58
 
48
59
  def mutually_exclusive(*attrs)