Skip to content

Commit c44896a

Browse files
author
Lewis Youl
committed
feat: add readable urls with friendly_id
1 parent 28aa8b3 commit c44896a

File tree

8 files changed

+160
-6
lines changed

8 files changed

+160
-6
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ gem 'redis'
3030
gem 'omniauth-github'
3131
gem 'omniauth-rails_csrf_protection'
3232
gem 'twitter'
33+
gem 'friendly_id', '~> 5.4.0'
3334

3435
# Use Redis adapter to run Action Cable in production
3536
# gem 'redis', '~> 4.0'

Gemfile.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ GEM
115115
ffi-compiler (1.0.1)
116116
ffi (>= 1.0.0)
117117
rake
118+
friendly_id (5.4.2)
119+
activerecord (>= 4.0.0)
118120
globalid (0.4.2)
119121
activesupport (>= 4.2.0)
120122
hashie (4.1.0)
@@ -305,6 +307,7 @@ DEPENDENCIES
305307
bootsnap (>= 1.4.2)
306308
byebug
307309
devise!
310+
friendly_id (~> 5.4.0)
308311
jbuilder (~> 2.7)
309312
letter_opener
310313
listen (>= 3.0.5, < 3.2)

app/controllers/users_controller.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def index
88
def show
99
respond_to do |format|
1010
format.html do
11-
@user = User.find_by(id: params[:id])
11+
@user = User.find_by(slug: params[:id])
1212
@page_title = "#{@user.name}"
1313
@serialized_user = @user.serialize.to_json
1414
@is_following = current_user ? current_user.following?(@user) : false
@@ -32,7 +32,7 @@ def show
3232
end
3333

3434
format.json do
35-
@user = User.find_by(id: params[:id])
35+
@user = User.find_by(slug: params[:id])
3636
@snippets = @user.filed_snippets.includes(:user, :folders, :language)
3737
@languages = Language.all.to_json
3838

@@ -80,15 +80,15 @@ def modify
8080
end
8181

8282
def follow
83-
@user = User.find_by(id: params[:id])
83+
@user = User.find_by(slug: params[:id])
8484

8585
current_user.follow(@user)
8686

8787
render partial: 'users/follow_button', locals: { user: @user }
8888
end
8989

9090
def unfollow
91-
@user = User.find_by(id: params[:id])
91+
@user = User.find_by(slug: params[:id])
9292

9393
current_user.unfollow(@user)
9494

app/models/user.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
require 'open-uri'
22

33
class User < ApplicationRecord
4+
extend FriendlyId
45
include Rails.application.routes.url_helpers
56

67
DEFAULT_FOLDER_NAME = 'My First Folder'.freeze
78
BIO_MISSING_TEXT = "This user hasn't updated their bio yet."
89
ALPHANUMERIC = /\A[a-zA-Z0-9]*\z/.freeze
910

11+
friendly_id :name, use: :slugged
1012
has_one_attached :avatar
1113

1214
# Include default devise modules. Others available are:
@@ -188,7 +190,6 @@ def attach_avatar_from_omniauth(avatar_url)
188190

189191
private
190192

191-
192193
def create_default_folder
193194
folders.create!(name: DEFAULT_FOLDER_NAME)
194195
end

