formation 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 +6 -0
- data/Gemfile +8 -0
- data/Rakefile +22 -0
- data/demo.rb +179 -0
- data/formation.gemspec +21 -0
- data/lib/formation.rb +24 -0
- data/lib/formation/element.rb +9 -0
- data/lib/formation/field.rb +32 -0
- data/lib/formation/fieldset.rb +14 -0
- data/lib/formation/form.rb +146 -0
- data/lib/formation/printer.rb +52 -0
- data/lib/formation/type.rb +19 -0
- data/lib/formation/types/checkbox.rb +15 -0
- data/lib/formation/types/password.rb +15 -0
- data/lib/formation/types/text.rb +15 -0
- data/lib/formation/types/text_area.rb +15 -0
- data/lib/formation/util.rb +11 -0
- data/lib/formation/version.rb +5 -0
- data/readme.md +61 -0
- data/test/field_spec.rb +17 -0
- data/test/form_spec.rb +86 -0
- data/test/forms/post_form.rb +16 -0
- data/test/forms/simple_form.rb +33 -0
- data/test/test_helper.rb +9 -0
- data/test/type_spec.rb +15 -0
- data/test/types/checkbox_spec.rb +56 -0
- data/test/types/password_spec.rb +42 -0
- data/test/types/text_area_spec.rb +42 -0
- data/test/types/text_spec.rb +42 -0
- data/test/util_spec.rb +21 -0
- metadata +107 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rake/testtask'
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.test_files = FileList['test/**/*_{test,spec}.rb']
|
7
|
+
# t.warning = true
|
8
|
+
end
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'rcov/rcovtask'
|
12
|
+
Rcov::RcovTask.new do |t|
|
13
|
+
t.test_files = FileList['test/**/*_{test,spec}.rb']
|
14
|
+
t.verbose = true
|
15
|
+
t.rcov_opts << %w{ --exclude .rvm }
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
desc 'Analyze code coverage with tests'
|
19
|
+
task :rcov do
|
20
|
+
puts "Install rcov"
|
21
|
+
end
|
22
|
+
end
|
data/demo.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'sinatra'
|
3
|
+
require 'datamapper'
|
4
|
+
require File.expand_path('../lib/formation', __FILE__)
|
5
|
+
|
6
|
+
class Post
|
7
|
+
|
8
|
+
include DataMapper::Resource
|
9
|
+
|
10
|
+
property :id, Serial
|
11
|
+
property :title, String
|
12
|
+
property :body, Text
|
13
|
+
property :created_at, DateTime
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class PostForm
|
18
|
+
|
19
|
+
include Formation::Form
|
20
|
+
|
21
|
+
resource :post do
|
22
|
+
fieldset :legend => 'New Post' do
|
23
|
+
field :title, :label => 'Title', :required => true
|
24
|
+
field :body, :label => 'Body', :type => :textarea, :required => true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(post)
|
29
|
+
@post = post
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
class RegistrationForm
|
35
|
+
|
36
|
+
include Formation::Form
|
37
|
+
|
38
|
+
fieldset :account do
|
39
|
+
field :login_name, :required => true
|
40
|
+
field :password, :required => true
|
41
|
+
end
|
42
|
+
|
43
|
+
fieldset :address do
|
44
|
+
field :address
|
45
|
+
field :city
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
DataMapper.setup :default, 'sqlite::memory:'
|
51
|
+
DataMapper.auto_migrate!
|
52
|
+
|
53
|
+
get '/styles.css' do
|
54
|
+
<<-CSS
|
55
|
+
*, html {
|
56
|
+
font-family: Verdana, Arial, Helvetica, sans-serif;
|
57
|
+
}
|
58
|
+
body, form, ul, li, p, h2, h3, h4, h5 {
|
59
|
+
margin: 0;
|
60
|
+
padding: 0;
|
61
|
+
}
|
62
|
+
body {
|
63
|
+
background-color: #606061;
|
64
|
+
color: #ffffff;
|
65
|
+
}
|
66
|
+
img {
|
67
|
+
border: none;
|
68
|
+
}
|
69
|
+
p {
|
70
|
+
font-size: 1em;
|
71
|
+
margin: 0 0 1em 0;
|
72
|
+
}
|
73
|
+
h2 {
|
74
|
+
font-size: 14px;
|
75
|
+
margin: 0 0 12px;
|
76
|
+
}
|
77
|
+
|
78
|
+
form {
|
79
|
+
margin: 20px auto;
|
80
|
+
width: 610px;
|
81
|
+
}
|
82
|
+
form fieldset {
|
83
|
+
margin: 0 0 20px;
|
84
|
+
padding: 20px;
|
85
|
+
-webkit-border-radius: 5px;
|
86
|
+
-moz-border-radius: 5px;
|
87
|
+
border-radius: 5px;
|
88
|
+
}
|
89
|
+
form ol {
|
90
|
+
list-style-type: none;
|
91
|
+
padding: 0;
|
92
|
+
margin: 0;
|
93
|
+
}
|
94
|
+
form li {
|
95
|
+
margin: 0 0 12px;
|
96
|
+
position: relative;
|
97
|
+
}
|
98
|
+
form label {
|
99
|
+
width: 150px;
|
100
|
+
display: inline-block;
|
101
|
+
vertical-align: top;
|
102
|
+
}
|
103
|
+
form input, form textarea, form select {
|
104
|
+
background: #fff;
|
105
|
+
display: inline-block;
|
106
|
+
width: 371px;
|
107
|
+
border: 1px solid #fff;
|
108
|
+
padding: 3px 26px 3px 3px;
|
109
|
+
-moz-transition: background-color 1s ease;
|
110
|
+
-webkit-transition: background-color 1s ease;
|
111
|
+
-o-transition: background-color 1s ease;
|
112
|
+
transition: background-color 1s ease;
|
113
|
+
-webkit-border-radius: 5px;
|
114
|
+
-moz-border-radius: 5px;
|
115
|
+
border-radius: 5px;
|
116
|
+
}
|
117
|
+
CSS
|
118
|
+
end
|
119
|
+
|
120
|
+
get '/register' do
|
121
|
+
@form = RegistrationForm.new
|
122
|
+
erb :register
|
123
|
+
end
|
124
|
+
|
125
|
+
post '/register' do
|
126
|
+
@form = RegistrationForm.new
|
127
|
+
@form.submit params
|
128
|
+
if @form.valid?
|
129
|
+
erb :thanks
|
130
|
+
else
|
131
|
+
erb :register
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
get '/posts/new' do
|
136
|
+
@form = PostForm.new(Post.new)
|
137
|
+
erb :new_post
|
138
|
+
end
|
139
|
+
|
140
|
+
post '/posts' do
|
141
|
+
@form = PostForm.new(Post.new)
|
142
|
+
@form.submit params
|
143
|
+
if @form.valid?
|
144
|
+
@form.post.save
|
145
|
+
raise @form.inspect
|
146
|
+
redirect "/posts/#{@form.post.id}"
|
147
|
+
else
|
148
|
+
erb :new_post
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
__END__
|
153
|
+
|
154
|
+
@@ layout
|
155
|
+
<html>
|
156
|
+
<head>
|
157
|
+
<link rel="stylesheet" type="text/css" href="/styles.css" media="screen" />
|
158
|
+
</head>
|
159
|
+
<body>
|
160
|
+
<%= yield %>
|
161
|
+
</body>
|
162
|
+
</html>
|
163
|
+
|
164
|
+
@@ register
|
165
|
+
<h1>Register</h1>
|
166
|
+
<form action="/register" method="post">
|
167
|
+
<%= Formation::Printer.new(@form).print %>
|
168
|
+
<input type="submit" />
|
169
|
+
</form>
|
170
|
+
|
171
|
+
@@ thanks
|
172
|
+
<h1>Thanks!</h1>
|
173
|
+
|
174
|
+
@@ new_post
|
175
|
+
<h1>New Post</h1>
|
176
|
+
<form action="/posts" method="post">
|
177
|
+
<%= Formation::Printer.new(@form).print %>
|
178
|
+
<input type="submit" />
|
179
|
+
</form>
|
data/formation.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "formation/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "formation"
|
7
|
+
s.version = Formation::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ['Chris Gibson']
|
10
|
+
s.email = ['chrislgibson@gmail.com']
|
11
|
+
s.homepage = "http://github.com/chrisgibson/formation"
|
12
|
+
s.summary = %q{first-class ruby forms}
|
13
|
+
s.description = %q{first-class ruby forms}
|
14
|
+
|
15
|
+
# s.rubyforge_project = "formation"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
data/lib/formation.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Formation
|
2
|
+
|
3
|
+
module Support
|
4
|
+
|
5
|
+
end
|
6
|
+
|
7
|
+
module Types
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
require File.expand_path('../formation/util', __FILE__)
|
14
|
+
require File.expand_path('../formation/element', __FILE__)
|
15
|
+
require File.expand_path('../formation/field', __FILE__)
|
16
|
+
require File.expand_path('../formation/fieldset', __FILE__)
|
17
|
+
require File.expand_path('../formation/form', __FILE__)
|
18
|
+
require File.expand_path('../formation/type', __FILE__)
|
19
|
+
require File.expand_path('../formation/types/checkbox', __FILE__)
|
20
|
+
require File.expand_path('../formation/types/password', __FILE__)
|
21
|
+
require File.expand_path('../formation/types/text', __FILE__)
|
22
|
+
require File.expand_path('../formation/types/text_area', __FILE__)
|
23
|
+
|
24
|
+
require File.expand_path('../formation/printer', __FILE__)
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Formation::Field < Formation::Element
|
2
|
+
|
3
|
+
attr_reader :label, :fieldset, :type
|
4
|
+
attr_accessor :value
|
5
|
+
|
6
|
+
def initialize(name, options = {})
|
7
|
+
@name = name
|
8
|
+
if @fieldset = options.delete(:fieldset)
|
9
|
+
@fieldset.fields << self
|
10
|
+
end
|
11
|
+
@type = options.delete(:type) || :text
|
12
|
+
@type = Formation::Type.create(self, @type)
|
13
|
+
@label = options.delete(:label) || Formation::Util.titleize(@name)
|
14
|
+
@required = options.delete(:required) || false
|
15
|
+
|
16
|
+
# Attach any left-over entries as accessors
|
17
|
+
options.each do |key, value|
|
18
|
+
metaclass = class << self; self; end
|
19
|
+
metaclass.send :attr_accessor, key.to_sym
|
20
|
+
send "#{key}=", value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def field?
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def required?
|
29
|
+
@required
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module Formation::Form
|
2
|
+
|
3
|
+
module ClassMethods
|
4
|
+
|
5
|
+
def elements
|
6
|
+
@elements ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
def field(name, options = {})
|
10
|
+
@_current_resource ||= nil
|
11
|
+
@_current_fieldset ||= nil
|
12
|
+
if @_current_resource
|
13
|
+
name = "#{@_current_resource}[#{name}]"
|
14
|
+
else
|
15
|
+
name = name.to_s
|
16
|
+
end
|
17
|
+
fields[name] = Formation::Field.new(name, { :fieldset => @_current_fieldset }.merge(options))
|
18
|
+
elements << fields[name] unless @_current_fieldset
|
19
|
+
end
|
20
|
+
|
21
|
+
def fields
|
22
|
+
@fields ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def fieldset(name, options = {})
|
26
|
+
if name.is_a? Hash
|
27
|
+
options = name
|
28
|
+
name = Formation::Util.underscore(options[:legend])
|
29
|
+
end
|
30
|
+
@_current_fieldset = (fieldsets[name] ||= Formation::Fieldset.new(name, options))
|
31
|
+
elements << @_current_fieldset
|
32
|
+
yield
|
33
|
+
@_current_fieldset = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def fieldsets
|
37
|
+
@fieldsets ||= {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def resource(resource)
|
41
|
+
resources << resource
|
42
|
+
class_eval <<-RUBY
|
43
|
+
attr_accessor :#{resource}
|
44
|
+
RUBY
|
45
|
+
if block_given?
|
46
|
+
@_current_resource = resource
|
47
|
+
yield
|
48
|
+
@_current_resource = nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def resources
|
53
|
+
@resources ||= []
|
54
|
+
end
|
55
|
+
|
56
|
+
def validates_with_method(method_name)
|
57
|
+
method_name = method_name.to_sym
|
58
|
+
validators << method_name
|
59
|
+
end
|
60
|
+
|
61
|
+
def validators
|
62
|
+
@validators ||= []
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.included(base)
|
68
|
+
base.extend(ClassMethods)
|
69
|
+
end
|
70
|
+
|
71
|
+
def elements
|
72
|
+
self.class.elements
|
73
|
+
end
|
74
|
+
|
75
|
+
def errors
|
76
|
+
@errors ||= []
|
77
|
+
end
|
78
|
+
|
79
|
+
def fields
|
80
|
+
fields = elements.map do |element|
|
81
|
+
element.field? ? element : element.fields
|
82
|
+
end
|
83
|
+
fields.flatten
|
84
|
+
end
|
85
|
+
|
86
|
+
def submit(params)
|
87
|
+
@submitted = true
|
88
|
+
errors.clear
|
89
|
+
fields.each do |field|
|
90
|
+
field.value = extract_value_from_params(field.name, params)
|
91
|
+
if field.required? && (field.value.nil? || field.value.empty?)
|
92
|
+
errors << "#{field.label} is required"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
validators.each do |validator|
|
96
|
+
send "#{validator}"
|
97
|
+
end
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
def valid?
|
102
|
+
!errors.any?
|
103
|
+
end
|
104
|
+
|
105
|
+
def validators
|
106
|
+
self.class.validators
|
107
|
+
end
|
108
|
+
|
109
|
+
def value_for(name)
|
110
|
+
@values[name.to_s]
|
111
|
+
end
|
112
|
+
|
113
|
+
def values
|
114
|
+
@values ||= {}
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def extract_value_from_params(name, params)
|
120
|
+
key = %r{([^\[\]=&]+)}.match(name).captures.first
|
121
|
+
value = if self.class.resources.include? key.to_sym
|
122
|
+
captures = %r{\[([^\[\]=&]+)\]}.match(name).captures
|
123
|
+
return nil unless captures.any?
|
124
|
+
resource = params.fetch(key, nil)
|
125
|
+
return nil unless resource
|
126
|
+
actual_value = resource.fetch(captures.first, nil)
|
127
|
+
return nil unless actual_value
|
128
|
+
send(key).send("#{captures.first}=", actual_value)
|
129
|
+
else
|
130
|
+
if params[key]
|
131
|
+
branch = params[key].dup
|
132
|
+
match = %r{\[([^\[\]=&]+)\]}.match(name)
|
133
|
+
if match
|
134
|
+
match.captures.each do |capture|
|
135
|
+
branch = branch[capture]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
branch
|
139
|
+
else
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
end
|
143
|
+
values[name] = value
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class Formation::Printer
|
2
|
+
|
3
|
+
attr_reader :form
|
4
|
+
|
5
|
+
def initialize(form)
|
6
|
+
@form = form
|
7
|
+
end
|
8
|
+
|
9
|
+
def print
|
10
|
+
html = ''
|
11
|
+
html << print_errors(form.errors)
|
12
|
+
form.elements.each do |element|
|
13
|
+
if element.field?
|
14
|
+
html << print_field(element)
|
15
|
+
else
|
16
|
+
html << print_fieldset(element)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
html
|
20
|
+
end
|
21
|
+
|
22
|
+
def print_error(error)
|
23
|
+
<<-HTML
|
24
|
+
<p style="color: red">#{error}</p>
|
25
|
+
HTML
|
26
|
+
end
|
27
|
+
|
28
|
+
def print_errors(errors)
|
29
|
+
errors.map { |error| print_error error }.join("\n")
|
30
|
+
end
|
31
|
+
|
32
|
+
def print_field(field)
|
33
|
+
<<-HTML
|
34
|
+
<li>
|
35
|
+
<label>#{field.label}</label>
|
36
|
+
#{field.type.to_html}
|
37
|
+
</li>
|
38
|
+
HTML
|
39
|
+
end
|
40
|
+
|
41
|
+
def print_fieldset(fieldset)
|
42
|
+
<<-HTML
|
43
|
+
<fieldset>
|
44
|
+
<legend>#{fieldset.legend}</legend>
|
45
|
+
<ol>
|
46
|
+
#{fieldset.fields.map { |f| print_field f }.join("\n")}
|
47
|
+
</ol>
|
48
|
+
</fieldset>
|
49
|
+
HTML
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Formation::Type
|
2
|
+
|
3
|
+
def self.create(field, type)
|
4
|
+
klass = case type.to_sym
|
5
|
+
when :checkbox
|
6
|
+
Formation::Types::Checkbox
|
7
|
+
when :password
|
8
|
+
Formation::Types::Password
|
9
|
+
when :text
|
10
|
+
Formation::Types::Text
|
11
|
+
when :textarea
|
12
|
+
Formation::Types::TextArea
|
13
|
+
else
|
14
|
+
raise "Invalid or unsupported field type: #{type}"
|
15
|
+
end
|
16
|
+
klass.new(field)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Formation::Types::Checkbox
|
2
|
+
|
3
|
+
attr_reader :field
|
4
|
+
|
5
|
+
def initialize(field)
|
6
|
+
@field = field
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_html
|
10
|
+
<<-HTML.strip
|
11
|
+
<input type="checkbox" name="#{field.name}" value="1" checked="#{field.value ? 'checked' : ''}" />
|
12
|
+
HTML
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
Formation
|
2
|
+
=========
|
3
|
+
|
4
|
+
`formation` is a ruby library for building, validating, and extending forms.
|
5
|
+
|
6
|
+
Simple:
|
7
|
+
|
8
|
+
class RegistrationForm
|
9
|
+
|
10
|
+
include Formation::Form
|
11
|
+
|
12
|
+
fieldset :account do
|
13
|
+
field :login_name, :type => :text, :required => true
|
14
|
+
field :password, :type => :text, :required => true
|
15
|
+
end
|
16
|
+
|
17
|
+
fieldset :address do
|
18
|
+
field :address, :type => :text
|
19
|
+
field :city, :type => :text
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
Model:
|
25
|
+
|
26
|
+
class Post
|
27
|
+
|
28
|
+
include DataMapper::Resource
|
29
|
+
|
30
|
+
property :id, Serial
|
31
|
+
property :title, String
|
32
|
+
property :body, Text
|
33
|
+
property :created_at, DateTime
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
class PostForm
|
38
|
+
|
39
|
+
include Formation::Form
|
40
|
+
|
41
|
+
resource :post do
|
42
|
+
fieldset :legend => 'New Post' do
|
43
|
+
field :title, :type => :text, :required => true
|
44
|
+
field :body, :type => :textarea, :required => true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(post)
|
49
|
+
@post = post
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
### Validating
|
55
|
+
...
|
56
|
+
|
57
|
+
### Printing
|
58
|
+
...
|
59
|
+
|
60
|
+
### Extending
|
61
|
+
...
|
data/test/field_spec.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Formation::Field do
|
4
|
+
|
5
|
+
describe '.label' do
|
6
|
+
|
7
|
+
it 'should use the given label, if specified' do
|
8
|
+
Formation::Field.new('first_name', :label => 'Name').label.must_equal 'Name'
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should use the titleized name, if not specified' do
|
12
|
+
Formation::Field.new('first_name').label.must_equal 'First Name'
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/test/form_spec.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Formation::Form do
|
4
|
+
|
5
|
+
describe '#fields' do
|
6
|
+
|
7
|
+
it 'should have the correct name' do
|
8
|
+
SimpleForm.fields['first_name'].name.must_equal 'first_name'
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should have the label of the fieldset it belongs to (if any)' do
|
12
|
+
SimpleForm.fields['address'].fieldset.legend.must_equal 'Address'
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should pass-through custom attributes' do
|
16
|
+
SimpleForm.fields['username'].custom.must_equal 'test'
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#fieldsets' do
|
22
|
+
|
23
|
+
it 'should contain the fields belonging to it' do
|
24
|
+
SimpleForm.fieldsets['Address'].fields.map(&:name).must_equal %w{ address city zip_code }
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'an instance' do
|
30
|
+
|
31
|
+
before do
|
32
|
+
@simple_form = SimpleForm.new(:first_name => 'Chris')
|
33
|
+
@post = OpenStruct.new(:title => 'Test')
|
34
|
+
@model_form = PostForm.new(@post)
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '.elements' do
|
38
|
+
|
39
|
+
it 'should return the elements in the correct order' do
|
40
|
+
@simple_form.elements.map(&:name).must_equal %w{ first_name Address login_details }
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '.submit' do
|
46
|
+
|
47
|
+
it 'should accept empty params' do
|
48
|
+
@simple_form.submit({})
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should extract values of fields' do
|
52
|
+
@simple_form.submit({'first_name' => 'Christopher' })
|
53
|
+
@simple_form.values['first_name'].must_equal 'Christopher'
|
54
|
+
|
55
|
+
@model_form.submit({'post' => { 'title' => 'Testing' }, 'author' => { 'name' => 'Chris'}})
|
56
|
+
@model_form.post.title.must_equal 'Testing'
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should be valid if all required values were provided' do
|
60
|
+
@simple_form.submit({'first_name' => 'Christopher', 'zip_code' => '12345' })
|
61
|
+
@simple_form.valid?.must_equal true
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should not be valid if all required values were not provided' do
|
65
|
+
@simple_form.submit({})
|
66
|
+
@simple_form.valid?.must_equal false
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should have an error for each missing required value' do
|
70
|
+
@simple_form.submit({})
|
71
|
+
@simple_form.errors.must_equal ['First Name is required', 'Zip Code is invalid']
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '.values' do
|
77
|
+
|
78
|
+
it 'should extract values from resource' do
|
79
|
+
@simple_form.values['first_name'].must_equal 'Chris'
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class SimpleForm
|
2
|
+
include Formation::Form
|
3
|
+
|
4
|
+
field 'first_name', :required => true
|
5
|
+
|
6
|
+
fieldset 'Address' do
|
7
|
+
field 'address', :type => :text
|
8
|
+
field 'city', :type => :text
|
9
|
+
field 'zip_code', :type => :text
|
10
|
+
end
|
11
|
+
|
12
|
+
fieldset :legend => 'Login Details' do
|
13
|
+
field 'username', :custom => 'test'
|
14
|
+
end
|
15
|
+
|
16
|
+
validates_with_method :validate_zip_code
|
17
|
+
|
18
|
+
def initialize(options = {})
|
19
|
+
options.each do |key, value|
|
20
|
+
values[key.to_s] = value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate_zip_code
|
25
|
+
if value_for(:zip_code) =~ /^\d{5}$/
|
26
|
+
true
|
27
|
+
else
|
28
|
+
errors << 'Zip Code is invalid'
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'minitest/pride'
|
5
|
+
require 'ostruct'
|
6
|
+
|
7
|
+
require File.expand_path('../../lib/formation', __FILE__)
|
8
|
+
require File.expand_path('../forms/simple_form', __FILE__)
|
9
|
+
require File.expand_path('../forms/post_form', __FILE__)
|
data/test/type_spec.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Formation::Type do
|
4
|
+
|
5
|
+
describe '#create' do
|
6
|
+
|
7
|
+
it 'should raise an exception if the type is unsupported' do
|
8
|
+
lambda {
|
9
|
+
Formation::Type.create(nil, :foo)
|
10
|
+
}.must_raise RuntimeError
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Formation::Types::Checkbox do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@field = Formation::Field.new('remember_me', :type => :checkbox)
|
7
|
+
@type = @field.type
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '.to_html' do
|
11
|
+
|
12
|
+
describe 'with no value' do
|
13
|
+
|
14
|
+
before do
|
15
|
+
@field.value = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should render correctly' do
|
19
|
+
@type.to_html.must_equal <<-HTML.strip
|
20
|
+
<input type="checkbox" name="remember_me" value="1" checked="" />
|
21
|
+
HTML
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'with a false value' do
|
27
|
+
|
28
|
+
before do
|
29
|
+
@field.value = false
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should render correctly' do
|
33
|
+
@type.to_html.must_equal <<-HTML.strip
|
34
|
+
<input type="checkbox" name="remember_me" value="1" checked="" />
|
35
|
+
HTML
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'with a true value' do
|
41
|
+
|
42
|
+
before do
|
43
|
+
@field.value = true
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should render correctly' do
|
47
|
+
@type.to_html.must_equal <<-HTML.strip
|
48
|
+
<input type="checkbox" name="remember_me" value="1" checked="checked" />
|
49
|
+
HTML
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Formation::Types::Password do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@field = Formation::Field.new('password', :type => :password)
|
7
|
+
@type = @field.type
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '.to_html' do
|
11
|
+
|
12
|
+
describe 'with no value' do
|
13
|
+
|
14
|
+
before do
|
15
|
+
@field.value = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should render correctly' do
|
19
|
+
@type.to_html.must_equal <<-HTML.strip
|
20
|
+
<input type="password" name="password" />
|
21
|
+
HTML
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'with a value' do
|
27
|
+
|
28
|
+
before do
|
29
|
+
@field.value = 'testing'
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should render correctly' do
|
33
|
+
@type.to_html.must_equal <<-HTML.strip
|
34
|
+
<input type="password" name="password" />
|
35
|
+
HTML
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Formation::Types::TextArea do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@field = Formation::Field.new('body', :type => :textarea)
|
7
|
+
@type = @field.type
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '.to_html' do
|
11
|
+
|
12
|
+
describe 'with no value' do
|
13
|
+
|
14
|
+
before do
|
15
|
+
@field.value = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should render correctly' do
|
19
|
+
@type.to_html.must_equal <<-HTML.strip
|
20
|
+
<textarea name="body"></textarea>
|
21
|
+
HTML
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'with a value' do
|
27
|
+
|
28
|
+
before do
|
29
|
+
@field.value = 'Blah, blah, blah'
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should render correctly' do
|
33
|
+
@type.to_html.must_equal <<-HTML.strip
|
34
|
+
<textarea name="body">Blah, blah, blah</textarea>
|
35
|
+
HTML
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Formation::Types::Text do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@field = Formation::Field.new('username', :type => :text)
|
7
|
+
@type = @field.type
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '.to_html' do
|
11
|
+
|
12
|
+
describe 'with no value' do
|
13
|
+
|
14
|
+
before do
|
15
|
+
@field.value = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should render correctly' do
|
19
|
+
@type.to_html.must_equal <<-HTML.strip
|
20
|
+
<input type="text" name="username" value="" />
|
21
|
+
HTML
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'with a value' do
|
27
|
+
|
28
|
+
before do
|
29
|
+
@field.value = 'chris'
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should render correctly' do
|
33
|
+
@type.to_html.must_equal <<-HTML.strip
|
34
|
+
<input type="text" name="username" value="chris" />
|
35
|
+
HTML
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/test/util_spec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Formation::Util do
|
4
|
+
|
5
|
+
describe '.titleize' do
|
6
|
+
|
7
|
+
it 'should handle underscores and case' do
|
8
|
+
Formation::Util.titleize('first_name').must_equal 'First Name'
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.underscore' do
|
14
|
+
|
15
|
+
it 'should handle spaces and case' do
|
16
|
+
Formation::Util.underscore('First Name').must_equal 'first_name'
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: formation
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Chris Gibson
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-03-23 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: first-class ruby forms
|
23
|
+
email:
|
24
|
+
- chrislgibson@gmail.com
|
25
|
+
executables: []
|
26
|
+
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files: []
|
30
|
+
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- Gemfile
|
34
|
+
- Rakefile
|
35
|
+
- demo.rb
|
36
|
+
- formation.gemspec
|
37
|
+
- lib/formation.rb
|
38
|
+
- lib/formation/element.rb
|
39
|
+
- lib/formation/field.rb
|
40
|
+
- lib/formation/fieldset.rb
|
41
|
+
- lib/formation/form.rb
|
42
|
+
- lib/formation/printer.rb
|
43
|
+
- lib/formation/type.rb
|
44
|
+
- lib/formation/types/checkbox.rb
|
45
|
+
- lib/formation/types/password.rb
|
46
|
+
- lib/formation/types/text.rb
|
47
|
+
- lib/formation/types/text_area.rb
|
48
|
+
- lib/formation/util.rb
|
49
|
+
- lib/formation/version.rb
|
50
|
+
- readme.md
|
51
|
+
- test/field_spec.rb
|
52
|
+
- test/form_spec.rb
|
53
|
+
- test/forms/post_form.rb
|
54
|
+
- test/forms/simple_form.rb
|
55
|
+
- test/test_helper.rb
|
56
|
+
- test/type_spec.rb
|
57
|
+
- test/types/checkbox_spec.rb
|
58
|
+
- test/types/password_spec.rb
|
59
|
+
- test/types/text_area_spec.rb
|
60
|
+
- test/types/text_spec.rb
|
61
|
+
- test/util_spec.rb
|
62
|
+
has_rdoc: true
|
63
|
+
homepage: http://github.com/chrisgibson/formation
|
64
|
+
licenses: []
|
65
|
+
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
requirements: []
|
90
|
+
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.5.3
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: first-class ruby forms
|
96
|
+
test_files:
|
97
|
+
- test/field_spec.rb
|
98
|
+
- test/form_spec.rb
|
99
|
+
- test/forms/post_form.rb
|
100
|
+
- test/forms/simple_form.rb
|
101
|
+
- test/test_helper.rb
|
102
|
+
- test/type_spec.rb
|
103
|
+
- test/types/checkbox_spec.rb
|
104
|
+
- test/types/password_spec.rb
|
105
|
+
- test/types/text_area_spec.rb
|
106
|
+
- test/types/text_spec.rb
|
107
|
+
- test/util_spec.rb
|