roadie-rails icon indicating copy to clipboard operation
roadie-rails copied to clipboard

Question: How to work with remote asset?

Open PikachuEXE opened this issue 10 years ago • 22 comments

I got something like //assets.my.domain/assets/mailer-4d19915455ba1fec1f18ca31aaccf9ce.css I got this generated URL with this code: = stylesheet_link_tag('mailer.css')

I have no idea how to get local assets

PikachuEXE avatar Jul 03 '14 02:07 PikachuEXE

Have you set an asset_host? That is most likely the "problem".

You should be able to create an asset provider that recognizes your asset host and strips it from the path and then finds it on the file system. This is most likely the way I'll solve this bug officially. Den 3 jul 2014 04:38 skrev "PikachuEXE" [email protected]:

I got something like //assets.my.domain/assets/mailer-4d19915455ba1fec1f18ca31aaccf9ce.css I got this generated URL with this code: = stylesheet_link_tag('mailer.css')

I have no idea how to get local assets

— Reply to this email directly or view it on GitHub https://github.com/Mange/roadie-rails/issues/10.

Mange avatar Jul 03 '14 03:07 Mange

production.rb:

asset_host = ENV["ASSET_HOST"]
  if asset_host.present?
    config.action_controller.asset_host = if asset_host =~ /%d/
      proc do |source|
        # has to call ENV["ASSET_HOST"] since `asset_host` here is not accessible on execution
        "//#{ENV["ASSET_HOST"] % (Zlib.crc32(source) % 4)}"
      end
    else
      "//#{asset_host}"
    end
    config.action_mailer.asset_host = config.action_controller.asset_host
  end

PikachuEXE avatar Jul 03 '14 03:07 PikachuEXE

+1

bcackerman avatar Jul 07 '14 14:07 bcackerman

I'll look into it.

Mange avatar Jul 08 '14 11:07 Mange

You should be able to turn off asset_host in your specific mailer as a workaround:

class MyMailer < ActionMailer::Base
  self.asset_host = nil
end

This will not use the asset host for your images, so it's not a full "solution" to the problem.

Mange avatar Jul 08 '14 12:07 Mange

Planning for the fix happens in #11. It's not trivial to support this, so be prepared to use a workaround for some time.

Mange avatar Jul 08 '14 12:07 Mange

I have managed to maintain use of the asset_host setting for images (can still use image_tag) and made Roadie use a relative path for the styles by explicitly defining the stylesheet path as relative rather than using the stylesheet_link_tag helper. I had to use a non-digest version of the stylesheet.

adamwaite avatar Aug 08 '14 12:08 adamwaite

Here's another workaround:

