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 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