Skip to content

Commit

Permalink
Add Roda forme_erubi_capture_block plugin to support erubi/capture_bl…
Browse files Browse the repository at this point in the history
…ock <%= form do %> <% end %> tags

This is significantly friendlier to users than the
forme_erubi_capture plugin, since you can use <%= instead of <|%=
and the like.
  • Loading branch information
jeremyevans committed Jun 14, 2024
1 parent af804ea commit fd04430
Show file tree
Hide file tree
Showing 6 changed files with 326 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .ci.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ source 'https://rubygems.org'
gem "minitest-global_expectations"
gem "roda"
gem "rack_csrf"
gem "erubi"
gem "erubi", '>= 1.13'
gem "tilt"

if RUBY_VERSION < '2.0'
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
=== master

* Add Roda forme_erubi_capture_block plugin to support erubi/capture_block <%= form do %> <% end %> tags (jeremyevans)

* Support :hr as select option value to use a hr tag instead of an option tag (jeremyevans)

* Support maxlength and minlength options as attributes for textareas (jeremyevans)
Expand Down
21 changes: 21 additions & 0 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ Forme ships with multiple Roda plugins
* forme_set (discussed above)
* forme
* forme_route_csrf
* forme_erubi_capture_block
* forme_erubi_capture

== forme_route_csrf and forme plugins
Expand Down Expand Up @@ -837,6 +838,26 @@ The forme plugin does not require any csrf plugin, but will transparently use
Rack::Csrf if it is available. If Rack::Csrf is available a CSRF tag if the form's
method is +POST+, with no configuration ability.

== forme_erubi_capture_block plugin

The forme_erubi_capture_block plugin builds on the forme_route_csrf plugin, but it supports
the erubi/capture_block engine, which allows this syntax:

<%= form(@obj, :action=>'/foo') do |f| %>
<%= f.input(:field) %>
<%= f.tag(:fieldset) do %>
<%= f.input(:field_two) %>
<% end %>
<% end %>

If you use the forme_erubi_capture)block plugin, you need to manually set Roda to use the
erubi/capture_block engine, which you can do via:

require 'erubi/capture_block'
app.plugin :render, :engine_opts=>{'erb'=>{:engine_class=>Erubi::CaptureBlockEngine}}

The forme_erubi_capture plugin requires Erubi 1.13.0+.

== forme_erubi_capture plugin

The forme_erubi_capture plugin builds on the forme_route_csrf plugin, but it supports
Expand Down
45 changes: 45 additions & 0 deletions lib/roda/plugins/forme_erubi_capture_block.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen-string-literal: true

require_relative '../../forme/template'

class Roda
module RodaPlugins
module FormeErubiCaptureBlock
def self.load_dependencies(app)
app.plugin :forme_route_csrf
end

class Form < ::Forme::Template::Form
%w'inputs tag subform'.each do |meth|
class_eval(<<-END, __FILE__, __LINE__+1)
def #{meth}(*)
if block_given?
@scope.instance_variable_get(@scope.render_opts[:template_opts][:outvar]).capture{super}
else
super
end
end
END
end
end

module InstanceMethods
def _forme_form(obj, attr, opts, &block)
if block && opts[:emit] != false
instance_variable_get(render_opts[:template_opts][:outvar]).capture{super}
else
super
end
end

private

def _forme_form_class
Form
end
end
end

register_plugin(:forme_erubi_capture_block, FormeErubiCaptureBlock)
end
end
211 changes: 211 additions & 0 deletions spec/erubi_capture_block_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
require_relative 'shared_erb_specs'

ERUBI_CAPTURE_BLOCK_BLOCK = lambda do |r|
r.get '' do
erb <<END
<%= form([:foo, :bar], :action=>'/baz') do |f| %>
<p>FBB</p>
<%= f.input(:first) %>
<%= f.input(:last) %>
<%= f.button('Save') %>
<% end %>
END
end

r.get 'inputs_block' do
erb <<END
<%= form([:foo, :bar], :action=>'/baz') do |f| %><%= f.inputs(:legend=>'FBB') do %>
<%= f.input(:last) %>
<% end %><% end %>
END
end

r.get 'inputs_block_wrapper' do
erb <<END
<%= form([:foo, :bar], {:action=>'/baz'}, :inputs_wrapper=>:fieldset_ol) do |f| %><%= f.inputs(:legend=>'FBB') do %>
<%= f.input(:last) %>
<% end %><% end %>
END
end