config/initializers/friendly_id.rb

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# FriendlyId Global Configuration
2+
#
3+
# Use this to set up shared configuration options for your entire application.
4+
# Any of the configuration options shown here can also be applied to single
5+
# models by passing arguments to the `friendly_id` class method or defining
6+
# methods in your model.
7+
#
8+
# To learn more, check out the guide:
9+
#
10+
# http://norman.github.io/friendly_id/file.Guide.html
11+
12+
FriendlyId.defaults do |config|
13+
# ## Reserved Words
14+
#
15+
# Some words could conflict with Rails's routes when used as slugs, or are
16+
# undesirable to allow as slugs. Edit this list as needed for your app.
17+
config.use :reserved
18+
19+
config.reserved_words = %w(new edit index session login logout users admin
20+
stylesheets assets javascripts images)
21+
22+
# This adds an option to treat reserved words as conflicts rather than exceptions.
23+
# When there is no good candidate, a UUID will be appended, matching the existing
24+
# conflict behavior.
25+
26+
# config.treat_reserved_as_conflict = true
27+
28+
# ## Friendly Finders
29+
#
30+
# Uncomment this to use friendly finders in all models. By default, if
31+
# you wish to find a record by its friendly id, you must do:
32+
#
33+
# MyModel.friendly.find('foo')
34+
#
35+
# If you uncomment this, you can do:
36+
#
37+
# MyModel.find('foo')
38+
#
39+
# This is significantly more convenient but may not be appropriate for
40+
# all applications, so you must explicity opt-in to this behavior. You can
41+
# always also configure it on a per-model basis if you prefer.
42+
#
43+
# Something else to consider is that using the :finders addon boosts
44+
# performance because it will avoid Rails-internal code that makes runtime
45+
# calls to `Module.extend`.
46+
#
47+
# config.use :finders
48+
#
49+
# ## Slugs
50+
#
51+
# Most applications will use the :slugged module everywhere. If you wish
52+
# to do so, uncomment the following line.
53+
#
54+
# config.use :slugged
55+
#
56+
# By default, FriendlyId's :slugged addon expects the slug column to be named
57+
# 'slug', but you can change it if you wish.
58+
#
59+
# config.slug_column = 'slug'
60+
#
61+
# By default, slug has no size limit, but you can change it if you wish.
62+
#
63+
# config.slug_limit = 255
64+
#
65+
# When FriendlyId can not generate a unique ID from your base method, it appends
66+
# a UUID, separated by a single dash. You can configure the character used as the
67+
# separator. If you're upgrading from FriendlyId 4, you may wish to replace this
68+
# with two dashes.
69+
#
70+
# config.sequence_separator = '-'
71+
#
72+
# Note that you must use the :slugged addon **prior** to the line which
73+
# configures the sequence separator, or else FriendlyId will raise an undefined
74+
# method error.
75+
#
76+
# ## Tips and Tricks
77+
#
78+
# ### Controlling when slugs are generated
79+
#
80+
# As of FriendlyId 5.0, new slugs are generated only when the slug field is
81+
# nil, but if you're using a column as your base method can change this
82+
# behavior by overriding the `should_generate_new_friendly_id?` method that
83+
# FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave
84+
# more like 4.0.
85+
# Note: Use(include) Slugged module in the config if using the anonymous module.
86+
# If you have `friendly_id :name, use: slugged` in the model, Slugged module
87+
# is included after the anonymous module defined in the initializer, so it
88+
# overrides the `should_generate_new_friendly_id?` method from the anonymous module.
89+
#
90+
# config.use :slugged
91+
# config.use Module.new {
92+
# def should_generate_new_friendly_id?
93+
# slug.blank? || <your_column_name_here>_changed?
94+
# end
95+
# }
96+
#
97+
# FriendlyId uses Rails's `parameterize` method to generate slugs, but for
98+
# languages that don't use the Roman alphabet, that's not usually sufficient.
99+
# Here we use the Babosa library to transliterate Russian Cyrillic slugs to
100+
# ASCII. If you use this, don't forget to add "babosa" to your Gemfile.
101+
#
102+
# config.use Module.new {
103+
# def normalize_friendly_id(text)
104+
# text.to_slug.normalize! :transliterations => [:russian, :latin]
105+
# end
106+
# }
107+
end
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class AddSlugToUsers < ActiveRecord::Migration[6.0]
2+
def change
3+
add_column :users, :slug, :string
4+
add_index :users, :slug, unique: true
5+
6+
User.all.each(&:save!)
7+
end
8+
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIGRATION_CLASS =
2+
if ActiveRecord::VERSION::MAJOR >= 5
3+
ActiveRecord::Migration["#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"]
4+
else
5+
ActiveRecord::Migration
6+
end
7+
8+
class CreateFriendlyIdSlugs < MIGRATION_CLASS
9+
def change
10+
create_table :friendly_id_slugs do |t|
11+
t.string :slug, :null => false
12+
t.integer :sluggable_id, :null => false
13+
t.string :sluggable_type, :limit => 50
14+
t.string :scope
15+
t.datetime :created_at
16+
end
17+
add_index :friendly_id_slugs, [:sluggable_type, :sluggable_id]
18+
add_index :friendly_id_slugs, [:slug, :sluggable_type], length: { slug: 140, sluggable_type: 50 }
19+
add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], length: { slug: 70, sluggable_type: 50, scope: 70 }, unique: true
20+
end
21+
end

db/schema.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema.define(version: 2021_03_13_161848) do
13+
ActiveRecord::Schema.define(version: 2021_03_15_202025) do
1414

1515
# These are extensions that must be enabled in order to support this database
1616
enable_extension "plpgsql"
@@ -62,6 +62,17 @@
6262
t.datetime "updated_at", precision: 6, null: false
6363
end
6464

65+
create_table "friendly_id_slugs", force: :cascade do |t|
66+
t.string "slug", null: false
67+
t.integer "sluggable_id", null: false
68+
t.string "sluggable_type", limit: 50
69+
t.string "scope"
70+
t.datetime "created_at"
71+
t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true
72+
t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type"
73+
t.index ["sluggable_type", "sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_type_and_sluggable_id"
74+
end
75+
6576
create_table "languages", force: :cascade do |t|
6677
t.string "name", null: false
6778
t.datetime "created_at", precision: 6, null: false
@@ -130,9 +141,11 @@
130141
t.string "unconfirmed_email"
131142
t.string "provider"
132143
t.string "uid"
144+
t.string "slug"
133145
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
134146
t.index ["email"], name: "index_users_on_email", unique: true
135147
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
148+
t.index ["slug"], name: "index_users_on_slug", unique: true
136149
end
137150

138151
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"

0 commit comments

Comments
 (0)