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