Rails routes for js file - ruby-on-rails

I got a ServiceWorker made with a webpack plugin that is public available with http://example.com/packs/sw.js URL. Since I want this Service Worker to control the whole website I need to serve it from http://example.com/. So, no redirect_to allowed
How do I have to set the routes.rb for this?
What I've tried is:
Added : get '/sw' => 'sw#show' to routes.rb
Created controller sw_controller.rb:
require 'net/http'
class SwController < ActionController::Base
def show
respond_to do |format|
format.all {
uri = URI('http://example.com/packs/sw.js')
res = Net::HTTP.get(uri)
render inline: res
}
end
end
end
It's ugly I know but should work:
Visiting http://example.com/sw.js gives No route matches [GET] "/sw.js"
Visiting http://example.com/packs/sw.js prints out the service worker correctly
Visiting http://example.com/sw prints out the service worker correctly
Also visiting http://example.com/sw.lol print the serviceworker

In config/initializers/mime_types.rb try adding:
Mime::Type.register "text/javascript", :js
This should allow format.all to recognise the Javascript request, which it will otherwise ignore.
Source: http://api.rubyonrails.org/classes/ActionController/MimeResponds.html

Related

Rendering action based on (random) route name

I'm trying to make a controller action that renders a random route from a set of given route names, without a redirect.
I know the method render controller: name, action: name but rendering fails because it tries to find a template on it's own instead of letting the target action determine the template.
Here is my code:
def random
# create basic route names
route_names = %w(root route1 route2)
# get route path
path = Rails.application.routes.url_helpers.send("#{route_names.sample}_path")
# {controller: name, action: name, param: val}
action_config = Rails.application.routes.recognize_path(path, {:method => :get})
# doesn't work
# fails with Missing template application/*action name*
return render action_config
# doesnt work
# require 'open-uri'
# render html: open("http://localhost:3000/#{path}") { |io| io.read }
# doesn't work
# require 'net/http'
# require 'uri'
# render html: Net::HTTP.get(URI.parse("http://localhost:3000/#{path}"))
# doesnt work
# ctrl = (action_config[:controller].camelize + "Controller").constantize.new
# ctrl.request = request
# ctrl.response = response
# ctrl.send(action_config[:action])
# works, but not for Derailed
# redirect_to path
# works but not for Derailed, since the server doesn't parse the <iframe>
#render html: "
# <iframe
# src='#{path}'
# width='100%'
# height='100%'
# style='overflow: visible; border: 0;'></iframe>
# <style>body {margin: 0;}</style>".html_safe
end
Could anyone make the render work properly?
background
I'm trying to debug a memory leak in my Rails app. I'm using the Derailed gem that retrieves a path from my app 10.000 times. Derailed only supports hitting a single path. So, to actually mimic site usage I'm trying to implement an action that renders a random route from a set of given routes. Derailed allows me to use a real webserver like Puma, but that configuration doesn't follow redirects, so I need Rails to render without a redirect.
You can write middleware for this:
class RandomMiddleware
def initialize(app)
#app = app
end
def call(env)
route_names = %w(/ /route1 /route2)
if env['PATH_INFO'] == '/random'
env['PATH_INFO'] = route_names.sample
end
#app.call(env)
end
end
and then insert this middleware in the stack (in config/application.rb):
config.middleware.use RandomMiddleware
You can try to open new app session inside controller, render action there and return the result:
session = ActionDispatch::Integration::Session.new(Rails.application)
session.get '/'
render html: session.body

Rails: How do I create a custom 404 error page that uses the asset pipeline?

