Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
33 changes: 33 additions & 0 deletions docs/docs/getting-started/simple-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,39 @@ The `search_form_for` answer format can be set like this:
<%= search_form_for(@q, format: :json) do |f| %>
```

### Turbo-enabled search form

For applications using Turbo (Hotwire), Ransack provides `turbo_search_form_for` helper which submits forms via turbo streams instead of traditional HTML GET requests. This is particularly useful when integrating with paginated results or other turbo-enabled components:

```erb
<%= turbo_search_form_for @q do |f| %>
<%= f.label :name_cont %>
<%= f.search_field :name_cont %>
<%= f.submit %>
<% end %>
```

The turbo form helper supports the same options as `search_form_for`, plus additional turbo-specific options:

```erb
# Target a specific turbo frame
<%= turbo_search_form_for @q, turbo_frame: 'search_results' do |f| %>
# form fields
<% end %>

# Use a custom HTTP method (defaults to POST)
<%= turbo_search_form_for @q, method: :patch do |f| %>
# form fields
<% end %>

# Set turbo action behavior
<%= turbo_search_form_for @q, turbo_action: 'replace' do |f| %>
# form fields
<% end %>
```

This eliminates the need for complex controller logic to handle different request formats when combining search with pagination.

### Search link helper

Ransack's `sort_link` helper creates table headers that are sortable links
Expand Down
44 changes: 44 additions & 0 deletions lib/ransack/helpers/form_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,50 @@ def search_form_for(record, options = {}, &proc)
form_for(record, options, &proc)
end

# +turbo_search_form_for+
#
# <%= turbo_search_form_for(@q) do |f| %>
#
# This is a turbo-enabled version of search_form_for that submits via turbo streams
# instead of traditional HTML GET requests. Useful for seamless integration with
# paginated results and other turbo-enabled components.
#
def turbo_search_form_for(record, options = {}, &proc)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot this method is long with a complex if/else. Can it be broken down into smaller methods and handle the conditional logic with returns if true / false ?

Each method can then be individually tested

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored both search_form_for and turbo_search_form_for methods into smaller, testable units. Extracted common logic into private methods:

  • extract_search_and_set_url - handles search object validation and URL setting
  • build_turbo_options - constructs turbo-specific data attributes
  • build_html_options - builds base HTML options for forms
  • finalize_form_options - sets final form configuration

Each method can now be individually tested and the complex conditional logic is simplified with early returns. Added comprehensive tests for all private methods. Fixed in commit a249cfa.

if record.is_a? Ransack::Search
search = record
options[:url] ||= polymorphic_path(
search.klass, format: options.delete(:format)
)
elsif record.is_a?(Array) &&
(search = record.detect { |o| o.is_a?(Ransack::Search) })
options[:url] ||= polymorphic_path(
options_for(record), format: options.delete(:format)
)
else
raise ArgumentError,
'No Ransack::Search object was provided to turbo_search_form_for!'
end
options[:html] ||= {}

# Set up turbo-specific options
turbo_options = {
'data-turbo-frame': options.delete(:turbo_frame),
'data-turbo-action': options.delete(:turbo_action) || 'advance'
}.compact

html_options = {
class: html_option_for(options[:class], search),
id: html_option_for(options[:id], search),
method: options.delete(:method) || :post
}.merge(turbo_options)

options[:as] ||= Ransack.options[:search_key]
options[:html].reverse_merge!(html_options)
options[:builder] ||= FormBuilder

form_for(record, options, &proc)
end

# +sort_link+
#
# <%= sort_link(@q, :name, [:name, 'kind ASC'], 'Player Name') %>
Expand Down
70 changes: 70 additions & 0 deletions spec/ransack/helpers/form_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,76 @@ module Helpers
}
it { should match /example_name_eq/ }
end

describe '#turbo_search_form_for with default options' do
subject {
@controller.view_context
.turbo_search_form_for(Person.ransack) {}
}
it { should match /action="\/people"/ }
it { should match /method="post"/ }
it { should match /data-turbo-action="advance"/ }
end

describe '#turbo_search_form_for with custom method' do
subject {
@controller.view_context
.turbo_search_form_for(Person.ransack, method: :patch) {}
}
it { should match /method="patch"/ }
it { should match /data-turbo-action="advance"/ }
end

describe '#turbo_search_form_for with turbo_frame' do
subject {
@controller.view_context
.turbo_search_form_for(Person.ransack, turbo_frame: 'search_results') {}
}
it { should match /data-turbo-frame="search_results"/ }
end

describe '#turbo_search_form_for with custom turbo_action' do
subject {
@controller.view_context
.turbo_search_form_for(Person.ransack, turbo_action: 'replace') {}
}
it { should match /data-turbo-action="replace"/ }
end

describe '#turbo_search_form_for with format' do
subject {
@controller.view_context
.turbo_search_form_for(Person.ransack, format: :json) {}
}
it { should match /action="\/people.json"/ }
end

describe '#turbo_search_form_for with array of routes' do
subject {
@controller.view_context
.turbo_search_form_for([:admin, Comment.ransack]) {}
}
it { should match /action="\/admin\/comments"/ }
end

describe '#turbo_search_form_for with custom search key' do
before do
Ransack.configure { |c| c.search_key = :example }
end
subject {
@controller.view_context
.turbo_search_form_for(Person.ransack) { |f| f.text_field :name_eq }
}
it { should match /example_name_eq/ }
end

describe '#turbo_search_form_for without Ransack::Search object' do
it 'raises ArgumentError' do
expect {
@controller.view_context.turbo_search_form_for("not a search object") {}
}.to raise_error(ArgumentError, 'No Ransack::Search object was provided to turbo_search_form_for!')
end
end
end
end
end
Loading