Dynamic database-based routes in Rails preventing schema:load - ruby-on-rails

We've just inhered a code base from a new client and are in the process of bringing it across to our systems.
We've got a small problem with the rake db:schema:load command, in that it's failing due to the following lines in the routes.rb file:
if Rails.env.development? || Rails.env.staging?
Country.all.each do |country|
get "/#{country.locale}", to: 'home_pages#index', locale: country.locale
end
end
As you can see, this is checking if we're in the development environment, then attempting to dynamically generate routes for each of the countries in the database - but of course, this is failing because there are no countries in the database.
A classic catch 22 ;)
What I was hoping to find out is if there some way I can avoid this from happening? I can temporarily comment out the offending lines from the routes.rb file, but this feels a bit like cheating, and figured there must be a way of doing this which was a bit more elegant.
Thank you.

I can see 2 possible solutions:
1 define a dynamic route with a constraint
get '/:locale', to: 'home_pages#index', constraint: -> { |req| Country.where(locale: req.parameters[:locale]).exists? }
Note: you don't need locale: country_locale anymore, because :locale (in the route declaration) will give you this param available in the request object.
What the constraint lambda trick does is - it checks the validity of the requested route after the app is loaded (so Companies does not have to be in the DB when the routes file is loaded and evaluated).
The downside is the extra hit to the DB each time there's a request matching that route. Maybe you can mitigate it with some caching.
The upside is that you can now add new countries with new locales at runtime (no need to restart the app), but I guess this is not a major concern.
2 dump locales to a file and keep it in sync with the DB
You can keep the way you create routes (iterate through all locales and define one route for each), but load the locales from the file.
File.open('locales.txt').each_line do |locale|
get "/#{locale}", to: 'home_pages#index', locale: locale
end
The downside is that you need to keep that in sync (for the occasion when some new country appears/disappears?) You can occasionally dump Company.pluck(:locale).join("\n") into this file.
I have a feeling that this particular rake task should not need routes defined, but probably it's the way the rails app environment is loaded. I had the same issue (we used solution 1 with cache BTW) and I think I reported it somewhere but I can't find it... will share when I succeed.

An answer which my coworker found was to do a check via ActiveRecord::Base.connection to see if the database table existed before running the code.
It now looks like this:
if (Rails.env.development? || Rails.env.staging? || Rails.env.uat?) && ActiveRecord::Base.connection.table_exists?('countries')
Country.all.each do |country|
get "/#{country.locale}", to: 'home_pages#index', locale: country.locale
end
end

Related

Rails: find routes/methods which no longer match any controller

I have a massive monolithic Rails repo with a couple thousand routes.
Over time, developers have either deleted a route which maps to an existing controller or have deleted a controller which still has a route mapped to it
Is there a smart way for me to systematically find these anomalies within my rails app to clean it up?
NOTE: Please assume that these mismatches do not present a user-facing issue but is merely for maintenance purposes to trim the number of bad routes
The traceroute gem looks very nice, but just in case it doesn't work as you expect, you could do this in rails console.
Get an array of all your controllers with their actions:
ApplicationController.descendants.map {|c| [c, c.action_methods]}
Get an array of all routes:
Rails.application.routes.routes.map &:defaults
Then you could iterate over the two arrays, selecting actions that appear in one array but not in the other one.
Before starting the console set config.eager_load = true in config/environments.development.rb. Without this you won't see all of your controllers.
There is no such command that will find you:
But here is a thing you can do:
Run command:
rails routes > routes.rb
It will write all your route in an text file and they you can match the manually if you have the time.
But there is no harm if you have extra routes present in Rails application its common in most of the Rails applications.
I found this gem which aims to solve my question somewhat
https://github.com/amatsuda/traceroute

Rails route sometime got an error not found

My Rails route sometimes get a not found error and resolves itself after a few seconds.
undefined local variable or method owner_root_path' for #<Owner::SessionsController:0x00007f30408d46f0>
/myapp/app/controllers/owner/sessions_controller.rb:30:increate’
/ruby/2.5.0/gems/actionpack-5.1.6.2/lib/action_controller/metal/basic_implicit_render.rb:4:in send_action'
/ruby/2.5.0/gems/actionpack-5.1.6.2/lib/abstract_controller/base.rb:186:inprocess_action’
My routes config
# routes/owner.rb
Rails.application.routes.draw do
constraints subdomain: /^owner/ do
root to: "owner/top_pages#show", as: :owner_root
...
end
end
# application.rb
config.paths["config/routes.rb"] = %w(
config/routes/owner.rb
config/routes.rb
).map {|relative_path| Rails.root.join(relative_path)}
Does anyone know why it happened?
Rails 6.1 guides introduced the draw macro which can be used to split a large route file into smaller files. You can read about it here: Breaking up very large route file into multiple small ones
I don't know whether your version of Rails supports this macro. If it doesn't, then you can easily define a draw method yourself, using the source. It will be something like this:
def draw(routes_name)
instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb")))
end
P.S: You should definitely check out the commit that introduced this method/macro.