There are many solutions for creating customized error handling pages, but almost none for Rails 4:
Basic Rails 404 Error Page
Dynamic error pages in Rails
The standard answer of encouraging people to modify 404.html in /public doesn't work for me because I want to use the CSS theme that resides in the asset pipeline. Is there a way that html files can access those styles defined in the asset pipeline? If not, is there a way to create a custom error handler that has access to the pipeline?
For Rails 4.1 I like this answer, add an asset type better; however I have not tried it. On Rails 4.0.8, these three references helped me:
Dynamic error pages is the second reference in the question. This worked just fine for me.
Custom error pages may have cribbed from the first reference, or the other way around, but goes the extra mile by adding some information about testing with Capybara.
I did not do the Capybara testing because I didn't want to change the test configuration; however, RSpec-Rails Request Specs clued me in to test these requests independently and see that they complete and return the correct content.
What follows is a nutshell description of what is taught by the three references:
Add the following setting to config/environments/production.rb
# Route exceptions to the application router vs. default
config.exceptions_app = self.routes
Edit the routing configuration, config/routes.rb to direct the error pages to an errors controller
# error pages
%w( 404 422 500 503 ).each do |code|
get code, :to => "errors#show", :code => code
end
will route the 404, 422, 500, and 503 page requests to the show action of the errors controller with a parameter code that has the value of the status code.
Create the controller, app/controllers/errors_controller.rb. Here is the entire content:
class ErrorsController < ApplicationController
def show
status_code = params[:code] || 500
flash.alert = "Status #{status_code}"
render status_code.to_s, status: status_code
end
end
My preference was to set a status message on flash.alert
Create the pages themselves. I use .erb Here is app/views/errors/500.html.erb
<p>Our apology. Your request caused an error.</p>
<%= render 'product_description' %>
So you see that you can render a partial. The page renders with all of the layout boilerplate from app/views/layouts/application.html.erb or any other layout boilerplate that you have configured. That includes the <div id='alert'><%= alert %></div> that displays the status message from the flash.
Tested with RSpec by adding a test file, spec/requests/errors_request_spec.rb. Here is abbreviated content of that file that shows a test of the 500 status page:
require 'rails_helper'
RSpec.describe "errors", :type => :request do
it "displays the 500 page" do
get "/500"
assert_select 'div#alert', 'Status 500'
assert_select 'div[itemtype]'
end
end
The first assertion checks for the flash alert. The second assertion checks for the partial.
We've made a gem which does this for you: exception_handler.
There is also a great tutorial here.
I also wrote an extensive answer on the subject here.
Middleware
# config/application.rb
config.exceptions_app = ->(env) { ExceptionController.action(:show).call(env) }
Controller
# app/controllers/exception_controller.rb
class ExceptionController < ApplicationController
respond_to :json, :js, :html
before_action :set_status
def show
respond_with #status
end
private
def set_status
def status
#exception = env['action_dispatch.exception']
#status = ActionDispatch::ExceptionWrapper.new(env, #exception).status_code
#response = ActionDispatch::ExceptionWrapper.rescue_responses[#exception.class.name]
end
end
end
View
# app/views/exception/show.html.erb
<h1>404 error</h1>
This is very simple version - I can explain more if you wish.
Basically, you need to hook into the config.exceptions_app middleware, it will capture any exception in the middleware stack (as opposed to rendering the entire environment), allowing you to send the request to your own controller#action.
If you comment, I'll help you out some more if you want!

Sending Format with Rspec GET

I'm attempted to test the mobile version of my rails site, but i can't seem to get the following code to work:
let(:uri) { '/' }
it 'routes to #mobile_index' do
get uri, :format => 'mobile'
controller.response.should route_to('home#index_mobile')
end
What's the proper way to send this sort of request so its seen by the app as coming from a mobile source? I've looked up a lot about setting the user agent, but i can't get any of those to work either. I'm using Rspec version 2.14.2.
How do you check if whether to redirect to mobile page or to the normal?
For this testcode to work you must be having something like this in your application#index
respond_to do |format|
format.mobile do
# redirect to mobile
end
format.html
end
This means if you call '/index' (or '/' ) and if you call '/index.mobile' it would be
redirecting to the mobile page
Because you've written something about the User Agent i guess this is your criterium for
distinguishing between mobile and normal version.
HTTP Headers in rails tests are set by the request.env method. Their names are prefixed
with HTTP_, capitalized and have dashes replaced by underscores.
so for setting the User-Agent header you just do
request.env['HTTP_USER_AGENT'] = "WWW-Mechanize"
and then perform the get call.
If you are checking only one and not multiple controllers in integration i would also make this a functional test of the Application Controller (or whatever controller responsible for the home action)
describe ApplicationController do
describe "GET index" do
it "redirects mobile agents to the mobile version" do
request.env['HTTP_USER_AGENT'] = 'Ipod ...'
#calls "/index" unless different routing configured
get :index
expect(response).to redirect_to <index_mobile_path> #path helper depends on route config
end
end
end

Rails - Suppress RoutingError 404 on server side for static content

