1
votes

I'm using the friendly_id to add custom slugs to my models and their corresponding urls. Currently I have a setup where a Post belongs to a Board. There will undoubtedly be cases where a post will have an identical title to another but from a different board. Often times I've noticed sites (SO included) prepend a unique set of numbers before the slug to insure there are no issues with uniqueness:

https://stackguides.com/questions/123456/my-example-question

I was wondering what would be the best approach to accomplish this? This couldn't be done solely through the routes file because there still poses the possibility of two or more identical posts being created. Would it be a combination of altering my model, routes file, and configuration of the friendly_id gem?

My end goal is to have a url generated like this for my posts:

https://example.com/boards/example-board/123456/example-post

class Board < ApplicationRecord
  extend FriendlyId

  has_many :posts

  friendly_id :name, use: :slugged
end


class Post < ApplicationRecord
  extend FriendlyId

  belongs_to :board

  friendly_id :title, use: :slugged
end

resources :boards do
  scope module: :boards do
    resources :posts
  end
end
2

2 Answers

2
votes

EDIT

You can do something like this in your routes:

resources :boards do
  resources :posts, path: ':board_real_id'
end

and add params[:board_real_id] to your query.

OLD

I don't think you need UUID for this (unless you want to). You can use candidates and if two posts have the same name and they belong to the same board, just insert the post's id and you'll be fine, you'll have something like https://example.com/boards/example-board/123456-example-post

From: http://norman.github.io/friendly_id/file.Guide.html

Since UUIDs are ugly, FriendlyId provides a "slug candidates" functionality to let you specify alternate slugs to use in the event the one you want to use is already taken. For example:

friendly_id :slug_candidates, use: :slugged

  # Try building a slug based on the following fields in
  # increasing order of specificity.
  def slug_candidates
    [
      :name,
      [:id, :name]
    ]
  end
0
votes

You need to use slug_candidates, see the docs here.

In your case all you need is to add a uuid in the end/start of the slug, and you can achieve this by using incremental uuid. If you have the a record with the current slug, get the max uuid and increase it by 1, save it!

class Post < ApplicationRecord
  extend FriendlyId

  belongs_to :board

  friendly_id :slug_candidates, use: :slugged


  def slug_url
    name
  end

  def slug_candidates
    [:slug_url, [:slug_url, :slug_uuid]]
  end

  def slug_uuid
    result = Post.select("COUNT(REGEXP_SUBSTR(name, '[0-9]+$')) AS cnt, MAX(REGEXP_SUBSTR(title, '[0-9]+$')) + 1 AS mx_uuid")
    result.cnt == 0 ? "" : result.mx_uuid + 1
  end
end

I am using MYSQL syntax to match the regex pattern.