forme 1.9.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +70 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +396 -202
  5. data/lib/forme/bs3.rb +19 -5
  6. data/lib/forme/erb.rb +18 -16
  7. data/lib/forme/form.rb +151 -118
  8. data/lib/forme/input.rb +1 -1
  9. data/lib/forme/rails.rb +41 -72
  10. data/lib/forme/raw.rb +2 -2
  11. data/lib/forme/sinatra.rb +6 -2
  12. data/lib/forme/tag.rb +3 -12
  13. data/lib/forme/template.rb +118 -0
  14. data/lib/forme/transformers/error_handler.rb +46 -1
  15. data/lib/forme/transformers/formatter.rb +36 -35
  16. data/lib/forme/transformers/helper.rb +0 -1
  17. data/lib/forme/transformers/inputs_wrapper.rb +6 -6
  18. data/lib/forme/transformers/labeler.rb +19 -0
  19. data/lib/forme/transformers/wrapper.rb +1 -1
  20. data/lib/forme/version.rb +2 -2
  21. data/lib/forme.rb +15 -2
  22. data/lib/roda/plugins/forme.rb +1 -1
  23. data/lib/roda/plugins/forme_erubi_capture.rb +62 -0
  24. data/lib/roda/plugins/forme_route_csrf.rb +16 -20
  25. data/lib/roda/plugins/forme_set.rb +177 -0
  26. data/lib/sequel/plugins/forme.rb +42 -55
  27. data/lib/sequel/plugins/forme_i18n.rb +3 -1
  28. data/lib/sequel/plugins/forme_set.rb +50 -28
  29. data/spec/all.rb +1 -1
  30. data/spec/bs3_reference_spec.rb +18 -18
  31. data/spec/bs3_sequel_plugin_spec.rb +7 -7
  32. data/spec/bs3_spec.rb +23 -11
  33. data/spec/erb_helper.rb +73 -58
  34. data/spec/erubi_capture_helper.rb +202 -0
  35. data/spec/forme_spec.rb +80 -29
  36. data/spec/rails_integration_spec.rb +47 -24
  37. data/spec/roda_integration_spec.rb +459 -48
  38. data/spec/sequel_helper.rb +0 -1
  39. data/spec/sequel_i18n_helper.rb +1 -1
  40. data/spec/sequel_i18n_plugin_spec.rb +3 -2
  41. data/spec/sequel_plugin_spec.rb +25 -8
  42. data/spec/sequel_set_plugin_spec.rb +10 -3
  43. data/spec/shared_erb_specs.rb +75 -0
  44. data/spec/sinatra_integration_spec.rb +5 -6
  45. data/spec/spec_helper.rb +23 -5
  46. metadata +30 -8
  47. data/lib/forme/erb_form.rb +0 -74
data/README.rdoc CHANGED
@@ -6,91 +6,75 @@ Forme is a HTML forms library for ruby with the following goals:
6
6
  2. Have a simple API
7
7
  3. Support forms both with and without related objects
8
8
  4. Allow compiling down to different types of output
9
+ 5. Integrate easily into web frameworks
9
10
 
10
11
  = Introduction
11
12
 
12
13
  Forme is designed to make creating HTML forms easier. Flexibility and
13
- simplicity are primary objectives. The basic usage involves creating
14
- a <tt>Forme::Form</tt> instance, and calling +input+ and +tag+ methods
15
- to return html strings for widgets, but it could also be used for
16
- serializing to other formats, or even as a DSL for a GUI application.
14
+ ease of use are the primary objectives. Here's a basic example,
15
+ showing usage without a related object:
17
16
 
18
- In order to be flexible, Forme stores tags in abstract form until
19
- output is requested. There are two separate abstract <i>forms</i> that Forme
20
- uses. One is <tt>Forme::Input</tt>, and the other is <tt>Forme::Tag</tt>.
21
- <tt>Forme::Input</tt> is a high level abstract form, while <tt>Forme::Tag</tt>
22
- is a low level abstract form.
17
+ Forme.form({:action=>'/foo'}) do |f|
18
+ f.input(:text, :name=>'bar')
19
+ f.tag(:fieldset) do
20
+ f.input(:textarea, :name=>'baz')
21
+ end
22
+ f.button('Update')
23
+ end
23
24
 
24
- The difference between <tt>Forme::Input</tt> and <tt>Forme::Tag</tt>
25
- is that <tt>Forme::Tag</tt> directly represents the underlying html
26
- tag, containing a type, optional attributes, and children, while the
27
- <tt>Forme::Input</tt> is more abstract and attempts to be user friendly.
28
- For example, these both compile by default to the same select tag:
25
+ This results in the following HTML:
29
26
 
30
- f.input(:select, :options=>[['foo', 1]])
31
- # or
32
- f.tag(:select, {}, [f.tag(:option, {:value=>1}, ['foo'])])
33
-
34
- The processing of high level <tt>Forme::Input</tt>s into raw html
35
- data is broken down to the following steps (called transformers):
27
+ <form action="/foo">
28
+ <input name="bar" type="text"/>
29
+ <fieldset>
30
+ <textarea name="baz"></textarea>
31
+ </fieldset>
32
+ <input type="submit" value="Update"/>
33
+ </form>
36
34
 
37
- +Formatter+ :: converts a <tt>Forme::Input</tt> instance into a
38
- <tt>Forme::Tag</tt> instance (or array of them).
39
- +ErrorHandler+ :: If the <tt>Forme::Input</tt> instance has a error,
40
- takes the formatted tag and marks it as having the error.
41
- +Helper+ :: If the <tt>Forme::Input</tt> instance has any help text,
42
- adds the help text in a separate tag.
43
- +Labeler+ :: If the <tt>Forme::Input</tt> instance has a label,
44
- takes the formatted output and labels it.
45
- +Wrapper+ :: Takes the output of the formatter, labeler, and
46
- error_handler transformers, and wraps it in another tag (or just
47
- returns it unmodified).
48
- +Serializer+ :: converts a <tt>Forme::Tag</tt> instance into an
49
- html string.
50
-
51
- Technically, only the +Serializer+ is necessary. The <tt>Forme::Form#input</tt>
52
- and <tt>Forme::Form#tag</tt> methods return +Input+ and +Tag+ objects. These objects
53
- both have +to_s+ defined to call the appropriate +Serializer+ with
54
- themselves. The +Serializer+ calls the appropriate +Formatter+ if
55
- it encounters an +Input+ instance, and attempts to serialize the
56
- output of that (which is usually a +Tag+ instance). It is up to
57
- the +Formatter+ to call the +Labeler+, +ErrorHandler+, +Helper+,
58
- and/or +Wrapper+.
59
-
60
- The <tt>Forme::Form</tt> object takes the 6 transformers as options (:formatter,
61
- :labeler, :error_handler, :helper, :wrapper, :inputs_wrapper, and :serializer), all of which
62
- should be objects responding to +call+ (so you can use +Proc+s) or be symbols
63
- registered with the library using <tt>Forme.register_transformer</tt>:
35
+ Forme also supports forms that are associated with objects, and
36
+ has specific support for Sequel::Model objects to allow easily building
37
+ forms for such objects. The Sequel support handles inputs based on
38
+ database columns, and automatically handles labels and errors:
64
39
 
