forme 1.7.0 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|