forme 1.9.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +70 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +396 -202
- data/lib/forme/bs3.rb +19 -5
- data/lib/forme/erb.rb +18 -16
- data/lib/forme/form.rb +151 -118
- data/lib/forme/input.rb +1 -1
- data/lib/forme/rails.rb +41 -72
- data/lib/forme/raw.rb +2 -2
- data/lib/forme/sinatra.rb +6 -2
- data/lib/forme/tag.rb +3 -12
- data/lib/forme/template.rb +118 -0
- data/lib/forme/transformers/error_handler.rb +46 -1
- data/lib/forme/transformers/formatter.rb +36 -35
- data/lib/forme/transformers/helper.rb +0 -1
- data/lib/forme/transformers/inputs_wrapper.rb +6 -6
- data/lib/forme/transformers/labeler.rb +19 -0
- data/lib/forme/transformers/wrapper.rb +1 -1
- data/lib/forme/version.rb +2 -2
- data/lib/forme.rb +15 -2
- data/lib/roda/plugins/forme.rb +1 -1
- data/lib/roda/plugins/forme_erubi_capture.rb +62 -0
- data/lib/roda/plugins/forme_route_csrf.rb +16 -20
- data/lib/roda/plugins/forme_set.rb +177 -0
- data/lib/sequel/plugins/forme.rb +42 -55
- data/lib/sequel/plugins/forme_i18n.rb +3 -1
- data/lib/sequel/plugins/forme_set.rb +50 -28
- data/spec/all.rb +1 -1
- data/spec/bs3_reference_spec.rb +18 -18
- data/spec/bs3_sequel_plugin_spec.rb +7 -7
- data/spec/bs3_spec.rb +23 -11
- data/spec/erb_helper.rb +73 -58
- data/spec/erubi_capture_helper.rb +202 -0
- data/spec/forme_spec.rb +80 -29
- data/spec/rails_integration_spec.rb +47 -24
- data/spec/roda_integration_spec.rb +459 -48
- data/spec/sequel_helper.rb +0 -1
- data/spec/sequel_i18n_helper.rb +1 -1
- data/spec/sequel_i18n_plugin_spec.rb +3 -2
- data/spec/sequel_plugin_spec.rb +25 -8
- data/spec/sequel_set_plugin_spec.rb +10 -3
- data/spec/shared_erb_specs.rb +75 -0
- data/spec/sinatra_integration_spec.rb +5 -6
- data/spec/spec_helper.rb +23 -5
- metadata +30 -8
- data/lib/forme/erb_form.rb +0 -74
@@ -0,0 +1,177 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'rack/utils'
|
4
|
+
require_relative '../../forme/template'
|
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
|
+
module InstanceMethods
|
38
|
+
# Return hash based on submitted parameters, with :values key
|
39
|
+
# being submitted values for the object, and :validations key
|
40
|
+
# being a hash of validation metadata for the object.
|
41
|
+
def forme_parse(obj)
|
42
|
+
h = _forme_parse(obj)
|
43
|
+
|
44
|
+
params = h.delete(:params)
|
45
|
+
columns = h.delete(:columns)
|
46
|
+
h[:validations] ||= {}
|
47
|
+
|
48
|
+
values = h[:values] = {}
|
49
|
+
columns.each do |col|
|
50
|
+
values[col.to_sym] = params[col]
|
51
|
+
end
|
52
|
+
|
53
|
+
h
|
54
|
+
end
|
55
|
+
|
56
|
+
# Set fields on the object based on submitted parameters, as
|
57
|
+
# well as validations for associated object values.
|
58
|
+
def forme_set(obj)
|
59
|
+
h = _forme_parse(obj)
|
60
|
+
|
61
|
+
obj.set_fields(h[:params], h[:columns])
|
62
|
+
|
63
|
+
if h[:validations]
|
64
|
+
obj.forme_validations.merge!(h[:validations])
|
65
|
+
end
|
66
|
+
|
67
|
+
if block_given?
|
68
|
+
yield h[:form_version], obj
|
69
|
+
end
|
70
|
+
|
71
|
+
obj
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# Raise error with message based on type
|
77
|
+
def _forme_set_handle_error(type, _obj)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Raise error with message based on type
|
81
|
+
def _forme_parse_error(type, obj)
|
82
|
+
_forme_set_handle_error(type, obj)
|
83
|
+
raise Error, ERROR_MESSAGES[type]
|
84
|
+
end
|
85
|
+
|
86
|
+
# Include a reference to the current scope to the form. This reference is needed
|
87
|
+
# to correctly construct the HMAC.
|
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
|
126
|
+
end
|
127
|
+
|
128
|
+
# Internals of forme_parse_hmac and forme_set_hmac.
|
129
|
+
def _forme_parse(obj)
|
130
|
+
params = request.params
|
131
|
+
return _forme_parse_error(:missing_data, obj) unless data = params['_forme_set_data']
|
132
|
+
return _forme_parse_error(:missing_hmac, obj) unless hmac = params['_forme_set_data_hmac']
|
133
|
+
|
134
|
+
data = data.to_s
|
135
|
+
hmac = hmac.to_s
|
136
|
+
actual = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, self.class.opts[:forme_set_hmac_secret], data)
|
137
|
+
unless Rack::Utils.secure_compare(hmac.ljust(64), actual) && hmac.length == actual.length
|
138
|
+
return _forme_parse_error(:hmac_mismatch, obj)
|
139
|
+
end
|
140
|
+
|
141
|
+
data = JSON.parse(data)
|
142
|
+
csrf_field, hmac_csrf_value = data['csrf']
|
143
|
+
if csrf_field
|
144
|
+
csrf_value = params[csrf_field].to_s
|
145
|
+
hmac_csrf_value = hmac_csrf_value.to_s
|
146
|
+
unless Rack::Utils.secure_compare(csrf_value.ljust(hmac_csrf_value.length), hmac_csrf_value) && csrf_value.length == hmac_csrf_value.length
|
147
|
+
return _forme_parse_error(:csrf_mismatch, obj)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
namespaces = data['namespaces']
|
152
|
+
namespaces.each do |key|
|
153
|
+
return _forme_parse_error(:missing_namespace, obj) unless params = params[key]
|
154
|
+
end
|
155
|
+
|
156
|
+
if valid_values = data['valid_values']
|
157
|
+
validations = {}
|
158
|
+
valid_values.each do |col, (type, values)|
|
159
|
+
value = params[col]
|
160
|
+
valid = if type == "subset"
|
161
|
+
!value || (value - values).empty?
|
162
|
+
else # type == "include"
|
163
|
+
values.include?(value)
|
164
|
+
end
|
165
|
+
|
166
|
+
validations[col.to_sym] = [:valid, valid]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
{:params=>params, :columns=>data["columns"], :validations=>validations, :form_version=>data['form_version']}
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
register_plugin(:forme_set, FormeSet)
|
176
|
+
end
|
177
|
+
end
|
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,47 @@ 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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
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"
|
68
|
+
|
69
|
+
contents = proc do
|
70
|
+
send(multiple ? :each_obj : :with_obj, nested_obj, ns) do |no, i|
|
71
|
+
input(ref.associated_class.primary_key, :type=>:hidden, :label=>nil, :wrapper=>nil) unless no.new? || opts[:skip_primary_key]
|
72
|
+
options = opts.dup
|
73
|
+
if grid
|
74
|
+
options.delete(:legend)
|
79
75
|
else
|
80
|
-
if
|
81
|
-
options[:legend]
|
76
|
+
if options.has_key?(:legend)
|
77
|
+
if options[:legend].respond_to?(:call)
|
78
|
+
options[:legend] = multiple ? options[:legend].call(no, i) : options[:legend].call(no)
|
79
|
+
end
|
82
80
|
else
|
83
|
-
|
81
|
+
if multiple
|
82
|
+
options[:legend] = humanize("#{obj.model.send(:singularize, association)} ##{i+1}")
|
83
|
+
else
|
84
|
+
options[:legend] = humanize(association)
|
85
|
+
end
|
84
86
|
end
|
85
87
|
end
|
88
|
+
options[:subform] = true
|
89
|
+
|
90
|
+
inputs(options[:inputs]||[], options, &block)
|
86
91
|
end
|
87
|
-
|
88
|
-
|
92
|
+
end
|
93
|
+
|
94
|
+
if grid
|
95
|
+
labels = opts.fetch(:labels){opts[:inputs].map{|l, *| humanize(l)} if opts[:inputs]}
|
96
|
+
legend = opts.fetch(:legend){humanize(association)}
|
97
|
+
inputs_opts = opts[:inputs_opts] || {}
|
98
|
+
inputs(inputs_opts.merge(:inputs_wrapper=>:table, :nested_inputs_wrapper=>:tr, :wrapper=>:td, :labeler=>nil, :labels=>labels, :legend=>legend), &contents)
|
99
|
+
else
|
100
|
+
contents.call
|
89
101
|
end
|
90
102
|
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
103
|
end
|
102
104
|
end
|
103
105
|
|
@@ -192,6 +194,7 @@ module Sequel # :nodoc:
|
|
192
194
|
|
193
195
|
# Set the error option correctly if the field contains errors
|
194
196
|
def handle_errors(f)
|
197
|
+
return if opts.has_key?(:error)
|
195
198
|
if e = obj.errors.on(f)
|
196
199
|
opts[:error] = e.join(', ')
|
197
200
|
end
|
@@ -346,7 +349,7 @@ module Sequel # :nodoc:
|
|
346
349
|
|
347
350
|
# Delegate to the +form+.
|
348
351
|
def humanize(s)
|
349
|
-
form.humanize(s)
|
352
|
+
form.respond_to?(:humanize) ? form.humanize(s) : s.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
|
350
353
|
end
|
351
354
|
|
352
355
|
# If the column allows +NULL+ values, use a three-valued select
|
@@ -451,29 +454,14 @@ module Sequel # :nodoc:
|
|
451
454
|
end
|
452
455
|
end
|
453
456
|
|
454
|
-
# Helper module used for Sequel forms using ERB template integration. Necessary for
|
455
|
-
# proper subform handling when using such forms with partials.
|
456
|
-
module ERBSequelForm
|
457
|
-
# Capture the inside of the inputs, injecting it into the template
|
458
|
-
# if a block is given, or returning it as a string if not.
|
459
|
-
def subform(*, &block)
|
460
|
-
if block
|
461
|
-
capture(block){super}
|
462
|
-
else
|
463
|
-
capture{super}
|
464
|
-
end
|
465
|
-
end
|
466
|
-
end
|
467
|
-
SinatraSequelForm = ERBSequelForm
|
468
|
-
|
469
457
|
class Form < ::Forme::Form
|
470
458
|
include SequelForm
|
471
459
|
end
|
472
460
|
|
473
|
-
|
474
|
-
|
475
|
-
FORM_CLASSES = {::Forme::Form=>Form}
|
461
|
+
MUTEX = Mutex.new
|
462
|
+
FORM_CLASSES = {::Forme::Form=>Form}
|
476
463
|
|
464
|
+
module InstanceMethods
|
477
465
|
# Configure the +form+ with support for <tt>Sequel::Model</tt>
|
478
466
|
# specific code, such as support for nested attributes.
|
479
467
|
def forme_config(form)
|
@@ -485,7 +473,6 @@ module Sequel # :nodoc:
|
|
485
473
|
unless klass = MUTEX.synchronize{FORM_CLASSES[base]}
|
486
474
|
klass = Class.new(base)
|
487
475
|
klass.send(:include, SequelForm)
|
488
|
-
klass.send(:include, ERBSequelForm) if defined?(::Forme::ERB::Form) && base == ::Forme::ERB::Form
|
489
476
|
MUTEX.synchronize{FORM_CLASSES[base] = klass}
|
490
477
|
end
|
491
478
|
klass
|
@@ -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
|
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')}
|
data/spec/bs3_reference_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require_relative 'sequel_helper'
|
3
|
+
require_relative '../lib/forme/bs3'
|
4
4
|
|
5
5
|
describe "Forme Bootstrap3 (BS3) forms" do
|
6
6
|
def sel(opts, s)
|
@@ -33,7 +33,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
33
33
|
end
|
34
34
|
|
35
35
|
it "should create an input[:text] tag with a label when ':id => bar' with an error" do
|
36
|
-
@f.input(:text, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-text-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input class="form-control foo" id="bar" type="text"/><span class="help-block with-errors">input-text-error</span></div>'
|
36
|
+
@f.input(:text, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-text-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input aria-describedby="bar_error_message" aria-invalid="true" class="form-control foo" id="bar" type="text"/><span class="help-block with-errors">input-text-error</span></div>'
|
37
37
|
end
|
38
38
|
|
39
39
|
describe 'from a Sequel model' do
|
@@ -43,7 +43,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
43
43
|
|
44
44
|
it "should correctly handle an input[:text] tag from Sequel model with error" do
|
45
45
|
@ac.errors.add(:name, 'name not valid')
|
46
|
-
@c.input(:name).to_s.must_equal '<div class="form-group has-error string"><label for="album_name">Name</label> <input class="form-control" id="album_name" maxlength="255" name="album[name]" type="text" value="c"/><span class="help-block with-errors">name not valid</span></div>'
|
46
|
+
@c.input(:name).to_s.must_equal '<div class="form-group has-error string"><label for="album_name">Name</label> <input aria-describedby="album_name_error_message" aria-invalid="true" class="form-control" id="album_name" maxlength="255" name="album[name]" type="text" value="c"/><span class="help-block with-errors">name not valid</span></div>'
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|
@@ -66,7 +66,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
66
66
|
end
|
67
67
|
|
68
68
|
it "should create an input[:password] tag with a label when ':id => bar' with an error" do
|
69
|
-
@f.input(:password, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-password-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input class="form-control foo" id="bar" type="password"/><span class="help-block with-errors">input-password-error</span></div>'
|
69
|
+
@f.input(:password, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-password-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input aria-describedby="bar_error_message" aria-invalid="true" class="form-control foo" id="bar" type="password"/><span class="help-block with-errors">input-password-error</span></div>'
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
@@ -88,7 +88,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
88
88
|
end
|
89
89
|
|
90
90
|
it "should create an input[:email] tag with a label when ':id => bar' with an error" do
|
91
|
-
@f.input(:email, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-email-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input class="form-control foo" id="bar" type="email"/><span class="help-block with-errors">input-email-error</span></div>'
|
91
|
+
@f.input(:email, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-email-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input aria-describedby="bar_error_message" aria-invalid="true" class="form-control foo" id="bar" type="email"/><span class="help-block with-errors">input-email-error</span></div>'
|
92
92
|
end
|
93
93
|
end
|
94
94
|
|
@@ -110,7 +110,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
110
110
|
end
|
111
111
|
|
112
112
|
it "should create an input[:file] tag with a label when ':id => bar' with an error" do
|
113
|
-
@f.input(:file, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-file-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input class="foo" id="bar" type="file"/><span class="help-block with-errors">input-file-error</span></div>'
|
113
|
+
@f.input(:file, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-file-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input aria-describedby="bar_error_message" aria-invalid="true" class="foo" id="bar" type="file"/><span class="help-block with-errors">input-file-error</span></div>'
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
@@ -132,7 +132,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
132
132
|
end
|
133
133
|
|
134
134
|
it "should create an input[:submit] tag without error message " do
|
135
|
-
@f.input(:submit, :value=>'Save', :id=>'foo', :error=>'error-message').to_s.must_equal '<input class="btn btn-default" id="foo" type="submit" value="Save"/>'
|
135
|
+
@f.input(:submit, :value=>'Save', :id=>'foo', :error=>'error-message').to_s.must_equal '<input aria-describedby="foo_error_message" aria-invalid="true" class="btn btn-default" id="foo" type="submit" value="Save"/>'
|
136
136
|
end
|
137
137
|
end
|
138
138
|
|
@@ -154,7 +154,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
154
154
|
end
|
155
155
|
|
156
156
|
it "should create an input[:reset] tag without error message " do
|
157
|
-
@f.input(:reset, :value=>'Save', :id=>'foo', :error=>'error-message').to_s.must_equal '<input class="btn btn-default" id="foo" type="reset" value="Save"/>'
|
157
|
+
@f.input(:reset, :value=>'Save', :id=>'foo', :error=>'error-message').to_s.must_equal '<input aria-describedby="foo_error_message" aria-invalid="true" class="btn btn-default" id="foo" type="reset" value="Save"/>'
|
158
158
|
end
|
159
159
|
end
|
160
160
|
|
@@ -180,7 +180,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
180
180
|
end
|
181
181
|
|
182
182
|
it "should create a textarea tag with .has-error and error message when ':error => is-a-string'" do
|
183
|
-
@f.input(:textarea, :error=>'input-textarea-error').to_s.must_equal '<div class="form-group has-error"><textarea class="form-control"></textarea><span class="help-block with-errors">input-textarea-error</span></div>'
|
183
|
+
@f.input(:textarea, :error=>'input-textarea-error').to_s.must_equal '<div class="form-group has-error"><textarea aria-invalid="true" class="form-control"></textarea><span class="help-block with-errors">input-textarea-error</span></div>'
|
184
184
|
end
|
185
185
|
|
186
186
|
describe 'from a Sequel model' do
|
@@ -190,7 +190,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
190
190
|
|
191
191
|
it "should correctly handle an input[:text] tag from Sequel model with error" do
|
192
192
|
@ac.errors.add(:name, 'name not valid')
|
193
|
-
@c.input(:name, :as=>:textarea).to_s.must_equal '<div class="form-group has-error string"><label for="album_name">Name</label> <textarea class="form-control" id="album_name" maxlength="255" name="album[name]">c</textarea><span class="help-block with-errors">name not valid</span></div>'
|
193
|
+
@c.input(:name, :as=>:textarea).to_s.must_equal '<div class="form-group has-error string"><label for="album_name">Name</label> <textarea aria-describedby="album_name_error_message" aria-invalid="true" class="form-control" id="album_name" maxlength="255" name="album[name]">c</textarea><span class="help-block with-errors">name not valid</span></div>'
|
194
194
|
end
|
195
195
|
end
|
196
196
|
end
|
@@ -225,7 +225,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
225
225
|
end
|
226
226
|
|
227
227
|
it "should create a select tag with .has-error and error message when ':error => is-a-string'" do
|
228
|
-
@f.input(:select, :error=>'input-select-error').to_s.must_equal '<div class="form-group has-error"><select class="form-control"></select><span class="help-block with-errors">input-select-error</span></div>'
|
228
|
+
@f.input(:select, :error=>'input-select-error').to_s.must_equal '<div class="form-group has-error"><select aria-invalid="true" class="form-control"></select><span class="help-block with-errors">input-select-error</span></div>'
|
229
229
|
end
|
230
230
|
|
231
231
|
it "should correctly handle a Sequel model output :as => :select" do
|
@@ -234,7 +234,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
234
234
|
|
235
235
|
it "should correctly handle a Sequel model output :as => :select with error" do
|
236
236
|
@ac.errors.add(:artist, 'error message')
|
237
|
-
@c.input(:artist, :as=>:select).to_s.must_equal '<div class="form-group has-error many_to_one"><label for="album_artist_id">Artist</label> <select class="form-control" id="album_artist_id" name="album[artist_id]"><option value=""></option><option value="1">a</option><option selected="selected" value="2">d</option></select><span class="help-block with-errors">error message</span></div>'
|
237
|
+
@c.input(:artist, :as=>:select).to_s.must_equal '<div class="form-group has-error many_to_one"><label for="album_artist_id">Artist</label> <select aria-describedby="album_artist_id_error_message" aria-invalid="true" class="form-control" id="album_artist_id" name="album[artist_id]"><option value=""></option><option value="1">a</option><option selected="selected" value="2">d</option></select><span class="help-block with-errors">error message</span></div>'
|
238
238
|
end
|
239
239
|
|
240
240
|
describe 'from a Sequel model' do
|
@@ -244,7 +244,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
244
244
|
|
245
245
|
it "should correctly handle a boolean attribute from a Sequel model with an error" do
|
246
246
|
@ac.errors.add(:gold, 'error message')
|
247
|
-
@c.input(:gold).to_s.must_equal '<div class="boolean form-group has-error"><label for="album_gold">Gold</label> <select class="form-control" id="album_gold" name="album[gold]"><option value=""></option><option selected="selected" value="t">True</option><option value="f">False</option></select><span class="help-block with-errors">error message</span></div>'
|
247
|
+
@c.input(:gold).to_s.must_equal '<div class="boolean form-group has-error"><label for="album_gold">Gold</label> <select aria-describedby="album_gold_error_message" aria-invalid="true" class="form-control" id="album_gold" name="album[gold]"><option value=""></option><option selected="selected" value="t">True</option><option value="f">False</option></select><span class="help-block with-errors">error message</span></div>'
|
248
248
|
end
|
249
249
|
end
|
250
250
|
end
|
@@ -267,7 +267,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
267
267
|
end
|
268
268
|
|
269
269
|
it "should create an input[:checkbox] tag with a label when ':id => bar' with an error" do
|
270
|
-
@f.input(:checkbox, :label=>"Gold", :id=>'bar', :class=>'foo', :error=>'input-checkbox-error').to_s.must_equal '<div class="has-error"><div class="checkbox"><label for="bar"><input class="foo" id="bar" type="checkbox"/> Gold</label></div><span class="help-block with-errors">input-checkbox-error</span></div>'
|
270
|
+
@f.input(:checkbox, :label=>"Gold", :id=>'bar', :class=>'foo', :error=>'input-checkbox-error').to_s.must_equal '<div class="has-error"><div class="checkbox"><label for="bar"><input aria-describedby="bar_error_message" aria-invalid="true" class="foo" id="bar" type="checkbox"/> Gold</label></div><span class="help-block with-errors">input-checkbox-error</span></div>'
|
271
271
|
end
|
272
272
|
|
273
273
|
describe 'from a Sequel model' do
|
@@ -277,7 +277,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
277
277
|
|
278
278
|
it "should correctly handle a boolean attribute ':as=>:checkbox' with an error" do
|
279
279
|
@ac.errors.add(:gold, 'error message')
|
280
|
-
@c.input(:gold, :as=>:checkbox).to_s.must_equal '<div class="boolean has-error"><div class="checkbox"><label for="album_gold"><input id="album_gold_hidden" name="album[gold]" type="hidden" value="f"/><input checked="checked" id="album_gold" name="album[gold]" type="checkbox" value="t"/> Gold</label></div><span class="help-block with-errors">error message</span></div>'
|
280
|
+
@c.input(:gold, :as=>:checkbox).to_s.must_equal '<div class="boolean has-error"><div class="checkbox"><label for="album_gold"><input id="album_gold_hidden" name="album[gold]" type="hidden" value="f"/><input aria-describedby="album_gold_error_message" aria-invalid="true" checked="checked" id="album_gold" name="album[gold]" type="checkbox" value="t"/> Gold</label></div><span class="help-block with-errors">error message</span></div>'
|
281
281
|
end
|
282
282
|
end
|
283
283
|
end
|
@@ -301,7 +301,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
|
|
301
301
|
end
|
302
302
|
|
303
303
|
it "should create an input[:radio] tag with a label when ':id => bar' with an error" do
|
304
|
-
@f.input(:radio, :label=>"Gold", :id=>'bar', :class=>'foo', :error=>'input-radio-error').to_s.must_equal '<div class="has-error"><div class="radio"><label for="bar"><input class="foo" id="bar" type="radio"/> Gold</label></div><span class="help-block with-errors">input-radio-error</span></div>'
|
304
|
+
@f.input(:radio, :label=>"Gold", :id=>'bar', :class=>'foo', :error=>'input-radio-error').to_s.must_equal '<div class="has-error"><div class="radio"><label for="bar"><input aria-describedby="bar_error_message" aria-invalid="true" class="foo" id="bar" type="radio"/> Gold</label></div><span class="help-block with-errors">input-radio-error</span></div>'
|
305
305
|
end
|
306
306
|
|
307
307
|
describe 'from a Sequel model' do
|