Problem with Rails page caching and automatic extensions - ruby-on-rails

I have a JSON and XML based API that needs to be page cached. I have setup my routes on the api to include the format as part of the URL, such that URL's like this work:
http://example.com/foo/1/bar/2/xml
http://example.com/foo/1/bar/2/json
The problem I am seeing is that in the server's public folder, the files are being saved as xml.xml and json.json, and this causes cache misses the next time the URL is accessed.
Is there a way to either:
Turn off the auto extension generation so that they are saved without an extension at all? (EX: RAILS_ROOT/public/foo/1/bar/2/json)
Force all the extensions to be .html for every call. (EX: RAILS_ROOT/public/foo/1/bar/2/json.html)
Either of these would cause my server to return the cached file instead of a miss. How can I do this?
EDIT:
Somebody asked for the relevant route:
scope '(foo/:foo_id)', :foo_id => /\d+/ do
get '/bar/:bar_id/:format' => 'bars#show', :bar_id => /\d+/, :format => /json|xml|html/
end
SOLUTION:
While I was looking for an official way to make this happen using the built in page caching support, I ended up just using an after filter and my own page cache method, as suggested by Anton
# application_controller.rb
def cache_api_page
if REDACTEDServer::Application.config.action_controller.perform_caching
self.class.cache_page(response.body, request.path, '')
puts "CACHED PATH: #{request.path}"
end
end
# bar_controller.rb
after_filter :cache_api_page, :only => [ :show, :index ]

You can do that like this:
class FooController < ApplicationController
after_filter(:only => :show, :if => Proc.new { |c| c.request.format.json? }) do |controller|
controller.class.cache_page(controller.response.body, controller.request.path, '.html')
end
end
When http://example.com/foo/1/bar/2/json is accessed, it will write page to cache (RAILS_ROOT/public/foo/1/bar/2/json.html)
And if you get http://example.com/foo/1/bar/2/json again, you receive RAILS_ROOT/public/foo/1/bar/2/json.html, but your http server(Apache?) should know about content type of this files.
Otherwise content type will set to 'text/html'
UPDATE
To you .htaccess
<FilesMatch "\/json$">
<IfModule mod_headers.c>
Header set Content-Type "text/json"
</IfModule>
</FilesMatch>
<FilesMatch "\/xml$">
<IfModule mod_headers.c>
Header set Content-Type "text/xml"
</IfModule>
</FilesMatch>

In your application.rb configuration block try adding:
config.action_controller.page_cache_extension = '.html'
It should ignore the extension calculated from the request, and always use this.
You could also try using this with an empty string instead.
EDIT: actually this won't work because this only sets a default. If the request has an extension (in your case, concluded from :format) it will be used.
I suggest changing :format in your routes to something else, that rails will not give special meaning to, like :fmt. Then rails should not add the extension and default to '.html'.
EDIT2: If you have to use :format you can monkeypatch Rails:
ActionController::Caching::Pages::ClassMethods
private
def page_cache_file(path)
name = (path.empty? || path == "/") ? "/index" : URI.unescape(path.chomp('/'))
name << page_cache_extension #unless (name.split('/').last || name).include? '.'
return name
end
end
Notice the comment sign I've added before 'unless'. This is the part that overrides the default from my first answer.

Related

How to add a trailing_slash to all urls without in Rails 4?

