forme 1.8.0 → 1.12.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 +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
|