65
- Forme.register_transformer(:wrapper, :p) do |tag, input|
66
- input.tag(:p, {}, tag)
40
+ Forme.form(Album[1], action: '/foo') do |f|
41
+ f.input :name
42
+ f.input :copies_sold
67
43
  end
68
44
 
69
- Most transformers are called with two arguments, +tag+ and +input+. +tag+
70
- is a <tt>Forme::Tag</tt> instance, and +input+ is a <tt>Forme::Input</tt>
71
- instance. The +Formatter+ and +Serializer+ transformers are the two
72
- exceptions, with +Formatter+ being called with just an +input+, and
73
- +Serializer+ potentionally being called with any object. The +Serializer+
74
- will in general recursively call itself with children of the argument
75
- given.
76
-
77
- There is also an +InputsWrapper+ transformer, that is called by
78
- <tt>Forme::Form#inputs</tt>. It's used to wrap up a group of
79
- related options (in a fieldset by default). It takes +form+
80
- (<tt>Forme::Form</tt> instance) and +input_opts+ (+Hash+) arguments.
81
-
82
- Most of the transformers can be overridden on a per instance basis by
83
- passing the appropriate option to +input+ or +inputs+:
45
+ This results in the following HTML:
84
46
 
85
- f.input(:name, :wrapper=>:p)
47
+ <form action="/foo" method="post">
48
+ <label>Name:
49
+ <input id="album_name" name="album[name]" type="text" value="Rising Force"/>
50
+ </label>
51
+ <label>Copies Sold:
52
+ <input id="album_copies_sold" name="album[copies_sold]" type="number" value="100000"/>
53
+ </label>
54
+ </form>
86
55
 
87
- Existing transformers can be easily extended (ie, to set the class attribute),
88
- by creating your own transformer and then calling the existing transformer.
56
+ In addition to integrating with Sequel, Form also integrates into
57
+ three separate web frameworks, Roda, Rails, and Sinatra, allowing
58
+ use of forms inside templates. This is the most common usage of Forme.
59
+
60
+ One distinct advantage of Forme over other form libraries is the use
61
+ of an abstract syntax tree internally, allowing the same form code to
62
+ compile to different HTML with different options. For example, it
63
+ allows using the exactly same form code to display a form you can modify
64
+ as well as a read-only view, just by passing a single option when
65
+ creating the form. For example, with the first example in this section,
66
+ if you pass the <tt>formatter: :readonly</tt> option, you get the
67
+ following HTML instead:
68
+
69
+ <form action="/foo">
70
+ <span class="readonly-text"></span>
71
+ <fieldset>
72
+ <div class="readonly-textarea"></div>
73
+ </fieldset>
74
+ </form>
89
75
 
90
- Forme.register_transformer(:labeler, :explicit) do |tag, input|
91
- input.opts[:label_attr] ||= { :class => 'label' }
92
- Forme::Labeler::Explicit.new.call(tag, input)
93
- end
76
+ This allows you to reuse the same form code in multiple context,
77
+ which can save considerable development time.
94
78
 
95
79
  = Installation
96
80
 
@@ -101,13 +85,12 @@ by creating your own transformer and then calling the existing transformer.
101
85
  Demo Site :: http://forme-demo.jeremyevans.net
102
86
  RDoc :: http://forme.jeremyevans.net
103
87
  Source :: https://github.com/jeremyevans/forme
104
- IRC :: irc://irc.freenode.net/forme
105
- Google Group :: https://groups.google.com/forum/#!forum/ruby-forme
88
+ Discussion Forum :: https://github.com/jeremyevans/forme/discussions
106
89
  Bug Tracker :: https://github.com/jeremyevans/forme/issues
107
90
 
108
- = Basic Usage
91
+ = Direct Instantiation
109
92
 
110
- Without an object, Forme is a simple form builder:
93
+ While not typically done, you can instantiate Forme::Form objects directly and use them:
111
94
 
112
95
  f = Forme::Form.new
113
96
  f.open(:action=>'/foo', :method=>:post) # '<form action="/foo" method="post">'