I've tried adding this in application.rb
config.action_controller.default_url_options = { :trailing_slash => true }
as well as having :trailing_slash => true in routes.rb
match '/download', to: 'welcome#download', via: 'get', :trailing_slash => true
But neither seems to work. I searched through rails 4.0 doc but couldn't find related info. What am I missing here?
Update:
I've tried adding
Rails.application.default_url_options[:trailing_slash] = true
in filter_parameter_logging.rb since this is the only place in the whole project where I could find Rails.application.*, but it's not working either. I found the line here among the releases and I am using 4.0.4. Am I adding this in the wrong place? And I did restarted server before rechecking.
And sorry for the simple question but from what I've gathered isn't trailing_slash supposed to be reflected in browser url as well, if not primarily? Because this is what I need, to go with historyjs.
I think you have the meaning of :trailing_slash => true wrong.
All it does is add the / to the end of you path helpers. No redirecting involved.
Your routes will still respond to both with and without the trailing slash.
If you want to redirect all non-trailing_slash uri's like /download to /download/ using a nginx http server you would do something like this:
rewrite ^([^.\?]*[^/])$ $1/ permanent;
You would still want to add the :trailing_slash => true to your routes so your path/url helpers generate the the correct uri's (so user don’t need to redirect).
Trailing_slash refers to a / after the name like page/ not like /page.
You have given your routes wrongly.
Change it to
match 'download/', to: 'welcome#download', via: 'get', :trailing_slash => true
There is also other way to achieve this by giving a trailing_slash => true option directly to your link_to helper.
link_to 'Downloads', downloads_path(:trailing_slash => true)
Though this work in Rails 3,not sure about Rails 4.
For more details see this SO.
I am using rails 4.0.2 for me it's working
routes.rb
get 'admin/update_price_qty' => 'admin#update_price_qty', :trailing_slash => true,:as => "price"
in console :-
irb(main):003:0* app.price_path
=> "/admin/update_price_qty/"
routes.rb
match '/download', to: 'welcome#index', via: 'get', :trailing_slash => true,:as => "welcome_price"
in console :-
`irb(main):002:0> app.welcome_price_path
=> "/download/"`
But I've tried adding this in application.rb
config.action_controller.default_url_options = { :trailing_slash => true }
not working.
You may add this line to config/application.rb:
config.action_controller.default_url_options = { trailing_slash: true }
If you do this, when you call a Rails path helper inside a controller or helper, the generated path will have a / at the end:
class ApplicationController
def index
download_path # returns "/download/"
end
end
module PathHelper
def path
download_path # returns "/download/"
end
end
If you need to use path helpers outside controllers and helpers, you need to include Rails.application.routes.url_helpers, but apparently, this ignores the trailing_slash configuration above:
class SomeClass
include Rails.application.routes.url_helpers
def path
download_path # returns "/download"
end
end
In this case, you should add { trailing_slash: true } as a parameter:
class SomeClass
include Rails.application.routes.url_helpers
def path
download_path(trailing_slash: true) # returns "/download/"
end
end

url_for not using default_url_options[:host] value

