formidable 0.0.1
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.
- 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
|