@@ -130,47 +113,11 @@ on the object (or using #[] if the object is a hash).
130
113
  f = Forme::Form.new([:foo])
131
114
  f.input(:first) # '<input id="first" name="first" type="text" value="foo"/>'
132
115
 
133
- = DSL
134
-
135
- Forme comes with a DSL:
136
-
137
- Forme.form(:action=>'/foo') do |f|
138
- f.input(:text, :name=>'bar')
139
- f.tag(:fieldset) do
140
- f.input(:textarea, :name=>'baz')
141
- end
142
- f.button('Update')
143
- end
144
- # <form action="/foo">
145
- # <input name="bar" type="text"/>
146
- # <fieldset>
147
- # <textarea name="baz"></textarea>
148
- # </fieldset>
149
- # <input type="submit" value="Update"/>
150
- # </form>
151
-
152
- You can wrap up multiple inputs with the <tt>:inputs</tt> method:
153
-
154
- Forme.form(:action=>'/foo') do |f|
155
- f.inputs([[:text, {:name=>'bar'}], [:textarea, {:name=>'baz'}]])
156
- end
157
- # <form action="/foo">
158
- # <fieldset class="inputs">
159
- # <input name="bar" type="text"/>
160
- # <textarea name="baz"></textarea>
161
- # </fieldset>
162
- # </form>
163
-
164
- You can even do everything in a single method call:
165
-
166
- Forme.form({:action=>'/foo'},
167
- :inputs=>[[:text, {:name=>'bar'}], [:textarea, {:name=>'baz'}]])
168
-
169
116
  = Forme::Form Creation
170
117
 
171
- As shown above, the general way to create Forme::Form instances is via the Forme.form method.
172
- This method takes up to 3 arguments, and yields the Forme::Form object to the block (if given).
173
- Here are the argument styles that you can use for Forme.form.
118
+ +Forme.form+ takes up to 3 arguments, and yields the Forme::Form
119
+ object to the block (if given). Here are the argument styles that you can
120
+ use for +Forme.form+.
174
121
 
175
122
  No args :: Creates a +Form+ object with no options and not associated
176
123
  to an +obj+, and with no attributes in the opening tag.
@@ -190,21 +137,21 @@ Examples:
190
137
  Forme.form
191
138
 
192
139
  # 1 hash argument (attributes)
193
- Forme.form(:action=>'/foo')
140
+ Forme.form(action: '/foo')
194
141
 
195
142
  # 1 non-hash argument (a reference object used when building the form)
196
143
  Forme.form(Album[1])
197
144
 
198
145
  # 2 hash arguments (attributes, and options)
199
- Forme.form({:action=>'/foo'}, :values=>params)
146
+ Forme.form({action: '/foo'}, values: params)
200
147
 
201
148
  # 1 non-hash argument, 1-2 hash arguments (ref obj, attributes, options)
202
- Forme.form(Album[1], :action=>'/foo')
203
- Forme.form(Album[1], {:action=>'/foo'}, :values=>params)
149
+ Forme.form(Album[1], action: '/foo')
150
+ Forme.form(Album[1], {action: '/foo'}, values: params)
204
151
 
205
152
  If you want a Forme::Form instance where the reference object is a Hash, then you need to pass the hash object using the :obj option:
206
153
 
207
- Forme.form({:action=>'/foo'}, :obj=>{:foo=>'bar'})
154
+ Forme.form({action: '/foo'}, obj: {foo: 'bar'})
208
155
 
209
156
  You can also create Forme::Form objects the normal ruby way using Forme::Form#new. The
210
157
  difference between Forme::Form#new and Forme.form is that Forme.form includes the enclosing
@@ -215,13 +162,13 @@ a hash of <form> tag attributes, so it has the following API:
215
162
  Forme::Form.new
216
163
 
217
164
  # 1 hash argument
218
- Forme::Form.new(:values=>params)
165
+ Forme::Form.new(values: params)
219
166
 
220
167
  # 1 non-hash argument
221
168
  Forme::Form.new(Album[1])
222
169
 
223
170
  # 1 non-hash argument, 1-2 hash arguments
224
- Forme::Form.new(Album[1], :values=>params)
171
+ Forme::Form.new(Album[1], values: params)
225
172
 
226
173
  = Forme::Form Methods
227
174
 
@@ -230,9 +177,9 @@ a hash of <form> tag attributes, so it has the following API:
230
177
  If you create a Form via Forme::Forme#new, you can use the form method to create a form tag:
231
178
 
232
179
  f = Forme::Form.new
233
- f.form(:action=>'/foo')
180
+ f.form(action: '/foo')
234
181
 
235
- This is what Forme.form uses internally to create the <form> tag
182
+ This is what Forme.form uses internally to create the +<form>+ tag
236
183
 
237
184
  == input
238
185
 
@@ -285,8 +232,8 @@ Which results in a form similar to the following:
285
232
 
286
233
  == inputs
287
234
 
288
- This wraps multiple inputs in a tag (it uses the inputs_wrapper transformer discussed below,
289
- so it uses a fieldset by default). You can give the inputs to add as an enumerable argument:
235
+ This wraps multiple inputs in a tag (it uses the +:inputs_wrapper+ transformer discussed below,
236
+ so it uses a +fieldset+ by default). You can give the inputs to add as an enumerable argument:
290
237
 
291
238
  f.inputs([:textarea, [:text, :value=>'a']])
292
239
  # <fieldset>
@@ -300,13 +247,13 @@ You can also provide a block:
300
247
  f.input(:text, :value=>'a')
301
248
  end
302
249
 
303
- Any options given are passed to the inputs_wrapper (so you can use options such as :legend
304
- to set a legend for the fieldset), and also to the the with_opts (so you can use options
305
- such as :wrapper to modify the default wrapper transformer for inputs inside the block).
250
+ Any options given are passed to the +inputs_wrapper+ (so you can use options such as +:legend+
251
+ to set a legend for the fieldset), and also to the +with_opts+ method (so you can use options
252
+ such as +:wrapper+ to modify the default wrapper transformer for inputs inside the block).
306
253
  There is also one option specific to the inputs method:
307
254
 
308
255
  :nested_inputs_wrapper :: Sets the default inputs_wrapper to use for calls to inputs inside
309
- the block. The reason for this option is that :inputs_wrapper
256
+ the block. The reason for this option is that +:inputs_wrapper+
310
257
  option affects the current call to inputs, so if you want to
311
258
  use a different inputs_wrapper for nested calls, you need this option.
312
259
 
@@ -324,30 +271,31 @@ It can be called with a string to provide a value for the button:
324
271
 
325
272
  It can be called with a hash to provide options for the submit input:
326
273
 
327
- f.button(:value=>'Search', :class=>'btn')
274
+ f.button(value: 'Search', class: 'btn')
328
275
  # <input class="btn" type="submit" value="Search"/>
329
276
 
330
277
  == with_opts
331
278
 
332
- This requires a block, and modifies the Form's options inside the block,
279
+ This requires a block, and modifies the Forme::Form's options inside the block,
333
280
  restoring the options when the block returns:
334
281
 
335
282
  f.input(:text)
336
283
  # <input type="text"/>
337
- f.with_opts(:wrapper=>:li) do
284
+
285
+ f.with_opts(wrapper: :li) do
338
286
  f.input(:text)
339
287
  end
340
288
  # <li><input type="text"/></li>
341
289
 
342
- This supports most options you can provide to the Form, but not all.
290
+ This supports most options you can provide to Forme::Form, but not all.
343
291
 
344
292
  == with_obj
345
293
 
346
- This uses with_opts to change the Form's object temporarily, but it
294
+ This uses +with_opts+ to change the Forme::Form object temporarily. It
347
295
  yields the object to the block, and also supports appending to the
348
296
  existing namespaces:
349
297
 
350
- Forme.form([:foo], :namespace=>'a') do |f|
298
+ Forme.form([:foo], {action: '/path'}, namespace: 'a') do |f|
351
299
  f.input(:first)
352
300
  # <input id="a_first" name="a[first]" type="text" value="foo"/>
353
301
  f.with_obj(['foobar'], 'b') do |o|
@@ -358,11 +306,11 @@ existing namespaces:
358
306
 
359
307
  == each_obj
360
308
 
361
- This allows you to provide an object-yielding enumerable. Forme will call with_obj with
362
- each object in the enumerable. It yields each object as well as the index of the
363
- object in the enumerable, and includes the index in the namespace:
309
+ This allows you to provide an object-yielding enumerable. +each_object+ will call
310
+ +with_obj+ with each object in the enumerable. It yields each object as well as the
311
+ index of the object in the enumerable, and includes the index in the namespace:
364
312
 
365
- objectlist = ['foobar', ['good']]
313
+ objectlist = [['foobar'], ['good']]
366
314
  Forme.form([:foo], :namespace=>'a') do |f|
367
315
  f.each_obj(objectlist, 'b') do |o, i|
368
316
  f.input(:first, :size=>10+i)
@@ -377,7 +325,7 @@ Forme ships with a Sequel plugin (use <tt>Sequel::Model.plugin :forme</tt> to en
377
325
  Sequel::Model instances support the +forme_config+ and +forme_input+ methods and return customized inputs.
378
326
  An additional instance method, +forme_namespace+ can optionally be defined to customize how model classnames
379
327
  are transformed into form classes and input IDs and names. This can be useful if your Sequel::Model classes
380
- are nested under a parent namespace. It falls back to the behaviour of Sequel::Model#underscore.
328
+ are nested under a parent namespace. The default namespace uses Sequel::Model#underscore.
381
329
 
382
330
  module Admin
383
331
  class Albums < Sequel::Model
@@ -387,27 +335,11 @@ are nested under a parent namespace. It falls back to the behaviour of Sequel::M
387
335
  end
388
336
  end
389
337
 
390
- == Basics
391
-
392
- The Sequel support handles inputs based on database columns, and automatically handles labels and errors.
393
-
394
- Forme.form(Album[1], :action=>'/foo') do |f|
395
- f.input :name
396
- f.input :copies_sold
397
- end
398
-
399
- This will create a form similar to:
400
-
401
- <form action="/foo" method="post">
402
- <label>Name: <input id="album_name" name="album[name]" type="text" value="Rising Force"/></label>
403
- <label>Copies Sold: <input id="album_copies_sold" name="album[copies_sold]" type="number" value="100000"/></label>
404
- </form>
405
-
406
- The Forme Sequel plugin also integerates with Sequel's validation reflection support with the
407
- +validation_class_methods+ plugin that ships with Sequel. It will add +pattern+ and +maxlength+ attributes
338
+ The Sequel :forme plugin also integerates with Sequel's validation reflection support that comes with the
339
+ Sequel validation_class_methods plugin. It will add +pattern+ and +maxlength+ attributes
408
340
  based on the format, numericality, and length validations.
409
341
 
410
- == specialized input options
342
+ == Specialized input options for specific column types
411
343
 
412
344
  In addition to the default Forme options, the Sequel support includes, for specific column types,
413
345
  these additional options to the #input method:
@@ -569,7 +501,7 @@ One way to handle the form submission is to use Sequel::Model#set_fields.
569
501
  Note that you have to specify the parameter names again as the second argument to
570
502
  set_fields.
571
503
 
572
- Handling Submitted parameters becomes more complex as your forms become more complex.
504
+ Handling submitted parameters becomes more complex as your forms become more complex.
573
505
  For example, if you are only displaying certain form fields in certain situations:
574
506
 
575
507
  album = Album[1]
@@ -589,9 +521,9 @@ As you can see, you basically need to recreate the conditionals used when creati
589
521
  the form, so that that the processing of the form submission handles only the
590
522
  inputs that were displayed on the form.
591
523
 
592
- === forme_set plugin
524
+ === Sequel forme_set plugin
593
525
 
594
- The forme_set plugin is designed to make handling form submissions easier. What it does
526
+ The Sequel forme_set plugin is designed to make handling form submissions easier. What it does
595
527
  is record the form fields that are used on the object, and then it uses those fields
596
528
  to handle input submitted for the object. For example:
597
529
 
@@ -670,12 +602,12 @@ this is specific to the web framework you are using, but is it similar to:
670
602
 
671
603
  forme_set is not perfect, there are ways to use Forme that forme_set will not handle
672
604
  correctly. First, forme_set only works with forms that use model objects, and doesn't
673
- handle inputs where the :obj option is provided to change the input.
605
+ handle inputs where the +:obj+ option is provided to change the input.
674
606
  Additionally, forme_set does not currently handle subform/nested_attributes.
675
607
 
676
- In cases where forme_set does not handle things correctly, you can use forme_parse,
677
- which will return metadata that forme_set uses (forme_set calls forme_parse
678
- internally). forme_parse returns a hash with the following keys:
608
+ In cases where forme_set does not handle things correctly, you can use +forme_parse+,
609
+ which will return metadata that +forme_set+ uses (+forme_set+ calls +forme_parse+
610
+ internally). +forme_parse+ returns a hash with the following keys:
679
611
 
680
612
  :values :: A hash of values that can be used to update the model, suitable for passing
681
613
  to Sequel::Model#set.
@@ -683,8 +615,168 @@ internally). forme_parse returns a hash with the following keys:
683
615
  check that the submitted values for associated objects match one of the
684
616
  options for the input in the form.
685
617
 
686
- It is possible to use forme_set for the values it can handle, and set other fields manually
687
- using set_fields.
618
+ It is possible to use +forme_set+ for the values it can handle, and set other fields manually
619
+ using +set_fields+.
620
+
621
+ === Roda forme_set plugin
622
+
623
+ The Roda forme_set plugin builds on the Sequel forme_set plugin and is designed to make
624
+ handling form submissions even easier. This plugin adds a hidden form input to store which
625
+ fields were used to build the form, as well as some other metadata. It adds another hidden
626
+ form input with an HMAC, so that on submission, if the HMAC matches, you can be sure that an
627
+ attacker didn't add extra fields.
628
+
629
+ There are a couple advantages to this plugin over using just the Sequel forme_set plugin.
630
+ One is that you do not need to record the form fields when processing the submission of a
631
+ form, since the information you need is included in the form submission. Another is that
632
+ calling the +forme_set+ method is simpler, since it can determine the necessary parameters.
633
+
634
+ While you need code like this when using just the Sequel forme_set plugin:
635
+
636
+ album = Album[1]
637
+ Forme.form(album, :action=>'/foo') do |f|
638
+ f.input :name
639
+ f.input :copies_sold if album.released?
640
+ end
641
+ album.forme_set(params['album'])
642
+
643
+ when you also use the Roda forme_set plugin, you can simplify it to:
644
+
645
+ album = Album[1]
646
+ forme_set(album)
647
+
648
+ ==== Validations
649
+
650
+ The Roda forme_set plugin supports and uses the same validations as the Sequel forme_set
651
+ plugin. However, the Roda plugin is more accurate because it uses the options that were
652
+ present on the form when it was originally built, instead of the options that would be
653
+ present on the form when the form was submitted. However, note that that can be a
654
+ negative if you are dynamically adding values to both the database and the form between
655
+ when the form was built and when it was submitted.
656
+
657
+ ==== Usage
658
+
659
+ Because the Roda forme_set plugin includes the metadata needed to process the form in form
660
+ submissions, you don't need to rearrange code to use it, or rerender templates.
661
+ You can do:
662
+
663
+ album = Album[1]
664
+ forme_set(album)
665
+
666
+ And the method will update the +album+ object using the appropriate form values.
667
+
668
+ Note that using the Roda forme_set plugin requires you set a secret for the HMAC. It
669
+ is important that you keep this value secret, because if an attacker has access to this,
670
+ they would be able to set arbitrary attributes for model objects. In your Roda class,
671
+ you can load the plugin via:
672
+
673
+ plugin :forme_set, :secret => ENV["APP_FORME_HMAC_SECRET"]
674
+
675
+ By default, invalid form submissions will raise an exception. If you want to change
676
+ that behavior (i.e. to display a nice error page), pass a block when loading the plugin:
677
+
678
+ plugin :forme_set do |error_type, obj|
679
+ # ...
680
+ end
681
+
682
+ The block arguments will be a symbol for the type of error (:missing_data, :missing_hmac,
683
+ :hmac_mismatch, :csrf_mismatch, or :missing_namespace) and the object passed to +forme_set+.
684
+ This block should raise or halt. If it does not, the default behavior of raising an
685
+ exception will be taken.
686
+
687
+ === Form Versions
688
+
689
+ The Roda forme_set plugin supports form versions. This allows you to gracefully handle
690
+ changes to forms, processing submissions of the form generated before the change (if
691
+ possible) as well as the processing submissions of the form generated after the change.
692
+
693
+ For example, maybe you have an existing form with just an input for the name:
694
+
695
+ form(album) do |f|
696
+ f.input(:name)
697
+ end
698
+
699
+ Then later, you want to add an input for the number of copies sold:
700
+
701
+ form(album) do |f|
702
+ f.input(:name)
703
+ f.input(:copies_sold)
704
+ end
705
+
706
+ Using the Roda forme_set plugin, submissions of the old form would only set the
707
+ name field, it wouldn't set the copies_sold field, since when the form was created,
708
+ only the name field was used.
709
+
710
+ You can handle this case be versioning the form when making changes to it:
711
+
712
+ form(album, {}, :form_version=>1) do |f|
713
+ f.input(:name)
714
+ f.input(:copies_sold)
715
+ end
716
+
717
+ When you are processing the form submission with forme_set, you pass a block, which
718
+ will be yielded the version for the form (nil if no version was set):
719
+
720
+ forme_set(album) do |version|
721
+ if version == nil
722
+ album.copies_sold = 0
723
+ end
724
+ end
725
+
726
+ The block is also yielded the object passed for forme_set, useful if you don't keep
727
+ a reference to it:
728
+
729
+ album = forme_set(Album.new) do |version, obj|
730
+ if version == nil
731
+ obj.copies_sold = 0
732
+ end
733
+ end
734
+
735
+ You only need to support old versions of the form for as long as their could be
736
+ active sessions that could use the old versions of the form. As long you as
737
+ are expiring sessions to prevent session fixation, you can remove the version
738
+ handling after the expiration period has passed since the change to the form
739
+ was made.
740
+
741
+ Note that this issue with handling changes to forms is not specific to the Roda
742
+ forme_set plugin, it affects pretty much all form submissions. The Roda forme_set
743
+ plugin just makes this issue easier to handle.
744
+
745
+ ==== Caveats
746
+
747
+ The Roda forme_set plugin has basically the same caveats as Sequel forme_set plugin.
748
+ Additionally, it has a couple other restrictions that the Sequel forme_set plugin
749
+ does not have.
750
+
751
+ First, the Roda forme_set plugin only handles a single object in forms,
752
+ which must be provided when creating the form. It does not handle multiple
753
+ objects in the same form, and ignores any fields set for an object different
754
+ from the one passed when creating the form. You can use the Sequel forme_set
755
+ plugin to handle form submissions involving multiple objects, or for the
756
+ objects that were not passed when creating the form.
757
+
758
+ Second, the Roda forme_set plugin does not handle cases where the field values
759
+ are placed outside the form's default namespace. The Sequel forme_set plugin
760
+ can handle those issues, as long as all values are in the same namespace, since
761
+ the Sequel forme_set plugin requires you pass in the specific hash to use (the
762
+ Roda forme_set plugin uses the form's namespace information and the submitted
763
+ parameters to determine the hash to use).
764
+
765
+ In cases where the Roda forme_set does not handle things correctly, you can use
766
+ forme_parse, which will return metadata in the same format as the Sequel plugin
767
+ forme_parse method, with the addition of a +:form_version+ key in the hash for the
768
+ form version.
769
+
770
+ It is possible to use the Roda forme_set plugin for the submissions it can handle, the
771
+ Sequel forme_set plugin for the submissions it can handle, and set other fields manually
772
+ using the Sequel +set_fields+ methods.
773
+
774
+ Note that when using the Roda forme_set plugin with an existing form, you should first
775
+ enable the Roda plugin without actually using the Roda forme_set method. Do not
776
+ start using the Roda forme_set method until all currently valid sessions were
777
+ established after the Roda forme_set plugin was enabled. Otherwise, sessions that
778
+ access the form before the Roda forme_set plugin was enabled will not work if they
779
+ submit the form after the Roda forme_set plugin is enabled.
688
780
 
689
781
  == Other Sequel Plugins
690
782
 
@@ -695,9 +787,17 @@ forme_i18n :: Handles translations for labels using i18n.
695
787
 
696
788
  = Roda Support
697
789
 
698
- Forme ships with two Roda plugins, forme and forme_route_csrf. For new code, it is
699
- recommended to use forme_route_csrf, as that uses Roda's route_csrf plugin, which
700
- supports more secure request-specific CSRF tokens. In both cases, usage in ERB
790
+ Forme ships with multiple Roda plugins
791
+
792
+ * forme_set (discussed above)
793
+ * forme
794
+ * forme_route_csrf
795
+ * forme_erubi_capture
796
+
797
+ == forme_route_csrf and forme plugins
798
+
799
+ For new code, it is recommended to use forme_route_csrf, as that uses Roda's route_csrf
800
+ plugin, which supports more secure request-specific CSRF tokens. In both cases, usage in ERB
701
801
  templates is the same:
702
802
 
703
803
  <% form(@obj, :action=>'/foo') do |f| %>
@@ -722,14 +822,29 @@ The forme plugin does not require any csrf plugin, but will transparently use
722
822
  Rack::Csrf if it is available. If Rack::Csrf is available a CSRF tag if the form's
723
823
  method is +POST+, with no configuration ability.
724
824
 
725
- Both plugins also support the following option:
825
+ == forme_erubi_capture plugin
826
+
827
+ The forme_erubi_capture plugin builds on the forme_route_csrf plugin, but it supports
828
+ the erubi/capture_end engine, which allows this syntax:
829
+
830
+ <%|= form(@obj, :action=>'/foo') do |f| %>
831
+ <%= f.input(:field) %>
832
+ <%|= f.tag(:fieldset) do %>
833
+ <%= f.input(:field_two) %>
834
+ <%| end %>
835
+ <%| end %>
836
+
837
+ If you use the forme_erubi_capture plugin, you need to manually set Roda to use the
838
+ erubi/capture_end engine, which you can do via:
726
839
 
727
- :output :: The object that the form output should be injected into when
728
- injecting output (+@_out_buf+ by default).
840
+ require 'erubi/capture_end'
841
+ app.plugin :render, :engine_opts=>{'erb'=>{:engine_class=>Erubi::CaptureEndEngine}}
842
+
843
+ The forme_erubi_capture plugin requires Roda 3.50.0+.
729
844
 
730
845
  = Sinatra Support
731
846
 
732
- Forme ships with a ERB extension that you can get by <tt>require "forme/erb"</tt> and using
847
+ Forme ships with a Sinatra extension that you can get by <tt>require "forme/erb"</tt> and using
733
848
  <tt>including Forme::ERB::Helper</tt>. This is tested to support ERB templates in Sinatra.
734
849
  It allows you to use the following API in your erb templates:
735
850
 
@@ -740,8 +855,8 @@ It allows you to use the following API in your erb templates:
740
855
  <% end %>
741
856
  <% end %>
742
857
 
743
- In order to this to work transparently, the ERB outvar needs to be <tt>@_out_buf</tt>. If that
744
- is not the case, use the :output option to +form+ to specify the outvar.
858
+ In order to this to work transparently, the ERB outvar needs to be <tt>@_out_buf</tt> (this is the
859
+ default in Sinatra).
745
860
 
746
861
  = Rails Support
747
862
 
@@ -756,7 +871,7 @@ in your Rails forms:
756
871
  <% end %>
757
872
  <% end %>
758
873
 
759
- This has been tested on Rails 3.2-5.2.
874
+ This has been tested on Rails 3.2-6.1.
760
875
 
761
876
  = Input Types and Options
762
877
 
@@ -767,8 +882,8 @@ created via Forme::Form#input:
767
882
 
768
883
  These options are supported by all of the input types:
769
884
 
770
- :attr :: The attributes hash to use for the given tag, takes precedence over
771
- other options that set attributes.
885
+ :attr :: The attributes hash to use for the given tag, attributes in this hash
886
+ take precedence over other options that set attributes.
772
887
  :autofocus :: Set the autofocus attribute if true
773
888
  :class :: A class to use. Unlike other options, this is combined with the
774
889
  classes set in the :attr hash.
@@ -780,6 +895,7 @@ These options are supported by all of the input types:
780
895
  :disabled :: Set the disabled attribute if true
781
896
  :error :: Set an error message, invoking the error_handler
782
897
  :error_handler :: Set a custom error_handler, overriding the form's default
898
+ :help :: Set help text to use, invoking the helper
783
899
  :helper :: Set a custom helper, overriding the form's default
784
900
  :id :: The id attribute to use
785
901
  :key :: The base to use for the name and id attributes, based on the current
@@ -796,7 +912,7 @@ These options are supported by all of the input types:
796
912
  for textarea tags, or the selected option(s) for select tags.
797
913
  :wrapper :: Set a custom wrapper, overriding the form's default
798
914
 
799
- == Input Types
915
+ == Input Type-Specific Options
800
916
 
801
917
  === :checkbox
802
918
 
@@ -814,16 +930,21 @@ Creates an input tag with type radio. Options:
814
930
 
815
931
  === :date / :datetime
816
932
 
817
- By default, creates an input tag with type date or datetime. With the :as=>:select option,
933
+ By default, creates an input tag with type date or datetime. With the <tt>as: :select</tt> option,
818
934
  creates multiple select options. Options:
819
935
 
820
936
  :as :: When value is :select, uses 3 or 6 select boxes by default.
821
- :order :: The order of select boxes when using :as=>:select. Entries should be a symbol
937
+ :order :: The order of select boxes when using <tt>as: :select</tt>. Entries should be a symbol
822
938
  for the select field and string to use a string
823
939
  (:date default: <tt>[:year, '-', :month, '-', :day]</tt>)
824
940
  (:datetime default: <tt>[:year, '-', :month, '-', :day, ' ', :hour, ':', :minute, ':', :second]</tt>)
941
+ :select_labels :: The labels to use for the select boxes. Should be a hash keyed by the
942
+ symbol used (e.g. <tt>{:month=>'Month'}</tt>). By default, no labels are used.
825
943
  :select_options :: The options to use for the select boxes. Should be a hash keyed by the
826
- symbol used in order (e.g. <tt>{:year=>1970..2020}</tt>)
944
+ symbol used in order (e.g. <tt>{:year=>1970..2020}</tt>). The values
945
+ can be a number used as both the value and the text of the option or
946
+ an array with two elements, the first of which is the value for the option
947
+ and the second of which is the text for the option.
827
948
 
828
949
  === :select
829
950
 
@@ -843,7 +964,8 @@ Creates a select tag, containing option tags specified by the :options option.
843
964
  :options :: An enumerable of options used for creating option tags.
844
965
  If the :text_method and :value_method are not given and the entry is an
845
966
  array, uses the first entry of the array as the text of the option, and
846
- the second entry of the array as the value of the option.
967
+ the last entry of the array as the value of the option. If the last entry
968
+ of the array is a hash, uses the hash as the attributes for the option.
847
969
  :selected :: The value that should be selected. Any options that are equal to
848
970
  this value (or included in this value if a multiple select box),
849
971
  are set to selected.
@@ -857,11 +979,12 @@ Creates a select tag, containing option tags specified by the :options option.
857
979
  === :checkboxset
858
980
 
859
981
  Creates a set of checkbox inputs all using the same name. Supports the same options
860
- as the select type, except that the :multiple option is assumed to be true. Also supports
982
+ as the :select type, except that the :multiple option is assumed to be true. Also supports
861
983
  the following options:
862
984
 
863
985
  :tag_wrapper :: The wrapper transformer for individual tags in the set
864
986
  :tag_labeler :: The labeler transformer for individual tags in the set
987
+ :tag_label_attr :: The attributes to use for labels for individual tags in the set
865
988
 
866
989
  === :radioset
867
990
 
@@ -888,6 +1011,10 @@ as text and password, as well as newer HTML5 inputs such as number or email. Opt
888
1011
  These are the options supported by Forme::Form object, mostly used to set
889
1012
  the defaults for Inputs created via the form:
890
1013
 
1014
+ :after :: A callable object that is yielded the Form instance after yielding to
1015
+ the block. Can be used to add hidden inputs to the end of the form.
1016
+ :before :: A callable object that is yielded the Form instance before yielding to
1017
+ the block. Can be used to add hidden inputs to the start of the form.
891
1018
  :config :: The configuration to use, which automatically sets defaults
892
1019
  for the transformers to use.
893
1020
  :errors :: A Hash of errors from a previous form submission, used to set
@@ -895,7 +1022,6 @@ the defaults for Inputs created via the form:
895
1022
  :error_handler :: Sets the default error_handler for the form's inputs
896
1023
  :helper :: Sets the default helper for the form's inputs
897
1024
  :formatter :: Sets the default formatter for the form's inputs
898
- :hidden_tags :: Sets the hidden tags to automatically add to this form, see below.
899
1025
  :input_defaults :: Sets the default options for each input type. This should
900
1026
  be a hash with input type keys, where the values are the
901
1027
  hash of default options to use for the input type.
@@ -916,17 +1042,7 @@ For forms created by Forme.form, the following options are supported:
916
1042
  to the block.
917
1043
  :button :: A button to add to the form, after yielding to the block.
918
1044
 
919
- === :hidden_tags
920
-
921
- This should be an array, where elements are one of the following types:
922
-
923
- String, Array, Forme::Tag :: Added directly as a child of the form tag.
924
- Hash :: Adds a hidden tag for each entry, with keys as the name of the hidden
925
- tag and values as the value of the hidden tag.
926
- Proc :: Will be called with the form tag object, and should return an instance
927
- of one of the handled types (or nil to not add a tag).
928
-
929
- = Basic Design
1045
+ = Internal Architecture
930
1046
 
931
1047
  Internally, Forme builds an abstract syntax tree of objects that
932
1048
  represent the form. The abstract syntax tree goes through a
@@ -936,15 +1052,88 @@ strings. Here are the main classes used by the library:
936
1052
 
937
1053
  <tt>Forme::Form</tt> :: main object
938
1054
  <tt>Forme::Input</tt> :: high level abstract tag (a single +Input+ could represent a select box with a bunch of options)
939
- <tt>Forme::Tag</tt> :: low level abstract tag representing an html tag (there would be a separate +Tag+ for each option in a select box)
1055
+ <tt>Forme::Tag</tt> :: low level abstract tag representing an HTML tag (there would be a separate +Tag+ for each option in a select box)
1056
+
1057
+ The difference between <tt>Forme::Input</tt> and <tt>Forme::Tag</tt>
1058
+ is that <tt>Forme::Tag</tt> directly represents the underlying HTML
1059
+ tag, containing a type, optional attributes, and children, while the
1060
+ <tt>Forme::Input</tt> is more abstract and attempts to be user friendly.
1061
+ For example, these both compile by default to the same select tag:
1062
+
1063
+ f.input(:select, :options=>[['foo', 1]])
1064
+ # or
1065
+ f.tag(:select, {}, [f.tag(:option, {:value=>1}, ['foo'])])
940
1066
 
941
1067
  The group of objects that perform the transformations to
942
1068
  the abstract syntax trees are known as transformers.
943
1069
  Transformers use a functional style, and all use a +call+-based
944
1070
  API, so you can use a +Proc+ for any custom transformer.
1071
+ The processing of high level <tt>Forme::Input</tt>s into raw HTML
1072
+ fragments is performed through the following transformers:
1073
+
1074
+ +Formatter+ :: converts a <tt>Forme::Input</tt> instance into a
1075
+ <tt>Forme::Tag</tt> instance (or array of them).
1076
+ +ErrorHandler+ :: If the <tt>Forme::Input</tt> instance has a error,
1077
+ takes the formatted tag and marks it as having the error.
1078
+ +Helper+ :: If the <tt>Forme::Input</tt> instance has any help text,
1079
+ adds the help text in a separate tag.
1080
+ +Labeler+ :: If the <tt>Forme::Input</tt> instance has a label,
1081
+ takes the formatted output and labels it.
1082
+ +Wrapper+ :: Takes the output of the formatter, labeler, and
1083
+ error_handler transformers, and wraps it in another tag (or just
1084
+ returns it unmodified).
1085
+ +Serializer+ :: converts a <tt>Forme::Tag</tt> instance into an
1086
+ HTML string.
1087
+
1088
+ Technically, only the +Serializer+ is necessary. The Forme::Form#input
1089
+ and Forme::Form#tag methods internally create +Input+ and
1090
+ +Tag+ objects. Before returning results, the input or tag is converted
1091
+ to a string using +to_s+, which calls the appropriate +Serializer+.
1092
+ The +Serializer+ calls the appropriate +Formatter+ if it encounters an
1093
+ +Input+ instance, and attempts to serialize the output of that (which is
1094
+ usually a +Tag+ instance). It is up to the +Formatter+ to call the +Labeler+,
1095
+ +ErrorHandler+, +Helper+, and/or +Wrapper+.
1096
+
1097
+ The Forme::Form object takes the transformers as options (:formatter,
1098
+ :labeler, :error_handler, :helper, :wrapper, and :serializer), all of which
1099
+ should be objects responding to +call+ (so you can use +Proc+s) or be symbols
1100
+ registered with the library using <tt>Forme.register_transformer</tt>:
1101
+
1102
+ Forme.register_transformer(:wrapper, :p) do |tag, input|
1103
+ input.tag(:p, {}, tag)
1104
+ end
1105
+
1106
+ Most transformers are called with two arguments, +tag+ and +input+. +tag+
1107
+ is a <tt>Forme::Tag</tt> instance, and +input+ is a <tt>Forme::Input</tt>
1108
+ instance. The +Formatter+ and +Serializer+ transformers are the two
1109
+ exceptions, with +Formatter+ being called with just an +input+, and
1110
+ +Serializer+ potentionally being called with any object. The +Serializer+
1111
+ will in general recursively call itself with children of the argument
1112
+ given until a string is returned.
1113
+
1114
+ There is also an +InputsWrapper+ transformer, that is called by
1115
+ Forme::Form#inputs. It's used to wrap up a group of
1116
+ related options (in a +fieldset+ by default). It takes +form+
1117
+ (Forme::Form instance) and +input_opts+ (+Hash+) arguments.
1118
+
1119
+ Most of the transformers can be overridden on a per instance basis by
1120
+ passing the appropriate option to +input+ or +inputs+:
1121
+
1122
+ f.input(:name, :wrapper=>:p)
1123
+
1124
+ Existing transformers can be easily extended (ie, to set the class attribute),
1125
+ by creating your own transformer and then calling the existing transformer.
1126
+
1127
+ Forme.register_transformer(:labeler, :explicit) do |tag, input|
1128
+ input.opts[:label_attr] ||= { :class => 'label' }
1129
+ Forme::Labeler::Explicit.new.call(tag, input)
1130
+ end
945
1131
 
946
1132
  == Transformer Types
947
1133
 
1134
+ You can override the type of transform for each form or input using the
1135
+ following options:
1136
+
948
1137
  +serializer+ :: tags input/tag, returns string
949
1138
  +formatter+ :: takes input, returns tag
950
1139
  +error_handler+ :: takes tag and input, returns version of tag with errors noted
@@ -980,7 +1169,9 @@ Forme ships with a bunch of built-in transformers that you can use:
980
1169
 
981
1170
  === +error_handler+
982
1171
 
1172
+ :after_legend :: designed for usage with :legend labeler, putting error message after legend, adding error for first input in the set
983
1173
  :default :: modifies tag to add an error class and adds a span with the error message
1174
+ :set :: default error_handler for checkboxset and radioset inputs, that adds an error to the last input in the set
984
1175
 
985
1176
  This supports the following options:
986
1177
 
@@ -998,8 +1189,10 @@ This supports the following options:
998
1189
 
999
1190
  :default :: uses implicit labels, where the tag is a child of the label tag
1000
1191
  :explicit :: uses explicit labels with the for attribute, where tag is a sibling of the label tag
1192
+ :legend :: adds a legend before the tags, mostly useful for accessible checkboxset and radioset inputs
1193
+ :span :: default labeler for checkboxset and radioset inputs that adds a span before the tags
1001
1194
 
1002
- Both of these respect the following options:
1195
+ The :default and :explicit labelers respect the following options:
1003
1196
 
1004
1197
  :label_position :: Can be set to :before or :after to place the label before or after the the input.
1005
1198
  :label_attr :: A hash of attributes to use for the label tag
@@ -1008,6 +1201,7 @@ Both of these respect the following options:
1008
1201
 
1009
1202
  :default :: returns tag without wrapping
1010
1203
  :div :: wraps tag in div tag
1204
+ :fieldset :: wraps tags in a fieldset, mostly useful for accessible checkboxset and radioset inputs
1011
1205
  :fieldset_ol :: same as :li, but also sets +inputs_wrapper+ to :fieldset_ol
1012
1206
  :li :: wraps tag in li tag
1013
1207
  :ol :: same as :li, but also sets +inputs_wrapper+ to :ol
@@ -1072,7 +1266,7 @@ You can mark a configuration as the default using:
1072
1266
 
1073
1267
  === Bootstrap 3 Support
1074
1268
 
1075
- Forme ships with support for Bootstrap 3 HTML formatting. This support is shipped in
1269
+ Forme ships with support for Bootstrap 3-4 HTML formatting. This support is shipped in
1076
1270
  it's own file, so if you don't use it, you don't pay the memory penalty for loading
1077
1271
  it.
1078
1272
 
@@ -1088,7 +1282,7 @@ All of these have external dependencies:
1088
1282
  3. simple_form
1089
1283
  4. padrino-helpers
1090
1284
 
1091
- Forme's DSL draws a lot of inspiration from both Formtastic and simple_form.
1285
+ Forme's API draws a lot of inspiration from both Formtastic and simple_form.
1092
1286
 
1093
1287
  = License
1094
1288