forme 1.8.0 → 1.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +50 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +181 -9
- data/lib/forme/bs3.rb +1 -1
- data/lib/forme/form.rb +1 -1
- data/lib/forme/transformers/error_handler.rb +46 -1
- data/lib/forme/transformers/formatter.rb +57 -43
- data/lib/forme/transformers/inputs_wrapper.rb +2 -2
- data/lib/forme/transformers/labeler.rb +19 -0
- data/lib/forme/transformers/serializer.rb +1 -12
- data/lib/forme/transformers/wrapper.rb +1 -1
- data/lib/forme/version.rb +1 -1
- data/lib/forme.rb +27 -0
- data/lib/roda/plugins/forme_route_csrf.rb +15 -1
- data/lib/roda/plugins/forme_set.rb +214 -0
- data/lib/sequel/plugins/forme.rb +10 -5
- data/lib/sequel/plugins/forme_set.rb +50 -28
- data/spec/bs3_reference_spec.rb +4 -4
- data/spec/bs3_sequel_plugin_spec.rb +45 -45
- data/spec/bs3_spec.rb +1 -1
- data/spec/erb_helper.rb +2 -2
- data/spec/forme_spec.rb +75 -18
- data/spec/rails_integration_spec.rb +43 -26
- data/spec/roda_integration_spec.rb +357 -1
- data/spec/sequel_i18n_plugin_spec.rb +5 -4
- data/spec/sequel_plugin_spec.rb +61 -54
- data/spec/sequel_set_plugin_spec.rb +63 -14
- data/spec/spec_helper.rb +2 -2
- metadata +25 -6
@@ -10,7 +10,7 @@ module Forme
|
|
10
10
|
# Wrap the inputs in a <fieldset>. If the :legend
|
11
11
|
# option is given, add a <legend> tag as the first
|
12
12
|
# child of the fieldset.
|
13
|
-
def call(form, opts)
|
13
|
+
def call(form, opts, &block)
|
14
14
|
attr = opts[:attr] ? opts[:attr].dup : {}
|
15
15
|
Forme.attr_classes(attr, 'inputs')
|
16
16
|
if legend = opts[:legend]
|
@@ -19,7 +19,7 @@ module Forme
|
|
19
19
|
yield
|
20
20
|
end
|
21
21
|
else
|
22
|
-
form.tag(:fieldset, attr, &
|
22
|
+
form.tag(:fieldset, attr, &block)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
@@ -77,4 +77,23 @@ module Forme
|
|
77
77
|
t
|
78
78
|
end
|
79
79
|
end
|
80
|
+
|
81
|
+
class Labeler::Span
|
82
|
+
Forme.register_transformer(:labeler, :span, new)
|
83
|
+
|
84
|
+
def call(tag, input)
|
85
|
+
label_attr = input.opts[:label_attr]
|
86
|
+
label_attr = label_attr ? label_attr.dup : {}
|
87
|
+
Forme.attr_classes(label_attr, "label")
|
88
|
+
[input.tag(:span, label_attr, input.opts[:label]), tag]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class Labeler::Legend
|
93
|
+
Forme.register_transformer(:labeler, :legend, new)
|
94
|
+
|
95
|
+
def call(tag, input)
|
96
|
+
[input.tag(:legend, input.opts[:label_attr], input.opts[:label]), tag]
|
97
|
+
end
|
98
|
+
end
|
80
99
|
end
|
@@ -8,12 +8,6 @@ module Forme
|
|
8
8
|
class Serializer
|
9
9
|
Forme.register_transformer(:serializer, :default, new)
|
10
10
|
|
11
|
-
# Borrowed from Rack::Utils, map of single character strings to html escaped versions.
|
12
|
-
ESCAPE_HTML = {"&" => "&", "<" => "<", ">" => ">", "'" => "'", '"' => """}
|
13
|
-
|
14
|
-
# A regexp that matches all html characters requiring escaping.
|
15
|
-
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
16
|
-
|
17
11
|
# Which tags are self closing (such tags ignore children).
|
18
12
|
SELF_CLOSING = [:img, :input]
|
19
13
|
|
@@ -43,7 +37,7 @@ module Forme
|
|
43
37
|
when Raw
|
44
38
|
tag.to_s
|
45
39
|
else
|
46
|
-
h
|
40
|
+
Forme.h(tag)
|
47
41
|
end
|
48
42
|
end
|
49
43
|
|
@@ -69,11 +63,6 @@ module Forme
|
|
69
63
|
time.is_a?(Time) ? (time.strftime('%Y-%m-%dT%H:%M:%S') + sprintf(".%03d", time.usec)) : (time.strftime('%Y-%m-%dT%H:%M:%S.') + time.strftime('%N')[0...3])
|
70
64
|
end
|
71
65
|
|
72
|
-
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
73
|
-
def h(string)
|
74
|
-
string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
|
75
|
-
end
|
76
|
-
|
77
66
|
# Join attribute values that are arrays with spaces instead of an empty
|
78
67
|
# string.
|
79
68
|
def attr_value(v)
|
data/lib/forme/version.rb
CHANGED
data/lib/forme.rb
CHANGED
@@ -8,6 +8,33 @@ module Forme
|
|
8
8
|
class Error < StandardError
|
9
9
|
end
|
10
10
|
|
11
|
+
begin
|
12
|
+
require 'cgi/escape'
|
13
|
+
# :nocov:
|
14
|
+
unless CGI.respond_to?(:escapeHTML) # work around for JRuby 9.1
|
15
|
+
CGI = Object.new
|
16
|
+
CGI.extend(defined?(::CGI::Escape) ? ::CGI::Escape : ::CGI::Util)
|
17
|
+
end
|
18
|
+
def self.h(value)
|
19
|
+
CGI.escapeHTML(value.to_s)
|
20
|
+
end
|
21
|
+
rescue LoadError
|
22
|
+
ESCAPE_TABLE = {'&' => '&', '<' => '<', '>' => '>', '"' => '"', "'" => '''}.freeze
|
23
|
+
ESCAPE_TABLE.each_value(&:freeze)
|
24
|
+
if RUBY_VERSION >= '1.9'
|
25
|
+
# Escape the following characters with their HTML/XML
|
26
|
+
# equivalents.
|
27
|
+
def self.h(value)
|
28
|
+
value.to_s.gsub(/[&<>"']/, ESCAPE_TABLE)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
def self.h(value)
|
32
|
+
value.to_s.gsub(/[&<>"']/){|s| ESCAPE_TABLE[s]}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
# :nocov:
|
37
|
+
|
11
38
|
@default_add_blank_prompt = nil
|
12
39
|
@default_config = :default
|
13
40
|
class << self
|
@@ -45,12 +45,26 @@ class Roda
|
|
45
45
|
csrf_token
|
46
46
|
end
|
47
47
|
|
48
|
+
options[:csrf] = [csrf_field, token]
|
48
49
|
options[:hidden_tags] ||= []
|
49
50
|
options[:hidden_tags] += [{csrf_field=>token}]
|
50
51
|
end
|
51
52
|
|
52
53
|
options[:output] = @_out_buf if block
|
53
|
-
|
54
|
+
_forme_form_options(options)
|
55
|
+
_forme_form_class.form(obj, attr, opts, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# The class to use for forms
|
61
|
+
def _forme_form_class
|
62
|
+
::Forme::ERB::Form
|
63
|
+
end
|
64
|
+
|
65
|
+
# The options to use for forms. Any changes should mutate this hash to set options.
|
66
|
+
def _forme_form_options(options)
|
67
|
+
options
|
54
68
|
end
|
55
69
|
end
|
56
70
|
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'rack/utils'
|
4
|
+
require 'forme/erb_form'
|
5
|
+
|
6
|
+
class Roda
|
7
|
+
module RodaPlugins
|
8
|
+
module FormeSet
|
9
|
+
# Require the forme_route_csrf plugin.
|
10
|
+
def self.load_dependencies(app, _ = nil)
|
11
|
+
app.plugin :forme_route_csrf
|
12
|
+
end
|
13
|
+
|
14
|
+
# Set the HMAC secret.
|
15
|
+
def self.configure(app, opts = OPTS, &block)
|
16
|
+
app.opts[:forme_set_hmac_secret] = opts[:secret] || app.opts[:forme_set_hmac_secret]
|
17
|
+
|
18
|
+
if block
|
19
|
+
app.send(:define_method, :_forme_set_handle_error, &block)
|
20
|
+
app.send(:private, :_forme_set_handle_error)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Error class raised for invalid form submissions.
|
25
|
+
class Error < StandardError
|
26
|
+
end
|
27
|
+
|
28
|
+
# Map of error types to error messages
|
29
|
+
ERROR_MESSAGES = {
|
30
|
+
:missing_data=>"_forme_set_data parameter not submitted",
|
31
|
+
:missing_hmac=>"_forme_set_data_hmac parameter not submitted",
|
32
|
+
:hmac_mismatch=>"_forme_set_data_hmac does not match _forme_set_data",
|
33
|
+
:csrf_mismatch=>"_forme_set_data CSRF token does not match submitted CSRF token",
|
34
|
+
:missing_namespace=>"no content in expected namespace"
|
35
|
+
}.freeze
|
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
|
+
module InstanceMethods
|
106
|
+
# Return hash based on submitted parameters, with :values key
|
107
|
+
# being submitted values for the object, and :validations key
|
108
|
+
# being a hash of validation metadata for the object.
|
109
|
+
def forme_parse(obj)
|
110
|
+
h = _forme_parse(obj)
|
111
|
+
|
112
|
+
params = h.delete(:params)
|
113
|
+
columns = h.delete(:columns)
|
114
|
+
h[:validations] ||= {}
|
115
|
+
|
116
|
+
values = h[:values] = {}
|
117
|
+
columns.each do |col|
|
118
|
+
values[col.to_sym] = params[col]
|
119
|
+
end
|
120
|
+
|
121
|
+
h
|
122
|
+
end
|
123
|
+
|
124
|
+
# Set fields on the object based on submitted parameters, as
|
125
|
+
# well as validations for associated object values.
|
126
|
+
def forme_set(obj)
|
127
|
+
h = _forme_parse(obj)
|
128
|
+
|
129
|
+
obj.set_fields(h[:params], h[:columns])
|
130
|
+
|
131
|
+
if h[:validations]
|
132
|
+
obj.forme_validations.merge!(h[:validations])
|
133
|
+
end
|
134
|
+
|
135
|
+
if block_given?
|
136
|
+
yield h[:form_version], obj
|
137
|
+
end
|
138
|
+
|
139
|
+
obj
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
# Raise error with message based on type
|
145
|
+
def _forme_set_handle_error(type, _obj)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Raise error with message based on type
|
149
|
+
def _forme_parse_error(type, obj)
|
150
|
+
_forme_set_handle_error(type, obj)
|
151
|
+
raise Error, ERROR_MESSAGES[type]
|
152
|
+
end
|
153
|
+
|
154
|
+
# Use form class that adds hidden fields for metadata.
|
155
|
+
def _forme_form_class
|
156
|
+
Form
|
157
|
+
end
|
158
|
+
|
159
|
+
# Include a reference to the current scope to the form. This reference is needed
|
160
|
+
# to correctly construct the HMAC.
|
161
|
+
def _forme_form_options(options)
|
162
|
+
options.merge!(:roda=>self)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Internals of forme_parse_hmac and forme_set_hmac.
|
166
|
+
def _forme_parse(obj)
|
167
|
+
params = request.params
|
168
|
+
return _forme_parse_error(:missing_data, obj) unless data = params['_forme_set_data']
|
169
|
+
return _forme_parse_error(:missing_hmac, obj) unless hmac = params['_forme_set_data_hmac']
|
170
|
+
|
171
|
+
data = data.to_s
|
172
|
+
hmac = hmac.to_s
|
173
|
+
actual = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, self.class.opts[:forme_set_hmac_secret], data)
|
174
|
+
unless Rack::Utils.secure_compare(hmac.ljust(64), actual) && hmac.length == actual.length
|
175
|
+
return _forme_parse_error(:hmac_mismatch, obj)
|
176
|
+
end
|
177
|
+
|
178
|
+
data = JSON.parse(data)
|
179
|
+
csrf_field, hmac_csrf_value = data['csrf']
|
180
|
+
if csrf_field
|
181
|
+
csrf_value = params[csrf_field].to_s
|
182
|
+
hmac_csrf_value = hmac_csrf_value.to_s
|
183
|
+
unless Rack::Utils.secure_compare(csrf_value.ljust(hmac_csrf_value.length), hmac_csrf_value) && csrf_value.length == hmac_csrf_value.length
|
184
|
+
return _forme_parse_error(:csrf_mismatch, obj)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
namespaces = data['namespaces']
|
189
|
+
namespaces.each do |key|
|
190
|
+
return _forme_parse_error(:missing_namespace, obj) unless params = params[key]
|
191
|
+
end
|
192
|
+
|
193
|
+
if valid_values = data['valid_values']
|
194
|
+
validations = {}
|
195
|
+
valid_values.each do |col, (type, values)|
|
196
|
+
value = params[col]
|
197
|
+
valid = if type == "subset"
|
198
|
+
!value || (value - values).empty?
|
199
|
+
else # type == "include"
|
200
|
+
values.include?(value)
|
201
|
+
end
|
202
|
+
|
203
|
+
validations[col.to_sym] = [:valid, valid]
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
{:params=>params, :columns=>data["columns"], :validations=>validations, :form_version=>data['form_version']}
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
register_plugin(:forme_set, FormeSet)
|
213
|
+
end
|
214
|
+
end
|
data/lib/sequel/plugins/forme.rb
CHANGED
@@ -192,6 +192,7 @@ module Sequel # :nodoc:
|
|
192
192
|
|
193
193
|
# Set the error option correctly if the field contains errors
|
194
194
|
def handle_errors(f)
|
195
|
+
return if opts.has_key?(:error)
|
195
196
|
if e = obj.errors.on(f)
|
196
197
|
opts[:error] = e.join(', ')
|
197
198
|
end
|
@@ -207,6 +208,7 @@ module Sequel # :nodoc:
|
|
207
208
|
# Update the attributes and options for any recognized validations
|
208
209
|
def handle_validations(f)
|
209
210
|
m = obj.model
|
211
|
+
|
210
212
|
if m.respond_to?(:validation_reflections) and (vs = m.validation_reflections[f])
|
211
213
|
attr = opts[:attr]
|
212
214
|
vs.each do |type, options|
|
@@ -218,7 +220,7 @@ module Sequel # :nodoc:
|
|
218
220
|
attr[:title] = options[:title] unless attr.has_key?(:title)
|
219
221
|
when :length
|
220
222
|
unless attr.has_key?(:maxlength)
|
221
|
-
if max =(options[:maximum] || options[:is])
|
223
|
+
if max = (options[:maximum] || options[:is])
|
222
224
|
attr[:maxlength] = max
|
223
225
|
elsif (w = options[:within]) && w.is_a?(Range)
|
224
226
|
attr[:maxlength] = if w.exclude_end? && w.end.is_a?(Integer)
|
@@ -345,7 +347,7 @@ module Sequel # :nodoc:
|
|
345
347
|
|
346
348
|
# Delegate to the +form+.
|
347
349
|
def humanize(s)
|
348
|
-
form.humanize(s)
|
350
|
+
form.respond_to?(:humanize) ? form.humanize(s) : s.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
|
349
351
|
end
|
350
352
|
|
351
353
|
# If the column allows +NULL+ values, use a three-valued select
|
@@ -442,6 +444,9 @@ module Sequel # :nodoc:
|
|
442
444
|
# is overridden.
|
443
445
|
def standard_input(type)
|
444
446
|
type = opts.delete(:type) || type
|
447
|
+
if type.to_s =~ /\A(text|textarea|password|email|tel|url)\z/ && !opts[:attr].has_key?(:maxlength) && (sch = obj.db_schema[field]) && (max_length = sch[:max_length])
|
448
|
+
opts[:attr][:maxlength] = max_length
|
449
|
+
end
|
445
450
|
opts[:value] = obj.send(field) unless opts.has_key?(:value)
|
446
451
|
_input(type, opts)
|
447
452
|
end
|
@@ -466,10 +471,10 @@ module Sequel # :nodoc:
|
|
466
471
|
include SequelForm
|
467
472
|
end
|
468
473
|
|
469
|
-
|
470
|
-
|
471
|
-
FORM_CLASSES = {::Forme::Form=>Form}
|
474
|
+
MUTEX = Mutex.new
|
475
|
+
FORM_CLASSES = {::Forme::Form=>Form}
|
472
476
|
|
477
|
+
module InstanceMethods
|
473
478
|
# Configure the +form+ with support for <tt>Sequel::Model</tt>
|
474
479
|
# specific code, such as support for nested attributes.
|
475
480
|
def forme_config(form)
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Sequel # :nodoc:
|
4
4
|
module Plugins # :nodoc:
|
5
5
|
# The forme_set plugin makes the model instance keep track of which form
|
6
|
-
# inputs have been added for it. It adds a forme_set method to handle
|
6
|
+
# inputs have been added for it. It adds a <tt>forme_set(params['model_name'])</tt> method to handle
|
7
7
|
# the intake of submitted data from the form. For more complete control,
|
8
8
|
# it also adds a forme_parse method that returns a hash of information that can be
|
9
9
|
# used to modify and validate the object.
|
@@ -18,6 +18,7 @@ module Sequel # :nodoc:
|
|
18
18
|
module InstanceMethods
|
19
19
|
# Hash with column name symbol keys and Forme::SequelInput values
|
20
20
|
def forme_inputs
|
21
|
+
return (@forme_inputs || {}) if frozen?
|
21
22
|
@forme_inputs ||= {}
|
22
23
|
end
|
23
24
|
|
@@ -25,12 +26,13 @@ module Sequel # :nodoc:
|
|
25
26
|
# is a boolean flag, if true, the uploaded values should be a subset of the allowed values,
|
26
27
|
# otherwise, there should be a single uploaded value that is a member of the allowed values.
|
27
28
|
def forme_validations
|
29
|
+
return (@forme_validations || {}) if frozen?
|
28
30
|
@forme_validations ||= {}
|
29
31
|
end
|
30
32
|
|
31
33
|
# Keep track of the inputs used.
|
32
34
|
def forme_input(_form, field, _opts)
|
33
|
-
forme_inputs[field] = super
|
35
|
+
frozen? ? super : (forme_inputs[field] = super)
|
34
36
|
end
|
35
37
|
|
36
38
|
# Given the hash of submitted parameters, return a hash containing information on how to
|
@@ -47,34 +49,11 @@ module Sequel # :nodoc:
|
|
47
49
|
validations = hash[:validations] = {}
|
48
50
|
|
49
51
|
forme_inputs.each do |field, input|
|
50
|
-
|
51
|
-
next if SKIP_FORMATTERS.include?(opts.fetch(:formatter){input.form.opts[:formatter]})
|
52
|
-
|
53
|
-
if attr = opts[:attr]
|
54
|
-
name = attr[:name] || attr['name']
|
55
|
-
end
|
56
|
-
name ||= opts[:name] || opts[:key] || next
|
57
|
-
|
58
|
-
# Pull out last component of the name if there is one
|
59
|
-
column = (name =~ /\[([^\[\]]+)\]\z/ ? $1 : name)
|
60
|
-
column = column.to_s.sub(/\[\]\z/, '').to_sym
|
61
|
-
|
52
|
+
next unless column = forme_column_for_input(input)
|
62
53
|
hash_values[column] = params[column] || params[column.to_s]
|
63
54
|
|
64
|
-
next unless
|
65
|
-
|
66
|
-
|
67
|
-
values = if opts[:text_method]
|
68
|
-
value_method = opts[:value_method] || opts[:text_method]
|
69
|
-
options.map(&value_method)
|
70
|
-
else
|
71
|
-
options.map{|obj| obj.is_a?(Array) ? obj.last : obj}
|
72
|
-
end
|
73
|
-
|
74
|
-
if ref[:type] == :many_to_one && !opts[:required]
|
75
|
-
values << nil
|
76
|
-
end
|
77
|
-
validations[column] = [ref[:type] != :many_to_one ? :subset : :include, values]
|
55
|
+
next unless validation = forme_validation_for_input(field, input)
|
56
|
+
validations[column] = validation
|
78
57
|
end
|
79
58
|
|
80
59
|
hash
|
@@ -88,6 +67,7 @@ module Sequel # :nodoc:
|
|
88
67
|
unless hash[:validations].empty?
|
89
68
|
forme_validations.merge!(hash[:validations])
|
90
69
|
end
|
70
|
+
nil
|
91
71
|
end
|
92
72
|
|
93
73
|
# Check associated values to ensure they match one of options in the form.
|
@@ -105,6 +85,8 @@ module Sequel # :nodoc:
|
|
105
85
|
!value || (value - values).empty?
|
106
86
|
when :include
|
107
87
|
values.include?(value)
|
88
|
+
when :valid
|
89
|
+
values
|
108
90
|
else
|
109
91
|
raise Forme::Error, "invalid type used in forme_validations"
|
110
92
|
end
|
@@ -115,6 +97,46 @@ module Sequel # :nodoc:
|
|
115
97
|
end
|
116
98
|
end
|
117
99
|
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
# Return the model column name to use for the given form input.
|
104
|
+
def forme_column_for_input(input)
|
105
|
+
opts = input.opts
|
106
|
+
return if SKIP_FORMATTERS.include?(opts.fetch(:formatter){input.form_opts[:formatter]})
|
107
|
+
|
108
|
+
if attr = opts[:attr]
|
109
|
+
name = attr[:name] || attr['name']
|
110
|
+
end
|
111
|
+
return unless name ||= opts[:name] || opts[:key]
|
112
|
+
|
113
|
+
# Pull out last component of the name if there is one
|
114
|
+
column = name.to_s.chomp('[]')
|
115
|
+
if column =~ /\[([^\[\]]+)\]\z/
|
116
|
+
$1
|
117
|
+
else
|
118
|
+
column
|
119
|
+
end.to_sym
|
120
|
+
end
|
121
|
+
|
122
|
+
# Return the validation metadata to use for the given field name and form input.
|
123
|
+
def forme_validation_for_input(field, input)
|
124
|
+
return unless ref = model.association_reflection(field)
|
125
|
+
opts = input.opts
|
126
|
+
return unless options = opts[:options]
|
127
|
+
|
128
|
+
values = if opts[:text_method]
|
129
|
+
value_method = opts[:value_method] || opts[:text_method]
|
130
|
+
options.map(&value_method)
|
131
|
+
else
|
132
|
+
options.map{|obj| obj.is_a?(Array) ? obj.last : obj}
|
133
|
+
end
|
134
|
+
|
135
|
+
if ref[:type] == :many_to_one && !opts[:required]
|
136
|
+
values << nil
|
137
|
+
end
|
138
|
+
[ref[:type] != :many_to_one ? :subset : :include, values]
|
139
|
+
end
|
118
140
|
end
|
119
141
|
end
|
120
142
|
end
|