forme 1.11.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +54 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +235 -209
  5. data/Rakefile +1 -7
  6. data/lib/forme/bs3.rb +18 -4
  7. data/lib/forme/erb.rb +13 -25
  8. data/lib/forme/form.rb +144 -149
  9. data/lib/forme/input.rb +1 -1
  10. data/lib/forme/rails.rb +39 -83
  11. data/lib/forme/raw.rb +2 -2
  12. data/lib/forme/tag.rb +3 -12
  13. data/lib/forme/template.rb +110 -0
  14. data/lib/forme/transformers/formatter.rb +4 -1
  15. data/lib/forme/transformers/helper.rb +0 -1
  16. data/lib/forme/transformers/inputs_wrapper.rb +4 -4
  17. data/lib/forme/version.rb +2 -2
  18. data/lib/forme.rb +15 -2
  19. data/lib/roda/plugins/forme.rb +1 -1
  20. data/lib/roda/plugins/forme_erubi_capture.rb +57 -0
  21. data/lib/roda/plugins/forme_route_csrf.rb +16 -34
  22. data/lib/roda/plugins/forme_set.rb +39 -76
  23. data/lib/sequel/plugins/forme.rb +40 -50
  24. data/lib/sequel/plugins/forme_i18n.rb +3 -1
  25. data/lib/sequel/plugins/forme_set.rb +3 -1
  26. data/spec/all.rb +1 -1
  27. data/spec/bs3_reference_spec.rb +18 -18
  28. data/spec/bs3_sequel_plugin_spec.rb +17 -17
  29. data/spec/bs3_spec.rb +23 -11
  30. data/spec/erb_helper.rb +69 -58
  31. data/spec/erubi_capture_helper.rb +198 -0
  32. data/spec/forme_spec.rb +21 -32
  33. data/spec/rails_integration_spec.rb +39 -25
  34. data/spec/roda_integration_spec.rb +118 -70
  35. data/spec/sequel_helper.rb +0 -1
  36. data/spec/sequel_i18n_helper.rb +1 -1
  37. data/spec/sequel_i18n_plugin_spec.rb +3 -2
  38. data/spec/sequel_plugin_spec.rb +29 -12
  39. data/spec/sequel_set_plugin_spec.rb +9 -2
  40. data/spec/shared_erb_specs.rb +71 -0
  41. data/spec/sinatra_integration_spec.rb +5 -6
  42. data/spec/spec_helper.rb +21 -8
  43. metadata +9 -7
  44. data/lib/forme/erb_form.rb +0 -74
  45. data/lib/forme/sinatra.rb +0 -17
data/Rakefile CHANGED
@@ -50,7 +50,7 @@ end
50
50
 
51
51
  desc "Run specs"
52
52
  task :spec do
53
- sh "#{FileUtils::RUBY} spec/all.rb"
53
+ sh "#{FileUtils::RUBY} #{'-w' if RUBY_VERSION >= '3'} spec/all.rb"
54
54
  end
55
55
  task :default => :spec
56
56
 
@@ -60,12 +60,6 @@ task :spec_cov do
60
60
  sh "#{FileUtils::RUBY} spec/all.rb"
61
61
  end
62
62
 
63
- desc "Run specs with -w, some warnings filtered"
64
- task :spec_w do
65
- ENV['WARNING'] = '1'
66
- sh "#{FileUtils::RUBY} -w spec/all.rb"
67
- end
68
-
69
63
  ### Other
70
64
 
71
65
  desc "Print #{NAME} version"
data/lib/forme/bs3.rb CHANGED
@@ -80,12 +80,26 @@ module Forme
80
80
  copy_options_to_attributes(ATTRIBUTE_OPTIONS)
81
81
  copy_boolean_options_to_attributes(ATTRIBUTE_BOOLEAN_OPTIONS)
82
82
  handle_key_option
83
+ handle_errors_option
83
84
 
84
85
  Forme.attr_classes(@attr, @opts[:class]) if @opts.has_key?(:class)
