rails: Rendering a partial outside of a controller results in the wrong host being used in Active Storage URLs

Steps to reproduce

There are various ways to reproduce this. What I did was render an Action Text content field with an image attachment within a Turbo stream using the broadcasts_to class method (which renders in a background job).

Other issues referring to this behavior:

It appears that this issue has been around for some time, and that it affects multiple out-of-the box uses of Rails.

Expected behavior

The url in the image tag <src> generated from the attachment should use the default host. In the case of my example from dev mode, it should be ‘localhost’.

Actual behavior

The attachment host in the <src> url is ‘example.org’. This differs from the behavior when rendering from a controller.

System configuration

Rails version: 6.1.3.1

Ruby version: 3.0.0

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 17 (12 by maintainers)

Commits related to this issue

Most upvoted comments

Note to self, and to anyone experiencing similar issues - you can’t do:

config.action_controller.default_url_options = { host: "localhost:3020" }

Instead, you must use an explicit port specification

config.action_controller.default_url_options = { host: "localhost", port: 3020 }

Likewise, your proposed fix seems like it would work well for jobs generated by a controller action, but does not appear to address jobs created outside a controller, like in a rake task. It also does not address any rendering done outside of background jobs.

Hmmm, good point on rake tasks. I don’t have any great ideas for that, for multi-host apps I think there’s only so much the framework can infer for you.

I would really like to see some feedback from others about the broader problem before committing to that fix, though, to make sure we’re not just playing whack-a-mole every time a library calls the renderer in a way we didn’t anticipate.

Yep, that’s not a bad idea.

Thanks for the great detail. This is an interesting bug. Some quick notes:

@lfalcao Thanks for taking a look at this. I’m not sure I follow what you’re doing. Are you proposing a change in Rails or a monkey patch in my app?

If you mean a change to Rails, then I’m still not sure how to pass in the defaults. This issue is triggered in my case by using library features (Hotwire’s various :broadcast_later features) that initiate rendering through some indirection, so I’m not well-positioned to alter rendering arguments through any new pathways.

If you mean to suggest that I patch my app, is that solution superior in any way to this?

# config/environments/production.rb
config.action_controller.default_url_options = { host: 'https://mysite.com' }

The code above is my current workaround, which is easy and reliable, but doesn’t work with apps accessible on multiple hosts. Thankfully, I have apps that hit this issue and apps that use multiple hosts, but no apps that do both. 🙂

My understanding of the problem is this:

  1. When something like a background rendering job or a mailer gets triggered in response to a web request, the environment of that request is lost by the time that rendering happens in the job/mailer/whatever.
  2. Some Rails-adjacent libraries (Hotwire in my case) aren’t set up to pass this environment information explicitly.
  3. Rails appears to have no framework defaults for this situation.
  4. The way of setting app defaults (as in my code above) is inadequate to the case of multi-host apps, as far as I could discover.

I wanted to lay this out point-by-point for discussion, but based on your post I’m pretty sure you understand this better than I do, so let me know if I got any of the summary points wrong or misunderstood what you were proposing. Thanks again for looking at this.

Hi @brian-kephart, I was suggesting a possible way to fix into Rails core. But its not the ideal, as it would not work with multipe hosts too. What you and @ghiculescu said, gave me some insights. Later I’ll try to take a look into this again.