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