forme 1.9.0 → 2.0.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 +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
|