1

Perhaps I'm misinterpreting the capabilities of the Friendly_id gem, but I haven't found any other way to accomplish this goal either:

I have an app where users get their own URL to their page, similar to what facebook does (e.g. http://www.notfacebook.com/mypermalink). I already have this capability working. Also similar to facebook, I'd like to enable pages to have their own URL (e.g. http://www.notfacebook.com/pagepermalink).

I added the friendly_id gem, thinking that it had the capability to check for uniqueness among User.permalink and Page.permalink - uniqueness across columns that are in two different tables/models. Instead, I get a pages URL pattern that looks like http://www.notfacebook.com/pages/pagepermalink.

I can't use

resources :pages, path: ''

nor

get '/:friendly_id', to: 'pages#show'

in routes.rb because that doesn't work with my existing Users permalink routes.

Is there a way to get unique page and user permalinks in my app?

I was considering custom subdomains instead of permalinks, but I use heroku and from what I've read, even though I have my own domain name, I cannot use subdomains on heroku. Is that correct? (Yeah, that's a separate question.)

ThinQtv
  • 103
  • 1
  • 1
  • 11
  • I think what I need is a Sites model that has_many pages & has_many users. The Sites table would have a permalink column. That way /:permalink can be routed to a unique landing page for Pages AND Users - http://notfacebook.com/permalink It's probably fine if Page subpages have url pattern http://notfacebook.com/pages/pagepermalink/subpage User subpages will still have pattern http://notfacebook.com/userpermalink/subpage Would Users & Pages still need permalink column for subpage routes to work? Seems redundant. – ThinQtv Oct 16 '14 at 16:28

2 Answers2

2

I am not familiar with friendly_id, but after skimming the docs seems like it would work with this approach.

You just need a wildcard route:

At the end of config/routes.rb:

get "*friendly_id" => "pages#show"

I do this in a wiki app when pulling up a page. You'll notice I had to put that route at the bottom of the file so it gets evaluated last.

csexton
  • 24,061
  • 15
  • 54
  • 57
  • That doesn't work because I have match '/:permalink' => "users#show", :as => :user_profile, via: 'get' in routes.rb first. Also, there isn't a way to make sure User.permalink and Page.permalink are unique. – ThinQtv Oct 16 '14 at 16:32
1

There is a nice solution to that problem using routes constraints.

Using routes constraints

As the rails routing guide suggests, you could define routes constraints in a way that they check if a path belongs to a user or a page.

# config/routes.rb
# ...
get ':permalink', to: 'users#show', constraints: lambda { |request| User.where(permalink: request[:permalink]).any? }
get ':permalink', to: 'pages#show', constraints: lambda { |request| Page.where(permalink: request[:permalink]).any? }

The order defines the priority. In the above example, if a language and a category have the same name, the language wins as its route is defined above the category route.

The above solution requres the models to have a permalink column that defines a url-friendly name. But if you already have an url-friendly attribute, e.g. User#alias, you can use this as well:

# config/routes.rb
# ...
get ':alias', to: 'users#show', constraints: lambda { |request| User.where(alias: request[:alias]).any? }
get ':permalink', to: 'pages#show', constraints: lambda { |request| Page.where(permalink: request[:permalink]).any? }

Using a Permalink model

If you want to make sure, all paths are uniqe, an easy way would be to define a Permalink model and using a validation there.

Generate the database table: rails generate model Permalink path:string reference_type:string reference_id:integer && rails db:migrate

And define the validation in the model:

class Permalink < ApplicationRecord
  belongs_to :reference, polymorphic: true
  validates :path, presence: true, uniqueness: true

end

And associate it with the other object types:

class Page < ApplicationRecord
  has_many :permalinks, as: :reference, dependent: :destroy

end

This also allows you to define several permalink paths for a record.

page_about_rails.permalinks.create path: 'rails'
page_about_rails.permalinks.create path: 'ruby-on-rails'

With this solution, the routes file has to look like this:

# config/routes.rb
# ...
get ':permalink', to: 'users#show', constraints: lambda { |request| Permalink.where(reference_type: 'User', path: request[:permalink]).any? }
get ':permalink', to: 'pages#show', constraints: lambda { |request| Permalink.where(reference_type: 'Page', path: request[:permalink]).any? }

And, as a side note for other users using the cancan gem and load_and_authorize_resource in the controller: You have to load the record by permalink before calling load_and_authorize_resource:

class Page < ApplicationRecord
  before_action :find_resource_by_permalink, only: :show
  load_and_authorize_resource

  private

  def find_resource_by_permalink
    @page ||= Permalink.find_by(path: params[:permalink]).try(:reference)
  end
end
fiedl
  • 5,667
  • 4
  • 44
  • 57