r.get 'no_emit' do
erb <<END
<%= form([:foo, :bar], {:action=>'/baz'}, :emit=>false) do |f|
f.tag(:p, {}, 'FBB')
f.tag(:div) do
f.input(:first)
f.input(:last)
end
end %>
END
end

r.get 'nest' do
erb <<END
<%= form([:foo, :bar], :action=>'/baz') do |f| %>
<%= f.tag(:p, {}, 'FBB') %>
<%= f.tag(:div) do %>
<%= f.input(:first) %>
<%= f.input(:last) %>
<% end %>
<% end %>
END
end

r.get 'nest_sep' do
@nest = <<END
n1
<%= f.tag(:div) do %>
n2
<%= f.input(:first) %>
<%= f.input(:last) %>
n3
<% end %>
n4
<%= f.inputs([:first, :last], :legend=>'Foo') %>
n5
END
erb <<END
0
<%= form([:foo, :bar], :action=>'/baz') do |f| %>
1
<%= f.tag(:p, {}, 'FBB') %>
2
<%= erb(@nest, :locals=>{:f=>f}) %>
3
<% end %>
4
END
end

r.get 'nest_seq_simple' do
@album = Album.load(:name=>'N', :copies_sold=>2, :id=>1)
@album.associations[:artist] = Artist.load(:name=>'A', :id=>2)
@nest = <<END
n1
<%= f.subform(:artist) do %>
n2
<%= f.input(:name2) %>
n3
<% end %>
n4
END
erb <<END
0
<%= form(@album, :action=>'/baz') do |f| %>
1
<%= erb(@nest, :locals=>{:f=>f}) %>
3
<% end %>
4
END
end

r.get 'nest_seq' do
@album = Album.load(:name=>'N', :copies_sold=>2, :id=>1)
@album.associations[:artist] = Artist.load(:name=>'A', :id=>2)
@nest = <<END
n1
<%= f.subform(:artist) do %>
n2
<%= f.input(:name2) %>
n3
<% end %>
n4
<%= f.subform(:artist, :inputs=>[:name3], :legend=>'Bar') %>
n5
END
erb <<END
0
<%= form(@album, :action=>'/baz') do |f| %>
1
<%= f.subform(:artist, :inputs=>[:name], :legend=>'Foo') %>
2
<%= erb(@nest, :locals=>{:f=>f}) %>
3
<% end %>
4
END
end

r.get 'grid-block' do
@album = Album.load(:name=>'N', :copies_sold=>2, :id=>1)
@album.associations[:artist] = Artist.load(:name=>'A', :id=>2)
erb <<END
0
<%= form(@album, {:action=>'/baz'}, :button=>'Sub') do |f| %>
1
<%= f.subform(:artist, :inputs=>[:name], :legend=>'Foo', :grid=>true, :labels=>%w'Name') do %>
2
<% end %>
3
<% end %>
4
END
end

r.get 'grid-noblock' do
@album = Album.load(:name=>'N', :copies_sold=>2, :id=>1)
@album.associations[:artist] = Artist.load(:name=>'A', :id=>2)
erb <<END
0
<%= form(@album, {:action=>'/baz'}, :button=>'Sub') do |f| %>
1
<%= f.subform(:artist, :inputs=>[:name], :legend=>'Foo', :grid=>true, :labels=>%w'Name') %>
2
<% end %>
3
END
end

r.get 'grid-noblock-multiple' do
@artist = Artist.load(:name=>'A', :id=>2)
@artist.associations[:albums] = [Album.load(:name=>'N', :copies_sold=>2, :id=>1)]
erb <<END
0
<%= form(@artist, {:action=>'/baz'}, :button=>'Sub') do |f| %>
1
<%= f.subform(:albums, :inputs=>[:name, :copies_sold], :legend=>'Foo', :grid=>true, :labels=>%w'Name Copies') %>
2
<% end %>
3
END
end

r.get 'hash' do
erb "<%= form({:action=>'/baz'}, :obj=>[:foo]) do |f| %> <%= f.input(:first) %> <% end %>"
end

