forme 1.12.0 → 2.2.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +54 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +228 -206
  5. data/Rakefile +1 -7
  6. data/lib/forme/bs3.rb +23 -9
  7. data/lib/forme/erb.rb +13 -25
  8. data/lib/forme/form.rb +146 -149
  9. data/lib/forme/input.rb +1 -1
  10. data/lib/forme/rails.rb +39 -83
  11. data/lib/forme/raw.rb +2 -2
  12. data/lib/forme/tag.rb +3 -12
  13. data/lib/forme/template.rb +110 -0
  14. data/lib/forme/transformers/error_handler.rb +10 -10
  15. data/lib/forme/transformers/formatter.rb +32 -34
  16. data/lib/forme/transformers/helper.rb +0 -1
  17. data/lib/forme/transformers/inputs_wrapper.rb +4 -4
  18. data/lib/forme/version.rb +2 -2
  19. data/lib/forme.rb +13 -2
  20. data/lib/roda/plugins/forme.rb +1 -1
  21. data/lib/roda/plugins/forme_erubi_capture.rb +57 -0
  22. data/lib/roda/plugins/forme_route_csrf.rb +16 -34
  23. data/lib/roda/plugins/forme_set.rb +39 -76
  24. data/lib/sequel/plugins/forme.rb +45 -54
  25. data/lib/sequel/plugins/forme_i18n.rb +3 -1
  26. data/lib/sequel/plugins/forme_set.rb +2 -4
  27. data/spec/all.rb +1 -1
  28. data/spec/bs3_reference_spec.rb +291 -314
  29. data/spec/bs3_sequel_plugin_spec.rb +155 -155
  30. data/spec/bs3_spec.rb +247 -206
  31. data/spec/erb_helper.rb +69 -58
  32. data/spec/erubi_capture_helper.rb +198 -0
  33. data/spec/forme_coverage.rb +1 -0
  34. data/spec/forme_spec.rb +438 -377
  35. data/spec/rails_integration_spec.rb +21 -11
  36. data/spec/roda_integration_spec.rb +136 -70
  37. data/spec/sequel_helper.rb +3 -2
  38. data/spec/sequel_i18n_helper.rb +1 -1
  39. data/spec/sequel_i18n_plugin_spec.rb +6 -6
  40. data/spec/sequel_plugin_spec.rb +262 -150
  41. data/spec/sequel_set_plugin_spec.rb +9 -3
  42. data/spec/shared_erb_specs.rb +71 -0
  43. data/spec/sinatra_integration_spec.rb +31 -6
  44. data/spec/spec_helper.rb +21 -8
  45. metadata +8 -6
  46. data/lib/forme/erb_form.rb +0 -74
  47. data/lib/forme/sinatra.rb +0 -17
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  require 'rack/utils'
4
- require 'forme/erb_form'
4
+ require_relative '../../forme/template'
5
5
 
6
6
  class Roda
7
7
  module RodaPlugins
@@ -34,74 +34,6 @@ class Roda
34
34
  :missing_namespace=>"no content in expected namespace"
35
35
  }.freeze
36
36
 
