visionmedia-dm-forms 0.0.2

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/History.rdoc ADDED
@@ -0,0 +1,4 @@
1
+
2
+ === 0.0.1 / 2009-01-13
3
+
4
+ * Initial release
data/Manifest ADDED
@@ -0,0 +1,27 @@
1
+ History.rdoc
2
+ Manifest
3
+ README.rdoc
4
+ Rakefile
5
+ Todo.rdoc
6
+ examples/benchmarks.rb
7
+ examples/datamapper.rb
8
+ examples/elements.rb
9
+ examples/haml.rb
10
+ examples/login.haml
11
+ lib/dm-forms.rb
12
+ lib/dm-forms/core_ext.rb
13
+ lib/dm-forms/elements.rb
14
+ lib/dm-forms/model_elements.rb
15
+ lib/dm-forms/tag.rb
16
+ lib/dm-forms/version.rb
17
+ spec/functional/core_ext_spec.rb
18
+ spec/functional/elements_spec.rb
19
+ spec/functional/tag_spec.rb
20
+ spec/integration/datamapper_spec.rb
21
+ spec/integration/haml_spec.rb
22
+ spec/spec_helper.rb
23
+ tasks/benchmarks.rake
24
+ tasks/docs.rake
25
+ tasks/gemspec.rake
26
+ tasks/spec.rake
27
+
data/README.rdoc ADDED
@@ -0,0 +1,62 @@
1
+
2
+ = DataMapper Forms
3
+
4
+ DataMapper model form generation.
5
+
6
+ == Features:
7
+
8
+ * Integrates with DataMapper
9
+ * Fast; over 1000 elements in 0.1 seconds (rake benchmark)
10
+ * Error reporting
11
+ * Handles restful HTTP verbs
12
+ * Provides low-level form elements uncoupled from DataMapper
13
+
14
+ == Examples:
15
+
16
+ Pretend in a magical world we have a User model, and only the email is valid while we are updating.
17
+ Based on the situation above, the form would render similar to the markup beneath the
18
+ example.
19
+
20
+ errors_for @user
21
+ form_for @user, :action => '/user' do |f|
22
+ f.textfield :name, :label => 'Name'
23
+ f.textfield :email, :label => 'Email'
24
+ f.submit :op, :value => 'Update'
25
+ end
26
+
27
+ <ul class="messages error">
28
+ <li>Name has an invalid format</li>
29
+ </ul>
30
+ <form action="/user" method="post" id="form-user">
31
+ <input type="hidden" name="_method" value="put" />
32
+ <label for="name">Name:</label>
33
+ <input type="textfield" class="error form-textfield form-name" name="name" />
34
+ <label for="email">Email:</label>
35
+ <input type="textfield" class="form-textfield form-email" name="email" />
36
+ <input type="submit" class="form-submit form-op" value="Update" name="op" />
37
+ </form>
38
+
39
+ == License:
40
+
41
+ (The MIT License)
42
+
43
+ Copyright (c) 2008 TJ Holowaychuk
44
+
45
+ Permission is hereby granted, free of charge, to any person obtaining
46
+ a copy of this software and associated documentation files (the
47
+ 'Software'), to deal in the Software without restriction, including
48
+ without limitation the rights to use, copy, modify, merge, publish,
49
+ distribute, sublicense, an d/or sell copies of the Software, and to
50
+ permit persons to whom the Software is furnished to do so, subject to
51
+ the following conditions:
52
+
53
+ The above copyright notice and this permission notice shall be
54
+ included in all copies or substantial portions of the Software.
55
+
56
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
57
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
58
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
59
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
60
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
61
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
62
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+
2
+ require 'rubygems'
3
+ require 'rake'
4
+ require 'echoe'
5
+ require './lib/dm-forms.rb'
6
+
7
+ Echoe.new("dm-forms", DataMapper::Form::VERSION::STRING) do |p|
8
+ p.author = "TJ Holowaychuk"
9
+ p.email = "tj@vision-media.ca"
10
+ p.summary = "DataMapper model form generation"
11
+ p.url = "http://github.com/visionmedia/dm-forms"
12
+ p.runtime_dependencies = ['dm-core']
13
+ p.development_dependencies = ['rspec_hpricot_matchers']
14
+ end
15
+
16
+ Dir['tasks/**/*.rake'].sort.each { |lib| load lib }
data/Todo.rdoc ADDED
@@ -0,0 +1,27 @@
1
+
2
+ == Major:
3
+
4
+ * match messages markup to jquery ui if reasonable
5
+ * <optgroup label
6
+ * assign :required based on model?
7
+ * clean up examples
8
+ * use have_tag matcher
9
+ * add enctype automatically
10
+ * Fix select option ordering ... poor unordered hashes :(
11
+ * Add nesting to select options, and select groups
12
+ * escape xml in attrs
13
+ * Support both instance_eval and block
14
+ * wrap all in div class="form-TYPE" and adjust spec...
15
+ * XHTML ... validate
16
+ * perform some indenting (or all but make it optional)
17
+ * increase performance
18
+ * preview rdoc / finish documenting ...
19
+
20
+ == Minor:
21
+
22
+ * Aggregate / faux elements ...
23
+ * date_field :bday, :format => 'YYYY-MM-DD', :value => '1987-05-25'
24
+
25
+ == Brainstorming:
26
+
27
+ * Nothing
data/dm-forms.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{dm-forms}
5
+ s.version = "0.0.2"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["TJ Holowaychuk"]
9
+ s.date = %q{2009-01-15}
10
+ s.description = %q{DataMapper model form generation}
11
+ s.email = %q{tj@vision-media.ca}
12
+ s.extra_rdoc_files = ["README.rdoc", "lib/dm-forms.rb", "lib/dm-forms/core_ext.rb", "lib/dm-forms/elements.rb", "lib/dm-forms/model_elements.rb", "lib/dm-forms/tag.rb", "lib/dm-forms/version.rb", "tasks/benchmarks.rake", "tasks/docs.rake", "tasks/gemspec.rake", "tasks/spec.rake"]
13
+ s.files = ["History.rdoc", "Manifest", "README.rdoc", "Rakefile", "Todo.rdoc", "examples/benchmarks.rb", "examples/datamapper.rb", "examples/elements.rb", "examples/haml.rb", "examples/login.haml", "lib/dm-forms.rb", "lib/dm-forms/core_ext.rb", "lib/dm-forms/elements.rb", "lib/dm-forms/model_elements.rb", "lib/dm-forms/tag.rb", "lib/dm-forms/version.rb", "spec/functional/core_ext_spec.rb", "spec/functional/elements_spec.rb", "spec/functional/tag_spec.rb", "spec/integration/datamapper_spec.rb", "spec/integration/haml_spec.rb", "spec/spec_helper.rb", "tasks/benchmarks.rake", "tasks/docs.rake", "tasks/gemspec.rake", "tasks/spec.rake", "dm-forms.gemspec"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://github.com/visionmedia/dm-forms}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Dm-forms", "--main", "README.rdoc"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{dm-forms}
19
+ s.rubygems_version = %q{1.3.1}
20
+ s.summary = %q{DataMapper model form generation}
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 2
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ s.add_runtime_dependency(%q<dm-core>, [">= 0"])
28
+ s.add_development_dependency(%q<rspec_hpricot_matchers>, [">= 0"])
29
+ else
30
+ s.add_dependency(%q<dm-core>, [">= 0"])
31
+ s.add_dependency(%q<rspec_hpricot_matchers>, [">= 0"])
32
+ end
33
+ else
34
+ s.add_dependency(%q<dm-core>, [">= 0"])
35
+ s.add_dependency(%q<rspec_hpricot_matchers>, [">= 0"])
36
+ end
37
+ end
@@ -0,0 +1,118 @@
1
+
2
+ $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+ require 'dm-forms'
4
+ require 'benchmark'
5
+ require 'dm-core'
6
+ require 'dm-validations'
7
+ DataMapper.setup :default, 'sqlite3::memory:'
8
+ include DataMapper::Form::ModelElements
9
+
10
+ #--
11
+ # Models
12
+ #++
13
+
14
+ class User
15
+ include DataMapper::Resource
16
+ property :id, Serial
17
+ property :name, String, :format => /^[\w]+$/
18
+ property :email, String, :format => :email_address
19
+ end
20
+
21
+ DataMapper.auto_migrate!
22
+
23
+ $user = User.new :name => 'tj', :email => 'invalid email@lame.com'
24
+
25
+ #--
26
+ # Benchmarks
27
+ #++
28
+
29
+ puts
30
+ puts 'Single element'
31
+ Benchmark.bm(25) do |x|
32
+ x.report("10 elements") {
33
+ 10.times do
34
+ textarea :comments, :value => 'Enter your comments here', :label => 'Comments:', :required => true
35
+ end
36
+ }
37
+ x.report("100 elements") {
38
+ 100.times do
39
+ textarea :comments, :value => 'Enter your comments here', :label => 'Comments:', :required => true
40
+ end
41
+ }
42
+ x.report("1000 elements") {
43
+ 1000.times do
44
+ textarea :comments, :value => 'Enter your comments here', :label => 'Comments:', :required => true
45
+ end
46
+ }
47
+ end
48
+
49
+ puts
50
+ puts 'Capture elements within a fieldset'
51
+ Benchmark.bm(25) do |x|
52
+ x.report("10 elements") {
53
+ 5.times do
54
+ fieldset :comments do |f|
55
+ f.textarea :comments, :value => 'Enter your comments here', :label => 'Comments:', :required => true
56
+ end
57
+ end
58
+ }
59
+ x.report("100 elements") {
60
+ 50.times do
61
+ fieldset :comments do |f|
62
+ f.textarea :comments, :value => 'Enter your comments here', :label => 'Comments:', :required => true
63
+ end
64
+ end
65
+ }
66
+ x.report("1000 elements") {
67
+ 500.times do
68
+ fieldset :comments do |f|
69
+ f.textarea :comments, :value => 'Enter your comments here', :label => 'Comments:', :required => true
70
+ end
71
+ end
72
+ }
73
+ end
74
+
75
+ # Not real-world examples ... just for benchmarking purposes
76
+ puts
77
+ puts 'Entire forms'
78
+ Benchmark.bm(25) do |x|
79
+ x.report("Login") {
80
+ form :login do |f|
81
+ f.textfield :name, :label => 'Username', :required => true
82
+ f.textfield :email, :label => 'Email', :required => true
83
+ f.textfield :pass, :label => 'Password', :required => true
84
+ f.submit :op, :value => 'Login'
85
+ end
86
+ }
87
+
88
+ x.report("Register") {
89
+ form :register do |f|
90
+ f.fieldset :general do |f|
91
+ f.textfield :name, :label => 'Username', :required => true
92
+ f.textfield :email, :label => 'Email', :required => true
93
+ f.textfield :pass, :label => 'Password', :required => true
94
+ f.password :pass_confirm
95
+ end
96
+ f.fieldset :details do |f|
97
+ f.textfield :city, :label => 'City'
98
+ f.textfield :zip, :label => 'Postal Code'
99
+ end
100
+ f.fieldset :forums do |f|
101
+ f.textarea :signature, :label => 'Signature', :description => 'Enter a signature which will appear below your forum posts.'
102
+ end
103
+ f.submit :op, :value => 'Register'
104
+ end
105
+ }
106
+ end
107
+
108
+ puts
109
+ puts 'Entires form with #form_for'
110
+ Benchmark.bm(25) do |x|
111
+ x.report("User") {
112
+ form_for $user do |f|
113
+ f.textfield :name, :label => 'Username', :required => true
114
+ f.textfield :email, :label => 'Email', :required => true
115
+ f.submit :op, :value => 'Login'
116
+ end
117
+ }
118
+ end
@@ -0,0 +1,37 @@
1
+
2
+ $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+ require 'dm-forms'
4
+ require 'dm-core'
5
+ require 'dm-validations'
6
+
7
+ include DataMapper::Form::ModelElements
8
+ DataMapper.setup :default, 'sqlite3::memory:'
9
+
10
+ class User
11
+ include DataMapper::Resource
12
+ property :id, Serial
13
+ property :name, String, :format => /^[\w]+$/
14
+ property :email, String, :format => :email_address
15
+ end
16
+
17
+ DataMapper.auto_migrate!
18
+ user = User.new :name => 'tj', :email => 'invalid email@lame.com'
19
+
20
+ s = errors_for(user)
21
+ s << form_for(user, :action => '/user') do |f|
22
+ f.textarea :name
23
+ f.textarea :email
24
+ f.submit :op, :value => 'Add'
25
+ end
26
+ puts s
27
+
28
+ user.email = 'tj@vision-media.ca'
29
+ user.save
30
+
31
+ s = errors_for(user)
32
+ s << form_for(user, :action => '/user') do |f|
33
+ f.textarea :name, :label => 'Name'
34
+ f.textarea :email, :label => 'Email'
35
+ f.submit :op, :value => 'Update'
36
+ end
37
+ puts s
@@ -0,0 +1,31 @@
1
+
2
+ $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+ require 'dm-forms'
4
+
5
+ include DataMapper::Form::Elements
6
+
7
+ def example before, &block
8
+ puts before << "\n\n"
9
+ puts yield.gsub!(/^/, ' ')
10
+ puts
11
+ end
12
+
13
+ example %(textarea :comments) do
14
+ textarea :comments
15
+ end
16
+
17
+ example %(textarea :comments, :label => 'Comments', :description => 'Tell us what you think') do
18
+ textarea :comments, :label => 'Comments', :description => 'Tell us what you think'
19
+ end
20
+
21
+ example %(textfield :email, :label => 'Email', :required => true) do
22
+ textfield :email, :label => 'Email', :required => true
23
+ end
24
+
25
+ example 'Login' do
26
+ form :login do |f|
27
+ f.textfield :name, :label => 'Username'
28
+ f.textfield :pass, :label => 'Password'
29
+ f.submit :op, :value => 'Login'
30
+ end
31
+ end
data/examples/haml.rb ADDED
@@ -0,0 +1,21 @@
1
+
2
+ $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+ require 'rubygems'
4
+ require 'haml'
5
+ require 'dm-forms'
6
+
7
+ include DataMapper::Form::ModelElements
8
+
9
+ # Haml does not play very nice with nested Ruby, but then again
10
+ # there is relatively nothing that cannot be altered via styling
11
+ # so placing forms directly within a Haml view is not entirely
12
+ # necessary.
13
+ def login_form
14
+ form :login, :action => '/user' do |f|
15
+ f.textarea :name, :label => 'Name'
16
+ f.textarea :email, :label => 'Email'
17
+ f.submit :op, :value => 'Login'
18
+ end
19
+ end
20
+
21
+ puts Haml::Engine.new(File.read(File.dirname(__FILE__) + '/login.haml')).render
@@ -0,0 +1,4 @@
1
+ #primary
2
+ .content
3
+ %h1 Login
4
+ = login_form
@@ -0,0 +1,39 @@
1
+
2
+ class NilClass
3
+ def to_xml_attributes #:nodoc:
4
+ ''
5
+ end
6
+ alias :to_html_attributes :to_xml_attributes
7
+ end
8
+
9
+ class String
10
+
11
+ ##
12
+ # Convert to a human readable string.
13
+ #
14
+ # === Examples:
15
+ #
16
+ # 'im_a_simple.String'.humanize # => 'im a simple String'
17
+ #
18
+
19
+ def humanize
20
+ gsub(/[^a-zA-Z\d]/, ' ') || self
21
+ end
22
+
23
+ ##
24
+ # Indent a string with pseudo +tabs+ (spaces). Defaults to a single tab.
25
+
26
+ def indent tabs = 1
27
+ gsub /^/, ' ' * tabs
28
+ end
29
+ end
30
+
31
+ class Symbol
32
+
33
+ ##
34
+ # Convert to a human readable string. See String#humanize
35
+
36
+ def humanize
37
+ to_s.humanize
38
+ end
39
+ end
@@ -0,0 +1,210 @@
1
+
2
+ module DataMapper
3
+ module Form
4
+ module Elements
5
+
6
+ ##
7
+ # Proxy object for capturing elements. See Elements#capture_elements.
8
+
9
+ class Proxy
10
+ def initialize model = nil
11
+ @model = model
12
+ end
13
+
14
+ def method_missing meth, *args, &block
15
+ add_error_class_to args if has_a_model_with_errors_on? args.first
16
+ (@elements ||= []) << Elements.send(meth, *args, &block)
17
+ end
18
+
19
+ def add_error_class_to args
20
+ ((args[1] ||= {})[:class] ||= '') << ' error'
21
+ end
22
+
23
+ def has_a_model_with_errors_on? meth
24
+ @model and !@model.valid? and @model.errors.on meth
25
+ end
26
+ end
27
+
28
+ module_function
29
+
30
+ ##
31
+ # Generates a generic HTML +name+ tag. Although this method is
32
+ # generally used internally by dm-forms, you may utilize it directly
33
+ # passing any of the following +options+.
34
+ #
35
+ # === Options:
36
+ #
37
+ # :self_closing Wither or not the element should self-close (<br />)
38
+ # :attributes Hash of attributes such as :type => :textfield
39
+ #
40
+
41
+ def tag name, options = {}, &block
42
+ Tag.new(name, options, &block).render
43
+ end
44
+
45
+ ##
46
+ # Generates a label.
47
+
48
+ def label value, options = {}
49
+ value << ':'
50
+ value << '<em>*</em>' if options.delete :required
51
+ %(<label for="#{options[:for]}">#{value}</label>\n)
52
+ end
53
+
54
+ ##
55
+ # Generates a legend.
56
+
57
+ def legend value
58
+ %(<legend>#{value}</legend>)
59
+ end
60
+
61
+ ##
62
+ # Generates a description.
63
+
64
+ def desc text
65
+ %(\n<p class="description">#{text}</p>) unless text.blank?
66
+ end
67
+
68
+ ##
69
+ # Generates an option.
70
+
71
+ def option value, title
72
+ %(<option value="#{value}">#{title}</option>\n)
73
+ end
74
+
75
+ ##
76
+ # Generates a form.
77
+
78
+ def form name, options = {}, &block
79
+ options = { :method => :post, :id => "form-#{name}" }.merge options
80
+ unless valid_http_verb? options
81
+ old_value = options[:value] || ''
82
+ options[:value] = hidden_method(options[:method]) << old_value
83
+ options[:method] = :post
84
+ end
85
+ tag :form, :attributes => options, &block
86
+ end
87
+
88
+ ##
89
+ # Generates a fieldset.
90
+
91
+ def fieldset name, options = {}, &block
92
+ legend_value = options.has_key?(:legend) ? options.delete(:legend) : name.humanize.capitalize
93
+ options = { :class => "fieldset-#{name}" }.merge options
94
+ options[:value] = "\n" << legend(legend_value) << (options.delete(:value) || '')
95
+ tag :fieldset, :attributes => options, &block
96
+ end
97
+
98
+ ##
99
+ # Generates a textfield.
100
+
101
+ def textfield name, options = {}
102
+ options = { :type => :textfield, :name => name }.merge options
103
+ tag :input, :self_closing => true, :attributes => options
104
+ end
105
+
106
+ ##
107
+ # Generates a password field.
108
+
109
+ def password name, options = {}
110
+ options = { :type => :password, :name => name }.merge options
111
+ tag :input, :self_closing => true, :attributes => options
112
+ end
113
+
114
+ ##
115
+ # Generates a checkbox.
116
+
117
+ def checkbox name, options = {}
118
+ options = { :type => :checkbox, :name => name }.merge options
119
+ tag :input, :self_closing => true, :attributes => options
120
+ end
121
+
122
+ ##
123
+ # Generates a hidden field.
124
+
125
+ def hidden name, options = {}
126
+ options = { :type => :hidden, :name => name }.merge options
127
+ tag :input, :self_closing => true, :attributes => options
128
+ end
129
+
130
+ ##
131
+ # Creates hidden _method, with value of +method+.
132
+
133
+ def hidden_method method
134
+ hidden :_method, :value => method
135
+ end
136
+
137
+ ##
138
+ # Generates a radio button.
139
+
140
+ def radio name, options = {}
141
+ options = { :type => :radio, :name => name }.merge options
142
+ tag :input, :self_closing => true, :attributes => options
143
+ end
144
+
145
+ ##
146
+ # Generates a file field.
147
+
148
+ def file name, options = {}
149
+ options = { :type => :file, :name => name }.merge options
150
+ tag :input, :self_closing => true, :attributes => options
151
+ end
152
+
153
+ ##
154
+ # Generates a select field.
155
+
156
+ def select name, options = {}, &block
157
+ options = { :name => name, :value => "\n" }.merge options
158
+ options[:value] << capture_elements(&block) if block_given?
159
+ options[:value] << select_options(options) if options.include? :options
160
+ tag :select, :attributes => options
161
+ end
162
+
163
+ ##
164
+ # Generates a textarea.
165
+
166
+ def textarea name, options = {}, &block
167
+ options = { :name => name }.merge options
168
+ tag :textarea, :attributes => options, &block
169
+ end
170
+
171
+ ##
172
+ # Generates a submit button.
173
+
174
+ def submit name, options = {}
175
+ options = { :type => :submit, :name => name }.merge options
176
+ tag :input, :self_closing => true, :attributes => options
177
+ end
178
+
179
+ ##
180
+ # Generates a button.
181
+
182
+ def button name, options = {}
183
+ type = options.has_key?(:src) ? :image : :button
184
+ options = { :type => type, :name => name }.merge options
185
+ tag :input, :self_closing => true, :attributes => options
186
+ end
187
+
188
+ ##
189
+ # Capture results of elements called within +block+. Optionally
190
+ # a DataMapper model may be passed, at which point error classes
191
+ # will be applied to invalid elements.
192
+
193
+ def capture_elements model = nil, &block
194
+ elements = yield Proxy.new(model)
195
+ elements.join
196
+ end
197
+
198
+ private
199
+
200
+ def select_options options #:nodoc:
201
+ options.delete(:options).collect { |value, title| option(value, title) }.join
202
+ end
203
+
204
+ def valid_http_verb? options #:nodoc:
205
+ [:get, :post].include? options[:method]
206
+ end
207
+
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,33 @@
1
+
2
+ module DataMapper
3
+ module Form
4
+ module ModelElements
5
+
6
+ include Elements
7
+
8
+ ##
9
+ # Generates a form.
10
+
11
+ def form_for model, options = {}, &block
12
+ id = model.class.to_s.downcase
13
+ method = model.new_record? ? :post : :put
14
+ options = { :model => model, :method => method }.merge options
15
+ form id, options, &block
16
+ end
17
+
18
+ ##
19
+ # Return markup for errors on +model+.
20
+
21
+ def errors_for model
22
+ if not model.all_valid?
23
+ s = %(<ul class="messages error">\n)
24
+ s << model.errors.collect { |error| "<li>#{error.first}</li>" }.join("\n")
25
+ s << "\n</ul>\n"
26
+ else
27
+ ''
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+ end