r.get 'legend' do
erb <<END
<%= form([:foo, :bar], :action=>'/baz') do |f| %>
<p>FBB</p>
<%= f.inputs([:first, :last], :legend=>'Foo') %>
<p>FBB2</p>
<% end %>
END
end

r.get 'combined' do
erb <<END
<%= form([:foo, :bar], {:action=>'/baz'}, :inputs=>[:first], :button=>'xyz', :legend=>'123') do |f| %>
<p>FBB</p>
<%= f.input(:last) %>
<% end %>
END
end

r.get 'noblock' do
erb "<%= form([:foo, :bar], {:action=>'/baz'}, :inputs=>[:first], :button=>'xyz', :legend=>'123') %>"
end

r.get 'noblock_post' do
erb "<%= form({:method=>:post}, :button=>'xyz') %>"
end

r.get 'noblock_empty' do
erb "<%= form(:action=>'/baz') %>"
end
end

68 changes: 46 additions & 22 deletions spec/roda_integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,6 @@
rescue LoadError
require 'tilt/erb'
end
else
begin
require 'erubi/capture_end'
require_relative 'erubi_capture_helper'
rescue LoadError
end
end

def FormeRodaTest(block=ERB_BLOCK)
Expand Down Expand Up @@ -110,26 +104,56 @@ module FormeRouteCsrfSpecs

begin
require 'roda/plugins/route_csrf'
require 'roda/plugins/capture_erb'
require 'roda/plugins/inject_erb'
rescue LoadError
warn "unable to load necessary Roda plugins, skipping forme_erubi_capture plugin spec"
warn "unable to load route_csrf Roda plugin, skipping related specs"
else
describe "Forme Roda Erubi::CaptureEnd integration with roda forme_route_csrf" do
app = FormeRodaTest(ERUBI_CAPTURE_BLOCK)
app.plugin :forme_erubi_capture
app.plugin :render, :engine_opts=>{'erb'=>{:engine_class=>Erubi::CaptureEndEngine}}

define_method(:app){app}
define_method(:plugin_opts){{}}
define_method(:sin_get) do |path|
s = String.new
app.call(@rack.merge('PATH_INFO'=>path))[2].each{|str| s << str}
s.gsub(/\s+/, ' ').strip
begin
require 'roda/plugins/capture_erb'
require 'roda/plugins/inject_erb'
require 'erubi/capture_end'
require_relative 'erubi_capture_helper'
rescue LoadError
warn "unable to load necessary Roda plugins, skipping forme_erubi_capture plugin spec"
else
describe "Forme Roda Erubi::CaptureEndEngine integration with roda forme_route_csrf" do
app = FormeRodaTest(ERUBI_CAPTURE_BLOCK)
app.plugin :forme_erubi_capture
app.plugin :render, :engine_opts=>{'erb'=>{:engine_class=>Erubi::CaptureEndEngine}}

define_method(:app){app}
define_method(:plugin_opts){{}}
define_method(:sin_get) do |path|
s = String.new
app.call(@rack.merge('PATH_INFO'=>path))[2].each{|str| s << str}
s.gsub(/\s+/, ' ').strip
end

include FormeRouteCsrfSpecs
end
end

include FormeRouteCsrfSpecs
end if defined?(ERUBI_CAPTURE_BLOCK)
begin
require 'erubi/capture_block'
require_relative 'erubi_capture_block_helper'
rescue LoadError
warn "unable to load erubi/capture_block, skipping forme_erubi_capture_block Roda plugin specs"
else
describe "Forme Roda Erubi::CaptureBlockEngine integration with roda forme_route_csrf" do
app = FormeRodaTest(ERUBI_CAPTURE_BLOCK_BLOCK)
app.plugin :forme_erubi_capture_block
app.plugin :render, :engine_opts=>{'erb'=>{:engine_class=>Erubi::CaptureBlockEngine}}

define_method(:app){app}
define_method(:plugin_opts){{}}
define_method(:sin_get) do |path|
s = String.new
app.call(@rack.merge('PATH_INFO'=>path))[2].each{|str| s << str}
s.gsub(/\s+/, ' ').strip
end

include FormeRouteCsrfSpecs
end
end

[{}, {:require_request_specific_tokens=>false}].each do |plugin_opts|
describe "Forme Roda ERB integration with roda forme_route_csrf and route_csrf plugin with #{plugin_opts}" do
Expand Down

0 comments on commit fd04430

Please sign in to comment.