37
- # Forme::Form subclass that adds hidden fields with metadata that can be used
38
- # to automatically process form submissions.
39
- class Form < ::Forme::ERB::Form
40
- def initialize(obj, opts=nil)
41
- super
42
- @forme_namespaces = @opts[:namespace]
43
- end
44
-
45
- # Try adding hidden fields to all forms
46
- def form(*)
47
- if block_given?
48
- super do |f|
49
- yield f
50
- hmac_hidden_fields
51
- end
52
- else
53
- t = super
54
- if tags = hmac_hidden_fields
55
- tags.each{|tag| t << tag}
56
- end
57
- t
58
- end
59
- end
60
-
61
- private
62
-
63
- # Add hidden fields with metadata, if the form has an object associated that
64
- # supports the forme_inputs method, and it includes inputs.
65
- def hmac_hidden_fields
66
- if (obj = @opts[:obj]) && obj.respond_to?(:forme_inputs) && (forme_inputs = obj.forme_inputs)
67
- columns = []
68
- valid_values = {}
69
-
70
- forme_inputs.each do |field, input|
71
- next unless col = obj.send(:forme_column_for_input, input)
72
- col = col.to_s
73
- columns << col
74
-
75
- next unless validation = obj.send(:forme_validation_for_input, field, input)
76
- validation[0] = validation[0].to_s
77
- has_nil = false
78
- validation[1] = validation[1].map do |v|
79
- has_nil ||= v.nil?
80
- v.to_s
81
- end
82
- validation[1] << nil if has_nil
83
- valid_values[col] = validation
84
- end
85
-
86
- return if columns.empty?
87
-
88
- data = {}
89
- data['columns'] = columns
90
- data['namespaces'] = @forme_namespaces
91
- data['csrf'] = @opts[:csrf]
92
- data['valid_values'] = valid_values unless valid_values.empty?
93
- data['form_version'] = @opts[:form_version] if @opts[:form_version]
94
-
95
- data = data.to_json
96
- tags = []
97
- tags << tag(:input, :type=>:hidden, :name=>:_forme_set_data, :value=>data)
98
- tags << tag(:input, :type=>:hidden, :name=>:_forme_set_data_hmac, :value=>OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, @opts[:roda].class.opts[:forme_set_hmac_secret], data))
99
- tags.each{|tag| emit(tag)}
100
- tags
101
- end
102
- end
103
- end
104
-
105
37
  module InstanceMethods
106
38
  # Return hash based on submitted parameters, with :values key
107
39
  # being submitted values for the object, and :validations key
@@ -151,15 +83,46 @@ class Roda
151
83
  raise Error, ERROR_MESSAGES[type]
152
84
  end
153
85
 
154
- # Use form class that adds hidden fields for metadata.
155
- def _forme_form_class
156
- Form
157
- end
158
-
159
86
  # Include a reference to the current scope to the form. This reference is needed
160
87
  # to correctly construct the HMAC.
161
- def _forme_form_options(options)
162
- options.merge!(:roda=>self)
88
+ def _forme_form_options(obj, attr, opts)
89
+ super
90
+
91
+ opts[:_after] = lambda do |form|
92
+ if (obj = form.opts[:obj]) && obj.respond_to?(:forme_inputs) && (forme_inputs = obj.forme_inputs)
93
+ columns = []
94
+ valid_values = {}
95
+
96
+ forme_inputs.each do |field, input|
97
+ next unless col = obj.send(:forme_column_for_input, input)
98
+ col = col.to_s
99
+ columns << col
100
+
101
+ next unless validation = obj.send(:forme_validation_for_input, field, input)
102
+ validation[0] = validation[0].to_s
103
+ has_nil = false
104
+ validation[1] = validation[1].map do |v|
105
+ has_nil ||= v.nil?
106
+ v.to_s
107
+ end
108
+ validation[1] << nil if has_nil
109
+ valid_values[col] = validation
110
+ end
111
+
112
+ return if columns.empty?
113
+
114
+ data = {}
115
+ data['columns'] = columns
116
+ data['namespaces'] = form.opts[:namespace]
117
+ data['csrf'] = form.opts[:csrf]
118
+ data['valid_values'] = valid_values unless valid_values.empty?
119
+ data['form_version'] = form.opts[:form_version] if form.opts[:form_version]
120
+
121
+ data = data.to_json
122
+ form.tag(:input, :type=>:hidden, :name=>:_forme_set_data, :value=>data)
123
+ form.tag(:input, :type=>:hidden, :name=>:_forme_set_data_hmac, :value=>OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, self.class.opts[:forme_set_hmac_secret], data))
124
+ end
125
+ end
163
126
  end
164
127
 
165
128
  # Internals of forme_parse_hmac and forme_set_hmac.
@@ -1,7 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require 'forme'
4
- require 'thread'
3
+ require_relative '../../forme'
5
4
 
6
5
  module Sequel # :nodoc:
7
6
  module Plugins # :nodoc:
@@ -37,7 +36,8 @@ module Sequel # :nodoc:
37
36
  # of the association for the form's +obj+. Inside the block, calls to
38
37
  # the +input+ and +inputs+ methods for the receiver treat the associated
39
38
  # object as the recevier's +obj+, using name and id attributes that work
40
- # with the Sequel +nested_attributes+ plugin.
39
+ # with the Sequel +nested_attributes+ plugin. Returns the HTML generated by
40
+ # the subform.
41
41
  #
42
42
  # The following options are currently supported:
