Commit b3edf94e by slainer68

first commit

parents
pkg
README.html
.rvmrc
.bundle
Gemfile.lock
*.gem
--colour
--format=documentation
rails_param
language: ruby
rvm:
- "2.1.2"
- "2.0.0"
- "1.9.3"
- jruby-19mode
gemfile:
- ".gemfiles/Gemfile.rails-3.2.x"
- ".gemfiles/Gemfile.rails-4.0.x"
matrix:
include:
- rvm: "1.8.7"
gemfile: ".gemfiles/Gemfile.rails-3.2.x"
- rvm: jruby-18mode
gemfile: ".gemfiles/Gemfile.rails-3.2.x"
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
source "https://rubygems.org"
gemspec
gem 'pry'
gem 'rails', '~> 4.1.4'
platforms :rbx do
gem 'racc'
gem 'rubysl'
end
# rails-param
_Parameter Validation & Type Coercion for Rails_
This is a port of the gem [sinatra-param](https://github.com/mattt/sinatra-param) for the Rails framework.
All the credits go to [@mattt](https://twitter.com/mattt).
It has all the features of the sinatra-param gem, I used bang methods (param! and one_of!) to indicate that they are destructive as they change the controller params object and may raise an exception.
REST conventions take the guesswork out of designing and consuming web APIs. Simply `GET`, `POST`, `PATCH`, or `DELETE` resource endpoints, and you get what you'd expect.
However, when it comes to figuring out what parameters are expected... well, all bets are off.
This Rails extension takes a first step to solving this problem on the developer side
**`rails-param` allows you to declare, validate, and transform endpoint parameters as you would in frameworks like [ActiveModel](http://rubydoc.info/gems/activemodel/3.2.3/frames) or [DataMapper](http://datamapper.org/).**
> Use `rails-param` in combination with [`Rack::PostBodyContentTypeParser` and `Rack::NestedParams`](https://github.com/rack/rack-contrib) to automatically parameterize JSON `POST` bodies and nested parameters.
## Example
``` ruby
# GET /search?q=example
# GET /search?q=example&categories=news
# GET /search?q=example&sort=created_at&order=ASC
def search
param! :q, String, required: true
param! :categories, Array
param! :sort, String, default: "title"
param! :order, String, in: ["ASC", "DESC"], transform: :upcase, default: "ASC"
param! :price, String, format: "[<\=>]\s*\$\d+"
{...}
end
end
```
### Parameter Types
By declaring parameter types, incoming parameters will automatically be transformed into an object of that type. For instance, if a param is `Boolean`, values of `'1'`, `'true'`, `'t'`, `'yes'`, and `'y'` will be automatically transformed into `true`.
- `String`
- `Integer`
- `Float`
- `Boolean` _("1/0", "true/false", "t/f", "yes/no", "y/n")_
- `Array` _("1,2,3,4,5")_
- `Hash` _(key1:value1,key2:value2)_
- `Date`, `Time`, & `DateTime`
### Validations
Encapsulate business logic in a consistent way with validations. If a parameter does not satisfy a particular condition, a `400` error is returned with a message explaining the failure.
- `required`
- `blank`
- `is`
- `in`, `within`, `range`
- `min` / `max`
- `format`
### Defaults and Transformations
Passing a `default` option will provide a default value for a parameter if none is passed. A `default` can defined as either a default or as a `Proc`:
```ruby
param! :attribution, String, default: "©"
param! :year, Integer, default: lambda { Time.now.year }
```
Use the `transform` option to take even more of the business logic of parameter I/O out of your code. Anything that responds to `to_proc` (including `Proc` and symbols) will do.
```ruby
param! :order, String, in: ["ASC", "DESC"], transform: :upcase, default: "ASC"
param! :offset, Integer, min: 0, transform: lambda {|n| n - (n % 10)}
```
## Thank you
Many thanks to:
* [Mattt Thompson (@mattt)](https://twitter.com/mattt)
* [Vincent Ollivier (@vinc686)](https://twitter.com/vinc686)
## Contact
Nicolas Blanco
- http://github.com/nicolasblanco
- http://twitter.com/nblanco_fr
- nicolas@nicolasblanco.fr
## License
rails-param is available under the MIT license. See the LICENSE file for more info.
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) do |t|
t.rspec_opts = ['--format progress']
end
task :default => :spec
require 'rails_param/param'
ActiveSupport.on_load(:action_controller) do
include RailsParam::Param
end
module RailsParam
module Param
class InvalidParameterError < StandardError
attr_accessor :param, :options
end
def param!(name, type, options = {})
name = name.to_s
return unless params.member?(name) || options[:default].present? || options[:required]
begin
params[name] = coerce(params[name], type, options)
params[name] = (options[:default].call if options[:default].respond_to?(:call)) || options[:default] if params[name].nil? and options[:default]
params[name] = options[:transform].to_proc.call(params[name]) if params[name] and options[:transform]
validate!(params[name], options)
# rescue InvalidParameterError => exception
# if options[:raise]
# exception.param, exception.options = name, options
# raise exception
# end
# error = "Invalid Parameter: #{name}"
# if content_type and content_type.match(mime_type(:json))
# error = {message: error, errors: {name => exception.message}}.to_json
# end
# # do something with error object
end
end
# def one_of!(*names)
# count = 0
# names.each do |name|
# if params[name] and params[name].present?
# count += 1
# next unless count > 1
#
# error = "Parameters #{names.join(', ')} are mutually exclusive"
# if content_type and content_type.match(mime_type(:json))
# error = {message: error}.to_json
# end
#
# # do something with error object
# end
# end
# end
private
def coerce(param, type, options = {})
begin
return nil if param.nil?
return param if (param.is_a?(type) rescue false)
return Integer(param) if type == Integer
return Float(param) if type == Float
return String(param) if type == String
return Date.parse(param) if type == Date
return Time.parse(param) if type == Time
return DateTime.parse(param) if type == DateTime
return Array(param.split(options[:delimiter] || ",")) if type == Array
return Hash[param.split(options[:delimiter] || ",").map{|c| c.split(options[:separator] || ":")}] if type == Hash
return (/(false|f|no|n|0)$/i === param.to_s ? false : (/(true|t|yes|y|1)$/i === param.to_s ? true : nil)) if type == TrueClass || type == FalseClass || type == Boolean
return nil
rescue ArgumentError
raise InvalidParameterError, "'#{param}' is not a valid #{type}"
end
end
def validate!(param, options)
options.each do |key, value|
case key
when :required
raise InvalidParameterError, "Parameter is required" if value && param.nil?
when :blank
raise InvalidParameterError, "Parameter cannot be blank" if !value && case param
when String
!(/\S/ === param)
when Array, Hash
param.empty?
else
param.nil?
end
when :format
raise InvalidParameterError, "Parameter must be a string if using the format validation" unless param.kind_of?(String)
raise InvalidParameterError, "Parameter must match format #{value}" unless param =~ value
when :is
raise InvalidParameterError, "Parameter must be #{value}" unless param === value
when :in, :within, :range
raise InvalidParameterError, "Parameter must be within #{value}" unless param.nil? || case value
when Range
value.include?(param)
else
Array(value).include?(param)
end
when :min
raise InvalidParameterError, "Parameter cannot be less than #{value}" unless param.nil? || value <= param
when :max
raise InvalidParameterError, "Parameter cannot be greater than #{value}" unless param.nil? || value >= param
when :min_length
raise InvalidParameterError, "Parameter cannot have length less than #{value}" unless param.nil? || value <= param.length
when :max_length
raise InvalidParameterError, "Parameter cannot have length greater than #{value}" unless param.nil? || value >= param.length
end
end
end
end
end
module RailsParam #:nodoc
VERSION = "0.0.1"
end
lib = File.expand_path('../lib/', __FILE__)
$:.unshift lib unless $:.include?(lib)
require 'rails_param/version'
Gem::Specification.new do |s|
s.name = 'rails_param'
s.version = RailsParam::VERSION
s.authors = ["Nicolas Blanco"]
s.email = 'nicolas@nicolasblanco.fr'
s.homepage = 'http://github.com/nicolasblanco/rails_param'
s.license = 'WTFPL'
s.description = %q{
Parameter Validation & Type Coercion for Rails
}
s.summary = 'Parameter Validation & Type Coercion for Rails'
s.required_rubygems_version = '>= 1.3.6'
s.files = Dir.glob("lib/**/*.rb") + %w(README.md)
s.rdoc_options = ["--charset=UTF-8"]
s.require_paths = ["lib"]
s.add_development_dependency 'rspec', '~> 2.11'
s.add_development_dependency 'rspec-rails', '~> 2.11'
s.add_development_dependency 'actionpack', '>= 4.1.0'
s.add_development_dependency 'activesupport', '>= 4.1.0'
end
require 'fixtures/fake_rails_application'
class FakeController < ActionController::Base
include Rails.application.routes.url_helpers
def show
render text: "Foo"
end
def index
param! :sort, String, in: %w(asc desc), default: "asc", transform: :downcase
param! :page, Integer, default: 1
render text: "index"
end
def new
render text: "new"
end
end
require 'active_support/all'
require 'action_controller'
require 'action_dispatch'
require 'rails'
require 'rails_param'
# Boilerplate
module Rails
class App
def env_config; {} end
def routes
return @routes if defined?(@routes)
@routes = ActionDispatch::Routing::RouteSet.new
@routes.draw do
get '/fake/new' => "fake#new"
get '/fakes' => "fake#index"
get '/fake/(:id)' => "fake#show"
end
@routes
end
end
def self.application
@app ||= App.new
end
end
require 'fixtures/controllers'
require 'rspec/rails'
describe FakeController, type: :controller do
describe "type coercion" do
it "coerces to integer" do
get :index, page: "666"
expect(controller.params[:page]).to eql(666)
end
end
describe ":raise parameter" do
it "raises an exception if set" do
expect { get :index, sort: "foo" }.to raise_error(RailsParam::Param::InvalidParameterError)
end
end
describe ":transform parameter" do
it "applies transformations" do
get :index, sort: "ASC"
expect(controller.params[:sort]).to eql("asc")
end
end
describe "default values" do
it "applies default values" do
get :index
expect(controller.params[:page]).to eql(1)
expect(controller.params[:sort]).to eql("asc")
end
end
end
require 'rails_param/param'
require 'action_controller'
class MyController < ActionController::Base
include RailsParam::Param
def params; end
end
describe RailsParam::Param do
describe ".param!" do
let(:controller) { MyController.new }
it "defines the method" do
controller.should respond_to(:param!)
end
end
end
require 'rails_param'
describe RailsParam do
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment