explicit-parameters 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6bd7f4d53421eeef0b59aef942588c4796600ccc
4
+ data.tar.gz: df62f44750a434068d5ee6dc03d19805a4b736db
5
+ SHA512:
6
+ metadata.gz: fab71e754354bfb120bd38a17e08f7d16af14e5b6dbc240a55ab3d95b785d83a86d81bf12f293d3ea5dd213d678f6bc1f82ba08d519a94deb32565b9df658836
7
+ data.tar.gz: 6180cc1a0871ca98b71a40b1f5ab8ce317e558fbe48f28961213981e04f929b5e040dc5e08dc5b4c39d667c62b8637ca5e49b84e544d28ec1aac7b4048cfad28
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ rvm:
2
+ 2.2
3
+
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in explicit_parameters.gemspec
4
+ gemspec
5
+
6
+ gem 'byebug'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Jean Boussier
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # ExplicitParameters
2
+
3
+ [![Build Status](https://secure.travis-ci.org/byroot/explicit-parameters.png)](http://travis-ci.org/byroot/explicit-parameters)
4
+ [![Gem Version](https://badge.fury.io/rb/explicit-parameters.png)](http://badge.fury.io/rb/explicit-parameters)
5
+
6
+
7
+ Explicit parameters validation and casting for Rails APIs.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'explicit_parameters'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ ## Usage
22
+
23
+ Example:
24
+
25
+ ```ruby
26
+ class DummyController < ApiController
27
+ params do
28
+ requires :search, String
29
+ accepts :limit, Integer, default: 30
30
+
31
+ validates :limit, :numericality: {greater_than: 0, less_than_or_equal_to: 100}
32
+ end
33
+ def index
34
+ Dummy.search(params.search).limit(params.limit)
35
+ end
36
+ end
37
+ ```
38
+
39
+ ## TODO
40
+
41
+ - Real README
42
+
43
+ ## Contributing
44
+
45
+ 1. Fork it ( https://github.com/byroot/explicit_parameters/fork )
46
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
47
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
48
+ 4. Push to the branch (`git push origin my-new-feature`)
49
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task default: :spec
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'explicit_parameters/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'explicit-parameters'
8
+ spec.version = ExplicitParameters::VERSION
9
+ spec.authors = ['Jean Boussier']
10
+ spec.email = ['jean.boussier@gmail.com']
11
+ spec.summary = %q{Explicit parameters validation and casting for Rails APIs}
12
+ spec.homepage = 'https://github.com/byroot/explicit-parameters'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split(?\0)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_dependency 'actionpack', '~> 4.0'
21
+ spec.add_dependency 'activemodel', '~> 4.0'
22
+ spec.add_dependency 'virtus', '~> 1.0'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.7'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.0'
27
+ end
@@ -0,0 +1 @@
1
+ require 'explicit_parameters'
@@ -0,0 +1,10 @@
1
+ module ExplicitParameters
2
+ BaseError = Class.new(StandardError)
3
+ InvalidParameters = Class.new(BaseError)
4
+ end
5
+
6
+ require 'explicit_parameters/version'
7
+ require 'explicit_parameters/parameters'
8
+ require 'explicit_parameters/controller'
9
+
10
+ require 'explicit_parameters/railtie' if defined? Rails
@@ -0,0 +1,49 @@
1
+ module ExplicitParameters
2
+ module Controller
3
+ extend ActiveSupport::Concern
4
+
5
+ Boolean = Axiom::Types::Boolean
6
+
7
+ class << self
8
+ attr_accessor :last_parameters
9
+ end
10
+
11
+ included do
12
+ self.parameters = {}
13
+ rescue_from ExplicitParameters::InvalidParameters, with: :render_parameters_error
14
+ end
15
+
16
+ module ClassMethods
17
+ attr_accessor :parameters
18
+
19
+ def method_added(action)
20
+ return unless Controller.last_parameters
21
+ parameters[action.to_s] = Controller.last_parameters
22
+ const_set("#{action.to_s.camelize}Parameters", Controller.last_parameters)
23
+ Controller.last_parameters = nil
24
+ end
25
+
26
+ def params(&block)
27
+ Controller.last_parameters = ExplicitParameters::Parameters.define(&block)
28
+ end
29
+
30
+ def parse_parameters_for(action_name, params)
31
+ if declaration = parameters[action_name]
32
+ declaration.parse!(params)
33
+ else
34
+ params
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def params
42
+ @validated_params ||= self.class.parse_parameters_for(action_name, super)
43
+ end
44
+
45
+ def render_parameters_error(error)
46
+ render json: error.message, status: :unprocessable_entity
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,82 @@
1
+ require 'virtus'
2
+ require 'active_model'
3
+
4
+ module ExplicitParameters
5
+ class Parameters
6
+ include Virtus.model
7
+ include ActiveModel::Validations
8
+ include Enumerable
9
+
10
+ class CoercionValidator < ActiveModel::EachValidator
11
+ def validate_each(record, attribute, value)
12
+ record.validate_attribute_coercion!(attribute, value)
13
+ end
14
+ end
15
+
16
+ class RequiredValidator < ActiveModel::EachValidator
17
+ def validate_each(record, attribute, value)
18
+ record.validate_attribute_provided!(attribute, value)
19
+ end
20
+ end
21
+
22
+ class << self
23
+ def parse!(params)
24
+ new(params).validate!
25
+ end
26
+
27
+ def define(&block)
28
+ Class.new(self, &block)
29
+ end
30
+
31
+ def requires(name, type, options = {}, &block)
32
+ accepts(name, type, options.merge(required: true))
33
+ end
34
+
35
+ def accepts(name, type, options = {}, &block)
36
+ attribute(name, type, options.slice(:default, :required))
37
+ validations = options.except(:default)
38
+ validations[:coercion] = true
39
+ validates(name, validations)
40
+ end
41
+
42
+ def optional_attributes
43
+ @optional_attributes ||= []
44
+ end
45
+ end
46
+
47
+ def initialize(attributes = {})
48
+ @original_attributes = attributes.stringify_keys
49
+ super
50
+ end
51
+
52
+ def validate!
53
+ raise InvalidParameters.new({errors: errors}.to_json) unless valid?
54
+ self
55
+ end
56
+
57
+ def validate_attribute_provided!(attribute_name, value)
58
+ errors.add attribute_name, "is required" unless @original_attributes.key?(attribute_name.to_s)
59
+ end
60
+
61
+ def validate_attribute_coercion!(attribute_name, value)
62
+ return unless @original_attributes.key?(attribute_name.to_s)
63
+ attribute = attribute_set[attribute_name]
64
+ return if value.nil? && !attribute.required?
65
+ return if attribute.value_coerced?(value)
66
+ errors.add attribute_name, "#{@original_attributes[attribute_name].inspect} is not a valid #{attribute.type.name.demodulize}"
67
+ end
68
+
69
+ def to_hash
70
+ super.except(*missing_attributes)
71
+ end
72
+
73
+ delegate :each, to: :to_hash
74
+ delegate :[], to: :@original_attributes
75
+
76
+ private
77
+
78
+ def missing_attributes
79
+ @missing_attributes ||= (attribute_set.map(&:name).map(&:to_s) - @original_attributes.keys).map(&:to_sym)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,9 @@
1
+ module ExplicitParameters
2
+ class Railtie < ::Rails::Railtie
3
+ initializer 'explicit_parameters.controller' do
4
+ ActiveSupport.on_load(:action_controller) do
5
+ include ExplicitParameters::Controller
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module ExplicitParameters
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ class DummyController < ActionController::Base
4
+ include ExplicitParameters::Controller
5
+
6
+ params do
7
+ accepts :page_size, Integer
8
+ accepts :published, Boolean, default: false
9
+ end
10
+ def index
11
+ render json: {value: params.page_size, type: params.page_size.class.name}
12
+ end
13
+
14
+ def no_declaration
15
+ render text: 'OK'
16
+ end
17
+
18
+ end
19
+
20
+ RSpec.describe DummyController do
21
+ it 'is optional' do
22
+ get :no_declaration
23
+ expect(response.code).to be == '200'
24
+ end
25
+
26
+ it 'coerce parameters to the required type' do
27
+ get :index, page_size: '42'
28
+ expect(json_response).to be == {value: 42, type: 'Fixnum'}
29
+ end
30
+
31
+ it 'returns a 422 if parameters are invalid' do
32
+ get :index, page_size: 'foobar'
33
+ expect(response.code).to be == '422'
34
+ end
35
+
36
+ it 'returns the list of errors if parameters are invalid' do
37
+ get :index, page_size: 'foobar'
38
+ expect(json_response).to be == {errors: {page_size: ['"foobar" is not a valid Integer']}}
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :response
44
+
45
+ def json_response
46
+ JSON.load(response.body).deep_symbolize_keys
47
+ end
48
+
49
+ def get(action, parameters = {})
50
+ request(action, 'GET', query: parameters)
51
+ end
52
+
53
+ def request(action, method, query: {}, body: '')
54
+ rack_response = subject.dispatch(action, ActionDispatch::Request.new(
55
+ 'REQUEST_METHOD' => method,
56
+ 'QUERY_STRING' => query.to_query,
57
+ 'rack.input' => StringIO.new(body)
58
+ ))
59
+ @response = ActionDispatch::Response.new(*rack_response)
60
+ end
61
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ExplicitParameters::Parameters do
4
+ TestParameters = ExplicitParameters::Parameters.define do
5
+ requires :id, Integer
6
+ accepts :name, String
7
+ accepts :title, String, default: 'Untitled'
8
+
9
+ validates :id, numericality: {greater_than: 0}
10
+ end
11
+
12
+ let :parameters do
13
+ {
14
+ 'id' => '42',
15
+ 'name' => 'George',
16
+ 'unexpected' => 'parameter',
17
+ }
18
+ end
19
+
20
+ let(:params) { TestParameters.parse!(parameters.with_indifferent_access) }
21
+
22
+ it 'casts parameters to the declared type' do
23
+ expect(params.id).to be == 42
24
+ end
25
+
26
+ it 'provides the default if the parameter is missing' do
27
+ expect(params.title).to be == 'Untitled'
28
+ end
29
+
30
+ it 'ignores unexpected parameters during iteration' do
31
+ params.each do |name, value|
32
+ expect(name).to_not be == 'unexpected'
33
+ end
34
+ end
35
+
36
+ it 'allows access to raw parameters when accessed like a Hash' do
37
+ expect(params[:id]).to be == '42'
38
+ expect(params[:unexpected]).to be == 'parameter'
39
+ end
40
+
41
+ it 'can perform any type of active model validations' do
42
+ message = {errors: {id: ['must be greater than 0']}}.to_json
43
+ expect {
44
+ TestParameters.parse!('id' => -1)
45
+ }.to raise_error(ExplicitParameters::InvalidParameters, message)
46
+ end
47
+ end
@@ -0,0 +1,15 @@
1
+ require 'action_controller'
2
+ require 'explicit_parameters'
3
+
4
+ RSpec.configure do |config|
5
+ config.expect_with :rspec do |expectations|
6
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
7
+ end
8
+ config.mock_with :rspec do |mocks|
9
+ mocks.verify_partial_doubles = true
10
+ end
11
+ config.disable_monkey_patching!
12
+ config.warnings = true
13
+ config.order = :random
14
+ Kernel.srand config.seed
15
+ end
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: explicit-parameters
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jean Boussier
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: actionpack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activemodel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: virtus
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.7'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.7'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ description:
98
+ email:
99
+ - jean.boussier@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - ".travis.yml"
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - explicit-parameters.gemspec
112
+ - lib/explicit-parameters.rb
113
+ - lib/explicit_parameters.rb
114
+ - lib/explicit_parameters/controller.rb
115
+ - lib/explicit_parameters/parameters.rb
116
+ - lib/explicit_parameters/railtie.rb
117
+ - lib/explicit_parameters/version.rb
118
+ - spec/controller_spec.rb
119
+ - spec/parameters_spec.rb
120
+ - spec/spec_helper.rb
121
+ homepage: https://github.com/byroot/explicit-parameters
122
+ licenses:
123
+ - MIT
124
+ metadata: {}
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 2.2.2
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: Explicit parameters validation and casting for Rails APIs
145
+ test_files:
146
+ - spec/controller_spec.rb
147
+ - spec/parameters_spec.rb
148
+ - spec/spec_helper.rb