Skip to content

Add default #render_in implementation to ActiveModel::Conversion#57349

Merged
guilleiguaran merged 1 commit into
rails:mainfrom
seanpdoyle:active-model-conversion-render-in
May 12, 2026
Merged

Add default #render_in implementation to ActiveModel::Conversion#57349
guilleiguaran merged 1 commit into
rails:mainfrom
seanpdoyle:active-model-conversion-render-in

Conversation

@seanpdoyle

@seanpdoyle seanpdoyle commented May 12, 2026

Copy link
Copy Markdown
Contributor

Motivation / Background

Follow-up to #50623
Follow-up to #46202

The #render_in method was introduced as an integration touch-point for views to intercept object rendering conventionally handled by Action View.

This PR aims to provide Active Model-level (and therefore Active Record-level) flexibility to control how Action View transform models into HTML and JSON strings.

For example, users of the view_component gem can "intercept" model rendering to directly an instance to a ViewComponent::Base instance.

Detail

With the following view partial:

<%# app/views/people/_person.html.erb %>

<%= local_assigns[:shout] ? person.name.upcase : person.name %>

Callers can render an instance of Person as a positional argument or a :renderable option:

person = Person.new(name: "Ralph")

render person                                       # => "Ralph"
render person, shout: true                          # => "RALPH"
render renderable: person                           # => "Ralph"
render renderable: person, locals: { shout: true }  # => "RALPH"

This change aims to preserve backward compatibility by providing a default #render_in method to relies on render the instance with a view partial inferred from combining partial: to_partial_path and object: self.

Checklist

Before submitting the PR make sure the following are checked:

  • This Pull Request is related to one change. Unrelated changes should be opened in separate PRs.
  • Commit message has a detailed description of what changed and why. If this PR fixes a related issue include it in the commit message. Ex: [Fix #issue-number]
  • Tests are added or updated if you fix a bug or add a feature.
  • CHANGELOG files are updated for the changed libraries if there is a behavior change or additional feature. Minor bug fixes and documentation changes should not be included.

Follow-up to [rails#46202][]

Without overriding the new `#render_in` method, previous behavior will
be preserved: render the view partial determined by calling
`#to_partial_path`, then pass `object: self`.

With the following view partial:

```erb
<%# app/views/people/_person.html.erb %>
<% local_assigns.with_defaults(shout: false) => { shout: } %>

<%= shout ? person.name.upcase : person.name %>
```

Callers can render an instance of `Person` as a positional argument or a
`:renderable` option:

```ruby
person = Person.new(name: "Ralph")

render person                                       # => "Ralph"
render person, shout: true                          # => "RALPH"
render renderable: person                           # => "Ralph"
render renderable: person, locals: { shout: true }  # => "RALPH"
```

This preserves backward compatibility. At the same time, the
`#render_in` method provides applications with an more flexibility, and
an opportunity to manage how to transform models into Strings. For
example, users of ViewComponent can map a model directly to a
`ViewComponent::Base` instance.

[rails#46202]: rails#46202 (comment)
@guilleiguaran guilleiguaran merged commit 54660ef into rails:main May 12, 2026
4 checks passed
@claudiob

Copy link
Copy Markdown
Member

@seanpdoyle Thanks for this PR! Unfortunately this broke my Rails app.

I'm going to try to create a trimmed down version of the app to better identify the issue.
Meanwhile I'll describe it here, maybe this can help already.


I have a standard route/controller for an update action. The update action is defined like this:

# app/controllers/api/bookings_controller.rb
class API::BookingsController < ApplicationController
  def create
    @booking = Booking.find params.expect(:id)
    @booking.update update_params
    response.status = :ok
  end
end

Since I'm not calling render, Rails automatically find the matching view to render, a JBuilder file:

# app/views/api/bookings/update.json.jbuilder
json.partial! @booking

Since I'm not specifying which partial to render, Rails would automatically find this matching one:

# app/views/api/bookings/_booking.json.jbuilder
json.extract! booking, :id, :status

This last part is what's broken after this PR. json.partial! @booking doesn't look for the _booking.json.builder partial in the same folder:

Error:
API::BookingsControllerTest#test_PUT_/api/bookings_responds_with_200_with_valid_authentication_and_params:
RuntimeError: Neutered Exception ActionView::Template::Error: Missing partial bookings/_booking with {locale: [:en], formats: [:json], variants: [], handlers: [:raw, :erb, :html, :builder, :ruby, :jbuilder]}.

Searched in:
  * "/Users/claudiob/code/fountain/app/views"
  * "/Users/claudiob/source/rails/actiontext/app/views"
  * "/Users/claudiob/source/rails/actionmailbox/app/views"

    app/views/api/bookings/update.json.jbuilder:1
    test/controllers/api/bookings_controller_test.rb:33:in 'block in <class:BookingsControllerTest>'
    test/controllers/api/base_controller_test.rb:6:in 'block in <class:BaseControllerTest>'

Let me know if any of this rings a bell. Happy to share a more complete example if that's needed.
Thank you 🙇

@seanpdoyle

Copy link
Copy Markdown
Contributor Author

@claudiob thank you for sharing that context.

@guilleiguaran I've opened #57374 to revert this PR. I'll open a follow-up to incorporate tests that exercise the use-case outlined by @claudiob.

guilleiguaran pushed a commit that referenced this pull request May 15, 2026
…ersion`"

This reverts commit 241f2a0.

Based on a [comment][] on the original PR ([#57349][]), the `render`
call does not incorporate controller-based view partial namespacing
based on the render context.

Since `ActiveModel::Conversion#render_in` is not yet released, this
commit reverts the change in favor of a subsequent re-submission of the
original PR to incorporate the newly documented edge case.

[comment]: #57349 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants