forme 1.7.0 → 1.8.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG +16 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +58 -12
- data/lib/forme/erb.rb +2 -68
- data/lib/forme/erb_form.rb +74 -0
- data/lib/forme/form.rb +6 -2
- data/lib/forme/rails.rb +1 -1
- data/lib/forme/transformers/formatter.rb +20 -0
- data/lib/forme/version.rb +15 -1
- data/lib/roda/plugins/forme_route_csrf.rb +60 -0
- data/lib/sequel/plugins/forme.rb +15 -1
- data/spec/all.rb +0 -1
- data/spec/bs3_spec.rb +1 -1
- data/spec/forme_spec.rb +33 -1
- data/spec/rails_integration_spec.rb +1 -1
- data/spec/roda_integration_spec.rb +84 -10
- data/spec/sequel_plugin_spec.rb +8 -0
- data/spec/spec_helper.rb +5 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd9180036757136c1db6fac34b523812491b687be4395d87ef09efee4066d85b
|
4
|
+
data.tar.gz: a627b8fdde2c50d73e28b9a7d9d66545de2df0e180dd06832beea3c4990db04c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 489e5bf2d0d1d3af91e85c36e864775395ff6aede61c591e519c951118add014491e6b9d1b01e10364408fafcc8cc51aeffdcf43a21ea8fe2f247e0932d6acd7
|
7
|
+
data.tar.gz: f6aa875870c75b068b2fd91846f2deba8e3f3304797c945895469e0903797ad0a9eb7a425b8185a3e707e13f30c02f3cb879a43c0d412b1c09f630d9eeefc526
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
=== 1.8.0 (2018-06-11)
|
2
|
+
|
3
|
+
* Add support for :errors form option for setting error information for multiple inputs, similar to :values form option (adam12) (#32)
|
4
|
+
|
5
|
+
* Add Roda forme_route_csrf plugin using route_csrf plugin for request-specific CSRF tokens (jeremyevans)
|
6
|
+
|
7
|
+
* Add forme_default_request_method as a method to check for object forms, setting the default form method (jeremyevans)
|
8
|
+
|
9
|
+
* Support :dasherize_data input option to convert underscores to dashes for :data hash symbol keys (janko-m) (#29)
|
10
|
+
|
11
|
+
* Omit labels for hidden inputs in Sequel plugin (janko-m) (#27)
|
12
|
+
|
13
|
+
* Allow use of :type option for specifying input type when using an associated object that doesn't respond to forme_input (janko-m) (#25)
|
14
|
+
|
15
|
+
* Ignore default values for Sequel inputs when type: :file option is used (janko-m) (#24)
|
16
|
+
|
1
17
|
=== 1.7.0 (2018-02-07)
|
2
18
|
|
3
19
|
* Have radioset and checkboxset inputs respect :error_attr option (jeremyevans)
|
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -242,15 +242,20 @@ object responds to +forme_input+, calls forme_input with the argument and option
|
|
242
242
|
f = Forme::Form.new(obj)
|
243
243
|
f.input(:field) # '<input id="obj_field" name="obj[field]" type="text" value="foo"/>'
|
244
244
|
|
245
|
-
If the form has an associated object, and that object does not respond to +
|
245
|
+
If the form has an associated object, and that object does not respond to +forme_input+,
|
246
246
|
calls the method on the object (or uses [] if the object is a hash), and uses the result
|
247
|
-
as the value:
|
247
|
+
as the value for a text input:
|
248
248
|
|
249
249
|
f = Forme::Form.new([:foo])
|
250
250
|
f.input(:first) # '<input id="first" name="first" type="text" value="foo"/>'
|
251
|
-
|
252
|
-
If the
|
253
|
-
type:
|
251
|
+
|
252
|
+
If the object does not respond to +forme_input+, you can change the type of the input
|
253
|
+
via the +:type+ option:
|
254
|
+
|
255
|
+
f = Forme::Form.new(obj)
|
256
|
+
f.input(:field, :type=>:email) # '<input id="obj_field" name="obj[field]" type="email" value="foo"/>'
|
257
|
+
|
258
|
+
If the form does not have an associated object, the first argument is used as the input type:
|
254
259
|
|
255
260
|
f = Forme::Form.new
|
256
261
|
f.input(:text) # '<input type="text" />'
|
@@ -419,7 +424,7 @@ these additional options to the #input method:
|
|
419
424
|
:true_label :: The value to use for the true label, 'Yes' by default.
|
420
425
|
:true_value :: The value to use for the true input, 't' by default.
|
421
426
|
|
422
|
-
|
427
|
+
=== string
|
423
428
|
|
424
429
|
:as :: Can be set to :textarea to use a textarea input. You can use the usual attributes hash or a stylesheet to
|
425
430
|
control the size of the textarea.
|
@@ -688,12 +693,45 @@ plugins:
|
|
688
693
|
|
689
694
|
forme_i18n :: Handles translations for labels using i18n.
|
690
695
|
|
691
|
-
=
|
696
|
+
= Roda Support
|
697
|
+
|
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
|
701
|
+
templates is the same:
|
702
|
+
|
703
|
+
<% form(@obj, :action=>'/foo') do |f| %>
|
704
|
+
<%= f.input(:field) %>
|
705
|
+
<% f.tag(:fieldset) do %>
|
706
|
+
<%= f.input(:field_two) %>
|
707
|
+
<% end %>
|
708
|
+
<% end %>
|
709
|
+
|
710
|
+
The forme_route_csrf plugin's +form+ method supports the following options
|
711
|
+
in addition to the default +Forme.form+ options:
|
712
|
+
|
713
|
+
:csrf :: Set to force whether a CSRF tag should be included. By default, a CSRF
|
714
|
+
tag is included if the form's method is one of the request methods checked
|
715
|
+
by the Roda route_csrf plugin.
|
716
|
+
:use_request_specific_token :: Set whether to force the use of a request specific
|
717
|
+
CSRF token. By default, uses a request specific
|
718
|
+
CSRF token unless the Roda route_csrf plugin has been
|
719
|
+
configured to support non-request specific tokens.
|
720
|
+
|
721
|
+
The forme plugin does not require any csrf plugin, but will transparently use
|
722
|
+
Rack::Csrf if it is available. If Rack::Csrf is available a CSRF tag if the form's
|
723
|
+
method is +POST+, with no configuration ability.
|
724
|
+
|
725
|
+
Both plugins also support the following option:
|
726
|
+
|
727
|
+
:output :: The object that the form output should be injected into when
|
728
|
+
injecting output (+@_out_buf+ by default).
|
729
|
+
|
730
|
+
= Sinatra Support
|
692
731
|
|
693
732
|
Forme ships with a ERB extension that you can get by <tt>require "forme/erb"</tt> and using
|
694
|
-
<tt>including Forme::ERB::Helper</tt>. This is tested to support ERB templates in
|
695
|
-
|
696
|
-
templates:
|
733
|
+
<tt>including Forme::ERB::Helper</tt>. This is tested to support ERB templates in Sinatra.
|
734
|
+
It allows you to use the following API in your erb templates:
|
697
735
|
|
698
736
|
<% form(@obj, :action=>'/foo') do |f| %>
|
699
737
|
<%= f.input(:field) %>
|
@@ -718,7 +756,7 @@ in your Rails forms:
|
|
718
756
|
<% end %>
|
719
757
|
<% end %>
|
720
758
|
|
721
|
-
This has been tested on Rails 3.2-
|
759
|
+
This has been tested on Rails 3.2-5.2.
|
722
760
|
|
723
761
|
= Input Types and Options
|
724
762
|
|
@@ -734,6 +772,9 @@ These options are supported by all of the input types:
|
|
734
772
|
:autofocus :: Set the autofocus attribute if true
|
735
773
|
:class :: A class to use. Unlike other options, this is combined with the
|
736
774
|
classes set in the :attr hash.
|
775
|
+
:dasherize_data :: Automatically replace underscores with hyphens for symbol
|
776
|
+
data attribute names in the +:data+ hash. Defaults to
|
777
|
+
+false+.
|
737
778
|
:data :: A hash of data-* attributes for the resulting tag. Keys in this hash
|
738
779
|
will have attributes created with data- prepended to the attribute name.
|
739
780
|
:disabled :: Set the disabled attribute if true
|
@@ -746,8 +787,11 @@ These options are supported by all of the input types:
|
|
746
787
|
:label :: Set a label, invoking the labeler
|
747
788
|
:labeler :: Set a custom labeler, overriding the form's default
|
748
789
|
:name :: The name attribute to use
|
790
|
+
:obj :: Set the form object, overriding the form's default
|
749
791
|
:placeholder :: The placeholder attribute to use
|
750
792
|
:required :: Set the required attribute if true
|
793
|
+
:type :: Override the type of the input when the form has an associated object
|
794
|
+
but the object does not respond to +forme_input+
|
751
795
|
:value :: The value attribute to use for input tags, the content of the textarea
|
752
796
|
for textarea tags, or the selected option(s) for select tags.
|
753
797
|
:wrapper :: Set a custom wrapper, overriding the form's default
|
@@ -779,7 +823,7 @@ creates multiple select options. Options:
|
|
779
823
|
(:date default: <tt>[:year, '-', :month, '-', :day]</tt>)
|
780
824
|
(:datetime default: <tt>[:year, '-', :month, '-', :day, ' ', :hour, ':', :minute, ':', :second]</tt>)
|
781
825
|
:select_options :: The options to use for the select boxes. Should be a hash keyed by the
|
782
|
-
|
826
|
+
symbol used in order (e.g. <tt>{:year=>1970..2020}</tt>)
|
783
827
|
|
784
828
|
=== :select
|
785
829
|
|
@@ -846,6 +890,8 @@ the defaults for Inputs created via the form:
|
|
846
890
|
|
847
891
|
:config :: The configuration to use, which automatically sets defaults
|
848
892
|
for the transformers to use.
|
893
|
+
:errors :: A Hash of errors from a previous form submission, used to set
|
894
|
+
default errors for inputs when the inputs use the :key option.
|
849
895
|
:error_handler :: Sets the default error_handler for the form's inputs
|
850
896
|
:helper :: Sets the default helper for the form's inputs
|
851
897
|
:formatter :: Sets the default formatter for the form's inputs
|
data/lib/forme/erb.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
|
-
require 'forme'
|
3
|
+
require 'forme/erb_form'
|
4
4
|
|
5
5
|
module Forme
|
6
6
|
module ERB
|
@@ -16,77 +16,11 @@ module Forme
|
|
16
16
|
|
17
17
|
# Add CSRF token tag by default for POST forms
|
18
18
|
add_hidden_tag do |tag|
|
19
|
-
if defined?(::Rack::Csrf) && (form = tag.form) && (env = form.opts[:env]) && env['rack.session'] && tag.attr[:method].to_s.upcase == 'POST'
|
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
20
|
{::Rack::Csrf.field=>::Rack::Csrf.token(env)}
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
# Subclass used when using Forme ERB integration.
|
25
|
-
# Handles integrating into the view template so that
|
26
|
-
# methods with blocks can inject strings into the output.
|
27
|
-
class Form < ::Forme::Form
|
28
|
-
# Template output object, where serialized output gets
|
29
|
-
# injected.
|
30
|
-
attr_reader :output
|
31
|
-
|
32
|
-
# Set the template output object when initializing.
|
33
|
-
def initialize(*)
|
34
|
-
super
|
35
|
-
@output = @opts[:output] ? @opts[:output] : String.new
|
36
|
-
end
|
37
|
-
|
38
|
-
# Serialize the tag and inject it into the output.
|
39
|
-
def emit(tag)
|
40
|
-
output << tag.to_s
|
41
|
-
end
|
42
|
-
|
43
|
-
# Capture the inside of the inputs, injecting it into the template
|
44
|
-
# if a block is given, or returning it as a string if not.
|
45
|
-
def inputs(*a, &block)
|
46
|
-
if block
|
47
|
-
capture(block){super}
|
48
|
-
else
|
49
|
-
capture{super}
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# Capture the inside of the form, injecting it into the template if
|
54
|
-
# a block is given, or returning it as a string if not.
|
55
|
-
def form(*a, &block)
|
56
|
-
if block
|
57
|
-
capture(block){super}
|
58
|
-
else
|
59
|
-
super
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
# If a block is given, inject an opening tag into the
|
64
|
-
# output, inject any given children into the output, yield to the
|
65
|
-
# block, inject a closing tag into the output.
|
66
|
-
# If a block is not given, just return the tag created.
|
67
|
-
def tag(type, attr={}, children=[], &block)
|
68
|
-
tag = _tag(type, attr, children)
|
69
|
-
if block
|
70
|
-
capture(block) do
|
71
|
-
emit(serialize_open(tag))
|
72
|
-
Array(tag.children).each{|c| emit(c)}
|
73
|
-
yield self
|
74
|
-
emit(serialize_close(tag))
|
75
|
-
end
|
76
|
-
else
|
77
|
-
tag
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def capture(block=String.new) # :nodoc:
|
82
|
-
buf_was, @output = @output, block.is_a?(Proc) ? (eval("@_out_buf", block.binding) || @output) : block
|
83
|
-
yield
|
84
|
-
ret = @output
|
85
|
-
@output = buf_was
|
86
|
-
ret
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
24
|
# This is the module used to add the Forme integration
|
91
25
|
# to ERB.
|
92
26
|
module Helper
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'forme'
|
4
|
+
|
5
|
+
module Forme
|
6
|
+
module ERB
|
7
|
+
# Subclass used when using Forme ERB integration.
|
8
|
+
# Handles integrating into the view template so that
|
9
|
+
# methods with blocks can inject strings into the output.
|
10
|
+
class Form < ::Forme::Form
|
11
|
+
# Template output object, where serialized output gets
|
12
|
+
# injected.
|
13
|
+
attr_reader :output
|
14
|
+
|
15
|
+
# Set the template output object when initializing.
|
16
|
+
def initialize(*)
|
17
|
+
super
|
18
|
+
@output = @opts[:output] ? @opts[:output] : String.new
|
19
|
+
end
|
20
|
+
|
21
|
+
# Serialize the tag and inject it into the output.
|
22
|
+
def emit(tag)
|
23
|
+
output << tag.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
# Capture the inside of the inputs, injecting it into the template
|
27
|
+
# if a block is given, or returning it as a string if not.
|
28
|
+
def inputs(*a, &block)
|
29
|
+
if block
|
30
|
+
capture(block){super}
|
31
|
+
else
|
32
|
+
capture{super}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Capture the inside of the form, injecting it into the template if
|
37
|
+
# a block is given, or returning it as a string if not.
|
38
|
+
def form(*a, &block)
|
39
|
+
if block
|
40
|
+
capture(block){super}
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# If a block is given, inject an opening tag into the
|
47
|
+
# output, inject any given children into the output, yield to the
|
48
|
+
# block, inject a closing tag into the output.
|
49
|
+
# If a block is not given, just return the tag created.
|
50
|
+
def tag(type, attr={}, children=[], &block)
|
51
|
+
tag = _tag(type, attr, children)
|
52
|
+
if block
|
53
|
+
capture(block) do
|
54
|
+
emit(serialize_open(tag))
|
55
|
+
Array(tag.children).each{|c| emit(c)}
|
56
|
+
yield self
|
57
|
+
emit(serialize_close(tag))
|
58
|
+
end
|
59
|
+
else
|
60
|
+
tag
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def capture(block=String.new) # :nodoc:
|
65
|
+
buf_was, @output = @output, block.is_a?(Proc) ? (eval("@_out_buf", block.binding) || @output) : block
|
66
|
+
yield
|
67
|
+
ret = @output
|
68
|
+
@output = buf_was
|
69
|
+
ret
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
data/lib/forme/form.rb
CHANGED
@@ -103,6 +103,9 @@ module Forme
|
|
103
103
|
|
104
104
|
# Create a form tag with the given attributes.
|
105
105
|
def form(attr={}, &block)
|
106
|
+
if obj && !attr[:method] && !attr['method'] && obj.respond_to?(:forme_default_request_method)
|
107
|
+
attr = attr.merge('method'=>obj.forme_default_request_method)
|
108
|
+
end
|
106
109
|
tag(:form, attr, method(:hidden_form_tags), &block)
|
107
110
|
end
|
108
111
|
|
@@ -138,14 +141,15 @@ module Forme
|
|
138
141
|
else
|
139
142
|
opts = opts.dup
|
140
143
|
opts[:key] = field unless opts.has_key?(:key)
|
141
|
-
|
144
|
+
type = opts.delete(:type) || :text
|
145
|
+
unless opts.has_key?(:value) || type == :file
|
142
146
|
opts[:value] = if obj.is_a?(Hash)
|
143
147
|
obj[field]
|
144
148
|
else
|
145
149
|
obj.send(field)
|
146
150
|
end
|
147
151
|
end
|
148
|
-
_input(
|
152
|
+
_input(type, opts)
|
149
153
|
end
|
150
154
|
else
|
151
155
|
_input(field, opts)
|
data/lib/forme/rails.rb
CHANGED
@@ -20,7 +20,7 @@ module Forme
|
|
20
20
|
|
21
21
|
# Add CSRF token tag by default for POST forms
|
22
22
|
add_hidden_tag do |tag|
|
23
|
-
if (form = tag.form) && (template = form.template) && template.protect_against_forgery? && tag.attr[:method].to_s.upcase == 'POST'
|
23
|
+
if (form = tag.form) && (template = form.template) && template.protect_against_forgery? && (tag.attr[:method] || tag.attr['method']).to_s.upcase == 'POST'
|
24
24
|
{template.request_forgery_protection_token=>template.form_authenticity_token}
|
25
25
|
end
|
26
26
|
end
|
@@ -316,12 +316,14 @@ module Forme
|
|
316
316
|
copy_options_to_attributes(ATTRIBUTE_OPTIONS)
|
317
317
|
copy_boolean_options_to_attributes(ATTRIBUTE_BOOLEAN_OPTIONS)
|
318
318
|
handle_key_option
|
319
|
+
handle_errors_option
|
319
320
|
|
320
321
|
Forme.attr_classes(@attr, @opts[:class]) if @opts.has_key?(:class)
|
321
322
|
Forme.attr_classes(@attr, 'error') if @opts[:error]
|
322
323
|
|
323
324
|
if data = opts[:data]
|
324
325
|
data.each do |k, v|
|
326
|
+
k = k.to_s.tr("_", "-") if k.is_a?(Symbol) && input.opts[:dasherize_data]
|
325
327
|
sym = :"data-#{k}"
|
326
328
|
@attr[sym] = v unless @attr.has_key?(sym)
|
327
329
|
end
|
@@ -347,6 +349,14 @@ module Forme
|
|
347
349
|
end
|
348
350
|
end
|
349
351
|
|
352
|
+
def handle_errors_option
|
353
|
+
if key = @opts[:key]
|
354
|
+
if !@attr.has_key?(:error) && !@attr.has_key?("error") && (errors = @form.opts[:errors])
|
355
|
+
set_error_from_namespaced_errors(namespaces, errors, key)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
350
360
|
# Array of namespaces to use for the input
|
351
361
|
def namespaces
|
352
362
|
input.form_opts[:namespace]
|
@@ -385,6 +395,16 @@ module Forme
|
|
385
395
|
@attr[:value] = values.fetch(key){values.fetch(key.to_s){return}}
|
386
396
|
end
|
387
397
|
|
398
|
+
def set_error_from_namespaced_errors(namespaces, errors, key)
|
399
|
+
namespaces.each do |ns|
|
400
|
+
e = errors[ns] || errors[ns.to_s]
|
401
|
+
return unless e
|
402
|
+
errors = e
|
403
|
+
end
|
404
|
+
|
405
|
+
@opts[:error] = errors.fetch(key){errors.fetch(key.to_s){return}}
|
406
|
+
end
|
407
|
+
|
388
408
|
# If :optgroups option is present, iterate over each of the groups
|
389
409
|
# inside of it and create options for each group. Otherwise, if
|
390
410
|
# :options option present, iterate over it and create options.
|
data/lib/forme/version.rb
CHANGED
@@ -1,8 +1,22 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
3
|
module Forme
|
4
|
+
# The major version of Forme, updated only for major changes that are
|
5
|
+
# likely to require modification to apps using Forme.
|
6
|
+
MAJOR = 1
|
7
|
+
|
8
|
+
# The minor version of Forme, updated for new feature releases of Forme.
|
9
|
+
MINOR = 8
|
10
|
+
|
11
|
+
# The patch version of Forme, updated only for bug fixes from the last
|
12
|
+
# feature release.
|
13
|
+
TINY = 0
|
14
|
+
|
4
15
|
# Version constant, use <tt>Forme.version</tt> instead.
|
5
|
-
VERSION =
|
16
|
+
VERSION = "#{MAJOR}.#{MINOR}.#{TINY}".freeze
|
17
|
+
|
18
|
+
# The full version of Forme as a number (1.8.0 => 10800)
|
19
|
+
VERSION_NUMBER = MAJOR*10000 + MINOR*100 + TINY
|
6
20
|
|
7
21
|
# Returns the version as a frozen string (e.g. '0.1.0')
|
8
22
|
def self.version
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'forme/erb_form'
|
4
|
+
|
5
|
+
class Roda
|
6
|
+
module RodaPlugins
|
7
|
+
module FormeRouteCsrf
|
8
|
+
# Require the render plugin, since forme template integration
|
9
|
+
# only makes sense with it.
|
10
|
+
def self.load_dependencies(app)
|
11
|
+
app.plugin :render
|
12
|
+
app.plugin :route_csrf
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
# Create a +Form+ object tied to the current output buffer,
|
17
|
+
# using the standard ERB hidden tags.
|
18
|
+
def form(obj=nil, attr={}, opts={}, &block)
|
19
|
+
if obj.is_a?(Hash)
|
20
|
+
attribs = obj
|
21
|
+
options = attr = attr.dup
|
22
|
+
else
|
23
|
+
attribs = attr
|
24
|
+
options = opts = opts.dup
|
25
|
+
end
|
26
|
+
|
27
|
+
apply_csrf = options[:csrf]
|
28
|
+
|
29
|
+
if apply_csrf || apply_csrf.nil?
|
30
|
+
unless method = attribs[:method] || attribs['method']
|
31
|
+
if obj && !obj.is_a?(Hash) && obj.respond_to?(:forme_default_request_method)
|
32
|
+
method = obj.forme_default_request_method
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
if apply_csrf.nil?
|
38
|
+
apply_csrf = csrf_options[:check_request_methods].include?(method.to_s.upcase)
|
39
|
+
end
|
40
|
+
|
41
|
+
if apply_csrf
|
42
|
+
token = if options.fetch(:use_request_specific_token){use_request_specific_csrf_tokens?}
|
43
|
+
csrf_token(csrf_path(attribs[:action]), method)
|
44
|
+
else
|
45
|
+
csrf_token
|
46
|
+
end
|
47
|
+
|
48
|
+
options[:hidden_tags] ||= []
|
49
|
+
options[:hidden_tags] += [{csrf_field=>token}]
|
50
|
+
end
|
51
|
+
|
52
|
+
options[:output] = @_out_buf if block
|
53
|
+
::Forme::ERB::Form.form(obj, attr, opts, &block)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
register_plugin(:forme_route_csrf, FormeRouteCsrf)
|
59
|
+
end
|
60
|
+
end
|
data/lib/sequel/plugins/forme.rb
CHANGED
@@ -21,7 +21,6 @@ module Sequel # :nodoc:
|
|
21
21
|
# Use the post method by default for Sequel forms, unless
|
22
22
|
# overridden with the :method attribute.
|
23
23
|
def form(attr={}, &block)
|
24
|
-
attr = {:method=>:post}.merge(attr)
|
25
24
|
attr[:class] = ::Forme.merge_classes(attr[:class], "forme", obj.forme_namespace)
|
26
25
|
super(attr, &block)
|
27
26
|
end
|
@@ -382,10 +381,21 @@ module Sequel # :nodoc:
|
|
382
381
|
|
383
382
|
# Use a file type for blobs.
|
384
383
|
def input_blob(sch)
|
384
|
+
input_file(sch)
|
385
|
+
end
|
386
|
+
|
387
|
+
# Ignore any default values for file inputs.
|
388
|
+
def input_file(sch)
|
385
389
|
opts[:value] = nil
|
386
390
|
standard_input(:file)
|
387
391
|
end
|
388
392
|
|
393
|
+
# Use hidden inputs without labels.
|
394
|
+
def input_hidden(sch)
|
395
|
+
opts[:label] = nil
|
396
|
+
standard_input(:hidden)
|
397
|
+
end
|
398
|
+
|
389
399
|
# Use the text type by default for other cases not handled.
|
390
400
|
def input_string(sch)
|
391
401
|
if opts[:as] == :textarea
|
@@ -482,6 +492,10 @@ module Sequel # :nodoc:
|
|
482
492
|
SequelInput.new(self, form, field, opts).input
|
483
493
|
end
|
484
494
|
|
495
|
+
def forme_default_request_method
|
496
|
+
'post'
|
497
|
+
end
|
498
|
+
|
485
499
|
# Use the underscored model name as the default namespace.
|
486
500
|
def forme_namespace
|
487
501
|
model.send(:underscore, model.name)
|
data/spec/all.rb
CHANGED
data/spec/bs3_spec.rb
CHANGED
@@ -604,7 +604,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
604
604
|
end
|
605
605
|
|
606
606
|
it "should format bigdecimals in standard notation" do
|
607
|
-
@f.tag(:div, :foo=>BigDecimal
|
607
|
+
@f.tag(:div, :foo=>BigDecimal('10000.010')).to_s.must_equal '<div foo="10000.01"></div>'
|
608
608
|
end
|
609
609
|
|
610
610
|
it "inputs should accept a :wrapper option to use a custom wrapper" do
|
data/spec/forme_spec.rb
CHANGED
@@ -114,6 +114,18 @@ describe "Forme plain forms" do
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
+
it "should consider form's :errors hash based on the :key option" do
|
118
|
+
@f.opts[:errors] = { 'foo' => 'must be present' }
|
119
|
+
@f.input(:text, :key=>"foo").to_s.must_equal "<input class=\"error\" id=\"foo\" name=\"foo\" type=\"text\"/><span class=\"error_message\">must be present</span>"
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should consider form's :errors hash based on the :key option when using namespaces" do
|
123
|
+
@f.opts[:errors] = { 'bar' => { 'foo' => 'must be present' } }
|
124
|
+
@f.with_opts(:namespace=>['bar']) do
|
125
|
+
@f.input(:text, :key=>"foo").to_s.must_equal "<input class=\"error\" id=\"bar_foo\" name=\"bar[foo]\" type=\"text\"/><span class=\"error_message\">must be present</span>"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
117
129
|
it "should support a with_obj method that changes the object and namespace for the given block" do
|
118
130
|
@f.with_obj([:a, :c], 'bar') do
|
119
131
|
@f.input(:first).to_s.must_equal '<input id="bar_first" name="bar[first]" type="text" value="a"/>'
|
@@ -195,6 +207,14 @@ describe "Forme plain forms" do
|
|
195
207
|
@f.input(:text, :data=>{:bar=>"foo"}).to_s.must_equal '<input data-bar="foo" type="text"/>'
|
196
208
|
end
|
197
209
|
|
210
|
+
it "should replace underscores with hyphens in symbol :data keys when :dasherize_data is set" do
|
211
|
+
@f.input(:text, :data=>{:foo_bar=>"baz"}).to_s.must_equal '<input data-foo_bar="baz" type="text"/>'
|
212
|
+
@f.input(:text, :data=>{"foo_bar"=>"baz"}).to_s.must_equal '<input data-foo_bar="baz" type="text"/>'
|
213
|
+
|
214
|
+
@f.input(:text, :data=>{:foo_bar=>"baz"}, :dasherize_data => true).to_s.must_equal '<input data-foo-bar="baz" type="text"/>'
|
215
|
+
@f.input(:text, :data=>{"foo_bar"=>"baz"}, :dasherize_data => true).to_s.must_equal '<input data-foo_bar="baz" type="text"/>'
|
216
|
+
end
|
217
|
+
|
198
218
|
it "should not have standard options override the :attr option" do
|
199
219
|
@f.input(:text, :name=>:bar, :attr=>{:name=>"foo"}).to_s.must_equal '<input name="foo" type="text"/>'
|
200
220
|
end
|
@@ -644,7 +664,7 @@ describe "Forme plain forms" do
|
|
644
664
|
end
|
645
665
|
|
646
666
|
it "should format bigdecimals in standard notation" do
|
647
|
-
@f.tag(:div, :foo=>BigDecimal
|
667
|
+
@f.tag(:div, :foo=>BigDecimal('10000.010')).to_s.must_equal '<div foo="10000.01"></div>'
|
648
668
|
end
|
649
669
|
|
650
670
|
it "inputs should accept a :wrapper option to use a custom wrapper" do
|
@@ -1082,6 +1102,18 @@ describe "Forme object forms" do
|
|
1082
1102
|
it "should be able to turn off obj handling per input using :obj=>nil option" do
|
1083
1103
|
Forme::Form.new([:foo]).input(:checkbox, :name=>"foo", :hidden_value=>"no", :obj=>nil).to_s.must_equal '<input name="foo" type="hidden" value="no"/><input name="foo" type="checkbox"/>'
|
1084
1104
|
end
|
1105
|
+
|
1106
|
+
it "should be able to change input type" do
|
1107
|
+
Forme::Form.new([:foo]).input(:first, :type=>:email).to_s.must_equal '<input id="first" name="first" type="email" value="foo"/>'
|
1108
|
+
end
|
1109
|
+
|
1110
|
+
it "should not have default value for file input" do
|
1111
|
+
Forme::Form.new([:foo]).input(:first, :type=>:file).to_s.must_equal '<input id="first" name="first" type="file"/>'
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
it "should be able to set value for file input" do
|
1115
|
+
Forme::Form.new([:foo]).input(:first, :type=>:file, :value=>"foo").to_s.must_equal '<input id="first" name="first" type="file" value="foo"/>'
|
1116
|
+
end
|
1085
1117
|
end
|
1086
1118
|
|
1087
1119
|
describe "Forme.form DSL" do
|
@@ -23,7 +23,7 @@ class FormeRails < Rails::Application
|
|
23
23
|
config.middleware.delete(ActionDispatch::ShowExceptions)
|
24
24
|
config.middleware.delete(Rack::Lock)
|
25
25
|
config.secret_key_base = 'foo'*15
|
26
|
-
config.secret_token = 'secret token'*15
|
26
|
+
config.secret_token = 'secret token'*15 if Rails.respond_to?(:version) && Rails.version < '5.2'
|
27
27
|
config.eager_load = true
|
28
28
|
initialize!
|
29
29
|
end
|
@@ -5,41 +5,115 @@ require File.join(File.dirname(File.expand_path(__FILE__)), 'erb_helper.rb')
|
|
5
5
|
require 'rubygems'
|
6
6
|
begin
|
7
7
|
require 'roda'
|
8
|
-
require '
|
8
|
+
require 'tilt'
|
9
9
|
rescue LoadError
|
10
|
-
warn "unable to load roda or
|
10
|
+
warn "unable to load roda or tilt, skipping roda specs"
|
11
11
|
else
|
12
12
|
begin
|
13
|
-
require 'tilt/
|
13
|
+
require 'tilt/erubi'
|
14
14
|
rescue LoadError
|
15
|
-
require 'tilt/erb'
|
16
15
|
begin
|
17
|
-
require 'erubis'
|
16
|
+
require 'tilt/erubis'
|
18
17
|
rescue LoadError
|
19
|
-
require 'erb'
|
18
|
+
require 'tilt/erb'
|
20
19
|
end
|
21
20
|
end
|
21
|
+
|
22
22
|
class FormeRodaTest < Roda
|
23
|
-
plugin :forme
|
24
23
|
use Rack::Session::Cookie, :secret => "__a_very_long_string__"
|
25
|
-
use Rack::Csrf
|
26
24
|
|
27
25
|
def erb(s, opts={})
|
28
26
|
render(opts.merge(:inline=>s))
|
29
27
|
end
|
30
28
|
|
31
29
|
route do |r|
|
30
|
+
r.get 'use_request_specific_token', :use do |use|
|
31
|
+
render :inline=>"[#{Base64.strict_encode64(send(:csrf_secret))}]<%= form({:method=>:post}, {:use_request_specific_token=>#{use == '1'}}) %>"
|
32
|
+
end
|
33
|
+
r.get 'hidden_tags' do |use|
|
34
|
+
render :inline=>"<%= form({:method=>:post}, {:hidden_tags=>[{:foo=>'bar'}]}) %>"
|
35
|
+
end
|
36
|
+
r.get 'csrf', :use do |use|
|
37
|
+
render :inline=>"<%= form({:method=>:post}, {:csrf=>#{use == '1'}}) %>"
|
38
|
+
end
|
32
39
|
instance_exec(r, &ERB_BLOCK)
|
33
40
|
end
|
34
41
|
end
|
35
42
|
|
36
|
-
|
43
|
+
begin
|
44
|
+
require 'rack/csrf'
|
45
|
+
rescue LoadError
|
46
|
+
warn "unable to load rack/csrf, skipping roda csrf plugin spec"
|
47
|
+
else
|
48
|
+
describe "Forme Roda ERB integration with roda forme and csrf plugins" do
|
49
|
+
app = FormeRodaCSRFTest = Class.new(FormeRodaTest)
|
50
|
+
app.plugin :csrf
|
51
|
+
app.plugin :forme
|
52
|
+
|
37
53
|
def sin_get(path)
|
38
54
|
s = String.new
|
39
|
-
|
55
|
+
FormeRodaCSRFTest.app.call(@rack.merge('PATH_INFO'=>path))[2].each{|str| s << str}
|
40
56
|
s.gsub(/\s+/, ' ').strip
|
41
57
|
end
|
42
58
|
|
43
59
|
include FormeErbSpecs
|
44
60
|
end
|
45
61
|
end
|
62
|
+
|
63
|
+
begin
|
64
|
+
require 'roda/plugins/route_csrf'
|
65
|
+
rescue LoadError
|
66
|
+
warn "unable to load roda/plugins/route_csrf, skipping forme_route_csrf plugin spec"
|
67
|
+
else
|
68
|
+
[{}, {:require_request_specific_tokens=>false}].each do |plugin_opts|
|
69
|
+
describe "Forme Roda ERB integration with roda forme_route_csrf and route_csrf plugin with #{plugin_opts}" do
|
70
|
+
app = Class.new(FormeRodaTest)
|
71
|
+
app.plugin :forme_route_csrf
|
72
|
+
app.plugin :route_csrf, plugin_opts
|
73
|
+
|
74
|
+
define_method(:sin_get) do |path|
|
75
|
+
s = String.new
|
76
|
+
app.call(@rack.merge('PATH_INFO'=>path))[2].each{|str| s << str}
|
77
|
+
s.gsub(/\s+/, ' ').strip
|
78
|
+
end
|
79
|
+
|
80
|
+
include FormeErbSpecs
|
81
|
+
|
82
|
+
it "should handle the :hidden_tags option" do
|
83
|
+
output = sin_get('/use_request_specific_token/1')
|
84
|
+
output =~ /\[([^\]]+)\].*?value=\"([^\"]+)\"/
|
85
|
+
secret = $1
|
86
|
+
token = $2
|
87
|
+
app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/1', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal true
|
88
|
+
app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/2', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal false
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should handle the :use_request_specific_token => true option" do
|
92
|
+
output = sin_get('/use_request_specific_token/1')
|
93
|
+
output =~ /\[([^\]]+)\].*?value=\"([^\"]+)\"/
|
94
|
+
secret = $1
|
95
|
+
token = $2
|
96
|
+
app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/1', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal true
|
97
|
+
app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/2', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal false
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should handle the :use_request_specific_token => false option" do
|
101
|
+
output = sin_get('/use_request_specific_token/0')
|
102
|
+
output =~ /\[([^\]]+)\].*?value=\"([^\"]+)\"/
|
103
|
+
secret = $1
|
104
|
+
token = $2
|
105
|
+
app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/0', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal(plugin_opts.empty? ? false : true)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should handle the :hidden_tags option" do
|
109
|
+
sin_get('/hidden_tags').must_include 'name="foo" type="hidden" value="bar"'
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should handle the :csrf option" do
|
113
|
+
sin_get('/csrf/1').must_include '<input name="_csrf" type="hidden" value="'
|
114
|
+
sin_get('/csrf/0').wont_include '<input name="_csrf" type="hidden" value="'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/spec/sequel_plugin_spec.rb
CHANGED
@@ -43,6 +43,10 @@ describe "Forme Sequel::Model forms" do
|
|
43
43
|
@b.input(:name, :type=>:textarea).to_s.must_equal '<label>Name: <textarea id="album_name" name="album[name]">b</textarea></label>'
|
44
44
|
@c.input(:name, :type=>:textarea).to_s.must_equal '<label>Name: <textarea id="album_name" name="album[name]">c</textarea></label>'
|
45
45
|
end
|
46
|
+
|
47
|
+
it "should not include labels for hidden inputs" do
|
48
|
+
@b.input(:name, :type=>:hidden).to_s.must_equal '<input id="album_name" name="album[name]" type="hidden" value="b"/>'
|
49
|
+
end
|
46
50
|
|
47
51
|
it "should use number inputs for integers" do
|
48
52
|
@b.input(:copies_sold).to_s.must_equal '<label>Copies sold: <input id="album_copies_sold" name="album[copies_sold]" type="number" value="10"/></label>'
|
@@ -61,6 +65,10 @@ describe "Forme Sequel::Model forms" do
|
|
61
65
|
@b.input(:created_at).to_s.must_equal '<label>Created at: <input id="album_created_at" name="album[created_at]" type="datetime-local" value="2011-06-05T00:00:00.000"/></label>'
|
62
66
|
end
|
63
67
|
|
68
|
+
it "should use file inputs without value" do
|
69
|
+
@b.input(:name, :type=>:file).to_s.must_equal '<label>Name: <input id="album_name" name="album[name]" type="file"/></label>'
|
70
|
+
end
|
71
|
+
|
64
72
|
it "should include type as wrapper class" do
|
65
73
|
@ab.values[:created_at] = DateTime.new(2011, 6, 5)
|
66
74
|
f = Forme::Form.new(@ab, :wrapper=>:li)
|
data/spec/spec_helper.rb
CHANGED
@@ -2,7 +2,7 @@ $:.unshift(File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'li
|
|
2
2
|
|
3
3
|
if ENV['WARNING']
|
4
4
|
require 'warning'
|
5
|
-
Warning.ignore(:missing_ivar)
|
5
|
+
Warning.ignore([:missing_ivar, :not_reached])
|
6
6
|
end
|
7
7
|
|
8
8
|
if ENV['COVERAGE']
|
@@ -11,4 +11,8 @@ if ENV['COVERAGE']
|
|
11
11
|
end
|
12
12
|
|
13
13
|
require 'forme'
|
14
|
+
|
15
|
+
require 'rubygems'
|
16
|
+
ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
|
17
|
+
gem 'minitest'
|
14
18
|
require 'minitest/autorun'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forme
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-06-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -158,6 +158,7 @@ files:
|
|
158
158
|
- lib/forme.rb
|
159
159
|
- lib/forme/bs3.rb
|
160
160
|
- lib/forme/erb.rb
|
161
|
+
- lib/forme/erb_form.rb
|
161
162
|
- lib/forme/form.rb
|
162
163
|
- lib/forme/input.rb
|
163
164
|
- lib/forme/rails.rb
|
@@ -173,6 +174,7 @@ files:
|
|
173
174
|
- lib/forme/transformers/wrapper.rb
|
174
175
|
- lib/forme/version.rb
|
175
176
|
- lib/roda/plugins/forme.rb
|
177
|
+
- lib/roda/plugins/forme_route_csrf.rb
|
176
178
|
- lib/sequel/plugins/forme.rb
|
177
179
|
- lib/sequel/plugins/forme_i18n.rb
|
178
180
|
- lib/sequel/plugins/forme_set.rb
|
@@ -219,7 +221,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
219
221
|
version: '0'
|
220
222
|
requirements: []
|
221
223
|
rubyforge_project:
|
222
|
-
rubygems_version: 2.7.
|
224
|
+
rubygems_version: 2.7.6
|
223
225
|
signing_key:
|
224
226
|
specification_version: 4
|
225
227
|
summary: HTML forms library
|