class MyMailer < ActionMailer::Base
  include Roadie::Rails::Automatic

  protected
  def roadie_options
    super.combine({
      before_transformation: method(:reverse_asset_host_on_stylesheets)
    })
  end

  def reverse_asset_host_on_stylesheets(dom)
    dom.all_css("link[rel=stylesheet]").each do |link_tag|
      if link_tag["href"]
        link_tag["href"] = link_tag["href"].sub(%r{^https?://assets\d\.mycoolapp\.com}, "")
      end
    end
  end
end

Does that make sense? At least you get asset_host applied to all your images while still having styles inlined. If this works out (or at least a variation of it), I might make this a first-class thing within AssetProviders; a callable you set to reverse the asset_host where possible.

Mange avatar Aug 14 '14 06:08 Mange

Just came across this and found that while it works in principle, one also has to filter out the digest from the asset path. My code now looks like this:

  def reverse_asset_host_on_stylesheets(dom)
    unless Rails.application.config.action_mailer.asset_host.nil?
      dom.search('link[rel=stylesheet]').each do |link_tag|
        if link_tag['href']
          pipeline_path = link_tag['href'][Rails.application.config.action_mailer.asset_host.length..-1]
          pipeline_path.sub!(/-[0-9a-f]{32,}(\.\w{1,4})(?:\?.*)\Z?/, '\1')
          link_tag['href'] = '/' + pipeline_path
        end
      end
    end
  end

Edited to fix regex.

milgner avatar Dec 22 '14 13:12 milgner

Thank you for the heads-up, Marcus! :+1:

For other people: Note that his code does not work if the asset host is configured with a block, or an array. Only a single string is supported by the above code. It is also important to remember what happens if you use a %s placeholder in the configured asset host: it will be replaced with a single character (1..4, I think) in the rendered markup, thereby reducing the length by one. In practice, this just means that an extra / might get stripped, but it's still useful to know about it. Make sure to write tests! :-)

Mange avatar Dec 22 '14 19:12 Mange

A new version of roadie is now out: 3.1.0.rc1. This version allows Roadie to actually handle external stylesheets, either by literally downloading them over the network, or by rewriting the paths into local paths (or any combination thereof).

Maybe you can use this capability to work around this problem? See the roadie README for examples.

You should also upgrade roadie-rails to 1.1.0.rc2 to get full support.

Mange avatar Sep 14 '15 18:09 Mange

Let me test it on Friday (if I have time then)

PikachuEXE avatar Sep 15 '15 02:09 PikachuEXE

Successful case (asset provider) for development:

class UserAssetsProvider
  ABSOLUTE_ASSET_PATH_REGEXP = /\A#{Regexp.escape("//")}.+#{Regexp.escape("/assets/")}/i

  def find_stylesheet(name)
    puts "name: #{name}"
    return nil unless file_exists?(name)

    Roadie::Stylesheet.new("whatever", stylesheet_content(name))
  end

  def find_stylesheet!(name)
    stylesheet = find_stylesheet(name)

    if stylesheet.nil?
      raise Roadie::CssNotFound.new(
        name,
        "does not exists",
        self,
      )
    end

    stylesheet
  end

  private

  def file_exists?(name)
    if assets_precompiled?
      File.exists?(local_file_path(name))
    else
      sprockets_asset(name)
    end
  end

  # If on-the-fly asset compilation is disabled, we must be precompiling assets.
  def assets_precompiled?
    !Rails.configuration.assets.compile rescue false
  end

  def local_file_path(name)
    asset_path = asset_path(name)

    if asset_path.match(ABSOLUTE_ASSET_PATH_REGEXP)
      asset_path.gsub!(ABSOLUTE_ASSET_PATH_REGEXP, "assets/")
    end

    File.join(Rails.public_path, asset_path)
  end

  def sprockets_asset(name)
    asset_path = asset_path(name)

    if asset_path.match(ABSOLUTE_ASSET_PATH_REGEXP)
      asset_path.gsub!(ABSOLUTE_ASSET_PATH_REGEXP, "")
    end

    Rails.application.assets.find_asset(asset_path)
  end

  def asset_path(name)
    name.gsub(%r{^[/]?assets/}, "")
  end

  Contract String => String
  def stylesheet_content(name)
    if assets_precompiled?
      File.read(local_file_path(name))
    else
      # This will compile and return the asset
      sprockets_asset(name).to_s
    end.strip
  end
end

I put this class within app to make it easier to test

And I put the following code in ApplicationMailer to make the image URLs absolute:

def roadie_options
  ::Rails.application.config.roadie.tap do |options|
    options.asset_providers = UserAssetsProvider.new
    options.external_asset_providers = UserAssetsProvider.new
    options.url_options = url_options.slice(*[
      :host,
      :port,
      :path,
      :protocol,
      :scheme,
    ])
  end
end

I have the following code to ensure roadie works on mail preview:

def mail(*args, &block)
  super.tap do |m|
    options = roadie_options
    next unless options

    Roadie::Rails::MailInliner.new(m, options).execute
  end
end

PikachuEXE avatar Sep 17 '15 03:09 PikachuEXE

Tested on a remote server (staging, similar env to production) Corrected code in previous comment for after testing

PikachuEXE avatar Sep 17 '15 05:09 PikachuEXE

With my code above, it's working on production :)

PikachuEXE avatar Sep 18 '15 01:09 PikachuEXE

I'm using the http provider in combination with the default Rails cache store. Seems to work fine :smile:

# config/initializers/roadie.rb
class RoadieRailsCacheStore

  def [](path)
    Rails.cache.read(path)
  end

  def []=(path, stylesheet)
    Rails.cache.write(path, stylesheet)
  end

end

Rails.configuration.roadie.external_asset_providers = 
  Roadie::CachedProvider.new(Roadie::NetHttpProvider.new, RoadieRailsCacheStore.new)

harmdewit avatar Sep 28 '15 14:09 harmdewit

@harmdewit you are a life saver!

jufemaiz avatar Jun 23 '16 01:06 jufemaiz

@harmdewit You saved my day!

QQism avatar Nov 03 '16 14:11 QQism

@harmdewit You're are awesome, man!

dinhhuydh avatar Nov 03 '16 14:11 dinhhuydh

@harmdewit You, legend!

barjo avatar Nov 03 '16 14:11 barjo

Haha :)

harmdewit avatar Nov 03 '16 14:11 harmdewit

Ok @harmdewit what happened in the last 8 hours?!!

jufemaiz avatar Nov 03 '16 22:11 jufemaiz