I've got a view for an ActionMailer that includes a few different links. I'm running it on localhost:3000 right now, and so I've set that as such in a file called setup_mail.rb in app/initializers (as indicated here):
ActionMailer::Base.default_url_options[:host] = "localhost:3000"
When I go to use url_for in the view, it doesn't seem to pull this value. If I then add :host => "localhost:3000" to each url_for tag, they work properly. But they don't work without that included.
I have another tag, project_url, which is as it appears: a link to a specified Project. This functions, including the host value, with just project_url(#project). Why would one work but not the other?
From everything I've read, setting the default_url_options[:host] in an initializer should allow me to omit the :host value in the url_for tag. Obviously, it's not the worst thing in the world to just add that value, but it seems unnecessary and it means that when I eventually host the project somewhere I'll have to go through and change that value all over the place. But worse than that, it's something that I don't understand. I'm still learning as I go here and so I'd like to know what I'm doing wrong.
The documentation is pretty clear on this
When you decide to set a default :host for your mailers, then you need to make sure to use the :only_path => false option when using url_for. Since the url_for view helper will generate relative URLs by default when a :host option isn’t explicitly provided, passing :only_path => false will ensure that absolute URLs are generated.
You could create your own helper to use instead of the url_for to force :only_path to be false
def your_url_for(options = {})
options.reverse_merge! only_path: false
url_for(options)
end
You could also monkey patch rails to force this as the default, but that's left up to you :)
This all would be in addition to adding
config.action_mailer.default_url_options = { host: "YOUR HOST" }
to config/application.rb or equivalent.
It seems :only_path option is false which is by default. so that is why you need to provide [:host] either explicitly for every tag or set default options for url_for which would apply to all tags. here is how to set default host:
put this code in your Application controller & it should work.
helper_method :url_for
def default_url_options(options)
{ host: 'localhost:3000' }
end
For more details check set url_for defaults
Instead of tampering with the global default setting which imho shouldn't be changed after initialization you can simply define a method default_url_options in your mailer just like you can do it in a controller:
class UserMailer < ActionMailer::Base
def default_url_options
{ host: Tenant.current(true).host }
end
def confirm(user)
#user = user
mail(to: #user.email, subject: t(".subject_confirm"))
end
end
You're setting the default in ActionMailer::Base, but appear to expect it to reset the default for ActionController::Base.
A <%= link_to %> inside your mailer view doesn't necessarily know anything about the fact that it's inside a mailer view.

How to serve a static JSON file in rails?

I have a file on my server that's outside of my app directory. It's a text file that holds a json object, /path/to/data.json.
What's the simplest way to serve this file in Rails, e.g. to return it in response to a GET request?
Here's what I've tried so far, inspired by this answer and others. (This may be way off-base -- I'm new to rails.)
added this line to routes.rb
resources :data
Wrote the following data_controller.rb
class DataController < ApplicationController
#data = File.read("/path/to/data.json")
def index
render :json => #data
end
end
This doesn't work. When I direct my browser to http://myserver.com/data.json I just see "null" instead of the data.json file.
Any idea what I'm doing wrong?
I think this is a scope issue; your outer #data is not the same as the #data in a method. That is, you can't use instance variables as expected outside of methods because there is no instance yet.
It should work if you use a class variable, eg
##data = File.read("/path/to/data.json")
then
render :json => ##data
Put it in public/assets/javascripts. Or app/assets/javascripts. The server may even return the correct content-type.
put data.json file into your project directory ( for example public/data.json)
#data = File.read("#{Rails.root}/public/data.json")
At last but not least render :json => #data
You can also do it in one line, e.g.
render :file => '/path/to/data.json',
:content_type => 'application/json',
:layout => false

Handling mix of HTTP and HTTPS links on a page

My setup: Rails 3.0.9, Ruby 1.9.2
My app requires that only a certain part of my site be SSL protected and the rest not. In case anyone thinks this isn't normal behavior, check out Amazon. When merely browsing for products, it's in HTTP mode, during checkout, it switches to HTTPS. Even in the middle of a secure checkout transaction, there are several other links on the same page that are HTTP only.
I looked at ssl_requirement gem and decided not to use it because it isn't a complete solution for my needs. I ended up setting up specific SSL routes like
resources :projects do
resources :tasks, :constraints => { :protocol => "https" }
end
In my view code, for HTTP specific links
<%= link_to 'Projects', project_url(#project, :protocol => "http") %>
and to handle HTTPS specific link
<%= link_to 'Task', new_project_task_url(#project, :protocol => "https") %>
I understand this isn't the cleanest way but it's what I have decided to do. The problem with this setup is how to properly set both HTTP and HTTPS links on every page. There is a proposed solution here but it requires wholesale changes _path to _url and I prefer to avoid that if at all possible. The solutions involves adding this method in
application_helper.rb
module ApplicationHelper
def url_for(options = nil)
if Hash === options
options[:protocol] ||= 'http'
end
super(options)
end
end
So my question is it possible to change this method or another one to change _path calls to explicit urls so I can use the above method to set the proper protocol.
you could try this, although I'm not 100% sure it works:
Use the proposed changes from the stackoverflow answer
Add this to application_controll.rb:
class ApplicationController < ActionController::Base
def url_options
{ :host => request.host }.merge(super)
end
end
According to the Docs it should add the full url even if you use _path:
:only_path - If true, returns the relative URL (omitting the protocol,
host name, and port) (true by default unless :host is specified).
My app requires that only a certain part of my site be SSL protected and the rest not.
That's your faulty assumption. The secure premise is that if some of your app requires SSL, then all of your app requires SSL. The correct assumption then is that your entire app requires SSL.
If your app requires SSL, then you should use something simple like rack-ssl which sets the HSTS header and enforces the secure flag on cookies in all responses.

Getting Rails URL helpers to automatically output https urls

I am developing a site that mixes http and https a lot - whats the best/easiest way to make the links use the right protocol for the route - can it be specified in the routes file?
Say I have the following route in Rails 3.
match "/test" => "test#index", :as => :test, :constraints => { :protocol => 'https' }
If I'm on a http page, and I use test_url(), it'll output http://domain.com/test. I want https://domain.com/test instead.
I know I can use test_url(:secure => true), but that's duplicating logic.
I know I could have http://domain.com/test to https://domain.com/test, but that's an extra redirect, plus it fails on form posts.
Ideas?
Use test_url(:protocol => 'https') for https urls.
Haven't tried but add this in your ApplicationController:
def default_url_options(options={})
{ :secure => true }
end
def default_url_options(options={})
{ :protocol => "https" }
end
For Rails 3.2 I used a combination of #apneadiving's answer. Adding the below code to my ApplicationController
def default_url_options(options={})
options.merge{ :protocol => "https" }
end
It looks like this will be solved in Rails 4! https://github.com/rails/rails/commit/9b4514c3b8ecfbc40a44dbd4c2ebd4ce67f4a459
Rails 3 SSL routing redirects from https to http answers this question pretty well. In short, there's not a great way to do it. I submitted the following Rails bug: https://github.com/rails/rails/issues/3571
To generate https url, add an option called protocol with value https
test_url(protocol: 'https')
You can use a plugin called ss_requirement, it will provide you with methods like
ssl_required
ssl_allowed
You can simply add this in your controller to enable ot disable https on any action
ssl_allowed :login, :update_profile
https://github.com/rails/ssl_requirement

Resources