forme 1.12.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 +34 -0
- data/README.rdoc +226 -204
- data/lib/forme/bs3.rb +18 -4
- data/lib/forme/erb.rb +18 -16
- data/lib/forme/form.rb +150 -117
- 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/helper.rb +0 -1
- data/lib/forme/transformers/inputs_wrapper.rb +4 -4
- data/lib/forme/version.rb +2 -2
- data/lib/forme.rb +13 -2
- data/lib/roda/plugins/forme.rb +1 -1
- data/lib/roda/plugins/forme_erubi_capture.rb +62 -0
- data/lib/roda/plugins/forme_route_csrf.rb +16 -34
- data/lib/roda/plugins/forme_set.rb +39 -76
- data/lib/sequel/plugins/forme.rb +37 -51
- data/lib/sequel/plugins/forme_i18n.rb +3 -1
- 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 +30 -15
- data/spec/rails_integration_spec.rb +17 -11
- data/spec/roda_integration_spec.rb +123 -68
- data/spec/sequel_helper.rb +0 -1
- data/spec/sequel_i18n_helper.rb +1 -1
- data/spec/sequel_i18n_plugin_spec.rb +2 -2
- data/spec/sequel_plugin_spec.rb +11 -2
- data/spec/sequel_set_plugin_spec.rb +3 -3
- data/spec/shared_erb_specs.rb +75 -0
- data/spec/sinatra_integration_spec.rb +5 -6
- data/spec/spec_helper.rb +21 -3
- metadata +8 -5
- data/lib/forme/erb_form.rb +0 -74
data/lib/forme/tag.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
3
|
module Forme
|
4
|
-
# Low level abstract tag form, where each instance represents
|
5
|
-
#
|
4
|
+
# Low level abstract tag form, where each instance represents an
|
5
|
+
# HTML tag with attributes and children.
|
6
6
|
class Tag
|
7
7
|
# The +Form+ object related to the receiver.
|
8
8
|
attr_reader :form
|
@@ -23,15 +23,6 @@ module Forme
|
|
23
23
|
@children = parse_children(children)
|
24
24
|
end
|
25
25
|
|
26
|
-
# Adds a child to the array of receiver's children.
|
27
|
-
def <<(child)
|
28
|
-
if children
|
29
|
-
children << child
|
30
|
-
else
|
31
|
-
@children = [child]
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
26
|
# Create a new +Tag+ instance with the given arguments and block
|
36
27
|
# related to the receiver's +form+.
|
37
28
|
def tag(*a, &block)
|
@@ -40,7 +31,7 @@ module Forme
|
|
40
31
|
|
41
32
|
# Return a string containing the serialized content of the receiver.
|
42
33
|
def to_s
|
43
|
-
|
34
|
+
Forme.transform(:serializer, @opts, @form.opts, self)
|
44
35
|
end
|
45
36
|
|
46
37
|
private
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require_relative '../forme'
|
4
|
+
|
5
|
+
module Forme
|
6
|
+
module Template
|
7
|
+
class Form
|
8
|
+
def initialize(form, scope)
|
9
|
+
@form = form
|
10
|
+
@scope = scope
|
11
|
+
end
|
12
|
+
|
13
|
+
# Delegate calls by default to the wrapped form
|
14
|
+
def method_missing(*a, &block)
|
15
|
+
@form.public_send(*a, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
# If a block is given to inputs, tag, or subform,
|
19
|
+
# emit the already generated HTML for the form before yielding
|
20
|
+
# to the block, and after returning, emit any HTML generated
|
21
|
+
# after returning from the block.
|
22
|
+
%w'inputs tag subform'.each do |meth|
|
23
|
+
class_eval(<<-END, __FILE__, __LINE__+1)
|
24
|
+
def #{meth}(*a, &block)
|
25
|
+
return @form.#{meth}(*a) unless block
|
26
|
+
|
27
|
+
buffer = @form.to_s
|
28
|
+
offset = buffer.length
|
29
|
+
@form.#{meth}(*a) do
|
30
|
+
emit(buffer[offset, buffer.length])
|
31
|
+
yield self
|
32
|
+
offset = buffer.length
|
33
|
+
end
|
34
|
+
emit(buffer[offset, buffer.length])
|
35
|
+
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
END
|
39
|
+
end
|
40
|
+
|
41
|
+
# Serialize the tag and inject it into the output.
|
42
|
+
def emit(tag)
|
43
|
+
return unless output = output()
|
44
|
+
output << tag
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def output
|
50
|
+
@scope.instance_variable_get(:@_out_buf)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# This is the module used to add the Forme integration
|
55
|
+
# to ERB.
|
56
|
+
module Helper
|
57
|
+
# Create a +Form+ object tied to the current output buffer,
|
58
|
+
# using the standard ERB hidden tags.
|
59
|
+
def form(obj=nil, attr={}, opts={}, &block)
|
60
|
+
if obj.is_a?(Hash)
|
61
|
+
attribs = obj
|
62
|
+
options = attr = attr.dup
|
63
|
+
else
|
64
|
+
attribs = attr
|
65
|
+
options = opts = opts.dup
|
66
|
+
end
|
67
|
+
|
68
|
+
_forme_form_options(obj, attribs, options)
|
69
|
+
_forme_form(obj, attr, opts, &block)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def _forme_form(obj, attr, opts, &block)
|
75
|
+
if block_given?
|
76
|
+
erb_form = buffer = offset = nil
|
77
|
+
block = proc do
|
78
|
+
wrapped_form = erb_form.instance_variable_get(:@form)
|
79
|
+
buffer = wrapped_form.to_s
|
80
|
+
offset = buffer.length
|
81
|
+
erb_form.emit(buffer[0, buffer.length])
|
82
|
+
yield erb_form
|
83
|
+
offset = buffer.length
|
84
|
+
end
|
85
|
+
|
86
|
+
f, attr, block = _forme_wrapped_form_class.form_args(obj, attr, opts, &block)
|
87
|
+
erb_form = _forme_form_class.new(f, self)
|
88
|
+
erb_form.form(attr, &block)
|
89
|
+
erb_form.emit(buffer[offset, buffer.length])
|
90
|
+
else
|
91
|
+
_forme_wrapped_form_class.form(obj, attr, opts, &block)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def _forme_wrapped_form_class
|
96
|
+
::Forme::Form
|
97
|
+
end
|
98
|
+
|
99
|
+
# The class to use for forms
|
100
|
+
def _forme_form_class
|
101
|
+
Form
|
102
|
+
end
|
103
|
+
|
104
|
+
# The options to use for forms. Any changes should mutate this hash to set options.
|
105
|
+
def _forme_form_options(obj, attr, opts)
|
106
|
+
if hidden_tags = _forme_form_hidden_tags
|
107
|
+
opts[:hidden_tags] ||= []
|
108
|
+
opts[:hidden_tags] += hidden_tags
|
109
|
+
end
|
110
|
+
opts[:hidden_tags_uplevel] = 1
|
111
|
+
end
|
112
|
+
|
113
|
+
def _forme_form_hidden_tags
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -15,7 +15,7 @@ module Forme
|
|
15
15
|
Forme.attr_classes(attr, 'inputs')
|
16
16
|
if legend = opts[:legend]
|
17
17
|
form.tag(:fieldset, attr) do
|
18
|
-
form.
|
18
|
+
form.tag(:legend, opts[:legend_attr], legend)
|
19
19
|
yield
|
20
20
|
end
|
21
21
|
else
|
@@ -32,7 +32,7 @@ module Forme
|
|
32
32
|
|
33
33
|
# Wrap the inputs in a <fieldset> and a <ol> tag.
|
34
34
|
def call(form, opts)
|
35
|
-
super(form, opts){form.
|
35
|
+
super(form, opts){form.tag(:ol){yield}}
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
@@ -83,12 +83,12 @@ module Forme
|
|
83
83
|
attr = opts[:attr] ? opts[:attr].dup : {}
|
84
84
|
form.tag(:table, attr) do
|
85
85
|
if legend = opts[:legend]
|
86
|
-
form.
|
86
|
+
form.tag(:caption, opts[:legend_attr], legend)
|
87
87
|
end
|
88
88
|
|
89
89
|
if (labels = opts[:labels]) && !labels.empty?
|
90
90
|
form.tag(:thead) do
|
91
|
-
form.
|
91
|
+
form.tag(:tr, {}, labels.map{|l| form._tag(:th, {}, l)})
|
92
92
|
end
|
93
93
|
end
|
94
94
|
|
data/lib/forme/version.rb
CHANGED
@@ -3,10 +3,10 @@
|
|
3
3
|
module Forme
|
4
4
|
# The major version of Forme, updated only for major changes that are
|
5
5
|
# likely to require modification to apps using Forme.
|
6
|
-
MAJOR =
|
6
|
+
MAJOR = 2
|
7
7
|
|
8
8
|
# The minor version of Forme, updated for new feature releases of Forme.
|
9
|
-
MINOR =
|
9
|
+
MINOR = 0
|
10
10
|
|
11
11
|
# The patch version of Forme, updated only for bug fixes from the last
|
12
12
|
# feature release.
|
data/lib/forme.rb
CHANGED
@@ -162,5 +162,16 @@ module Forme
|
|
162
162
|
end
|
163
163
|
end
|
164
164
|
|
165
|
-
|
166
|
-
|
165
|
+
require_relative 'forme/form'
|
166
|
+
require_relative 'forme/input'
|
167
|
+
require_relative 'forme/tag'
|
168
|
+
require_relative 'forme/raw'
|
169
|
+
require_relative 'forme/version'
|
170
|
+
|
171
|
+
require_relative 'forme/transformers/error_handler'
|
172
|
+
require_relative 'forme/transformers/formatter'
|
173
|
+
require_relative 'forme/transformers/helper'
|
174
|
+
require_relative 'forme/transformers/inputs_wrapper'
|
175
|
+
require_relative 'forme/transformers/labeler'
|
176
|
+
require_relative 'forme/transformers/serializer'
|
177
|
+
require_relative 'forme/transformers/wrapper'
|
data/lib/roda/plugins/forme.rb
CHANGED
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require_relative '../../forme/template'
|
4
|
+
|
5
|
+
class Roda
|
6
|
+
module RodaPlugins
|
7
|
+
module FormeErubiCapture
|
8
|
+
def self.load_dependencies(app)
|
9
|
+
app.plugin :forme_route_csrf
|
10
|
+
app.plugin :capture_erb
|
11
|
+
app.plugin :inject_erb
|
12
|
+
end
|
13
|
+
|
14
|
+
class Form < ::Forme::Template::Form
|
15
|
+
%w'inputs tag subform'.each do |meth|
|
16
|
+
class_eval(<<-END, __FILE__, __LINE__+1)
|
17
|
+
def #{meth}(*)
|
18
|
+
if block_given?
|
19
|
+
@scope.capture_erb do
|
20
|
+
super
|
21
|
+
@scope.instance_variable_get(@scope.render_opts[:template_opts][:outvar])
|
22
|
+
end
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
END
|
28
|
+
end
|
29
|
+
|
30
|
+
def emit(tag)
|
31
|
+
@scope.inject_erb(tag)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module InstanceMethods
|
36
|
+
def form(*)
|
37
|
+
if block_given?
|
38
|
+
capture_erb do
|
39
|
+
super
|
40
|
+
instance_variable_get(render_opts[:template_opts][:outvar])
|
41
|
+
end
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def _forme_form_class
|
50
|
+
Form
|
51
|
+
end
|
52
|
+
|
53
|
+
def _forme_form_options(obj, attr, opts)
|
54
|
+
super
|
55
|
+
opts[:hidden_tags_uplevel] = 2
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
register_plugin(:forme_erubi_capture, FormeErubiCapture)
|
61
|
+
end
|
62
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative '../../forme/template'
|
4
4
|
|
5
5
|
class Roda
|
6
6
|
module RodaPlugins
|
@@ -13,21 +13,18 @@ class Roda
|
|
13
13
|
end
|
14
14
|
|
15
15
|
module InstanceMethods
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
if obj.is_a?(Hash)
|
20
|
-
attribs = obj
|
21
|
-
options = attr = attr.dup
|
22
|
-
else
|
23
|
-
attribs = attr
|
24
|
-
options = opts = opts.dup
|
25
|
-
end
|
16
|
+
include ::Forme::Template::Helper
|
17
|
+
|
18
|
+
private
|
26
19
|
|
27
|
-
|
20
|
+
# Add the csrf and hidden tags options if needed.
|
21
|
+
def _forme_form_options(obj, attr, opts)
|
22
|
+
super
|
23
|
+
|
24
|
+
apply_csrf = opts[:csrf]
|
28
25
|
|
29
26
|
if apply_csrf || apply_csrf.nil?
|
30
|
-
unless method =
|
27
|
+
unless method = attr[:method] || attr['method']
|
31
28
|
if obj && !obj.is_a?(Hash) && obj.respond_to?(:forme_default_request_method)
|
32
29
|
method = obj.forme_default_request_method
|
33
30
|
end
|
@@ -39,32 +36,17 @@ class Roda
|
|
39
36
|
end
|
40
37
|
|
41
38
|
if apply_csrf
|
42
|
-
token = if
|
43
|
-
csrf_token(csrf_path(
|
39
|
+
token = if opts.fetch(:use_request_specific_token){use_request_specific_csrf_tokens?}
|
40
|
+
csrf_token(csrf_path(attr[:action]), method)
|
44
41
|
else
|
45
42
|
csrf_token
|
46
43
|
end
|
47
44
|
|
48
|
-
|
49
|
-
|
50
|
-
|
45
|
+
opts[:csrf] = [csrf_field, token]
|
46
|
+
opts[:_before] = lambda do |form|
|
47
|
+
form.tag(:input, :type=>:hidden, :name=>csrf_field, :value=>token)
|
48
|
+
end
|
51
49
|
end
|
52
|
-
|
53
|
-
options[:output] = @_out_buf if block
|
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
|
68
50
|
end
|
69
51
|
end
|
70
52
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
3
|
require 'rack/utils'
|
4
|
-
|
4
|
+
require_relative '../../forme/template'
|
5
5
|
|
6
6
|
class Roda
|
7
7
|
module RodaPlugins
|
@@ -34,74 +34,6 @@ class Roda
|
|
34
34
|
:missing_namespace=>"no content in expected namespace"
|
35
35
|
}.freeze
|
36
36
|
|
37
|
-
# Forme::Form subclass that adds hidden fields with metadata that can be used
|
38
|
-
# to automatically process form submissions.
|
39
|
-
class Form < ::Forme::ERB::Form
|
40
|
-
def initialize(obj, opts=nil)
|
41
|
-
super
|
42
|
-
@forme_namespaces = @opts[:namespace]
|
43
|
-
end
|
44
|
-
|
45
|
-
# Try adding hidden fields to all forms
|
46
|
-
def form(*)
|
47
|
-
if block_given?
|
48
|
-
super do |f|
|
49
|
-
yield f
|
50
|
-
hmac_hidden_fields
|
51
|
-
end
|
52
|
-
else
|
53
|
-
t = super
|
54
|
-
if tags = hmac_hidden_fields
|
55
|
-
tags.each{|tag| t << tag}
|
56
|
-
end
|
57
|
-
t
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
private
|
62
|
-
|
63
|
-
# Add hidden fields with metadata, if the form has an object associated that
|
64
|
-
# supports the forme_inputs method, and it includes inputs.
|
65
|
-
def hmac_hidden_fields
|
66
|
-
if (obj = @opts[:obj]) && obj.respond_to?(:forme_inputs) && (forme_inputs = obj.forme_inputs)
|
67
|
-
columns = []
|
68
|
-
valid_values = {}
|
69
|
-
|
70
|
-
forme_inputs.each do |field, input|
|
71
|
-
next unless col = obj.send(:forme_column_for_input, input)
|
72
|
-
col = col.to_s
|
73
|
-
columns << col
|
74
|
-
|
75
|
-
next unless validation = obj.send(:forme_validation_for_input, field, input)
|
76
|
-
validation[0] = validation[0].to_s
|
77
|
-
has_nil = false
|
78
|
-
validation[1] = validation[1].map do |v|
|
79
|
-
has_nil ||= v.nil?
|
80
|
-
v.to_s
|
81
|
-
end
|
82
|
-
validation[1] << nil if has_nil
|
83
|
-
valid_values[col] = validation
|
84
|
-
end
|
85
|
-
|
86
|
-
return if columns.empty?
|
87
|
-
|
88
|
-
data = {}
|
89
|
-
data['columns'] = columns
|
90
|
-
data['namespaces'] = @forme_namespaces
|
91
|
-
data['csrf'] = @opts[:csrf]
|
92
|
-
data['valid_values'] = valid_values unless valid_values.empty?
|
93
|
-
data['form_version'] = @opts[:form_version] if @opts[:form_version]
|
94
|
-
|
95
|
-
data = data.to_json
|
96
|
-
tags = []
|
97
|
-
tags << tag(:input, :type=>:hidden, :name=>:_forme_set_data, :value=>data)
|
98
|
-
tags << tag(:input, :type=>:hidden, :name=>:_forme_set_data_hmac, :value=>OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, @opts[:roda].class.opts[:forme_set_hmac_secret], data))
|
99
|
-
tags.each{|tag| emit(tag)}
|
100
|
-
tags
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
37
|
module InstanceMethods
|
106
38
|
# Return hash based on submitted parameters, with :values key
|
107
39
|
# being submitted values for the object, and :validations key
|
@@ -151,15 +83,46 @@ class Roda
|
|
151
83
|
raise Error, ERROR_MESSAGES[type]
|
152
84
|
end
|
153
85
|
|
154
|
-
# Use form class that adds hidden fields for metadata.
|
155
|
-
def _forme_form_class
|
156
|
-
Form
|
157
|
-
end
|
158
|
-
|
159
86
|
# Include a reference to the current scope to the form. This reference is needed
|
160
87
|
# to correctly construct the HMAC.
|
161
|
-
def _forme_form_options(
|
162
|
-
|
88
|
+
def _forme_form_options(obj, attr, opts)
|
89
|
+
super
|
90
|
+
|
91
|
+
opts[:_after] = lambda do |form|
|
92
|
+
if (obj = form.opts[:obj]) && obj.respond_to?(:forme_inputs) && (forme_inputs = obj.forme_inputs)
|
93
|
+
columns = []
|
94
|
+
valid_values = {}
|
95
|
+
|
96
|
+
forme_inputs.each do |field, input|
|
97
|
+
next unless col = obj.send(:forme_column_for_input, input)
|
98
|
+
col = col.to_s
|
99
|
+
columns << col
|
100
|
+
|
101
|
+
next unless validation = obj.send(:forme_validation_for_input, field, input)
|
102
|
+
validation[0] = validation[0].to_s
|
103
|
+
has_nil = false
|
104
|
+
validation[1] = validation[1].map do |v|
|
105
|
+
has_nil ||= v.nil?
|
106
|
+
v.to_s
|
107
|
+
end
|
108
|
+
validation[1] << nil if has_nil
|
109
|
+
valid_values[col] = validation
|
110
|
+
end
|
111
|
+
|
112
|
+
return if columns.empty?
|
113
|
+
|
114
|
+
data = {}
|
115
|
+
data['columns'] = columns
|
116
|
+
data['namespaces'] = form.opts[:namespace]
|
117
|
+
data['csrf'] = form.opts[:csrf]
|
118
|
+
data['valid_values'] = valid_values unless valid_values.empty?
|
119
|
+
data['form_version'] = form.opts[:form_version] if form.opts[:form_version]
|
120
|
+
|
121
|
+
data = data.to_json
|
122
|
+
form.tag(:input, :type=>:hidden, :name=>:_forme_set_data, :value=>data)
|
123
|
+
form.tag(:input, :type=>:hidden, :name=>:_forme_set_data_hmac, :value=>OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, self.class.opts[:forme_set_hmac_secret], data))
|
124
|
+
end
|
125
|
+
end
|
163
126
|
end
|
164
127
|
|
165
128
|
# Internals of forme_parse_hmac and forme_set_hmac.
|
data/lib/sequel/plugins/forme.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require 'thread'
|
3
|
+
require_relative '../../forme'
|
5
4
|
|
6
5
|
module Sequel # :nodoc:
|
7
6
|
module Plugins # :nodoc:
|
@@ -37,7 +36,8 @@ module Sequel # :nodoc:
|
|
37
36
|
# of the association for the form's +obj+. Inside the block, calls to
|
38
37
|
# the +input+ and +inputs+ methods for the receiver treat the associated
|
39
38
|
# object as the recevier's +obj+, using name and id attributes that work
|
40
|
-
# with the Sequel +nested_attributes+ plugin.
|
39
|
+
# with the Sequel +nested_attributes+ plugin. Returns the HTML generated by
|
40
|
+
# the subform.
|
41
41
|
#
|
42
42
|
# The following options are currently supported:
|
43
43
|
# :inputs :: Automatically call +inputs+ with the given values. Using
|
@@ -59,45 +59,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
|
|
@@ -452,21 +454,6 @@ module Sequel # :nodoc:
|
|
452
454
|
end
|
453
455
|
end
|
454
456
|
|
455
|
-
# Helper module used for Sequel forms using ERB template integration. Necessary for
|
456
|
-
# proper subform handling when using such forms with partials.
|
457
|
-
module ERBSequelForm
|
458
|
-
# Capture the inside of the inputs, injecting it into the template
|
459
|
-
# if a block is given, or returning it as a string if not.
|
460
|
-
def subform(*, &block)
|
461
|
-
if block
|
462
|
-
capture(block){super}
|
463
|
-
else
|
464
|
-
capture{super}
|
465
|
-
end
|
466
|
-
end
|
467
|
-
end
|
468
|
-
SinatraSequelForm = ERBSequelForm
|
469
|
-
|
470
457
|
class Form < ::Forme::Form
|
471
458
|
include SequelForm
|
472
459
|
end
|
@@ -486,7 +473,6 @@ module Sequel # :nodoc:
|
|
486
473
|
unless klass = MUTEX.synchronize{FORM_CLASSES[base]}
|
487
474
|
klass = Class.new(base)
|
488
475
|
klass.send(:include, SequelForm)
|
489
|
-
klass.send(:include, ERBSequelForm) if defined?(::Forme::ERB::Form) && base == ::Forme::ERB::Form
|
490
476
|
MUTEX.synchronize{FORM_CLASSES[base] = klass}
|
491
477
|
end
|
492
478
|
klass
|
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')}
|