forme 1.12.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 a
5
- # html tag with attributes and children.
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
- form.raw_output(Forme.transform(:serializer, @opts, @form.opts, self))
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
@@ -16,4 +16,3 @@ module Forme
16
16
  end
17
17
  end
18
18
  end
19
-
@@ -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.emit(form.tag(:legend, opts[:legend_attr], legend))
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.tag_(:ol){yield}}
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.emit(form.tag(:caption, opts[:legend_attr], legend))
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.emit(form.tag(:tr, {}, labels.map{|l| form._tag(:th, {}, l)}))
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 = 1
6
+ MAJOR = 2
7
7
 
8
8
  # The minor version of Forme, updated for new feature releases of Forme.
9
- MINOR = 12
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
- %w'form input tag raw version'.each{|f| require File.expand_path("../forme/#{f}", __FILE__)}
166
- %w'error_handler formatter helper inputs_wrapper labeler serializer wrapper'.each{|f| require File.expand_path("../forme/transformers/#{f}", __FILE__)}
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'
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require 'forme/erb'
3
+ require_relative '../../forme/erb'
4
4
 
5
5
  class Roda
6
6
  module RodaPlugins
@@ -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
- require 'forme/erb_form'
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
- # Create a +Form+ object tied to the current output buffer,
17
- # using the standard ERB hidden tags.
18
- def form(obj=nil, attr={}, opts={}, &block)
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
- apply_csrf = options[:csrf]
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 = attribs[:method] || attribs['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 options.fetch(:use_request_specific_token){use_request_specific_csrf_tokens?}
43
- csrf_token(csrf_path(attribs[:action]), method)
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
- options[:csrf] = [csrf_field, token]
49
- options[:hidden_tags] ||= []
50
- options[:hidden_tags] += [{csrf_field=>token}]
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
- require 'forme/erb_form'
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(options)
162
- options.merge!(:roda=>self)
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.
@@ -1,7 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require 'forme'
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
- nested_obj = opts.has_key?(:obj) ? opts[:obj] : obj.send(association)
63
- ref = obj.class.association_reflection(association)
64
- multiple = ref.returns_array?
65
- grid = opts[:grid]
66
- ns = "#{association}_attributes"
67
-
68
- contents = proc do
69
- send(multiple ? :each_obj : :with_obj, nested_obj, ns) do |no, i|
70
- emit(input(ref.associated_class.primary_key, :type=>:hidden, :label=>nil, :wrapper=>nil)) unless no.new? || opts[:skip_primary_key]
71
- options = opts.dup
72
- if grid
73
- options.delete(:legend)
74
- else
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 multiple
81
- options[:legend] = humanize("#{obj.model.send(:singularize, association)} ##{i+1}")
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
- options[:legend] = humanize(association)
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
- options[:subform] = true
88
- _inputs(options[:inputs]||[], options, &block)
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
@@ -1,5 +1,7 @@
1
+ # frozen-string-literal: true
2
+
1
3
  require 'i18n'
2
- require 'sequel/plugins/forme'
4
+ require_relative 'forme'
3
5
 
4
6
  module Sequel # :nodoc:
5
7
  module Plugins # :nodoc:
data/spec/all.rb CHANGED
@@ -1,2 +1,2 @@
1
1
  $: << 'lib'
2
- Dir['./spec/*_spec.rb'].each{|f| require f}
2
+ Dir.new(File.dirname(__FILE__)).each{|f| require_relative f if f.end_with?('_spec.rb')}