I'm fairly new to Rails, and my problem is as follows:
I'm making a GET request to a remote website via my server and I dish up the response as HTML to my front-end.
Unfortunately, the HTML I'm parsing from the remote site has references to static files which is causing a ActionController::RoutingError (No route matches [GET] "...")
How can I suppress these errors in my controller so that they don't appear in my browser console?
Code is as follows:
def get_page
url = URI.parse('[website]')
req = Net::HTTP::Get.new(url.path)
res = Net::HTTP.start(url.host, url.port) {|http|
http.request(req)
}
#result = res.body
respond_to do |format|
format.html { render :text => #result }
end
end
Appreciate any help!
I've figured this out, but I'm not sure if it's a very elegant solution.
In my routes.rb I put the following line at the bottom of all defined routes:
get '*static' => 'home#static_404'
I then put the following method in my "home controller":
def static_404
render :nothing => true
end
Seems to work, but I don't like how Rails forces me to use *[random_letters] to define this error route. * doesn't seem to work by itself, but if I type in anything afterwards it does.

Rails: dynamic robots.txt with erb

I'm trying to render a dynamic text file (robots.txt) in my Rails (3.0.10) app, but it continues to render it as HTML (says the console).
match 'robots.txt' => 'sites#robots'
Controller:
class SitesController < ApplicationController
respond_to :html, :js, :xml, :css, :txt
def robots
#site = Site.find_by_subdomain # blah blah
end
end
app/views/sites/robots.txt.erb:
Sitemap: <%= #site.url %>/sitemap.xml
But when I visit http://www.example.com/robots.txt I get a blank page/source, and the log says:
Started GET "/robots.txt" for 127.0.0.1 at 2011-11-21 11:22:13 -0500
Processing by SitesController#robots as HTML
Site Load (0.4ms) SELECT `sites`.* FROM `sites` WHERE (`sites`.`subdomain` = 'blah') ORDER BY created_at DESC LIMIT 1
Completed 406 Not Acceptable in 828ms
Any idea what I'm doing wrong?
Note: I added this to config/initializers/mime_types, cause Rails was complaining about not knowing what the .txt mime type was:
Mime::Type.register_alias "text/plain", :txt
Note 2: I did remove the stock robots.txt from the public directory.
NOTE: This is a repost from coderwall.
Reading up on some advice to a similar answer on Stackoverflow, I currently use the following solution to render a dynamic robots.txt based on the request's host parameter.
Routing
# config/routes.rb
#
# Dynamic robots.txt
get 'robots.:format' => 'robots#index'
Controller
# app/controllers/robots_controller.rb
class RobotsController < ApplicationController
# No layout
layout false
# Render a robots.txt file based on whether the request
# is performed against a canonical url or not
# Prevent robots from indexing content served via a CDN twice
def index
if canonical_host?
render 'allow'
else
render 'disallow'
end
end
private
def canonical_host?
request.host =~ /plugingeek\.com/
end
end
Views
Based on the request.host we render one of two different .text.erb view files.
Allowing robots
# app/views/robots/allow.text.erb # Note the .text extension
# Allow robots to index the entire site except some specified routes
# rendered when site is visited with the default hostname
# http://www.robotstxt.org/
# ALLOW ROBOTS
User-agent: *
Disallow:
Banning spiders
# app/views/robots/disallow.text.erb # Note the .text extension
# Disallow robots to index any page on the site
# rendered when robot is visiting the site
# via the Cloudfront CDN URL
# to prevent duplicate indexing
# and search results referencing the Cloudfront URL
# DISALLOW ROBOTS
User-agent: *
Disallow: /
Specs
Testing the setup with RSpec and Capybara can be done quite easily, too.
# spec/features/robots_spec.rb
require 'spec_helper'
feature "Robots" do
context "canonical host" do
scenario "allow robots to index the site" do
Capybara.app_host = 'http://www.plugingeek.com'
visit '/robots.txt'
Capybara.app_host = nil
expect(page).to have_content('# ALLOW ROBOTS')
expect(page).to have_content('User-agent: *')
expect(page).to have_content('Disallow:')
expect(page).to have_no_content('Disallow: /')
end
end
context "non-canonical host" do
scenario "deny robots to index the site" do
visit '/robots.txt'
expect(page).to have_content('# DISALLOW ROBOTS')
expect(page).to have_content('User-agent: *')
expect(page).to have_content('Disallow: /')
end
end
end
# This would be the resulting docs
# Robots
# canonical host
# allow robots to index the site
# non-canonical host
# deny robots to index the site
As a last step, you might need to remove the static public/robots.txt in the public folder if it's still present.
I hope you find this useful. Feel free to comment, helping to improve this technique even further.
One solution that works in Rails 3.2.3 (not sure about 3.0.10) is as follows:
1) Name your template file robots.text.erb # Emphasis on text vs. txt
2) Setup your route like this: match '/robots.:format' => 'sites#robots'
3) Leave your action as is (you can remove the respond_with in the controller)
def robots
#site = Site.find_by_subdomain # blah blah
end
This solution also eliminates the need to explicitly specify txt.erb in the render call mentioned in the accepted answer.
For my rails projects I usually have a seperate controller for the robots.txt response
class RobotsController < ApplicationController
layout nil
def index
host = request.host
if host == 'lawc.at' then #liveserver
render 'allow.txt', :content_type => "text/plain"
else #testserver
render 'disallow.txt', :content_type => "text/plain"
end
end
end
Then I have views named : disallow.txt.erb and allow.txt.erb
And in my routes.rb I have
get "robots.txt" => 'robots#index'
I don't like the idea of robots.txt reaching my Web Server.
If you are using Nginx/Apache as your reverse proxy, Static files would be much faster to handle by them than the request reaching rails itself.
This is much cleaner, and I think this is more faster too.
Try using the following setting.
nginx.conf - for production
location /robots.txt {
alias /path-to-your-rails-public-directory/production-robots.txt;
}
nginx.conf - for stage
location /robots.txt {
alias /path-to-your-rails-public-directory/stage-robots.txt;
}
I think the problem is that if you define respond_to in your controller, you have to use respond_with in the action:
def robots
#site = Site.find_by_subdomain # blah blah
respond_with #site
end
Also, try explicitly specifying the .erb file to be rendered:
def robots
#site = Site.find_by_subdomain # blah blah
render 'sites/robots.txt.erb'
respond_with #site
end

Resources