forme 1.12.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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')}