Replace homegrown slug with friendly_id
This commit is contained in:
parent
31abbd99e7
commit
19b6d7a8cd
9 changed files with 142 additions and 37 deletions
2
Gemfile
2
Gemfile
|
@ -20,6 +20,8 @@ gem 'slim-rails'
|
||||||
gem 'bootstrap-sass'
|
gem 'bootstrap-sass'
|
||||||
gem 'simple_form'
|
gem 'simple_form'
|
||||||
|
|
||||||
|
gem 'friendly_id', '~> 5.1.0'
|
||||||
|
|
||||||
gem 'bcrypt'
|
gem 'bcrypt'
|
||||||
|
|
||||||
gem 'sdoc', '~> 0.4.0', group: :doc
|
gem 'sdoc', '~> 0.4.0', group: :doc
|
||||||
|
|
|
@ -125,6 +125,8 @@ GEM
|
||||||
ruby_parser (~> 3.1, > 3.1.0)
|
ruby_parser (~> 3.1, > 3.1.0)
|
||||||
sexp_processor (~> 4.4)
|
sexp_processor (~> 4.4)
|
||||||
formatador (0.2.5)
|
formatador (0.2.5)
|
||||||
|
friendly_id (5.1.0)
|
||||||
|
activerecord (>= 4.0.0)
|
||||||
globalid (0.3.5)
|
globalid (0.3.5)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
guard (2.12.8)
|
guard (2.12.8)
|
||||||
|
@ -375,6 +377,7 @@ DEPENDENCIES
|
||||||
dotenv-rails
|
dotenv-rails
|
||||||
factory_girl_rails
|
factory_girl_rails
|
||||||
faker
|
faker
|
||||||
|
friendly_id (~> 5.1.0)
|
||||||
guard-rspec
|
guard-rspec
|
||||||
jbuilder (~> 2.0)
|
jbuilder (~> 2.0)
|
||||||
jquery-rails
|
jquery-rails
|
||||||
|
|
|
@ -45,6 +45,6 @@ class SubcredditsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_subcreddit
|
def set_subcreddit
|
||||||
@subcreddit = Subcreddit.find_by_slug(params[:id])
|
@subcreddit = Subcreddit.friendly.find(params[:id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,28 +1,25 @@
|
||||||
RESERVED_SLUGS = %w(new edit)
|
|
||||||
|
|
||||||
class Subcreddit < ActiveRecord::Base
|
class Subcreddit < ActiveRecord::Base
|
||||||
|
extend FriendlyId
|
||||||
|
|
||||||
belongs_to :owner, class_name: 'User'
|
belongs_to :owner, class_name: 'User'
|
||||||
has_many :posts
|
has_many :posts
|
||||||
|
|
||||||
|
friendly_id :name, use: :slugged
|
||||||
|
|
||||||
attr_accessor :closed
|
attr_accessor :closed
|
||||||
|
|
||||||
before_save :set_slug
|
|
||||||
before_save :set_closed_at
|
before_save :set_closed_at
|
||||||
|
|
||||||
validates :name,
|
validates :name,
|
||||||
presence: true,
|
presence: true,
|
||||||
format: /\A(?! )[a-z0-9 ]*(?<! )\z/i,
|
format: /\A(?! )[a-z0-9 ]*(?<! )\z/i,
|
||||||
uniqueness: { case_sensitive: false },
|
uniqueness: true, #{ case_sensitive: false },
|
||||||
length: { minimum: 3, maximum: 21 }
|
length: { minimum: 3, maximum: 21 }
|
||||||
|
|
||||||
validate :slug_is_not_a_route
|
|
||||||
|
|
||||||
validates :closed,
|
validates :closed,
|
||||||
format: /\A[01]?\z/
|
format: /\A[01]?\z/
|
||||||
|
|
||||||
def to_param
|
validate :slug_does_not_have_uuid
|
||||||
self.slug
|
|
||||||
end
|
|
||||||
|
|
||||||
def closed?
|
def closed?
|
||||||
self.closed_at != nil
|
self.closed_at != nil
|
||||||
|
@ -30,14 +27,6 @@ class Subcreddit < ActiveRecord::Base
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_slug
|
|
||||||
self.slug = sluggify_name
|
|
||||||
end
|
|
||||||
|
|
||||||
def sluggify_name
|
|
||||||
self.name.downcase.tr(' ', '_')
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_closed_at
|
def set_closed_at
|
||||||
if closed == '1' && closed_at == nil
|
if closed == '1' && closed_at == nil
|
||||||
self.closed_at = Time.now
|
self.closed_at = Time.now
|
||||||
|
@ -46,9 +35,9 @@ class Subcreddit < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def slug_is_not_a_route
|
def slug_does_not_have_uuid
|
||||||
if RESERVED_SLUGS.include?(sluggify_name)
|
if self.slug.match /([a-z0-9]+\-){4}[a-z0-9]+\z/
|
||||||
errors.add(:name, 'cannot be a reserved keyword')
|
errors.add(:name, 'must be unique')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
88
config/initializers/friendly_id.rb
Normal file
88
config/initializers/friendly_id.rb
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
# FriendlyId Global Configuration
|
||||||
|
#
|
||||||
|
# Use this to set up shared configuration options for your entire application.
|
||||||
|
# Any of the configuration options shown here can also be applied to single
|
||||||
|
# models by passing arguments to the `friendly_id` class method or defining
|
||||||
|
# methods in your model.
|
||||||
|
#
|
||||||
|
# To learn more, check out the guide:
|
||||||
|
#
|
||||||
|
# http://norman.github.io/friendly_id/file.Guide.html
|
||||||
|
|
||||||
|
FriendlyId.defaults do |config|
|
||||||
|
# ## Reserved Words
|
||||||
|
#
|
||||||
|
# Some words could conflict with Rails's routes when used as slugs, or are
|
||||||
|
# undesirable to allow as slugs. Edit this list as needed for your app.
|
||||||
|
config.use :reserved
|
||||||
|
|
||||||
|
config.reserved_words = %w(new edit index session login logout users admin
|
||||||
|
stylesheets assets javascripts images)
|
||||||
|
|
||||||
|
# ## Friendly Finders
|
||||||
|
#
|
||||||
|
# Uncomment this to use friendly finders in all models. By default, if
|
||||||
|
# you wish to find a record by its friendly id, you must do:
|
||||||
|
#
|
||||||
|
# MyModel.friendly.find('foo')
|
||||||
|
#
|
||||||
|
# If you uncomment this, you can do:
|
||||||
|
#
|
||||||
|
# MyModel.find('foo')
|
||||||
|
#
|
||||||
|
# This is significantly more convenient but may not be appropriate for
|
||||||
|
# all applications, so you must explicity opt-in to this behavior. You can
|
||||||
|
# always also configure it on a per-model basis if you prefer.
|
||||||
|
#
|
||||||
|
# Something else to consider is that using the :finders addon boosts
|
||||||
|
# performance because it will avoid Rails-internal code that makes runtime
|
||||||
|
# calls to `Module.extend`.
|
||||||
|
#
|
||||||
|
# config.use :finders
|
||||||
|
#
|
||||||
|
# ## Slugs
|
||||||
|
#
|
||||||
|
# Most applications will use the :slugged module everywhere. If you wish
|
||||||
|
# to do so, uncomment the following line.
|
||||||
|
#
|
||||||
|
# config.use :slugged
|
||||||
|
#
|
||||||
|
# By default, FriendlyId's :slugged addon expects the slug column to be named
|
||||||
|
# 'slug', but you can change it if you wish.
|
||||||
|
#
|
||||||
|
# config.slug_column = 'slug'
|
||||||
|
#
|
||||||
|
# When FriendlyId can not generate a unique ID from your base method, it appends
|
||||||
|
# a UUID, separated by a single dash. You can configure the character used as the
|
||||||
|
# separator. If you're upgrading from FriendlyId 4, you may wish to replace this
|
||||||
|
# with two dashes.
|
||||||
|
#
|
||||||
|
# config.sequence_separator = '-'
|
||||||
|
#
|
||||||
|
# ## Tips and Tricks
|
||||||
|
#
|
||||||
|
# ### Controlling when slugs are generated
|
||||||
|
#
|
||||||
|
# As of FriendlyId 5.0, new slugs are generated only when the slug field is
|
||||||
|
# nil, but if you're using a column as your base method can change this
|
||||||
|
# behavior by overriding the `should_generate_new_friendly_id` method that
|
||||||
|
# FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave
|
||||||
|
# more like 4.0.
|
||||||
|
#
|
||||||
|
# config.use Module.new {
|
||||||
|
# def should_generate_new_friendly_id?
|
||||||
|
# slug.blank? || <your_column_name_here>_changed?
|
||||||
|
# end
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# FriendlyId uses Rails's `parameterize` method to generate slugs, but for
|
||||||
|
# languages that don't use the Roman alphabet, that's not usually sufficient.
|
||||||
|
# Here we use the Babosa library to transliterate Russian Cyrillic slugs to
|
||||||
|
# ASCII. If you use this, don't forget to add "babosa" to your Gemfile.
|
||||||
|
#
|
||||||
|
# config.use Module.new {
|
||||||
|
# def normalize_friendly_id(text)
|
||||||
|
# text.to_slug.normalize! :transliterations => [:russian, :latin]
|
||||||
|
# end
|
||||||
|
# }
|
||||||
|
end
|
15
db/migrate/20150716045747_create_friendly_id_slugs.rb
Normal file
15
db/migrate/20150716045747_create_friendly_id_slugs.rb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
class CreateFriendlyIdSlugs < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :friendly_id_slugs do |t|
|
||||||
|
t.string :slug, :null => false
|
||||||
|
t.integer :sluggable_id, :null => false
|
||||||
|
t.string :sluggable_type, :limit => 50
|
||||||
|
t.string :scope
|
||||||
|
t.datetime :created_at
|
||||||
|
end
|
||||||
|
add_index :friendly_id_slugs, :sluggable_id
|
||||||
|
add_index :friendly_id_slugs, [:slug, :sluggable_type]
|
||||||
|
add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], :unique => true
|
||||||
|
add_index :friendly_id_slugs, :sluggable_type
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddIndexToSubcredditSlug < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_index :subcreddits, :slug, unique: true
|
||||||
|
end
|
||||||
|
end
|
16
db/schema.rb
16
db/schema.rb
|
@ -11,7 +11,20 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 20150714200610) do
|
ActiveRecord::Schema.define(version: 20150716050055) do
|
||||||
|
|
||||||
|
create_table "friendly_id_slugs", force: :cascade do |t|
|
||||||
|
t.string "slug", null: false
|
||||||
|
t.integer "sluggable_id", null: false
|
||||||
|
t.string "sluggable_type", limit: 50
|
||||||
|
t.string "scope"
|
||||||
|
t.datetime "created_at"
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "friendly_id_slugs", ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true
|
||||||
|
add_index "friendly_id_slugs", ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type"
|
||||||
|
add_index "friendly_id_slugs", ["sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_id"
|
||||||
|
add_index "friendly_id_slugs", ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type"
|
||||||
|
|
||||||
create_table "posts", force: :cascade do |t|
|
create_table "posts", force: :cascade do |t|
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
|
@ -36,6 +49,7 @@ ActiveRecord::Schema.define(version: 20150714200610) do
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "subcreddits", ["owner_id"], name: "index_subcreddits_on_owner_id"
|
add_index "subcreddits", ["owner_id"], name: "index_subcreddits_on_owner_id"
|
||||||
|
add_index "subcreddits", ["slug"], name: "index_subcreddits_on_slug", unique: true
|
||||||
|
|
||||||
create_table "user_sessions", force: :cascade do |t|
|
create_table "user_sessions", force: :cascade do |t|
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
|
require 'securerandom'
|
||||||
|
|
||||||
describe Subcreddit, type: :model do
|
describe Subcreddit, type: :model do
|
||||||
let(:subcreddit) { build(:subcreddit) }
|
let(:subcreddit) { build(:subcreddit) }
|
||||||
|
|
||||||
|
@ -15,12 +17,6 @@ describe Subcreddit, type: :model do
|
||||||
expect(subcreddit).to be_valid
|
expect(subcreddit).to be_valid
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should sluggify the name' do
|
|
||||||
subcreddit.name = 'Testing Spaces 1'
|
|
||||||
subcreddit.save
|
|
||||||
expect(subcreddit.slug).to eq('testing_spaces_1')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should set closed_at if closed is true and closed_at is nil' do
|
it 'should set closed_at if closed is true and closed_at is nil' do
|
||||||
Timecop.freeze do
|
Timecop.freeze do
|
||||||
subcreddit.closed = '1'
|
subcreddit.closed = '1'
|
||||||
|
@ -92,15 +88,8 @@ describe Subcreddit, type: :model do
|
||||||
expect(duplicate).to be_invalid
|
expect(duplicate).to be_invalid
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should not allow duplicate names (case insensitive)' do
|
it 'should not allow a slug with a UUID' do
|
||||||
original = create(:subcreddit)
|
subcreddit.slug = "test-#{SecureRandom.uuid}"
|
||||||
duplicate = build(:subcreddit, name: original.name.upcase)
|
|
||||||
|
|
||||||
expect(duplicate).to be_invalid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should not use a reserved keyword' do
|
|
||||||
subcreddit.name = 'New'
|
|
||||||
|
|
||||||
expect(subcreddit).to be_invalid
|
expect(subcreddit).to be_invalid
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue