Rails4 Friendly_id Уникальное форматирование слагов
Я использую самообладание friendly_id для создания моделей. Так как слаг должен быть уникальным, когда я ввожу одни и те же данные, чтобы проверить, я получаю длинный хеш, добавляющийся в слаг.
Explore explore
Explore explore-7a8411ac-5af5-41a3-ab08-d32387679f2b
Есть ли способ сказать friendly_id, чтобы дать лучше отформатированные слагы, такие как explore-1
а также explore-2
Версия: friendly_id 5.0.4
5 ответов
Согласен, это выглядит как довольно грубое поведение.
Если вы посмотрите на код friendly_id/slugged.rb
Есть 2 функции для обработки логики разрешения конфликтов:
def resolve_friendly_id_conflict(candidates)
candidates.first + friendly_id_config.sequence_separator + SecureRandom.uuid
end
# Sets the slug.
def set_slug(normalized_slug = nil)
if should_generate_new_friendly_id?
candidates = FriendlyId::Candidates.new(self, normalized_slug || send(friendly_id_config.base))
slug = slug_generator.generate(candidates) || resolve_friendly_id_conflict(candidates)
send "#{friendly_id_config.slug_column}=", slug
end
end
Итак, идея состоит в том, чтобы обезьяна залатать его. Я вижу 2 варианта:
Просто патч
resolve_friendly_id_conflict
, добавьте свой случайный суффикс.Измените логику обоих методов с намерением попробовать всех кандидатов до
slug_generator.generate(candidates)
возвращает что-то не пустое. Если все кандидаты даютnil
затем отступить кresolve_friendly_id_conflict
метод. Используя эту технику, вы можете использовать кандидатов на слаг для добавления моделейid
когда слизняк не уникален.
В идеале было бы неплохо, если бы авторы gem добавили опцию config для обработки уникального разрешения слагов (символ метода или proc, принимающий генератор и кандидатов в качестве параметров) или просто проверили, реагирует ли модель на какой-либо метод.
Кроме того, в некоторых случаях уникальное разрешение слагов вообще не требуется. Например, если мы просто хотим положиться на validates_uniqueness_of :slug
или проверка уникальности кандидатов.
Так что, если кто-то сталкивается с этим в какой-то момент, у меня есть обновление, которое я бы предпочел поместить в качестве комментария в комментарии tirdadc, но не могу (недостаточно репутации). Итак, поехали:
Теоретически, ответ Tirdadc идеален, но, к сожалению, идентификатор объекта еще не назначен в точке, в которой вызывается slug_candidates, так что вам нужно сделать небольшую хитрость. Вот полный способ получить слаг с идентификатором объекта там:
class YourModel < ActiveRecord::Base
extend FriendlyId
friendly_id :slug_candidates, use: :slugged
after_create :remake_slug
# Try building a slug based on the following fields in
# increasing order of specificity.
def slug_candidates
[
:name,
[:name, :id],
]
end
def remake_slug
self.update_attribute(:slug, nil)
self.save!
end
#You don't necessarily need this bit, but I have it in there anyways
def should_generate_new_friendly_id?
new_record? || self.slug.nil?
end
end
Таким образом, вы в основном устанавливаете слаг после создания объекта, а затем после того, как объект будет завершен, вы удаляете слаг и выполняете сохранение, которое переназначает слаг (теперь с идентификатором без изменений). Опасно ли сохранять объект в вызове after_create? Возможно, но это, кажется, работает для меня.
Я бы порекомендовал использовать :scoped
модуль, если вы хотите избежать UUID в слизняках при столкновениях. Вот документация вместе с примером:
http://norman.github.io/friendly_id/file.Guide.html
Попробуйте использовать :scope => :id
так как каждый идентификатор в любом случае будет уникальным и посмотрите, будет ли это работать для вас.
ОБНОВИТЬ:
Чтобы получить именно то, что вы хотите, теперь у вас есть candidates
для этой цели в версии 5:
class YourModel < ActiveRecord::Base
extend FriendlyId
friendly_id :slug_candidates, use: :slugged
# Try building a slug based on the following fields in
# increasing order of specificity.
def slug_candidates
[
:name,
[:name, :id],
]
end
end
Сегодня я столкнулся с этой проблемой, и хотя другой ответ помог мне начать, я не был удовлетворен, потому что, как и вы, я хотел, чтобы слизни появлялись в такой последовательности, как explore
, explore-2
, explore-3
,
Итак, вот как я это исправил:
class Thing < ActiveRecord::Base
extend FriendlyId
friendly_id :slug_candidates, use: :slugged
validates :name, presence: true, uniqueness: { case_sensitive: false }
validates :slug, uniqueness: true
def slug_candidates
[:name, [:name, :id_for_slug]]
end
def id_for_slug
generated_slug = normalize_friendly_id(name)
things = self.class.where('slug REGEXP :pattern', pattern: "#{generated_slug}(-[0-9]+)?$")
things = things.where.not(id: id) unless new_record?
things.count + 1
end
def should_generate_new_friendly_id?
name_changed? || super
end
end
Я использовал проверку уникальности для :slug
на всякий случай эта модель используется в параллельном коде.
Здесь вы можете увидеть это работает:
irb(main):001:0> Thing.create(name: 'New thing')
(0.1ms) begin transaction
(0.2ms) SELECT COUNT(*) FROM "things" WHERE (slug REGEXP 'new-thing(-[0-9]+)?$')
Thing Exists (0.1ms) SELECT 1 AS one FROM "things" WHERE ("things"."id" IS NOT NULL) AND "things"."slug" = ? LIMIT 1 [["slug", "new-thing"]]
Thing Exists (0.1ms) SELECT 1 AS one FROM "things" WHERE LOWER("things"."name") = LOWER('New thing') LIMIT 1
Thing Exists (0.1ms) SELECT 1 AS one FROM "things" WHERE "things"."slug" = 'new-thing' LIMIT 1
SQL (0.4ms) INSERT INTO "things" ("name", "slug") VALUES (?, ?) [["name", "New thing"], ["slug", "new-thing"]]
(115.7ms) commit transaction
=> #<Thing id: 1, name: "New thing", slug: "new-thing">
irb(main):002:0> Thing.create(name: 'New thing')
(0.2ms) begin transaction
(0.9ms) SELECT COUNT(*) FROM "things" WHERE (slug REGEXP 'new-thing(-[0-9]+)?$')
Thing Exists (0.1ms) SELECT 1 AS one FROM "things" WHERE ("things"."id" IS NOT NULL) AND "things"."slug" = ? LIMIT 1 [["slug", "new-thing"]]
Thing Exists (0.1ms) SELECT 1 AS one FROM "things" WHERE ("things"."id" IS NOT NULL) AND "things"."slug" = ? LIMIT 1 [["slug", "new-thing-2"]]
Thing Exists (0.1ms) SELECT 1 AS one FROM "things" WHERE LOWER("things"."name") = LOWER('New thing') LIMIT 1
Thing Exists (0.1ms) SELECT 1 AS one FROM "things" WHERE "things"."slug" = 'new-thing-2' LIMIT 1
(0.1ms) rollback transaction
=> #<Thing id: nil, name: "New thing", slug: "new-thing-2">
irb(main):003:0> Thing.create(name: 'New-thing')
(0.2ms) begin transaction
(0.5ms) SELECT COUNT(*) FROM "things" WHERE (slug REGEXP 'new-thing(-[0-9]+)?$')
Thing Exists (0.1ms) SELECT 1 AS one FROM "things" WHERE ("things"."id" IS NOT NULL) AND "things"."slug" = ? LIMIT 1 [["slug", "new-thing"]]
Thing Exists (0.1ms) SELECT 1 AS one FROM "things" WHERE ("things"."id" IS NOT NULL) AND "things"."slug" = ? LIMIT 1 [["slug", "new-thing-2"]]
Thing Exists (0.3ms) SELECT 1 AS one FROM "things" WHERE LOWER("things"."name") = LOWER('New-thing') LIMIT 1
Thing Exists (0.3ms) SELECT 1 AS one FROM "things" WHERE "things"."slug" = 'new-thing-2' LIMIT 1
SQL (0.4ms) INSERT INTO "things" ("name", "slug") VALUES (?, ?) [["name", "New-thing"], ["slug", "new-thing-2"]]
(108.9ms) commit transaction
=> #<Thing id: 2, name: "New-thing", slug: "new-thing-2">
irb(main):004:0> Thing.create(name: 'New!thing')
(0.2ms) begin transaction
(0.6ms) SELECT COUNT(*) FROM "things" WHERE (slug REGEXP 'new-thing(-[0-9]+)?$')
Thing Exists (0.0ms) SELECT 1 AS one FROM "things" WHERE ("things"."id" IS NOT NULL) AND "things"."slug" = ? LIMIT 1 [["slug", "new-thing"]]
Thing Exists (0.1ms) SELECT 1 AS one FROM "things" WHERE ("things"."id" IS NOT NULL) AND "things"."slug" = ? LIMIT 1 [["slug", "new-thing-3"]]
Thing Exists (0.1ms) SELECT 1 AS one FROM "things" WHERE LOWER("things"."name") = LOWER('New!thing') LIMIT 1
Thing Exists (0.1ms) SELECT 1 AS one FROM "things" WHERE "things"."slug" = 'new-thing-3' LIMIT 1
SQL (0.1ms) INSERT INTO "things" ("name", "slug") VALUES (?, ?) [["name", "New!thing"], ["slug", "new-thing-3"]]
(112.4ms) commit transaction
=> #<Thing id: 3, name: "New!thing", slug: "new-thing-3">
irb(main):005:0>
Кроме того, если вы используете адаптер sqlite3, вам нужно установить sqlite3_ar_regexp
gem (это не будет очень быстро, потому что SQLite не имеет REGEXP() и вместо этого он оценивает код Ruby).
Решение Альмира мне очень помогло, но метод user920303 у меня не сработал.
def id_for_slug
generated_slug = normalize_friendly_id(name)
things = self.class.where('slug REGEXP :pattern', pattern: "#generated_slug}(-[0-9]+)?$")
things = things.where.not(id: id) unless new_record?
things.count + 1
end
Итак, я изменил этот метод, например:
def id_for_slug
Model.where('LOWER(table_name.name) = ?', name.downcase).count
end
и я думаю, should_generate_new_friendly_id? метод не является обязательным.
Я прикрепляю изображение моей модели здесь: slug_implementation