Skip to content

Support custom form builders #291

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions lib/phlex/rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ module Rails
class HelpersCalledBeforeRenderError < StandardError; end

autoload :Buffered, "phlex/rails/buffered"
autoload :Decorator, "phlex/rails/decorator"

autoload :FormBuilder, "phlex/rails/form_builder"
autoload :LabelBuilder, "phlex/rails/label_builder"
autoload :CheckboxBuilder, "phlex/rails/checkbox_builder"
autoload :RadioButtonBuilder, "phlex/rails/radio_button_builder"

autoload :CSV, "phlex/rails/csv"

Expand All @@ -26,6 +20,8 @@ class HelpersCalledBeforeRenderError < StandardError; end
autoload :Layout, "phlex/rails/layout"
autoload :Partial, "phlex/rails/partial"
autoload :Streaming, "phlex/rails/streaming"

autoload :Builder, "phlex/rails/builder"
end

CSV.prepend(Phlex::Rails::CSV)
Expand Down
31 changes: 31 additions & 0 deletions lib/phlex/rails/builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

class Phlex::Rails::Builder < BasicObject
def initialize(object, component:)
@object = object
@component = component
end

define_method :send, ::Kernel.instance_method(:send)

def respond_to_missing?(method_name, include_private = false)
@object.respond_to?(method_name, include_private)
end

def method_missing(*, **, &block)
output = if block
@object.public_send(*, **) do |builder|
yield ::Phlex::Rails::Builder.new(builder, component: @component)
end
else
@object.public_send(*, **)
end

case output
when ::ActiveSupport::SafeBuffer
@component.raw(output)
else
output
end
end
end
10 changes: 0 additions & 10 deletions lib/phlex/rails/checkbox_builder.rb

This file was deleted.

28 changes: 0 additions & 28 deletions lib/phlex/rails/decorator.rb

This file was deleted.

123 changes: 0 additions & 123 deletions lib/phlex/rails/form_builder.rb

This file was deleted.

4 changes: 2 additions & 2 deletions lib/phlex/rails/helper_macros.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ def #{method_name}(*args, **kwargs, &block)
RUBY
end

def register_builder_yielding_helper(method_name, builder)
def register_builder_yielding_helper(method_name)
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
# frozen_string_literal: true

def #{method_name}(*args, **kwargs)
output = if block_given?
view_context.#{method_name}(*args, **kwargs) { |builder|
yield #{builder.name}.new(builder, component: self)
yield Phlex::Rails::Builder.new(builder, component: self)
}
else
view_context.#{method_name}(*args, **kwargs)
Expand Down
2 changes: 1 addition & 1 deletion lib/phlex/rails/helpers/collection_check_boxes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ module Phlex::Rails::Helpers::CollectionCheckBoxes
extend Phlex::Rails::HelperMacros

# [Rails Docs](https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-collection_check_boxes)
register_builder_yielding_helper def collection_check_boxes(...) = nil, Phlex::Rails::CheckboxBuilder
register_builder_yielding_helper def collection_check_boxes(...) = nil
end
2 changes: 1 addition & 1 deletion lib/phlex/rails/helpers/collection_radio_buttons.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ module Phlex::Rails::Helpers::CollectionRadioButtons
extend Phlex::Rails::HelperMacros

# [Rails Docs](https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-collection_radio_buttons)
register_builder_yielding_helper def collection_radio_buttons(...) = nil, Phlex::Rails::RadioButtonBuilder
register_builder_yielding_helper def collection_radio_buttons(...) = nil
end
2 changes: 1 addition & 1 deletion lib/phlex/rails/helpers/fields.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ module Phlex::Rails::Helpers::Fields
extend Phlex::Rails::HelperMacros

# [Rails Docs](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields)
register_builder_yielding_helper def fields(...) = nil, Phlex::Rails::FormBuilder
register_builder_yielding_helper def fields(...) = nil
end
2 changes: 1 addition & 1 deletion lib/phlex/rails/helpers/fields_for.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ module Phlex::Rails::Helpers::FieldsFor
extend Phlex::Rails::HelperMacros

# [Rails Docs](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for)
register_builder_yielding_helper def fields_for(...) = nil, Phlex::Rails::FormBuilder
register_builder_yielding_helper def fields_for(...) = nil
end
2 changes: 1 addition & 1 deletion lib/phlex/rails/helpers/form_for.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ module Phlex::Rails::Helpers::FormFor
extend Phlex::Rails::HelperMacros

# [Rails Docs](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for)
register_builder_yielding_helper def form_for(...) = nil, Phlex::Rails::FormBuilder
register_builder_yielding_helper def form_for(...) = nil
end
2 changes: 1 addition & 1 deletion lib/phlex/rails/helpers/form_with.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ module Phlex::Rails::Helpers::FormWith
extend Phlex::Rails::HelperMacros

# [Rails Docs](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with)
register_builder_yielding_helper def form_with(...) = nil, Phlex::Rails::FormBuilder
register_builder_yielding_helper def form_with(...) = nil
end
2 changes: 1 addition & 1 deletion lib/phlex/rails/helpers/label.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ module Phlex::Rails::Helpers::Label
extend Phlex::Rails::HelperMacros

# [Rails Docs](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-label)
register_builder_yielding_helper def label(...) = nil, Phlex::Rails::LabelBuilder
register_builder_yielding_helper def label(...) = nil
end
6 changes: 0 additions & 6 deletions lib/phlex/rails/label_builder.rb

This file was deleted.

10 changes: 0 additions & 10 deletions lib/phlex/rails/radio_button_builder.rb

This file was deleted.

33 changes: 33 additions & 0 deletions test/helpers/form_with.test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,36 @@
</form>
HTML
end

class CustomFormBuilder < ActionView::Helpers::FormBuilder
def fancy_input(attribute, options = {})
@template.content_tag(:div, class: "fancy-input") do
"Fancy input for #{attribute}"
end
end
end

test "form_with with custom builder" do
component = Class.new(Phlex::HTML) do
include Phlex::Rails::Helpers::FormWith

define_method :view_template do
form_with(url: "/", builder: CustomFormBuilder) do |form|
form.fancy_input :bar
nil # important, otherwise the return value is output
end
end
end

controller.define_singleton_method(:form_authenticity_token) { |_| "(example form authenticity token)" }

output = render(component)

assert_equivalent_html output, <<~HTML
<form action="/" accept-charset="UTF-8" method="post">
<input name="utf8" type="hidden" value="✓" autocomplete="off">
<input type="hidden" name="authenticity_token" value="(example form authenticity token)" autocomplete="off">
<div class="fancy-input">Fancy input for bar</div>
</form>
HTML
end