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