Rails4 дружественный id уникальный Slug форматирование
Я использую friendly_id gem для удара моих моделей. Поскольку пуля должна быть уникальной, когда я ввожу те же данные, чтобы проверить, я получаю длинное хешированное добавление в пуле.
Explore explore
Explore explore-7a8411ac-5af5-41a3-ab08-d32387679f2b
есть ли способ сказать friendly_id, чтобы дать лучше отформатированные слизни, такие как explore-1
и explore-2
версия: friendly_id 5.0.4
4 ответов
согласен, это похоже на довольно грубое поведение.
если вы посмотрите на код 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
метод. Используя этот метод, вы можете использовать кандидатов slug для добавления модели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
модуль, если вы хотите избежать UUIDs в ваших слизнях при работе с столкновениями. Вот документация вместе с примером:
http://norman.github.io/friendly_id/file.Guide.html#Unique_Slugs_by_Scope
попробуйте использовать :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).