2

I'm using a controller in my Rails 4 app to serve some images, to which I want to restrict access (similar to this answer). The images that are being served are not stored in the asset pipeline themselves, and are programmatically generated. If the image hasn't been generated yet, I want to show a default / fallback image to the user. This image is stored with other static assets.

So how can I get the actual path to this image file to give to send_file in my controller? As noted here the asset_path and image_path helpers do not give the local filesystem path, but because of the asset pipeline fingerprinting stuff, I can't just construct the path myself either.

class ThingsController

  before_filter :authorize_for_thing

  def show
    respond_to do |format|
      format.all {
        if @thing.graph.file.present?
          file = @thing.graph_url()
        else 
     ### SERVE THE STATIC DEFAULT IMAGE IF THE FILE ISN'T THERE: ###
          file = File.join(Rails.root, 
          ActionController::Base.helpers.asset_path("default_graph.png")
          )
        end
        send_file file, :type=>"image/png", :disposition => 'inline'
      }
    end
  end
end

But asset_path("default_graph.png") give something of the form:

assets/default_graph[possible-md5].png

When the file actually lives elsewhere, and send_file needs the literal path to the file.

Community
  • 1
  • 1
Bee
  • 14,277
  • 6
  • 35
  • 49

3 Answers3

4

In development, to get the full file path to an asset use Rails.application.assets[FILENAME]

e.g. Rails.application.assets['default_graph.png']

This method returns a Sprocket::Asset with info on the file. When that asset exists, use the #filename method to get the path to the file.

In production, assets are precompiled and the asset method will be nil; in that case, Rails.application.assets_manifest.assets[FILENAME] returns the hashed name of the file, while Rails.application.assets_manifest.directory contains the directory the file is in.

The best option is to use a combination of both:

def get_full_path_to_asset(filename)
  manifest_file = Rails.application.assets_manifest.assets[filename]
  if manifest_file
    File.join(Rails.application.assets_manifest.directory, manifest_file)
  else
    Rails.application.assets&.[](filename)&.filename
  end
end

get_full_path_to_asset 'default_graph.png' # => '/app/public/assets/default_graph.123456789.png' or '/local/path/to/repo/app/assets/images/default_graph.png'
NOTE: just to help those who don't know look up how it's used, &. is the "Safe Navigation Operator" introduced in Ruby 2.3.0
Sampson Crowley
  • 1,244
  • 1
  • 9
  • 22
1

The asset_path method generates a URL, instead of a local file path which is what send_file expects to use. The following could help you address this between development and production. However it's not very elegant.

  if @thing.graph.file.present?
    file = @thing.graph_url()
  else 
    if Rails.env.production?
      basename = File.basename(ActionController::Base.helpers.asset_path("default_graph.png"))
      file = Rails.root.join("public", "assets", basename)
    else
      file = Rails.root.join("app", "assets", "images", "default_graph.png")
    end
  end

The code serves the file from its default path in the development environment, and in production, it builds the file path for the asset using the asset_path method to retrieve the file name + the hash. File.basename essentially strips off the leading portion of the URL, and then Rails.root.join is used to find the file (with the hash) in the public/assets folder. The file location may need to be changed based on where your assets get compiled.

I'd be interested if anyone has a more elegant solution myself (that doesn't require the if Rails.env.production? line.

Edit: In case the above doesn't work due to X-Sendfile not finding the app/assets folder, you can also also send the data directly using send_data

  if @thing.graph.file.present?
    file = @thing.graph_url()
    send_file file, :type=>"image/png", :disposition => 'inline'
  else 
    file = Rails.root.join("app", "assets", "images", "default_graph.png")
    File.open(file, "r") do |f|
      send_data f.read, type: "image/png", filename: "default_graph.png"
    end
  end
remo
  • 498
  • 5
  • 8
-1

Rails.application.assets_manifest.assets[name]

fxtentacle
  • 763
  • 10
  • 15
  • 4
    While this code snippet may solve the question, [including an explanation](http://meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. – Nahuel Ianni Feb 23 '17 at 08:29