How to run copy of the app running on '/' on '/someurl' as well?

Let's say I have a site like Digg running on bookmarks.com and want to create bookmarks.com/girls which shares all the routes, but all the links point to /girls/<something> instead of just /<something>.
Without changing all the links in views, I would like to modify routes.rb to do something like this:
all_routes = lambda {
...
}
scope '/girls', &all_routes, constraint: { |r| r.session[:subsite] == girls }
scope '/', &all_routes
However I am stuck with trial and error and haven't been able to come up with a solution.
Bonus points: how would I route this, if I had dynamic multiple sub-sites?
I think you can achieve this by subclassing your Rails app, and then mounting it using Rack.mount in your routes
mount AnotherRailsApp, :at => "/something"
Alternatively, you could use a rackup file, so in config.ru:
map "/" do
run RailsApp::Application
end
map "/something" do
run AnotherRailsApp::Application
end
However, with either approach, I'm not sure Rails' helper methods for URLs will be able to generate valid URLs when mounted at "/something", and you're probably going to get some interesting issues with your data too, unless you have one db per app.

Recursively redirecting in Rails 3 Routes

We are updating our large web app and getting rid of our URL synonym system. For example, though we only use one model for 'advertiser', it is aliased to 'merchants' and 'sellers', using synonymous paths. Thus, /merchant/1 and /seller/1 use the same exact controller and action as advertiser/1.
We no longer need this functionality, but we do need for old URLs to correctly redirect users to the proper place (i.e. proper controller actions).
So far, I've tried:
1) reading http://guides.rubyonrails.org/routing.html - This lead me to try the following suggestions
2) match '/merchants' => redirect('/advertisers') - this didn't seem to work at all, though the syntax seems correct.
3) iterating over resources (this produces a TON of routes and may cause insecurities):
ADVERTISER_SYNONOYMS = ['affiliates', 'sellers']
ADVERTISER_SYNONYMS.each do |a|
resources :advertisers, :path => a, :as => :advertiser
resources :other_controller do
member do
get :test
match :generate_test
end
end
end
end
We use nested resources in our routes.rb, so I'm struggling with getting these synonymns to be recognized all throughout more complex URLs. If anyone dealt with this problem or has a more elegant suggestion, I'd be most grateful.
There are many ways to handle this, including additional routes in routes.rb like you've tried, or rewrites at the webserver level such as Apache or Nginx.
One solution I like is the rack-rewrite gem; it keeps routes clean and the rules are plain ruby. It provides a set of methods for configuring rewrites and redirects. For your situation, I would want to use permanent redirects. Though I haven't tested it out myself, something like the following may work for you:
# Gemfile
gem 'rack-rewrite'
# application.rb
config.middleware.use(Rack::Rewrite) do
r301 %r{/merchant|seller/(\w+)}, '/advertiser/$1'
end

Is there any way to display Rails' log files in the browser?

This might be (read: probably is) a dumb question, but here goes...
Is there a simple, preferably non third-party, way to display Rails logs in the browser? It gets kind of old opening a log file, then closing it and re-opening it on the next error. It would be superfantastic to just to go domain.tld/logs, which would require user authentication, of course.
For reference, there is a very simple way to do this. However, you would want to protect this page with some sort of authentication/authorization.
Controller:
lines = params[:lines]
if Rails.env == "production"
#logs = `tail -n #{lines} log/production.log`
else
#logs = `tail -n #{lines} log/development.log`
end
Log File View:
<pre><%= #logs %></pre>
View to display link:
<%= link_to "log file", log_path(lines: 100) %>
Routes:
get 'logs/:lines' => "pages#log", as: "log"
All you need is to open log file and put its content into browser.
Realted topic: ruby: all ways to read from file.
Also you should know that your logs grow very fast, and it's not a good idea to show whole log in browser. You can just open last 100-200 rows. Related topic: Reading the last n lines of a file in Ruby?
Also you can try this solution: http://newrelic.com/. It is more complex and little oftopic, but quite useful.
I created a for this purpose called browserlog.
Installing is just a matter of adding the gem to the Gemfile:
gem 'browserlog'
Afterwards all you need is to mount the engine on the route you want:
MyApp::Application.routes.draw do
mount Browserlog::Engine => '/logs'
end
Once that's set up, accessing /logs/development (or production, test, etc) will show a window like this:
(source: andredieb.com)
Pimp my Log can be used to visualization many types of logs including Ruby on Rails.
It also includes the management of users and notification.
[Edited]
By default, the PimpMyLog does not support Ruby on Rails logs. It's necessary to implement the regex to works with it.
[/Edited]

Resources