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,10 @@
1
+ *~
2
+ .*.swp
3
+ .#*
4
+
5
+ .DS_Store
6
+ .cache
7
+ .yardoc
8
+
9
+ /*.gem
10
+ .bundle
@@ -0,0 +1,2 @@
1
+ = Version 0.0.1
2
+ * Support for renderers
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ # gem "nokogiri"
4
+
5
+ gem "nake"
6
+ gem "rspec"
7
+ gem "code-cleaner"
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Jakub Stastny aka Botanicus & Pavel Kunc
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,78 @@
1
+ h1. About
2
+
3
+ Formidable takes care about your forms. You write a class and
4
+
5
+ * Get logic out of your views.
6
+ * Get logic out of your controllers (presenter pattern).
7
+ * Validations will work even if the form can't be mapped directly to the models.
8
+ * You can unit test your forms directly without touching the template layer at all.
9
+
10
+ * Validations
11
+ * Coercions
12
+
13
+ Forms contains quite complex logic which definitely shouldn't be in your views
14
+
15
+ h2. Defining Forms
16
+
17
+ h2. Saving Forms
18
+
19
+ h2. Validations
20
+
21
+ h2. Renderers
22
+
23
+ h2. Custom Fields
24
+
25
+ h2. Ideas
26
+
27
+ JS validation plugin
28
+
29
+ h1. Usage
30
+
31
+ h2. Form Definitions
32
+
33
+ <pre>
34
+
35
+ </pre>
36
+
37
+ h2. Controller Code
38
+
39
+ <pre>
40
+ class Posts
41
+ def new
42
+ @form ||= PostForm.new
43
+ @form.attributes[:method] = "POST"
44
+ end
45
+
46
+ def edit
47
+ @form ||= PostForm.new
48
+ @form.attributes[:method] = "PUT"
49
+ end
50
+
51
+ def create(raw_data)
52
+ @form = PostForm.new(nil, raw_data)
53
+ if @form.save
54
+ redirect url(:posts)
55
+ else
56
+ self.new
57
+ end
58
+ end
59
+
60
+ def update(id, raw_data)
61
+ @form = PostForm.new(nil, raw_data)
62
+ if @form.update(id)
63
+ redirect url(:posts)
64
+ else
65
+ self.edit
66
+ end
67
+ end
68
+ end
69
+ </pre>
70
+
71
+ You can find more examples at the @examples@ directory.
72
+
73
+ h1. Links
74
+
75
+ * "Source Code":http://github.com/botanicus/formidable
76
+ * "Wiki":http://wiki.github.com/botanicus/formidable
77
+ * "API Docs":http://rdoc.info/projects/botanicus/formidable
78
+ * "Bug reporting":http://github.com/botanicus/formidable/issues
@@ -0,0 +1,3 @@
1
+ - code & specs
2
+ - release 0.0.1
3
+ - rubyflow, twitter
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env rip install
2
+
3
+ # Syntax:
4
+ # repository [tag or commit to install]
5
+ # git://github.com/botanicus/rubyexts.git
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
4
+
5
+ require "formidable"
6
+ require "formidable/validations/presence"
7
+
8
+ class BasicForm < Formidable::Elements::Form
9
+ def setup(&block)
10
+ text_field(:name, id: "basic_form-name")
11
+ .validate_presence
12
+ .coerce { |value| value.to_i }
13
+
14
+ text_field(:rating, id: "basic_form-rating")
15
+ .validate_presence
16
+ .coerce(Integer)
17
+
18
+ fieldset(:i_brake_it) do
19
+ legend("yyyy", id: 'xxx')
20
+ text_field(:nono, id: "nono").validate_presence
21
+ end
22
+
23
+ group(:i_m_group) do
24
+ text_field(:groupa, id: 'xy')
25
+ end
26
+ block.call if block
27
+ submit("Save")
28
+ end
29
+
30
+ def save
31
+
32
+ end
33
+ end
34
+
35
+ class MyForm < BasicForm
36
+ def setup
37
+ super do
38
+ text_field(:super_duper, id: 'super_duper')
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+
3
+ # text_field, text_area -> value = raw_data[key]
4
+ # raw_data == user: {}, tags: [{name: "Ruby"}]
5
+ # form = PostForm.new(@post.values, Author.all)
6
+ class PostForm < Formidable::Form
7
+ namespace :post
8
+ def initialize(raw_data, authors)
9
+ super(raw_data)
10
+ @authors = authors
11
+ end
12
+
13
+ text_field(:title)
14
+ .validate_presence.
15
+ text_field(:title) do
16
+ validate_presence
17
+ end
18
+
19
+ check_box(:published)
20
+ text_area
21
+
22
+ group(:tydenni_menu) do
23
+ check_box :svickova
24
+ check_box :knedlo_zelo
25
+ validate do
26
+ self.fields.inject(0) do |sum, check_box|
27
+ sum += 1 if check_box.selected?
28
+ sum
29
+ end
30
+ end
31
+ end
32
+
33
+ select(:author) do
34
+ @authors.each do |author|
35
+ option selected: (author == raw_data[:author])
36
+ end
37
+ end
38
+
39
+ submit(value: "Save")
40
+ button(type: "submit") { "Save" }
41
+
42
+ field(:password, id: "sdf") do
43
+ validate_length (0..10)
44
+ validate do
45
+ form.password_confirmation == value
46
+ end
47
+ end
48
+
49
+ field(:password_confirmation) do
50
+ validate { form.password == value }
51
+ end
52
+
53
+ fields_for(:author) do
54
+ field()
55
+ end
56
+
57
+ # <fieldset>
58
+ # <legend>User</legend>
59
+ # </fieldset>
60
+ fieldset("User") do
61
+ field(:name)
62
+ end
63
+
64
+ field(:tags, id: -> { |tag| "tag-#{tag.id}"}, array: true)
65
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+
3
+ class Posts
4
+ def self.dispatcher(action)
5
+ Proc.new do |env|
6
+ self.new(env).send(action)
7
+ end
8
+ end
9
+
10
+ def initialize(env)
11
+ @env = env
12
+ end
13
+
14
+ def index
15
+ end
16
+
17
+ def new
18
+ @form ||= PostForm.new
19
+ @form.attributes[:method] = "POST"
20
+ end
21
+
22
+ def edit
23
+ @form ||= PostForm.new
24
+ @form.attributes[:method] = "PUT"
25
+ end
26
+
27
+ def create(raw_data)
28
+ @form = PostForm.new(nil, raw_data)
29
+ if @form.save
30
+ redirect url(:posts)
31
+ else
32
+ self.new
33
+ end
34
+ end
35
+
36
+ def update(id, raw_data)
37
+ @form = PostForm.new(nil, raw_data)
38
+ if @form.update(id)
39
+ redirect url(:posts)
40
+ else
41
+ self.edit
42
+ end
43
+ end
44
+ end
45
+
46
+ map("/posts") do
47
+ Posts.dispatcher(:index)
48
+ end
@@ -0,0 +1,19 @@
1
+ / You might not need to have a template for your form. In case you
2
+ / don't have any special need, just use form.render and that's it.
3
+
4
+ / Specify renderer you want to use.
5
+ = form.errors.render(ErrorsListRenderer.new)
6
+
7
+ / Haml tags can take hash as an argument, so why not simply use form.attributes.
8
+ %form{form.attributes}
9
+ / Render tags you want to display.
10
+ = form.post.title.render
11
+ / Iterate over elements
12
+ - form.post.tags.each do |tag|
13
+ = tag.render
14
+ / Or write fully customized HTML.
15
+ %label{for: "input-body"}
16
+ - if form.post.errors.on(:title)
17
+ %input#input-title.errors{name: "post[title]", value: form.post.title}
18
+ - else
19
+ %input#input-title{name: "post[title]", value: form.post.title}
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ class Post < Sequel::Model
4
+ end
5
+
6
+ class Tag < Sequel::Model
7
+ end
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env gem build
2
+ # encoding: utf-8
3
+
4
+ # Run ./form.gemspec or gem build form.gemspec
5
+ # NOTE: we can't use require_relative because when we run gem build, it use eval for executing this file
6
+ require File.expand_path("../lib/formidable/version", __FILE__)
7
+ require "base64"
8
+
9
+ Gem::Specification.new do |s|
10
+ s.name = "formidable"
11
+ s.version = Formidable::VERSION
12
+ s.authors = ["Jakub Stastny aka Botanicus", "Pavel Kunc"]
13
+ s.homepage = "http://github.com/botanicus/formidable"
14
+ s.summary = "" # TODO: summary
15
+ s.description = "" # TODO: long description
16
+ s.cert_chain = nil
17
+ s.email = Base64.decode64("c3Rhc3RueUAxMDFpZGVhcy5jeg==\n")
18
+ s.has_rdoc = true
19
+
20
+ # files
21
+ s.files = `git ls-files`.split("\n")
22
+
23
+ s.require_paths = ["lib"]
24
+
25
+ # Ruby version
26
+ s.required_ruby_version = ::Gem::Requirement.new(">= 1.9")
27
+
28
+ begin
29
+ require "changelog"
30
+ rescue LoadError
31
+ warn "You have to have changelog gem installed for post install message"
32
+ else
33
+ s.post_install_message = CHANGELOG.new.version_changes
34
+ end
35
+
36
+ # RubyForge
37
+ s.rubyforge_project = "formidable"
38
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env gem build
2
+ # encoding: utf-8
3
+
4
+ # You might think this is a terrible mess and guess what, you're
5
+ # right mate! However say thanks to authors of RubyGems, not me.
6
+ eval(File.read("form.gemspec")).tap do |specification|
7
+ specification.version = "#{specification.version}.pre"
8
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ # TODO: linked list rather than arrays
4
+ # plnenei dat
5
+
6
+ require "formidable/elements"
7
+ require "formidable/rendering"
8
+ require "formidable/validations"
9
+ require "formidable/renderers/string"
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ module Formidable
4
+ module Coercions
5
+ MissingCoercion = Class.new(StandardError)
6
+ IncompatibleInterface = Class.new(StandardError)
7
+
8
+ def self.coercions
9
+ @coercions ||= Hash.new do |hash, key|
10
+ raise MissingCoercion, "No coercion defined for #{key}"
11
+ end
12
+ end
13
+
14
+ # default coercions
15
+ coercions[:integer] = Proc.new { |value| value.to_i }
16
+ coercions[:float] = Proc.new { |value| value.to_f }
17
+
18
+ def self.included(klass)
19
+ if ! klass.method_defined?(:raw_data) || ! klass.method_defined?(:cleaned_data=)
20
+ raise IncompatibleInterface, "You are supposed to define #{klass}#raw_data and #{klass}#cleaned_data= in order to get coercions running!"
21
+ end
22
+ end
23
+
24
+ def cleaned_data
25
+ @cleaned_data ||= self.coerce!
26
+ end
27
+
28
+ def coercions
29
+ @coercions ||= Array.new
30
+ end
31
+
32
+ def coerce(type = nil, &block)
33
+ if type && block.nil?
34
+ coercions << Coercions.coercions[type]
35
+ elsif type.nil? && block
36
+ coercions << block
37
+ else
38
+ raise ArgumentError, "provide block or type"
39
+ end
40
+ end
41
+
42
+ def coerce!
43
+ self.cleaned_data = begin
44
+ coercions.inject(self.raw_data) do |coercion, raw_data|
45
+ coercion.call(raw_data)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,196 @@
1
+ # encoding: utf-8
2
+
3
+ require "formidable/coercions"
4
+ require "formidable/rendering"
5
+ require "formidable/validations"
6
+ require "formidable/renderers/string"
7
+
8
+ module Formidable
9
+ module Elements
10
+ class Element
11
+ include Rendering
12
+ include Validations
13
+ include Coercions
14
+
15
+ attr_accessor :name, :raw_data, :content
16
+ attr_reader :attributes
17
+
18
+ def initialize(name, attributes = Hash.new, raw_data = nil)
19
+ @name, @attributes, @raw_data = name, attributes, raw_data
20
+ @attributes.merge!(name: name, value: raw_data)
21
+ end
22
+
23
+ def set_raw_data(raw_data)
24
+ @raw_data = self.attributes[:value] = raw_data
25
+ end
26
+ end
27
+
28
+ class ElementList < Element # pro form, group, fieldset
29
+ # We had a few beers and we decided that this is pretty cool :)
30
+ # This will define DSL method for creating email_field
31
+ # @example Formidable::ElementList.register self, :email_field
32
+ def self.register(klass, method_name)
33
+ define_method(method_name) do |name, *args, &block|
34
+ element = klass.new(name, *args, &block)
35
+ elements << element
36
+ warn "Overriding method #{name}" if self.class.method_defined?(name)
37
+ self.class.send(:define_method, name) do
38
+ element
39
+ end
40
+ element
41
+ end
42
+ end
43
+
44
+ include GroupValidations
45
+ def initialize(*args, &block)
46
+ super(*args) # we need to get elements first
47
+ self.instance_eval(&block) if block
48
+ end
49
+
50
+ def elements
51
+ @elements ||= Array.new
52
+ end
53
+
54
+ def cleaned_data
55
+ self.elements.inject(Hash.new) do |result, element|
56
+ result[element.name] = element.cleaned_data
57
+ result
58
+ end
59
+ end
60
+
61
+ def raw_data
62
+ self.elements.inject(Hash.new) do |result, element|
63
+ result[element.name] = element.raw_data
64
+ result
65
+ end
66
+ end
67
+
68
+ protected
69
+ def set_raw_data(raw_data)
70
+ self.elements.each do |element|
71
+ element.set_raw_data(raw_data[element.name]) if raw_data
72
+ end
73
+ end
74
+ end
75
+
76
+ class Form < ElementList
77
+ renderer Renderers::Form
78
+
79
+ # TODO: prefix should be in the form definition (name or namespace)
80
+ def initialize(prefix = nil, attributes = Hash.new, raw_data = nil)
81
+ self.setup
82
+ super(prefix, attributes, raw_data)
83
+ set_raw_data(raw_data)
84
+ end
85
+
86
+ def setup
87
+ raise NotImplementedError, "You are supposed to redefine the setup method in subclasses of Element!"
88
+ end
89
+
90
+ def save(*args)
91
+ raise NotImplementedError, "You have to redefine this method in subclasses!"
92
+ end
93
+ alias_method :save!, :save
94
+
95
+ def update(*args)
96
+ raise NotImplementedError, "You have to redefine this method in subclasses!"
97
+ end
98
+ alias_method :update!, :update
99
+ end
100
+
101
+
102
+ class TextField < Element
103
+ ElementList.register(self, :text_field)
104
+
105
+ def initialize(name, attributes = Hash.new, raw_data = nil)
106
+ super(name, attributes.merge!(type: "text"), raw_data)
107
+ end
108
+
109
+ renderer Renderers::LabeledInputRenderer
110
+ end
111
+
112
+ class TextArea < Element
113
+ ElementList.register(self, :text_area)
114
+
115
+ renderer Renderers::LabeledInputRenderer
116
+ end
117
+
118
+ class HiddenField < Element
119
+ ElementList.register(self, :hidden_field)
120
+
121
+ renderer Renderers::LabeledInputRenderer
122
+ end
123
+
124
+ class Submit < Element
125
+ ElementList.register(self, :submit)
126
+
127
+ renderer Renderers::SimpleInputRenderer
128
+
129
+ def initialize(value = "Submit", attributes = Hash.new, raw_data = Hash.new)
130
+ super(:submit, attributes.merge(value: value, type: "submit"), raw_data)
131
+ end
132
+ end
133
+
134
+ class Button < Element
135
+ ElementList.register(self, :button)
136
+
137
+ renderer Renderers::Button
138
+ end
139
+
140
+ class Group < ElementList
141
+ ElementList.register(self, :group)
142
+
143
+ renderer Renderers::Group
144
+ end
145
+
146
+ class Fieldset < ElementList
147
+ ElementList.register(self, :fieldset)
148
+
149
+ renderer Renderers::Fieldset
150
+ end
151
+
152
+ class Legend < Element
153
+ Fieldset.register(self, :legend)
154
+
155
+ renderer Renderers::SimpleTagRenderer
156
+
157
+ def initialize(content, attributes = Hash.new)
158
+ self.content = content
159
+ super(:legend, attributes)
160
+ end
161
+ end
162
+
163
+ class FileField < Element
164
+ ElementList.register(self, :file_field)
165
+
166
+ renderer Renderers::LabeledInputRenderer
167
+
168
+ def initialize(*args)
169
+ super(*args)
170
+ self.form.multipart = true
171
+ end
172
+ end
173
+
174
+ class EmailField < TextField
175
+ ElementList.register(self, :email_field)
176
+ end
177
+
178
+ # zna to svoje name => vi kde v params to najit
179
+ # {date: "27-07-2010"} => {date: #<Date:x0>}
180
+ class DateSelectField < Element
181
+ # TOHLE BUDE V SUPERCLASS
182
+ # jak to ma dostat params?
183
+ def deserialize
184
+ # [:post, :title]
185
+ self.name.inject(params) do |hash, key|
186
+ hash[key]
187
+ end
188
+ end
189
+
190
+ # TOHLE BUDE TADY
191
+ def deserialize
192
+ Date.new(super)
193
+ end
194
+ end
195
+ end
196
+ end