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 варианта:

  1. просто патч resolve_friendly_id_conflict, добавьте случайный суффикс.

  2. измените логику обоих методов с намерением попробовать всех кандидатов до 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).