43
43
  # :inputs :: Automatically call +inputs+ with the given values. Using
@@ -59,45 +59,50 @@ module Sequel # :nodoc:
59
59
  # :skip_primary_key :: Skip adding a hidden primary key field for existing
60
60
  # objects.
61
61
  def subform(association, opts={}, &block)
62
- nested_obj = opts.has_key?(:obj) ? opts[:obj] : obj.send(association)
63
- ref = obj.class.association_reflection(association)
64
- multiple = ref.returns_array?
65
- grid = opts[:grid]
66
- ns = "#{association}_attributes"
62
+ content_added do
63
+ nested_obj = opts.has_key?(:obj) ? opts[:obj] : obj.send(association)
64
+ ref = obj.class.association_reflection(association)
65
+ multiple = ref.returns_array?
66
+ grid = opts[:grid]
67
+ ns = "#{association}_attributes"
67
68
 
68
- contents = proc do
69
69
  send(multiple ? :each_obj : :with_obj, nested_obj, ns) do |no, i|
70
- emit(input(ref.associated_class.primary_key, :type=>:hidden, :label=>nil, :wrapper=>nil)) unless no.new? || opts[:skip_primary_key]
71
- options = opts.dup
72
- if grid
73
- options.delete(:legend)
74
- else
75
- if options.has_key?(:legend)
76
- if options[:legend].respond_to?(:call)
77
- options[:legend] = multiple ? options[:legend].call(no, i) : options[:legend].call(no)
78
- end
70
+ input(ref.associated_class.primary_key, :type=>:hidden, :label=>nil, :wrapper=>nil) unless no.new? || opts[:skip_primary_key]
71
+ end
72
+
73
+ contents = proc do
74
+ send(multiple ? :each_obj : :with_obj, nested_obj, ns) do |no, i|
75
+ options = opts.dup
76
+ if grid
77
+ options.delete(:legend)
79
78
  else
80
- if multiple
81
- options[:legend] = humanize("#{obj.model.send(:singularize, association)} ##{i+1}")
79
+ if options.has_key?(:legend)
80
+ if options[:legend].respond_to?(:call)
81
+ options[:legend] = multiple ? options[:legend].call(no, i) : options[:legend].call(no)
82
+ end
82
83
  else
83
- options[:legend] = humanize(association)
84
+ if multiple
85
+ options[:legend] = humanize("#{obj.model.send(:singularize, association)} ##{i+1}")
86
+ else
87
+ options[:legend] = humanize(association)
88
+ end
84
89
  end
85
90
  end
91
+ options[:subform] = true
92
+
93
+ inputs(options[:inputs]||[], options, &block)
86
94
  end
87
- options[:subform] = true
88
- _inputs(options[:inputs]||[], options, &block)
95
+ end
96
+
97
+ if grid
98
+ labels = opts.fetch(:labels){opts[:inputs].map{|l,| humanize(l)} if opts[:inputs]}
99
+ legend = opts.fetch(:legend){humanize(association)}
100
+ inputs_opts = opts[:inputs_opts] || {}
101
+ inputs(inputs_opts.merge(:inputs_wrapper=>:table, :nested_inputs_wrapper=>:tr, :wrapper=>:td, :labeler=>nil, :labels=>labels, :legend=>legend), &contents)
102
+ else
103
+ contents.call
89
104
  end
90
105
  end
91
-
92
- if grid
93
- labels = opts.fetch(:labels){opts[:inputs].map{|l, *| humanize(l)} if opts[:inputs]}
94
- legend = opts.fetch(:legend){humanize(association)}
95
- inputs_opts = opts[:inputs_opts] || {}
96
- inputs(inputs_opts.merge(:inputs_wrapper=>:table, :nested_inputs_wrapper=>:tr, :wrapper=>:td, :labeler=>nil, :labels=>labels, :legend=>legend), &contents)
97
- else
98
- contents.call
99
- end
100
- nil
101
106
  end
102
107
  end
103
108
 
@@ -358,9 +363,10 @@ module Sequel # :nodoc:
358
363
  opts[:as] = (sch[:allow_null] || opts[:required] == false) ? :select : :checkbox
359
364
  end
360
365
 
366
+ v = opts.has_key?(:value) ? opts[:value] : obj.send(field)
367
+
361
368
  case opts[:as]
