formidable 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/CHANGELOG +2 -0
- data/Gemfile +7 -0
- data/LICENSE +20 -0
- data/README.textile +78 -0
- data/TODO.txt +3 -0
- data/deps.rip +5 -0
- data/examples/basic.rb +41 -0
- data/examples/post.rb +65 -0
- data/examples/posts/config.ru +48 -0
- data/examples/posts/form.html.haml +19 -0
- data/examples/posts/models.rb +7 -0
- data/formidable.gemspec +38 -0
- data/formidable.pre.gemspec +8 -0
- data/lib/formidable.rb +9 -0
- data/lib/formidable/coercions.rb +50 -0
- data/lib/formidable/elements.rb +196 -0
- data/lib/formidable/renderers/nokogiri.rb +53 -0
- data/lib/formidable/renderers/string.rb +115 -0
- data/lib/formidable/rendering.rb +42 -0
- data/lib/formidable/validations.rb +94 -0
- data/lib/formidable/validations/class.rb +24 -0
- data/lib/formidable/validations/confirmation.rb +58 -0
- data/lib/formidable/validations/equality.rb +25 -0
- data/lib/formidable/validations/format.rb +23 -0
- data/lib/formidable/validations/length.rb +39 -0
- data/lib/formidable/validations/presence.rb +17 -0
- data/lib/formidable/version.rb +5 -0
- data/spec/formidable/coercions_spec.rb +106 -0
- data/spec/formidable/elements_spec.rb +21 -0
- data/spec/formidable/renderers/nokogiri_spec.rb +9 -0
- data/spec/formidable/renderers/string_spec.rb +9 -0
- data/spec/formidable/rendering_spec.rb +0 -0
- data/spec/formidable/validations/class_spec.rb +0 -0
- data/spec/formidable/validations/confirmation_spec.rb +0 -0
- data/spec/formidable/validations/equality_spec.rb +0 -0
- data/spec/formidable/validations/format_spec.rb +0 -0
- data/spec/formidable/validations/length_spec.rb +0 -0
- data/spec/formidable/validations/presence_spec.rb +0 -0
- data/spec/formidable/validations_spec.rb +9 -0
- data/spec/formidable_spec.rb +51 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +13 -0
- data/tasks.rb +38 -0
- data/vendor/cache/validatable-1.6.7.gem +0 -0
- metadata +107 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "nokogiri"
|
5
|
+
rescue LoadError
|
6
|
+
raise LoadError, "You have to install nokogiri gem if you want to use nokogiri renderer!"
|
7
|
+
end
|
8
|
+
|
9
|
+
module Formidable
|
10
|
+
module Renderers
|
11
|
+
class Nokogiri < Renderer
|
12
|
+
register(Form) do |element|
|
13
|
+
raise NotImplemented, "We'll implement the renderer soon"
|
14
|
+
end
|
15
|
+
|
16
|
+
register(Elements::TextField) do |element|
|
17
|
+
raise NotImplemented, "We'll implement the renderer soon"
|
18
|
+
end
|
19
|
+
|
20
|
+
register(Elements::TextArea) do |element|
|
21
|
+
raise NotImplemented, "We'll implement the renderer soon"
|
22
|
+
end
|
23
|
+
|
24
|
+
register(Elements::HiddenField) do |element|
|
25
|
+
raise NotImplemented, "We'll implement the renderer soon"
|
26
|
+
end
|
27
|
+
|
28
|
+
register(Elements::Submit) do |element|
|
29
|
+
raise NotImplemented, "We'll implement the renderer soon"
|
30
|
+
end
|
31
|
+
|
32
|
+
register(Elements::Button) do |element|
|
33
|
+
raise NotImplemented, "We'll implement the renderer soon"
|
34
|
+
end
|
35
|
+
|
36
|
+
register(Elements::Group) do |element|
|
37
|
+
raise NotImplemented, "We'll implement the renderer soon"
|
38
|
+
end
|
39
|
+
|
40
|
+
register(Elements::Fieldset) do |element|
|
41
|
+
raise NotImplemented, "We'll implement the renderer soon"
|
42
|
+
end
|
43
|
+
|
44
|
+
register(Elements::FileField) do |element|
|
45
|
+
raise NotImplemented, "We'll implement the renderer soon"
|
46
|
+
end
|
47
|
+
|
48
|
+
register(Elements::EmailField) do |element|
|
49
|
+
raise NotImplemented, "We'll implement the renderer soon"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# TODO: support for errors (validate before render)
|
4
|
+
module Formidable
|
5
|
+
module Renderers
|
6
|
+
class Renderer
|
7
|
+
attr_reader :element
|
8
|
+
def initialize(element)
|
9
|
+
@element = element
|
10
|
+
end
|
11
|
+
|
12
|
+
def tag(name, attributes = nil, &block)
|
13
|
+
"<#{name}#{hash_to_attributes_string(attributes) if attributes}>#{"\n#{block.call}\n" if block}</#{name}>"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self_close_tag(name, attributes = nil)
|
17
|
+
"<#{name}#{hash_to_attributes_string(attributes) if attributes} />"
|
18
|
+
end
|
19
|
+
|
20
|
+
def render
|
21
|
+
raise NotImplementedError, "You are supposed to redefine #render method in subclasses of Renderer"
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
def hash_to_attributes_string(hash)
|
26
|
+
hash.inject("") do |buffer, pair|
|
27
|
+
attribute, value = pair
|
28
|
+
if value
|
29
|
+
buffer += " #{attribute}='#{value}'" # TODO: h(value)
|
30
|
+
end
|
31
|
+
buffer
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class SimpleInputRenderer < Renderer
|
37
|
+
def render
|
38
|
+
self_close_tag(:input, element.attributes) + "\n"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class SimpleTagRenderer < Renderer
|
43
|
+
def render
|
44
|
+
tag(element.name, element.attributes) { element.content } + "\n"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class LabeledInputRenderer < SimpleInputRenderer
|
49
|
+
def render
|
50
|
+
id = element.attributes[:id]
|
51
|
+
raise "You have to provide id attribute if you want to use LabeledInputRenderer!" if id.nil?
|
52
|
+
buffer = tag(:label, for: id) { element.attributes[:title] } + "\n"
|
53
|
+
buffer + super
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Button < SimpleInputRenderer
|
58
|
+
def render
|
59
|
+
tag(:button, element.attributes)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Fieldset < SimpleInputRenderer
|
64
|
+
def render
|
65
|
+
tag(:fieldset) do
|
66
|
+
element.elements.map do |element|
|
67
|
+
element.render
|
68
|
+
end.join("\n")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class Blank < Renderer
|
74
|
+
def render
|
75
|
+
""
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Group < Renderer
|
80
|
+
def render
|
81
|
+
element.elements.map do |element|
|
82
|
+
element.render
|
83
|
+
end.join("\n")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class Form < Renderer
|
88
|
+
def render
|
89
|
+
if method = element.attributes[:method]
|
90
|
+
set_method(method)
|
91
|
+
end
|
92
|
+
|
93
|
+
tag(:form, element.attributes) do
|
94
|
+
element.elements.map do |element|
|
95
|
+
element.render
|
96
|
+
end.join("\n")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
def set_method(method)
|
102
|
+
if method
|
103
|
+
if ["GET", "POST"].include?(method)
|
104
|
+
element.attributes[:method] = method
|
105
|
+
elsif ["PUT", "DELETE"].include?(method)
|
106
|
+
element.attributes[:method] = "POST"
|
107
|
+
hidden_field(:_method, value: method)
|
108
|
+
else
|
109
|
+
raise ArgumentError, "Method can be GET, POST, PUT or DELETE, but not #{method}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Formidable
|
4
|
+
module Rendering
|
5
|
+
RendererNotAssigned = Class.new(StandardError)
|
6
|
+
|
7
|
+
def self.included(klass)
|
8
|
+
klass.extend(ClassMethods)
|
9
|
+
klass.extend Module.new {
|
10
|
+
def inherited(subclass)
|
11
|
+
subclass.renderer(default_renderer)
|
12
|
+
end
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_writer :renderer
|
17
|
+
def renderer
|
18
|
+
@renderer ||= begin
|
19
|
+
renderer = self.class.default_renderer
|
20
|
+
renderer.new(self) unless renderer.nil?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def render
|
25
|
+
if renderer.nil?
|
26
|
+
raise RendererNotAssigned, "You have to assign renderer. You can set default_renderer via #{self.class}.renderer(renderer_class) or you can set renderer per instance via #renderer=(renderer_instance) method."
|
27
|
+
elsif renderer && ! renderer.respond_to?(:render)
|
28
|
+
raise RendererNotAssigned, "You assigned #{self.renderer.inspect} to the #{self.inspect}, but it doesn't respond to #render method"
|
29
|
+
else
|
30
|
+
renderer.render
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
attr_reader :default_renderer
|
36
|
+
|
37
|
+
def renderer(default_renderer)
|
38
|
+
@default_renderer = default_renderer
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
=begin rdoc
|
4
|
+
Co musi validace delat:
|
5
|
+
- mixin s instancnima metodama:
|
6
|
+
- errors
|
7
|
+
- validations
|
8
|
+
- valid?
|
9
|
+
- validate
|
10
|
+
- mixin s makrama (taky instancni metody)
|
11
|
+
- vlastni validacni klasy
|
12
|
+
=end
|
13
|
+
|
14
|
+
module Formidable
|
15
|
+
class Errors < Hash
|
16
|
+
alias_method :on, :[]
|
17
|
+
alias_method :add, :[]=
|
18
|
+
end
|
19
|
+
|
20
|
+
module Elements
|
21
|
+
class Element
|
22
|
+
def self.register_validation(klass, method_name) # TODO: pridavat to spis do validations mixinu nez primo do elementu
|
23
|
+
define_method(method_name) do |*args, &block|
|
24
|
+
validation = klass.new(self, *args, &block)
|
25
|
+
validations << validation
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module Validations
|
33
|
+
def errors
|
34
|
+
@errors ||= Array.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def validations
|
38
|
+
@validations ||= Array.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def valid?
|
42
|
+
@errors || self.validate
|
43
|
+
end
|
44
|
+
|
45
|
+
def validate
|
46
|
+
self.validations.inject(true) do |is_valid, validation|
|
47
|
+
validation_passed = validation.valid?
|
48
|
+
unless validation_passed
|
49
|
+
self.errors.push(validation.message)
|
50
|
+
end
|
51
|
+
is_valid && validation_passed
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module GroupValidations # TODO: better name
|
57
|
+
include Validations
|
58
|
+
def errors
|
59
|
+
@errors ||= Errors.new
|
60
|
+
end
|
61
|
+
|
62
|
+
def before_validate
|
63
|
+
end
|
64
|
+
|
65
|
+
def validate
|
66
|
+
self.before_validate
|
67
|
+
self.elements.each do |element|
|
68
|
+
unless element.valid?
|
69
|
+
errors[element.name] = element.errors
|
70
|
+
end
|
71
|
+
end
|
72
|
+
self.errors.empty?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Validations::Validation
|
77
|
+
def self.register(method_name)
|
78
|
+
Elements::Element.register_validation(self, method_name)
|
79
|
+
end
|
80
|
+
|
81
|
+
attr_reader :element
|
82
|
+
def initialize(element)
|
83
|
+
@element = element
|
84
|
+
end
|
85
|
+
|
86
|
+
def valid?
|
87
|
+
raise NotImplementedError, "You have to redefine #valid? in subclasses of the Validation class"
|
88
|
+
end
|
89
|
+
|
90
|
+
def message
|
91
|
+
raise NotImplementedError, "You have to redefine #message in subclasses of the Validation class"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# validate_class Fixnum
|
4
|
+
# validate_class Numeric
|
5
|
+
module Formidable
|
6
|
+
module Validations
|
7
|
+
class ValidateClass < Validation
|
8
|
+
register(:validate_class)
|
9
|
+
|
10
|
+
def initialize(element, klass)
|
11
|
+
@klass = klass
|
12
|
+
super(element)
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid?
|
16
|
+
element.cleaned_data.is_a?(@klass)
|
17
|
+
end
|
18
|
+
|
19
|
+
def message
|
20
|
+
"can't be empty"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# NOTE: this could be done via validate_equality with block as well
|
4
|
+
# confirmation = text_field(:password_confirmation)
|
5
|
+
# text_field(:password).validate_confirmation(confirmation)
|
6
|
+
|
7
|
+
# text_field(:password).validate_confirmation do
|
8
|
+
# text_field(:password_confirmation)
|
9
|
+
# end
|
10
|
+
module Formidable
|
11
|
+
module Validations
|
12
|
+
class ValidateConfirmation < Validation
|
13
|
+
register(:validate_confirmation)
|
14
|
+
|
15
|
+
def initialize(element, confirmation_field, &block)
|
16
|
+
set_confirmation_field(confirmation_field, block)
|
17
|
+
super(element)
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid?
|
21
|
+
@confirmation_field.cleaned_data == element.cleaned_data
|
22
|
+
end
|
23
|
+
|
24
|
+
def message
|
25
|
+
"can't be empty"
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
def set_confirmation_field(field, callable)
|
30
|
+
@confirmation_field = begin
|
31
|
+
if field && callable.nil
|
32
|
+
field
|
33
|
+
elsif field.nil? && callable
|
34
|
+
callable.call
|
35
|
+
else
|
36
|
+
raise ArgumentError, "You are supposed to provide field or callable"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module Validatable
|
45
|
+
class ValidatesConfirmationOf < ValidationBase #:nodoc:
|
46
|
+
option :case_sensitive
|
47
|
+
default :case_sensitive => true
|
48
|
+
|
49
|
+
def valid?(instance)
|
50
|
+
return instance.send(self.attribute) == instance.send("#{self.attribute}_confirmation".to_sym) if case_sensitive
|
51
|
+
instance.send(self.attribute).to_s.casecmp(instance.send("#{self.attribute}_confirmation".to_sym).to_s) == 0
|
52
|
+
end
|
53
|
+
|
54
|
+
def message(instance)
|
55
|
+
super || "doesn't match confirmation"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# validate_equality
|
4
|
+
# validates_in [true, false, nil]
|
5
|
+
# it can work as validate_length (0..10) as well
|
6
|
+
module Formidable
|
7
|
+
module Validations
|
8
|
+
class ValidateEquality < Validation
|
9
|
+
register(:validate_equality)
|
10
|
+
|
11
|
+
def initialize(element, *values)
|
12
|
+
@values = values
|
13
|
+
super(element)
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid?
|
17
|
+
@values.include?(element.cleaned_data)
|
18
|
+
end
|
19
|
+
|
20
|
+
def message
|
21
|
+
"can't be empty"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# validate_format /\d+/
|
4
|
+
module Formidable
|
5
|
+
module Validations
|
6
|
+
class ValidateFormat < Validation
|
7
|
+
register(:validate_format)
|
8
|
+
|
9
|
+
def initialize(element, format)
|
10
|
+
@format = format
|
11
|
+
super(element)
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid?
|
15
|
+
element.cleaned_data.match(@format)
|
16
|
+
end
|
17
|
+
|
18
|
+
def message
|
19
|
+
"can't be empty"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Formidable
|
4
|
+
module Validations
|
5
|
+
class ValidateLength < Validation
|
6
|
+
# element.validate_length(10)
|
7
|
+
# element.validate_length(10..20)
|
8
|
+
# element.validate_length([10, 20])
|
9
|
+
register(:validate_length)
|
10
|
+
|
11
|
+
attr_reader :length
|
12
|
+
def initialize(element, length)
|
13
|
+
super(element)
|
14
|
+
@length = length
|
15
|
+
end
|
16
|
+
|
17
|
+
def valid?
|
18
|
+
length === element.length
|
19
|
+
end
|
20
|
+
|
21
|
+
def message # TODO: user can set message
|
22
|
+
if length.respond_to?(:first)
|
23
|
+
message_for_range
|
24
|
+
else
|
25
|
+
message_for_integer
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
def message_for_integer
|
31
|
+
"has to be #{length} characters long"
|
32
|
+
end
|
33
|
+
|
34
|
+
def message_for_range
|
35
|
+
"has to be between #{length.first} and #{length.last} characters long"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|