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.
- checksums.yaml +4 -4
- data/CHANGELOG +54 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +228 -206
- data/Rakefile +1 -7
- data/lib/forme/bs3.rb +23 -9
- data/lib/forme/erb.rb +13 -25
- data/lib/forme/form.rb +146 -149
- data/lib/forme/input.rb +1 -1
- data/lib/forme/rails.rb +39 -83
- data/lib/forme/raw.rb +2 -2
- data/lib/forme/tag.rb +3 -12
- data/lib/forme/template.rb +110 -0
- data/lib/forme/transformers/error_handler.rb +10 -10
- data/lib/forme/transformers/formatter.rb +32 -34
- data/lib/forme/transformers/helper.rb +0 -1
- data/lib/forme/transformers/inputs_wrapper.rb +4 -4
- data/lib/forme/version.rb +2 -2
- data/lib/forme.rb +13 -2
- data/lib/roda/plugins/forme.rb +1 -1
- data/lib/roda/plugins/forme_erubi_capture.rb +57 -0
- data/lib/roda/plugins/forme_route_csrf.rb +16 -34
- data/lib/roda/plugins/forme_set.rb +39 -76
- data/lib/sequel/plugins/forme.rb +45 -54
- data/lib/sequel/plugins/forme_i18n.rb +3 -1
- data/lib/sequel/plugins/forme_set.rb +2 -4
- data/spec/all.rb +1 -1
- data/spec/bs3_reference_spec.rb +291 -314
- data/spec/bs3_sequel_plugin_spec.rb +155 -155
- data/spec/bs3_spec.rb +247 -206
- data/spec/erb_helper.rb +69 -58
- data/spec/erubi_capture_helper.rb +198 -0
- data/spec/forme_coverage.rb +1 -0
- data/spec/forme_spec.rb +438 -377
- data/spec/rails_integration_spec.rb +21 -11
- data/spec/roda_integration_spec.rb +136 -70
- data/spec/sequel_helper.rb +3 -2
- data/spec/sequel_i18n_helper.rb +1 -1
- data/spec/sequel_i18n_plugin_spec.rb +6 -6
- data/spec/sequel_plugin_spec.rb +262 -150
- data/spec/sequel_set_plugin_spec.rb +9 -3
- data/spec/shared_erb_specs.rb +71 -0
- data/spec/sinatra_integration_spec.rb +31 -6
- data/spec/spec_helper.rb +21 -8
- metadata +8 -6
- data/lib/forme/erb_form.rb +0 -74
- 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
|
-
|
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(
|
162
|
-
|
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.
|
data/lib/sequel/plugins/forme.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
81
|
-
options[:legend]
|
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
|
-
|
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
|
-
|
88
|
-
|
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] =
|
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
|
427
|
+
# Use inputmode and pattern attributes for integers.
|
423
428
|
def input_integer(sch)
|
424
|
-
|
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
|
@@ -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
|
-
|
109
|
-
|
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
|
2
|
+
Dir.new(File.dirname(__FILE__)).each{|f| require_relative f if f.end_with?('_spec.rb')}
|