362
369
  when :radio
363
- v = opts.has_key?(:value) ? opts[:value] : obj.send(field)
364
370
  true_value = opts[:true_value]||'t'
365
371
  false_value = opts[:false_value]||'f'
366
372
  opts[:options] = [[opts[:true_label]||'Yes', {:value=>true_value, :key_id=>'yes'}], [opts[:false_label]||'No', {:value=>false_value, :key_id=>'no'}]]
@@ -369,13 +375,12 @@ module Sequel # :nodoc:
369
375
  end
370
376
  _input(:radioset, opts)
371
377
  when :select
372
- v = opts[:value] || obj.send(field)
373
378
  opts[:value] = (v ? 't' : 'f') unless v.nil?
374
379
  opts[:add_blank] = true unless opts.has_key?(:add_blank)
375
380
  opts[:options] = [[opts[:true_label]||'True', opts[:true_value]||'t'], [opts[:false_label]||'False', opts[:false_value]||'f']]
376
381
  _input(:select, opts)
377
382
  else
378
- opts[:checked] = obj.send(field)
383
+ opts[:checked] = v
379
384
  opts[:value] = 't'
380
385
  _input(:checkbox, opts)
381
386
  end
@@ -419,9 +424,11 @@ module Sequel # :nodoc:
419
424
  end
420
425
  end
421
426
 
422
- # Use number inputs for integers.
427
+ # Use inputmode and pattern attributes for integers.
423
428
  def input_integer(sch)
424
- standard_input(:number)
429
+ opts[:attr][:inputmode] ||= 'numeric'
430
+ opts[:attr][:pattern] ||= '-?[0-9]*'
431
+ standard_input(:text)
425
432
  end
426
433
 
427
434
  # Use date inputs for dates.
@@ -452,21 +459,6 @@ module Sequel # :nodoc:
452
459
  end
453
460
  end
454
461
 
455
- # Helper module used for Sequel forms using ERB template integration. Necessary for
456
- # proper subform handling when using such forms with partials.
457
- module ERBSequelForm
458
- # Capture the inside of the inputs, injecting it into the template
459
- # if a block is given, or returning it as a string if not.
460
- def subform(*, &block)
461
- if block
462
- capture(block){super}
463
- else
464
- capture{super}
465
- end
466
- end
467
- end
468
- SinatraSequelForm = ERBSequelForm
469
-
470
462
  class Form < ::Forme::Form
471
463
  include SequelForm
472
464
  end
@@ -486,7 +478,6 @@ module Sequel # :nodoc:
486
478
  unless klass = MUTEX.synchronize{FORM_CLASSES[base]}
487
479
  klass = Class.new(base)
488
480
  klass.send(:include, SequelForm)
489
- klass.send(:include, ERBSequelForm) if defined?(::Forme::ERB::Form) && base == ::Forme::ERB::Form
490
481
  MUTEX.synchronize{FORM_CLASSES[base] = klass}
491
482
  end
492
483
  klass
@@ -1,5 +1,7 @@
1
+ # frozen-string-literal: true
2
+
1
3
  require 'i18n'
2
- require 'sequel/plugins/forme'
4
+ require_relative 'forme'
3
5
 
4
6
  module Sequel # :nodoc:
5
7
  module Plugins # :nodoc:
@@ -105,10 +105,8 @@ module Sequel # :nodoc:
105
105
  opts = input.opts
106
106
  return if SKIP_FORMATTERS.include?(opts.fetch(:formatter){input.form_opts[:formatter]})
107
107
 
108
- if attr = opts[:attr]
109
- name = attr[:name] || attr['name']
110
- end
111
- return unless name ||= opts[:name] || opts[:key]
108
+ attr = opts[:attr] || {}
109
+ return unless name ||= attr[:name] || attr['name'] || opts[:name] || opts[:key]
112
110
 
113
111
  # Pull out last component of the name if there is one
114
112
  column = name.to_s.chomp('[]')
data/spec/all.rb CHANGED
@@ -1,2 +1,2 @@
1
1
  $: << 'lib'
2
- Dir['./spec/*_spec.rb'].each{|f| require f}
2
+ Dir.new(File.dirname(__FILE__)).each{|f| require_relative f if f.end_with?('_spec.rb')}