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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c126daca3403b2140b2059d04c6ef405a11f8d56418ce4e971025ee8f861ec2
4
- data.tar.gz: 347c183344c4fe77f42753d0ec0515ce11065e648a9d37d14665b7d10fb090ed
3
+ metadata.gz: bd9180036757136c1db6fac34b523812491b687be4395d87ef09efee4066d85b
4
+ data.tar.gz: a627b8fdde2c50d73e28b9a7d9d66545de2df0e180dd06832beea3c4990db04c
5
5
  SHA512:
6
- metadata.gz: 220b3e73af81b0db1f4187ba2f65505b775780ff8b9459d3b95f139d5160fcaa3eacf5e2172b5f9e883c7f7fc9bf7b4bb23ef05b54abbf815fe2d481844a5038
7
- data.tar.gz: 5742f5edf32047920fb23f73bdd86a3c29823d1c2f9b0834718a304d157cabea627767448c534e9b2a0e21c0ab78dbb240c66692706bb68c59e84c1a9c618c2a
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)
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2017 Jeremy Evans
1
+ Copyright (c) 2011-2018 Jeremy Evans
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
@@ -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 +forme_iuput+,
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 form does not have an assocaited object, uses the first argument as the input
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
- == string
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
- = ERB Support
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 both the
695
- Sinatra and Roda web frameworks. It allows you to use the following API in your erb
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-4.1, but should hopefully work on Rails 3.0+.
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
- symbol used in order (e.g. <tt>{:year=>1970..2020}</tt>)
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
@@ -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
+
@@ -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
- unless opts.has_key?(:value)
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(:text, opts)
152
+ _input(type, opts)
149
153
  end
150
154
  else
151
155
  _input(field, opts)
@@ -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.
@@ -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 = '1.7.0'.freeze
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
@@ -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)
@@ -1,3 +1,2 @@
1
- require 'rubygems'
2
1
  $: << 'lib'
3
2
  Dir['./spec/*_spec.rb'].each{|f| require f}
@@ -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.new('10000.010')).to_s.must_equal '<div foo="10000.01"></div>'
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
@@ -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.new('10000.010')).to_s.must_equal '<div foo="10000.01"></div>'
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 'rack/csrf'
8
+ require 'tilt'
9
9
  rescue LoadError
10
- warn "unable to load roda or rack/csrf, skipping roda spec"
10
+ warn "unable to load roda or tilt, skipping roda specs"
11
11
  else
12
12
  begin
13
- require 'tilt/erubis'
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
- describe "Forme Roda ERB integration" do
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
- FormeRodaTest.app.call(@rack.merge('PATH_INFO'=>path))[2].each{|str| s << str}
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
@@ -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)
@@ -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.7.0
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-02-07 00:00:00.000000000 Z
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.3
224
+ rubygems_version: 2.7.6
223
225
  signing_key:
224
226
  specification_version: 4
225
227
  summary: HTML forms library