Rails adds an md5 hash to images names when moving them to the /public/assets directory during precompilation. The problem is that these hashes are unpredictable, so how can I know what they're going to be called when trying to link to them?
For example, if I'm hosting an image named flowers.jpg, and then try to access it at www.mysite.com/flowers.jpg, it fails, because the file has been renamed flowers-4182172ae014ec23dc02739229c08dcc.
I know Rails has helpers that will automatically find these images. But what if you're trying to link to these images from a completely different website or application? Is there a way to get Rails to say, "Well I can't find a precompiled version of flowers.jpg, so instead of serving from /public/assets I'll serve from /app/assets."?
EDIT: According to this post (http://stackoverflow.com/questions/10045892/rails-compiles-assets-both-with-and-without-md5-hash-why), Rails should be compiling a version of my assets both with and WITHOUT an md5 hash? Any idea why my copy of Rails isn't generating a version without a fingerprint?
Rails handle that for you using image_tag:
image_tag "myimage.jpg"
and this will get you the right url for it. You might be able to write a small service that generates the image url for your as (untested):
Class AssetsService < ApplicationController
def index
end
end
index.js.haml
= image_tag "myimage.jpg"
The answer wasn't with Rails. I don't think Rails is supposed to compile images without the fingerprint. It's supposed to still be able to serve them, however, and I had added some code to my nginx config file that prevented this from happening. This was the offending code:
location ~* ^/assets/ {
# Per RFC2616 - 1 year maximum expiry
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
expires 1y;
add_header Cache-Control public;
# Some browsers still send conditional-GET requests if there's a
# Last-Modified header or an ETag header even if they haven't
# reached the expiry date sent in the Expires header.
add_header Last-Modified "";
add_header ETag "";
break;
}
Related
In our web application built in Rails we have several clients using the same application who will have different assets that are used dependant on which subdomain is used.
To achieve this we swap out what folder is being used on the CDN like so:
config.action_controller.asset_host = Proc.new { |source, request|
if request.subdomain.present?
"http#{request.ssl? ? 's' : ''}://cdn.domain.com/#{request.subdomain}/"
else
"http#{request.ssl? ? 's' : ''}://#{request.host_with_port}/"
end
}
Each time we create a new client we compile the assets manually using a custom build tool that uses Sprockets to build the assets the same way Rails would and then upload them to our CDN under a folder that matches the subdomain. This then allows us to have different sets of assets based purely on the subdomain.
Now this works fine except that when we update an asset the digest will change for that file but Rails will still try and load the old asset digests because the sprockets-manifest file (which is in /public/assets) e.g. .sprockets-manifest-12345.json is being loaded instead of the one that's on the CDN. Even though the asset host is different it still loads the local one.
Rails it seems doesn't care about other manifest files as the file itself only stores the filename to the fingerprinted version so even when things like the host changes it would normally be able to find the correct asset. It would seem as though Rails has been designed this way deliberately.
However we really need to get Rails to use the manifest file that is on the CDN itself rather than use the one in the public folder local to the application.
After reading the docs, it seems you can change the manifest location. We tried doing it by using the same logic as above for the manifest like so:
config.assets.manifest = Proc.new { |source, request|
if request.subdomain.present?
"http#{request.ssl? ? 's' : ''}://cdn.domain.com/#{request.subdomain}/"
else
"http#{request.ssl? ? 's' : ''}://#{request.host_with_port}/"
end
}
But Rails/Sprockets is still using the local sprockets file... Any ideas why?
I have a pretty standard rails 5.1 app which is mounted under a subdir under our main domain:
https://www.company.com/subir/
This is done in routes.rb
scope :subdir
the other routes
end
I use NGINX in our DMZ to pass incoming requests to my application server:
...
server_name www.company.com;
location /subdir {
proxy_pass https://my-app-server;
}
...
on my app-server is the pretty common combo Nginx/Puma installed and almost everything works fine, except the urls which are in the emails I send via actionmailer.
In my view I have a link:
link_to 'approve customer', admin_customer_url(#customer)
This creates the following:
https://www.company.com/**subdir/subdir**/admin/customer/:id
On my local machine these links are generated correctly in my emails, but not in staging environment on my app-server.
I dumped the request object in a view to see if my nginx setup is crazy but there is nothing obviously crazy...
Any ideas?
Check if you have config.action_mailer.default_url_options set in your environment file.
http://api.rubyonrails.org/classes/ActionMailer/Base.html
For the records: in deploy/staging.rb I still exported ENV['RAILS_RELATIVE_URL_ROOT']. This added the subdir twice to app.base_path and this was causing my issue.
Trying to figure out a way to change up the asset host when accessed by a certain controller.
The controller is to be strictly accessed by the https protocol, so I need the asset host to be switched over to using https. At the moment the asset host is set to a CNAME subdomain that is linked to the S3 and there is no SSL cert associated to it. What I'm trying to achieve is replace the current asset host with the https Amazon S3 URL. The only assets I'm worried about are the CSS and JS includes.
I was thinking of using a helper to strip the host from the stylesheet_link_tag and javascript_include_tag and replace them with the https Amazon S3 url. Seems a bit hackish to me though.
Or perhaps there is a way to changed asset hosts if request.ssl? is true?
I'm using Rails 3.2.x.
Figure out a solution for my case.
Ended up using a Proc on config.action_controller.action_host in my Production environment file to handle a logic on request.ssl? and respond accordingly. Here is the code
config.action_controller.asset_host = Proc.new { |source, request = nil, *_|
request && request.ssl? ? 'https://s3.amazonaws.com/my_bucket' : 'http://s3.my-domain.com'
}
'request' is set to nil to accomodate the cases where asset_host is called in asset files (CSS and JS if you are using the asset helper tags). Since request doesn't exist and if request isn't assigned in the args, then the error will be thrown when assets are compiled (as shown below).
This asset host cannot be computed without a request in scope. Remove the second argument to your asset_host Proc if you do not need the request, or make it optional.
The *_ is present due to a bug with option arguments in Proc http://bugs.ruby-lang.org/issues/5694
I have Rails serving my static assets. Most of them have hashes in their name and are served with far-future expiration dates. But for one file, I can't serve it with a hash in its name, so I need to control the expiration date.
I have this in my application.rb which applies to all static assets:
config.static_cache_control = "public, max-age=2592000"
Is there a way for me to have a different max-age for just one file? I know I can make a new middleware that comes after ActionDispatch::Static and changes the value for certain files (see this writeup)... but then this will run for every single request, even those which aren't static assets. Is there a more elegant solution?
A bad technique can be to fix the URL of your file in your route.rb. You can define a Controller to this route fixing the cache_control you want and use send_data method to server the file.
Whether I do:
head 302
or
head 307
or
redirect_to
calls in the same controller action to
response.headers['Cache-Control'] = "public, max-age=86400"
have no effect. Rails sends:
Cache-Control: no-cache
no matter what. I need to send the Cache-Control header to instruct an edge cache to serve the redirect for a day. Is this possible?
You can't set Cache-Control directly into the headers (anymore?), as you need to modify the response.cache_control object (since it will be used to set the Cache-Control header later).
Luckily, the expires_in method takes care of this for you:
expires_in 1.day, :public => true
See more here:
http://apidock.com/rails/ActionController/ConditionalGet/expires_in
Try using this instead
response.headers['Cache-Control'] = 'public, max-age=300'
and make sure your in production mode. Rails wont cache in development.
With Rails 5 you can do
response.cache_control = 'public, max-age=86400'
. I need to send the Cache-Control header to instruct an edge cache to serve the redirect for a day.
How is this possible ? in case of temp redirect , browsers will always try to get original url first and on redirect they will try other url,which if cached on proxies can be served from there.
But again browser will still make first contact with your server.