poncho 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ module Poncho
2
+ class Param
3
+ def self.type
4
+ @type ||= begin
5
+ full_name = name.split('::').last
6
+ full_name = full_name.gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
7
+ full_name.sub(/_param$/, '').to_sym
8
+ end
9
+ end
10
+
11
+ attr_reader :name, :options
12
+
13
+ def initialize(name, options = {})
14
+ @name = name.to_sym
15
+ @options = options
16
+ end
17
+
18
+ def type
19
+ self.class.type
20
+ end
21
+
22
+ def validate_each(record, attribute, value)
23
+ end
24
+
25
+ def convert(value)
26
+ value
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,92 @@
1
+ module Poncho
2
+ module Params
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ VALIDATES_DEFAULT_KEYS = [:resource, :type, :required, :format, :in, :not_in, :length]
9
+
10
+ def params
11
+ @params ||= {}
12
+ end
13
+
14
+ def param(name, options = {})
15
+ type = options[:type]
16
+ type ||= options[:resource] ? :resource : :string
17
+
18
+ klass = param_for_type(type)
19
+ param = klass.new(name, options)
20
+ params[param.name] = param
21
+
22
+ create_validations_for(param)
23
+
24
+ param
25
+ end
26
+
27
+ def string(name, options = {})
28
+ param(name, options.merge(:type => :string))
29
+ end
30
+
31
+ def integer(name, options = {})
32
+ param(name, options.merge(:type => :integer))
33
+ end
34
+
35
+ private
36
+
37
+ def param_for_type(type)
38
+ return type if type.is_a?(Class)
39
+ name = type.to_s.split('_').map {|w| w.capitalize }.join
40
+ const_get("#{name}Param")
41
+ rescue NameError
42
+ raise ArgumentError, "Unknown param: #{type}"
43
+ end
44
+
45
+ def create_validations_for(param)
46
+ attribute = param.name
47
+
48
+ unless param.options[:validates] == false
49
+ validates_param(attribute, :param => param)
50
+ end
51
+
52
+ if param.options[:required]
53
+ validates_presence_of(attribute)
54
+ end
55
+
56
+ if param.options[:format]
57
+ validates_format_of(attribute, :with => param.options[:format])
58
+ end
59
+
60
+ if param.options[:in]
61
+ validates_inclusion_of(attribute, :in => param.options[:in])
62
+ end
63
+
64
+ if param.options[:not_in]
65
+ validates_exclusion_of(attribute, :in => param.options[:not_in])
66
+ end
67
+
68
+ if param.options[:length]
69
+ length_options = case param.options[:length]
70
+ when Integer
71
+ {:minimum => 0, :maximum => param.options[:length]}
72
+ when Range
73
+ {:within => param.options[:length]}
74
+ when Hash
75
+ param.options[:length]
76
+ end
77
+ validates_length_of(attribute, length_options)
78
+ end
79
+
80
+ validators = param.options.reject {|key, _| VALIDATES_DEFAULT_KEYS.include?(key) }
81
+ validates(attribute, validators) if validators.any?
82
+ end
83
+ end
84
+ end
85
+
86
+ alias :param_for_validation? :respond_to?
87
+ end
88
+
89
+ Dir[File.dirname(__FILE__) + "/params/*.rb"].sort.each do |path|
90
+ filename = File.basename(path)
91
+ require "poncho/params/#{filename}"
92
+ end
@@ -0,0 +1,15 @@
1
+ module Poncho
2
+ module Params
3
+ class ArrayParam < Param
4
+ def validate_each(record, attribute, value)
5
+ if !value.is_a?(Array)
6
+ record.errors.add(attribute, :invalid_array, options.merge(:value => value))
7
+ end
8
+ end
9
+
10
+ def convert(value)
11
+ Array(value)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ module Poncho
2
+ module Params
3
+ class BooleanParam < Param
4
+ def validate_each(record, attribute, value)
5
+ converted = convert(value)
6
+
7
+ if !(converted.is_a?(TrueClass) || converted.is_a?(FalseClass))
8
+ record.errors.add(attribute, :invalid_boolean, options.merge(:value => value))
9
+ end
10
+ end
11
+
12
+ def convert(value)
13
+ if value.is_a?(TrueClass) || value.is_a?(FalseClass)
14
+ return value
15
+ end
16
+ nil
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module Poncho
2
+ module Params
3
+ class BooleanStringParam < Param
4
+ def validate_each(record, attribute, value)
5
+ converted = convert(value)
6
+
7
+ if !(converted.is_a?(TrueClass) || converted.is_a?(FalseClass))
8
+ record.errors.add(attribute, :invalid_boolean_string, options.merge(:value => value))
9
+ end
10
+ end
11
+
12
+ def convert(value)
13
+ return true if value == 'yes'
14
+ return false if value == 'no'
15
+ nil
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module Poncho
2
+ module Params
3
+ class IntegerParam < Param
4
+ def validate_each(record, attribute, value)
5
+ unless convert(value).is_a?(Integer)
6
+ record.errors.add(attribute, :invalid_integer, options.merge(:value => value))
7
+ end
8
+ end
9
+
10
+ def convert(value)
11
+ Integer(value)
12
+ rescue TypeError, ArgumentError
13
+ nil
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module Poncho
2
+ module Params
3
+ class ObjectParam < Param
4
+ def validate_each(record, attribute, value)
5
+ if !value || !value.is_a?(Object)
6
+ record.errors.add(attribute, :invalid_object, options.merge(:value => value))
7
+ end
8
+ end
9
+
10
+ def convert(value)
11
+ value
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ module Poncho
2
+ module Params
3
+ class ResourceParam < Param
4
+ def initialize(name, options = {})
5
+ super
6
+
7
+ unless options[:resource]
8
+ throw ArgumentError, ':resource required'
9
+ end
10
+ end
11
+
12
+ def validate_each(record, attribute, value)
13
+ resource = convert(value)
14
+
15
+ unless resource.valid?
16
+ resource.errors.to_hash.each do |attr, messages|
17
+ record.messages[:"#{attribute}[#{attr}]"] |= messages
18
+ end
19
+ end
20
+ end
21
+
22
+ def convert(value)
23
+ options[:resource].new(value)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ module Poncho
2
+ module Params
3
+ class StringParam < Param
4
+ def validate_each(record, attribute, value)
5
+ unless value.is_a?(String)
6
+ record.errors.add(attribute, :invalid_string, options.merge(:value => value))
7
+ end
8
+ end
9
+
10
+ def convert(value)
11
+ value && value.to_s
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ module Poncho
2
+ module Validations
3
+ class ParamValidator < EachValidator
4
+ def check_validity!
5
+ unless options[:param]
6
+ raise ArgumentError, ':param option required'
7
+ end
8
+ end
9
+
10
+ def validate_each(record, attribute, value)
11
+ if record.param_for_validation?(attribute)
12
+ options[:param].validate_each(record, attribute, value)
13
+ end
14
+ end
15
+ end
16
+
17
+ module HelperMethods
18
+ def validates_param(*attr_names)
19
+ options = attr_names.last.is_a?(::Hash) ? attr_names.pop : {}
20
+ validates_with ParamValidator, options.merge(:attributes => attr_names)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,71 @@
1
+ module Poncho
2
+ class Request < Rack::Request
3
+ # Returns an array of acceptable media types for the response
4
+ def accept
5
+ @env['poncho.accept'] ||= begin
6
+ entries = @env['HTTP_ACCEPT'].to_s.split(',')
7
+ entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first)
8
+ end
9
+ end
10
+
11
+ def preferred_type(*types)
12
+ return accept.first if types.empty?
13
+ types.flatten!
14
+ accept.detect do |pattern|
15
+ type = types.detect { |t| File.fnmatch(pattern, t) }
16
+ return type if type
17
+ end
18
+ end
19
+
20
+ alias accept? preferred_type
21
+ alias secure? ssl?
22
+
23
+ def forwarded?
24
+ @env.include? 'HTTP_X_FORWARDED_HOST'
25
+ end
26
+
27
+ def safe?
28
+ get? or head? or options? or trace?
29
+ end
30
+
31
+ def idempotent?
32
+ safe? or put? or delete?
33
+ end
34
+
35
+ def params
36
+ @params ||= begin
37
+ params = super.merge(action_dispatch_params)
38
+ indifferent_params(params)
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # Enable string or symbol key access to the nested params hash.
45
+ def indifferent_params(params)
46
+ params = indifferent_hash.merge(params)
47
+ params.each do |key, value|
48
+ next unless value.is_a?(Hash)
49
+ params[key] = indifferent_params(value)
50
+ end
51
+ end
52
+
53
+ # Creates a Hash with indifferent access.
54
+ def indifferent_hash
55
+ Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
56
+ end
57
+
58
+ # Pass in params from Rails routing
59
+ def action_dispatch_params
60
+ action_dispatch_params = env['action_dispatch.request.path_parameters'] || {}
61
+ action_dispatch_params.inject({}) {|hash, (key, value)| hash[key.to_s] = value; hash }
62
+ end
63
+
64
+ def accept_entry(entry)
65
+ type, *options = entry.delete(' ').split(';')
66
+ quality = 0 # we sort smallest first
67
+ options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' }
68
+ [type, [quality, type.count('*'), 1 - options.size]]
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,80 @@
1
+ module Poncho
2
+ class Resource
3
+ include Params
4
+ include Validations
5
+
6
+ attr_reader :record
7
+
8
+ def initialize(record)
9
+ if record.nil?
10
+ raise ResourceValidationError, 'Invalid nil record'
11
+ end
12
+
13
+ @record = record
14
+ end
15
+
16
+ # Params
17
+
18
+ def param(name)
19
+ param = self.class.params[name]
20
+ raise Error, "Undefined param #{name}" unless param
21
+ param.convert(param_before_type_cast(name))
22
+ end
23
+
24
+ def param_before_type_cast(name)
25
+ record.send(name) if record.respond_to?(name)
26
+ end
27
+
28
+ alias_method :param?, :respond_to?
29
+
30
+ # Serialization
31
+
32
+ def each
33
+ [to_json]
34
+ end
35
+
36
+ def to_json(options = nil)
37
+ as_json.to_json
38
+ end
39
+
40
+ def as_json(options = nil)
41
+ validate!
42
+ to_hash
43
+ end
44
+
45
+ def to_hash
46
+ self.class.params.keys.inject({}) do |hash, key|
47
+ hash[key] = send(key)
48
+ hash
49
+ end
50
+ end
51
+
52
+ alias_method :describe, :to_hash
53
+
54
+ # Validation
55
+
56
+ # We want to validate an attribute if its
57
+ # uncoerced value is not nil
58
+ def param_for_validation?(name)
59
+ if respond_to?(name)
60
+ !send(name).nil?
61
+ else
62
+ !param_before_type_cast(name).nil?
63
+ end
64
+ end
65
+
66
+ alias_method :read_attribute_for_validation, :send
67
+
68
+ def validate!
69
+ raise ResourceValidationError, errors.to_s unless valid?
70
+ end
71
+
72
+ def method_missing(method_symbol, *arguments) #:nodoc:
73
+ if self.class.params.keys.include?(method_symbol)
74
+ return param(method_symbol)
75
+ end
76
+
77
+ super
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,28 @@
1
+ module Poncho
2
+ class Response < Rack::Response
3
+ def body=(value)
4
+ value = value.body while Rack::Response === value
5
+ @body = String === value ? [value.to_str] : value
6
+ end
7
+
8
+ def each
9
+ block_given? ? super : enum_for(:each)
10
+ end
11
+
12
+ def finish
13
+ if status.to_i / 100 == 1
14
+ headers.delete 'Content-Length'
15
+ headers.delete 'Content-Type'
16
+ elsif Array === body and not [204, 304].include?(status.to_i)
17
+ # if some other code has already set Content-Length, don't muck with it
18
+ # currently, this would be the static file-handler
19
+ headers['Content-Length'] ||= body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s
20
+ end
21
+
22
+ # Rack::Response#finish sometimes returns self as response body. We don't want that.
23
+ status, headers, result = super
24
+ result = body if result == self
25
+ [status, headers, result]
26
+ end
27
+ end
28
+ end