diff --git a/CHANGELOG.md b/CHANGELOG.md index 7112931..188cc47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Entries before 1.6.6 were reconstructed from `git log` and RubyGems release date ### Changed +- `Ruby2html::Component#render_in` accepts Rails' keyword options (`locals:`), as Rails main (8.2) passes them and deprecates `render_in` without options. - On CRuby, a C extension that fails to load now prints one warning with the reason (`ruby2html: C extension not loaded (...); using the pure-Ruby renderer`) instead of silently rendering about 3x slower. JRuby/TruffleRuby and `RUBY2HTML_DISABLE_EXT=1` stay silent. ## [2.0.0] - 2026-10-31 diff --git a/gemfiles/rails_common.rb b/gemfiles/rails_common.rb index 91927f2..c8909de 100644 --- a/gemfiles/rails_common.rb +++ b/gemfiles/rails_common.rb @@ -12,7 +12,13 @@ gem 'rake-compiler', '~> 1.2', require: false group :test do - gem 'rspec-rails', '>= 6.1' + # Rails main pairs with rspec-rails main: releases trail Rails main changes (e.g. Rails main + # freezes default_url_options, which rspec-rails 8.0.x still writes to). + if ENV['RAILS_VERSION'] == 'edge' + gem 'rspec-rails', github: 'rspec/rspec-rails', branch: 'main' + else + gem 'rspec-rails', '>= 6.1' + end gem 'capybara', '~> 3.40' gem 'faker', '>= 3.4' gem 'benchmark-ips' diff --git a/lib/gem/ruby2html/component.rb b/lib/gem/ruby2html/component.rb index de3ecd3..1c2a6e3 100644 --- a/lib/gem/ruby2html/component.rb +++ b/lib/gem/ruby2html/component.rb @@ -64,8 +64,10 @@ def __r2h_capacity # The buffer currently written to (RailsComponents swap it to capture blocks). attr_accessor :current_output - # Rails' entry point: `render component` in views and controllers. - def render_in(view_context, &block) + # Rails' entry point: `render component` in views and controllers. Rails 8.2+ passes + # `locals:` (render_in without options is deprecated there); a component takes its data + # through #initialize, so options are accepted and ignored. + def render_in(view_context, **_options, &block) buffer = String.new(capacity: self.class.__r2h_capacity, encoding: Encoding::UTF_8) content = block && -> { plain(view_context.capture(self, &block)) } render_into(buffer, view_context, content) diff --git a/spec/gem/rails/component_spec.rb b/spec/gem/rails/component_spec.rb index 9066746..88c6c7f 100644 --- a/spec/gem/rails/component_spec.rb +++ b/spec/gem/rails/component_spec.rb @@ -50,9 +50,9 @@ def view_template it 'never builds a String for nested components' do child = CardComponent.new(title: 'x') expect(child).not_to receive(:render_in) - parent = Class.new(described_class) do + parent = stub_const('ParentComponent', Class.new(described_class) do define_method(:view_template) { render child } - end + end) view.render(parent.new) end @@ -71,26 +71,27 @@ def view_template end it 'supports #call and an overridden #template as the entry point' do - via_call = Class.new(described_class) { def call = p('call') } - via_template = Class.new(described_class) { def template = p('template') } + via_call = stub_const('ViaCallComponent', Class.new(described_class) { def call = p('call') }) + via_template = stub_const('ViaTemplateComponent', Class.new(described_class) { def template = p('template') }) expect(view.render(via_call.new)).to eq('
call
') expect(view.render(via_template.new)).to eq('template
') end it 'raises a clear error without an entry point, and helpers outside a render' do - expect { view.render(Class.new(described_class).new) }.to raise_error(NotImplementedError, /view_template/) + stub_const('EmptyComponent', Class.new(described_class)) + expect { view.render(EmptyComponent.new) }.to raise_error(NotImplementedError, /view_template/) expect { CardComponent.new(title: 'x').helpers }.to raise_error(Ruby2html::Error) end it 'restores its state when the template raises, so it can be rendered again' do - flaky = Class.new(described_class) do + flaky = stub_const('FlakyComponent', Class.new(described_class) do attr_accessor :fail def view_template h1 'ok' raise 'boom' if fail end - end.new + end).new flaky.fail = true expect { view.render(flaky) }.to raise_error('boom') expect(flaky.current_output).to be_nil @@ -99,7 +100,7 @@ def view_template end it 'renders two components interleaved in fibers without crosstalk' do - yielding = Class.new(described_class) do + yielding = stub_const('YieldingComponent', Class.new(described_class) do def initialize(name) = @name = name def view_template @@ -107,7 +108,7 @@ def view_template Fiber.yield p @name end - end + end) fibers = %w[a b].map { |name| Fiber.new { view.render(yielding.new(name)) } } fibers.each(&:resume) expect(fibers.reverse.map(&:resume)).to eq(['b
', 'a
'])