1

I have a Rails application setup where, after all of the other site routes are defined, I have a catch-all wildcard for my Users to display their Profiles on selected root-level "vanity" URLs of non-reserved paths/keywords:

get '*path' => 'profiles#show'

The Profiles controller then checks to make sure the path defines a valid Profile, otherwise redirects to root. This works fine.

What I need to do now is create a mechanism where the catch-all path could define either a Profile or a Blog, based on the database lookup of the path for the proper controller to route to.

I do not want to do a redirect ... I want to load either the Profile or Blog content on the original wildcard URL.

What are my options to go from wildcard route -> db lookup -> proper controller?

In other words, where might this logic properly go?

Thanks.

kayatela
  • 394
  • 5
  • 21

1 Answers1

2

It seem like you want a route constraint that will match some pattern (regex) that defines if a path matches a route or if it tries the subsequent routes. Maybe something like this.

get '*path', :to => 'profiles#show', :constraints => { path: /blog\/.+/ }

The idea is that you must know something at the routing level, if it can be path based then the above thing will work otherwise if it needs to be more complex you can use a custom constraints class.

# lib/blog_constraint.rb
class BlogConstraint
  def initialize
    @slugs = Blog.pluck(:slug)
  end

  def matches?(request)
    request.url =~ /blog\/(.+)/
    @slugs.include?($1)
  end
end

# config/routes.rb
YourApp::Application.routes.draw do
  get '*path', :to => 'blogs#show', :constraints => BlogConstraint.new
  get '*path', :to => 'profiles#show'
end
nort
  • 1,625
  • 13
  • 12
  • Or if you want to have more control over the items that are considered for matching, you could by the slug lookup into the matches method with a call to cache it for some short time. `Rails.cache.fetch(:expires => 5.minutes) { @slugs = Blog.all.map(&:slug) }` – nort Jul 18 '14 at 15:00
  • 1
    Much more efficient is to remove the `initialize` and replace `@slugs.include?($1)` with `Blog.where(slug: $1).exists?`. The other solution loads *all* your blog posts into memory on *every* request, which is obviously not optimal. – ben Aug 21 '16 at 17:04
  • @ben On each request, the *instance of* `BlogConstraint` passed to `get` is used. – nort Aug 21 '16 at 21:04
  • At which point does `BlogConstraint` initialize then? When the app loads? Or per request? – ben Aug 21 '16 at 23:11
  • @ben When the app loads the routes.rb file – nort Aug 21 '16 at 23:29
  • So that means that if new blog posts are added after the app loads, they won't be loaded in? – ben Aug 22 '16 at 03:02
  • @ben that is true, the assumption was the blog post addition could trigger a reload of the app (heroku restart or something equivalent) -- caching is about tradeoffs and this is just one way and one example of how they might do this type of thing – nort Aug 25 '16 at 03:34