Rails Friendly_id独特的Slug格式

时间:2014-06-19 12:15:05

标签: ruby-on-rails ruby-on-rails-4 rubygems friendly-id

我正在使用friendly_id gem来阻止我的模型。因为当我输入相同的数据来检查时,slug必须是唯一的,我会在slug中附加一个长的哈希值。

Explore     explore 
Explore     explore-7a8411ac-5af5-41a3-ab08-d32387679f2b

有没有办法告诉friendly_id提供更好的格式化帖子,例如explore-1explore-2

版本: friendly_id 5.0.4

4 个答案:

答案 0 :(得分:10)

所以,如果有人在某个时候遇到过这个问题,我会更新我希望在tirdadc的评论中作为评论,但我不能(声誉不够)。那么,你走了:

从理论上讲,Tirdadc的答案是完美的,但不幸的是,在调用slug_candidates这一点时,还没有分配对象的id,所以你需要做一些小技巧。这里有一个完整的方法来获得一个带有对象id的slug:

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

所以你在创建对象之后基本上设置了slug,然后在创建对象之后,你将slug nil out并执行一个save,它会重新分配slug(现在id完整了)。在after_create调用中保存对象是否危险?可能,但它似乎对我有用。

答案 1 :(得分:9)

同意,这似乎是非常粗暴的行为。

如果查看friendly_id/slugged.rb的代码,有两个函数处理冲突解决逻辑:

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

所以,这个想法只是为了修补它。我看到两个选项:

  1. 只需修补resolve_friendly_id_conflict,添加随机后缀。

  2. 更改两种方法的逻辑,意图尝试所有候选人,直到slug_generator.generate(candidates)返回非空的内容。如果所有候选人都提供nil,那么请回退到resolve_friendly_id_conflict方法。 使用这种技术,当slug不唯一时,你可以使用slug候选者来附加模型的id

  3. 理想情况下,如果gem的作者添加了一个配置选项来处理独特的slugs解析(方法符号或proc将生成器和候选者作为参数),或者只是检查模型是否响应某种方法,那将是很好的。

    此外,在某些使用案例中,根本不需要独特的段塞分辨率。例如,如果我们只想依靠validates_uniqueness_of :slug或候选人的唯一性验证。

答案 2 :(得分:7)

如果您想在处理冲突时避免使用slu U中的UUID,我建议使用:scoped模块。这是文档和示例:

http://norman.github.io/friendly_id/file.Guide.html#Unique_Slugs_by_Scope

尝试使用:scope => :id,因为无论如何每个ID都是唯一的,看看它是否适合您。

更新:

为了获得您想要的内容,您现在可以在第5版中使用candidates

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

答案 3 :(得分:3)

今天我遇到了这个问题,虽然其他答案帮助我开始,但我并不满意,因为和你一样,我想让slug像exploreexplore-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代码)。