85
- # Forme.attr_classes(@attr, 'error') if @opts[:error]
86
+
87
+ if @opts[:error]
88
+ # Forme.attr_classes(@attr, 'error')
89
+ @attr["aria-invalid"] = "true"
90
+ if @opts.fetch(:error_handler, true)
91
+ unless @opts[:error_id]
92
+ if id = @attr[:id] || @attr['id']
93
+ error_id = @attr['aria-describedby'] ||= "#{id}_error_message"
94
+ @opts[:error_id] = error_id
95
+ end
96
+ end
97
+ end
98
+ end
86
99
 
87
100
  if data = opts[:data]
88
101
  data.each do |k, v|
102
+ k = k.to_s.tr("_", "-") if k.is_a?(Symbol) && input.opts[:dasherize_data]
89
103
  sym = :"data-#{k}"
90
104
  @attr[sym] = v unless @attr.has_key?(sym)
91
105
  end
@@ -210,7 +224,7 @@ module Forme
210
224
  Forme.attr_classes(attr, 'inputs')
211
225
  if legend = opts[:legend]
212
226
  form.tag(:fieldset, attr) do
213
- form.emit(form.tag(:legend, opts[:legend_attr], legend))
227
+ form.tag(:legend, opts[:legend_attr], legend)
214
228
  yield
215
229
  end
216
230
  else
@@ -230,11 +244,11 @@ module Forme
230
244
  attr = opts[:attr] ? opts[:attr].dup : { :class=>'table table-bordered'}
231
245
  form.tag(:table, attr) do
232
246
  if legend = opts[:legend]
233
- form.emit(form.tag(:caption, opts[:legend_attr], legend))
247
+ form.tag(:caption, opts[:legend_attr], legend)
234
248
  end
235
249
 
236
250
  if (labels = opts[:labels]) && !labels.empty?
237
- form.emit(form.tag(:tr, {}, labels.map{|l| form._tag(:th, {}, l)}))
251
+ form.tag(:tr, {}, labels.map{|l| form._tag(:th, {}, l)})
238
252
  end
239
253
 
240
254
  yield
data/lib/forme/erb.rb CHANGED
@@ -1,36 +1,24 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require 'forme/erb_form'
3
+ require_relative 'template'
4
4
 
5
5
  module Forme
6
6
  module ERB
7
- HIDDEN_TAGS = []
7
+ # This is the module used to add the Forme integration to ERB templates, with optional support for
8
+ # rack_csrf for CSRF handling.
9
+ module Helper
10
+ include Template::Helper
8
11
 
9
- # Add a hidden tag proc that will be used for all forms created via Forme::ERB::Helper#form.
10
- # The block is yielded the Forme::Tag object for the form tag.
11
- # The block should return either nil if hidden tag should be added, or a Forme::Tag object (or an array of them),
12
- # or a hash with keys specifying the name of the tags and the values specifying the values of the tags .
13
- def self.add_hidden_tag(&block)
14
- HIDDEN_TAGS << block
15
- end
12
+ private
16
13
 
17
- # Add CSRF token tag by default for POST forms
18
- add_hidden_tag do |tag|
19
- if defined?(::Rack::Csrf) && (form = tag.form) && (env = form.opts[:env]) && env['rack.session'] && (tag.attr[:method] || tag.attr['method']).to_s.upcase == 'POST'
20
- {::Rack::Csrf.field=>::Rack::Csrf.token(env)}
21
- end
22
- end
14
+ def _forme_form_options(obj, attr, opts)
15
+ super
23
16
 
24
- # This is the module used to add the Forme integration
25
- # to ERB.
26
- module Helper
27
- # Create a +Form+ object tied to the current output buffer,
28
- # using the standard ERB hidden tags.
29
- def form(obj=nil, attr={}, opts={}, &block)
30
- h = {:hidden_tags=>Forme::ERB::HIDDEN_TAGS, :env=>env}
31
- h[:output] = @_out_buf if block
32
- (obj.is_a?(Hash) ? attr = attr.merge(h) : opts = opts.merge(h))
33
- Form.form(obj, attr, opts, &block)
17
+ if defined?(::Rack::Csrf) && env['rack.session']
18
+ opts[:_before_post] = lambda do |form|
19
+ form.tag(:input, :type=>:hidden, :name=>::Rack::Csrf.field, :value=>::Rack::Csrf.token(env))
20
+ end
21
+ end
34
22
  end
35
23
  end
36
24
  end
data/lib/forme/form.rb CHANGED
@@ -2,9 +2,8 @@
2
2
 
3
3
  module Forme
4
4
  # The +Form+ class is the main entry point to the library.
5
- # Using the +form+, +input+, +tag+, and +inputs+ methods, one can easily build
6
- # an abstract syntax tree of +Tag+ and +Input+ instances, which can be serialized
7
- # to a string using +to_s+.
5
+ # Using the +form+, +input+, +tag+, and +inputs+ methods, one can return HTML
6
+ # form tag string (or fragments of an HTML form tag).
8
7
  class Form
9
8
  # A hash of options for the form.
10
9
  attr_reader :opts
@@ -13,13 +12,17 @@ module Forme
13
12
  # input type keys and values that are hashes of input options.
14
13
  attr_reader :input_defaults
15
14
 
16
- # The hidden tags to automatically add to the form.
17
- attr_reader :hidden_tags
15
+ # The attributes used for the form tag for this form.
16
+ attr_reader :form_tag_attributes
18
17
 
19
18
  # The +serializer+ determines how +Tag+ objects are transformed into strings.
20
19
  # Must respond to +call+ or be a registered symbol.
21
20
  attr_reader :serializer
22
21
 
22
+ # The contents of the form as a string. This should not be mutated by
23
+ # external code.
24
+ attr_reader :to_s
25
+
23
26
  # Use appropriate Form subclass for object based on the current class, if the
24
27
  # object responds to +forme_form_class+.
25
28
  def self.new(obj=nil, opts={})
@@ -30,23 +33,9 @@ module Forme
30
33
  end
31
34
  end
32
35
 
33
- # Create a +Form+ instance and yield it to the block,
34
- # injecting the opening form tag before yielding and
35
- # the closing form tag after yielding.
36
- #
37
- # Argument Handling:
38
- # No args :: Creates a +Form+ object with no options and not associated
39
- # to an +obj+, and with no attributes in the opening tag.
40
- # 1 hash arg :: Treated as opening form tag attributes, creating a
41
- # +Form+ object with no options.
42
- # 1 non-hash arg :: Treated as the +Form+'s +obj+, with empty options
43
- # and no attributes in the opening tag.
44
- # 2 hash args :: First hash is opening attributes, second hash is +Form+
45
- # options.
46
- # 1 non-hash arg, 1-2 hash args :: First argument is +Form+'s obj, second is
47
- # opening attributes, third if provided is
48
- # +Form+'s options.
49
- def self.form(obj=nil, attr={}, opts={}, &block)
36
+ # Parse the args given to #form and return Form instance, form tag attributes,
37
+ # and block for form.
38
+ def self.form_args(obj, attr, opts, &block)
50
39
  f = if obj.is_a?(Hash)
51
40
  raise Error, "Can't provide 3 hash arguments to form" unless opts.empty?
52
41
  opts = attr
@@ -60,12 +49,32 @@ module Forme
60
49
  button = opts[:button]
61
50
  if ins || button
62
51
  block = proc do |form|
63
- form._inputs(ins, opts) if ins
52
+ form.inputs(ins, opts) if ins
64
53
  yield form if block_given?
65
- form.emit(form.button(button)) if button
54
+ form.button(button) if button
66
55
  end
67
56
  end
68
57
 
58
+ [f, attr, block]
59
+ end
60
+
61
+ # Create a +Form+ instance and yield it to the block. Returns an HTML string
62
+ # for the form tag.
63
+ #
64
+ # Argument Handling:
65
+ # No args :: Creates a +Form+ object with no options and not associated
66
+ # to an +obj+, and with no attributes in the opening tag.
67
+ # 1 hash arg :: Treated as opening form tag attributes, creating a
68
+ # +Form+ object with no options.
69
+ # 1 non-hash arg :: Treated as the +Form+'s +obj+, with empty options
70
+ # and no attributes in the opening tag.
71
+ # 2 hash args :: First hash is opening attributes, second hash is +Form+
72
+ # options.
73
+ # 1 non-hash arg, 1-2 hash args :: First argument is +Form+'s obj, second is
74
+ # opening attributes, third if provided is
75
+ # +Form+'s options.
76
+ def self.form(obj=nil, attr={}, opts={}, &block)
77
+ f, attr, block = form_args(obj, attr, opts, &block)
69
78
  f.form(attr, &block)
70
79
  end
71
80
 
@@ -97,27 +106,33 @@ module Forme
97
106
 
98
107
  @serializer = @opts[:serializer]
99
108
  @input_defaults = @opts[:input_defaults] || {}
100
- @hidden_tags = @opts[:hidden_tags]
101
- @nesting = []
109
+ @to_s = String.new
102
110
  end
103
111
 
104
- # Create a form tag with the given attributes.
105
- def form(attr={}, &block)
112
+ # Create a form tag with the given attributes. Returns an HTML string for
113
+ # the generated form tag.
114
+ def form(attr={})
106
115
  if obj && !attr[:method] && !attr['method'] && obj.respond_to?(:forme_default_request_method)
107
- attr = attr.merge('method'=>obj.forme_default_request_method)
116
+ attr = Hash[attr]
117
+ attr['method'] = obj.forme_default_request_method
118
+ end
119
+ @form_tag_attributes = attr
120
+
121
+ tag(:form, attr) do
122
+ before_form_yield
123
+ yield self if block_given?
124
+ after_form_yield
108
125
  end
109
- tag(:form, attr, method(:hidden_form_tags), &block)
110
126
  end
111
127
 
112
- # Empty method designed to ease integration with other libraries where
113
- # Forme is used in template code and some output implicitly
114
- # created by Forme needs to be injected into the template output.
115
- def emit(tag)
128
+ # Whether the method for this form is POST. Only callable after calling
129
+ # #form.
130
+ def post?
131
+ (form_tag_attributes[:method] || form_tag_attributes['method']).to_s.upcase == 'POST'
116
132
  end
117
133
 
118
134
  # Creates an +Input+ with the given +field+ and +opts+ associated with
119
- # the receiver, and add it to the list of children to the currently
120
- # open tag.
135
+ # the receiver. Returns the HTML generated by the given input.
121
136
  #
122
137
  # If the form is associated with an +obj+, or the :obj key exists in
123
138
  # the +opts+ argument, treats the +field+ as a call to the +obj+. If
@@ -129,36 +144,39 @@ module Forme
129
144
  # type (e.g. <tt>:text</tt>, <tt>:textarea</tt>, <tt>:select</tt>), and
130
145
  # an input is created directly with the +field+ and +opts+.
131
146
  def input(field, opts={})
132
- if opts.has_key?(:obj)
133
- opts = opts.dup
134
- obj = opts.delete(:obj)
135
- else
136
- obj = self.obj
137
- end
138
- input = if obj
139
- if obj.respond_to?(:forme_input)
140
- obj.forme_input(self, field, opts.dup)
141
- else
147
+ content_added do
148
+ if opts.has_key?(:obj)
142
149
  opts = opts.dup
143
- opts[:key] = field unless opts.has_key?(:key)
144
- type = opts.delete(:type) || :text
145
- unless opts.has_key?(:value) || type == :file
146
- opts[:value] = if obj.is_a?(Hash)
147
- obj[field]
148
- else
149
- obj.send(field)
150
+ obj = opts.delete(:obj)
151
+ else
152
+ obj = self.obj
153
+ end
154
+
155
+ input = if obj
156
+ if obj.respond_to?(:forme_input)
157
+ obj.forme_input(self, field, opts.dup)
158
+ else
159
+ opts = opts.dup
160
+ opts[:key] = field unless opts.has_key?(:key)
161
+ type = opts.delete(:type) || :text
162
+ unless opts.has_key?(:value) || type == :file
163
+ opts[:value] = if obj.is_a?(Hash)
164
+ obj[field]
165
+ else
166
+ obj.send(field)
167
+ end
150
168
  end
169
+ _input(type, opts)
151
170
  end
152
- _input(type, opts)
171
+ else
172
+ _input(field, opts)
153
173
  end
154
- else
155
- _input(field, opts)
174
+
175
+ self << input
156
176
  end
157
- self << input
158
- input
159
177
  end
160
178
 
161
- # Create a new +Input+ associated with the receiver with the given
179
+ # Return a new +Input+ associated with the receiver with the given
162
180
  # arguments, doing no other processing.
163
181
  def _input(*a)
164
182
  Input.new(self, *a)
@@ -182,6 +200,8 @@ module Forme
182
200
  # wrapper to use for this inputs call. You can use the :nested_inputs_wrapper
183
201
  # option to set the default :inputs_wrapper option for the duration of the block.
184
202
  #
203
+ # Returns the HTML generated by the inputs added to the form.
204
+ #
185
205
  # This can also be called with a single hash argument to just use an options hash:
186
206
  #
187
207
  # f.inputs(:legend=>'Foo') do
@@ -193,32 +213,28 @@ module Forme
193
213
  # f.inputs do
194
214
  # # ...
195
215
  # end
196
- def inputs(inputs=[], opts={}, &block)
197
- _inputs(inputs, opts, &block)
198
- end
199
-
200
- # Internals of #inputs, should be used internally by the library, where #inputs
201
- # is designed for external use.
202
- def _inputs(inputs=[], opts={}) # :nodoc:
203
- if inputs.is_a?(Hash)
204
- opts = inputs.merge(opts)
205
- inputs = []
206
- end
216
+ def inputs(inputs=[], opts={})
217
+ content_added do
218
+ if inputs.is_a?(Hash)
219
+ opts = inputs.merge(opts)
220
+ inputs = []
221
+ end
207
222
 
208
- form_opts = {}
209
- form_opts[:inputs_wrapper] = opts[:nested_inputs_wrapper] if opts[:nested_inputs_wrapper]
210
- TRANSFORMER_TYPES.each do |t|
211
- if opts.has_key?(t) && t != :inputs_wrapper
212
- form_opts[t] = opts[t]
223
+ form_opts = {}
224
+ form_opts[:inputs_wrapper] = opts[:nested_inputs_wrapper] if opts[:nested_inputs_wrapper]
225
+ TRANSFORMER_TYPES.each do |t|
226
+ if opts.has_key?(t) && t != :inputs_wrapper
227
+ form_opts[t] = opts[t]
228
+ end
213
229
  end
214
- end
215
230
 
216
- Forme.transform(:inputs_wrapper, opts, @opts, self, opts) do
217
- with_opts(form_opts) do
218
- inputs.each do |i|
219
- emit(input(*i))
231
+ Forme.transform(:inputs_wrapper, opts, @opts, self, opts) do
232
+ with_opts(form_opts) do
233
+ inputs.each do |i|
234
+ input(*i)
235
+ end
236
+ yield if block_given?
220
237
  end
221
- yield if block_given?
222
238
  end
223
239
  end
224
240
  end
@@ -255,35 +271,34 @@ module Forme
255
271
  end
256
272
 
257
273
  # Creates a +Tag+ associated to the receiver with the given arguments.
258
- # Add the tag to the the list of children for the currently open tag.
259
- # If a block is given, make this tag the currently open tag while inside
260
- # the block.
274
+ # If a block is given, yield to the block inside the generated tag.
275
+ # Returns the HTML added to the form by the addition of this tag.
261
276
  def tag(*a, &block)
262
- tag = _tag(*a)
263
- self << tag
264
- nest(tag, &block) if block
265
- tag
266
- end
267
-
268
- # Aliased for tag. Workaround for issue with rails plugin.
269
- def tag_(*a, &block) # :nodoc:
270
- tag(*a, &block)
277
+ content_added do
278
+ tag = _tag(*a)
279
+ if block
280
+ self << serialize_open(tag)
281
+ if children = tag.children
282
+ children.each{|child| self << child}
283
+ end
284
+ yield self
285
+ self << serialize_close(tag)
286
+ else
287
+ self << tag
288
+ end
289
+ end
271
290
  end
272
291
 
273
- # Creates a :submit +Input+ with the given opts, adding it to the list
274
- # of children for the currently open tag.
292
+ # Creates a :submit +Input+ with the given opts. Returns the generated
293
+ # HTML for the input.
275
294
  def button(opts={})
276
295
  opts = {:value=>opts} if opts.is_a?(String)
277
- input = _input(:submit, opts)
278
- self << input
279
- input
296
+ content_added{self << _input(:submit, opts)}
280
297
  end
281
298
 
282
- # Add the +Input+/+Tag+ instance given to the currently open tag.
299
+ # Add the +Input+/+Tag+ instance to the HTML buffer.
283
300
  def <<(tag)
284
- if n = @nesting.last
285
- n << tag
286
- end
301
+ @to_s << tag.to_s
287
302
  end
