poncho 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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