formidable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +10 -0
  2. data/CHANGELOG +2 -0
  3. data/Gemfile +7 -0
  4. data/LICENSE +20 -0
  5. data/README.textile +78 -0
  6. data/TODO.txt +3 -0
  7. data/deps.rip +5 -0
  8. data/examples/basic.rb +41 -0
  9. data/examples/post.rb +65 -0
  10. data/examples/posts/config.ru +48 -0
  11. data/examples/posts/form.html.haml +19 -0
  12. data/examples/posts/models.rb +7 -0
  13. data/formidable.gemspec +38 -0
  14. data/formidable.pre.gemspec +8 -0
  15. data/lib/formidable.rb +9 -0
  16. data/lib/formidable/coercions.rb +50 -0
  17. data/lib/formidable/elements.rb +196 -0
  18. data/lib/formidable/renderers/nokogiri.rb +53 -0
  19. data/lib/formidable/renderers/string.rb +115 -0
  20. data/lib/formidable/rendering.rb +42 -0
  21. data/lib/formidable/validations.rb +94 -0
  22. data/lib/formidable/validations/class.rb +24 -0
  23. data/lib/formidable/validations/confirmation.rb +58 -0
  24. data/lib/formidable/validations/equality.rb +25 -0
  25. data/lib/formidable/validations/format.rb +23 -0
  26. data/lib/formidable/validations/length.rb +39 -0
  27. data/lib/formidable/validations/presence.rb +17 -0
  28. data/lib/formidable/version.rb +5 -0
  29. data/spec/formidable/coercions_spec.rb +106 -0
  30. data/spec/formidable/elements_spec.rb +21 -0
  31. data/spec/formidable/renderers/nokogiri_spec.rb +9 -0
  32. data/spec/formidable/renderers/string_spec.rb +9 -0
  33. data/spec/formidable/rendering_spec.rb +0 -0
  34. data/spec/formidable/validations/class_spec.rb +0 -0
  35. data/spec/formidable/validations/confirmation_spec.rb +0 -0
  36. data/spec/formidable/validations/equality_spec.rb +0 -0
  37. data/spec/formidable/validations/format_spec.rb +0 -0
  38. data/spec/formidable/validations/length_spec.rb +0 -0
  39. data/spec/formidable/validations/presence_spec.rb +0 -0
  40. data/spec/formidable/validations_spec.rb +9 -0
  41. data/spec/formidable_spec.rb +51 -0
  42. data/spec/spec.opts +6 -0
  43. data/spec/spec_helper.rb +13 -0
  44. data/tasks.rb +38 -0
  45. data/vendor/cache/validatable-1.6.7.gem +0 -0
  46. 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