288
303
 
289
304
  # Calls the block for each object in objs, using with_obj with the given namespace
@@ -296,18 +311,11 @@ module Forme
296
311
  end
297
312
  end
298
313
 
299
- # Return a new string that will not be html escaped by the default serializer.
314
+ # Return a new string that will not be HTML escaped by the default serializer.
300
315
  def raw(s)
301
316
  Forme.raw(s)
302
317
  end
303
318
 
304
- # Marks the string as containing already escaped output. Returns string given
305
- # by default, but subclasses for specific web frameworks can handle automatic
306
- # html escaping by overriding this.
307
- def raw_output(s)
308
- s
309
- end
310
-
311
319
  # Temporarily override the given object and namespace for the form. Any given
312
320
  # namespaces are appended to the form's current namespace.
313
321
  def with_obj(obj, namespace=nil)
@@ -338,53 +346,40 @@ module Forme
338
346
  end
339
347
  end
340
348
 
341
- # Return array of hidden tags to use for this form,
342
- # or nil if the form does not have hidden tags added automatically.
343
- def hidden_form_tags(form_tag)
344
- if hidden_tags
345
- tags = []
346
- hidden_tags.each do |hidden_tag|
347
- hidden_tag = hidden_tag.call(form_tag) if hidden_tag.respond_to?(:call)
348
- tags.concat(parse_hidden_tags(hidden_tag))
349
- end
350
- tags
351
- end
352
- end
353
-
354
- # Handle various types of hidden tags for the form.
355
- def parse_hidden_tags(hidden_tag)
356
- case hidden_tag
357
- when Array
358
- hidden_tag
359
- when Tag, String
360
- [hidden_tag]
361
- when Hash
362
- hidden_tag.map{|k,v| _tag(:input, :type=>:hidden, :name=>k, :value=>v)}
363
- when nil
364
- []
365
- else
366
- raise Error, "unhandled hidden_tag response: #{hidden_tag.inspect}"
367
- end
368
- end
369
-
370
- # Make the given tag the currently open tag, and yield. After the
371
- # block returns, make the previously open tag the currently open
372
- # tag.
373
- def nest(tag)
374
- @nesting << tag
375
- yield self
376
- ensure
377
- @nesting.pop
349
+ # Return the HTML content added by the block.
350
+ def content_added
351
+ offset = @to_s.length
352
+ yield
353
+ @to_s[offset, @to_s.length]
378
354
  end
379
355
 
380
356
  # Return a serialized opening tag for the given tag.
381
357
  def serialize_open(tag)
382
- raw_output(serializer.serialize_open(tag)) if serializer.respond_to?(:serialize_open)
358
+ serializer.serialize_open(tag) if serializer.respond_to?(:serialize_open)
383
359
  end
384
360
 
385
361
  # Return a serialized closing tag for the given tag.
386
362
  def serialize_close(tag)
387
- raw_output(serializer.serialize_close(tag)) if serializer.respond_to?(:serialize_close)
363
+ serializer.serialize_close(tag) if serializer.respond_to?(:serialize_close)
364
+ end
365
+
366
+ # Call before hooks if defined.
367
+ def before_form_yield
368
+ # _before_post and _before hooks are only for internal use
369
+ opts[:_before_post].call(self) if opts[:_before_post] && post?
370
+ opts[:_before].call(self) if opts[:_before]
371
+
372
+ # before hook is for external use
373
+ opts[:before].call(self) if opts[:before]
374
+ end
375
+
376
+ # Call after hooks if defined.
377
+ def after_form_yield
378
+ # after hook is for external use
379
+ opts[:after].call(self) if opts[:after]
380
+
381
+ # _after hook is only for internal use
382
+ opts[:_after].call(self) if opts[:_after]
388
383
  end
389
384
  end
390
385
  end
data/lib/forme/input.rb CHANGED
@@ -32,7 +32,7 @@ module Forme
32
32
 
33
33
  # Return a string containing the serialized content of the receiver.
34
34
  def to_s
35
- form.raw_output(Forme.transform(:serializer, @opts, @form_opts, self))
35
+ Forme.transform(:serializer, @opts, @form_opts, self)
36
36
  end
37
37
 
38
38
  # Transform the receiver into a lower level +Tag+ form (or an array