【问题标题】:Rails4 Friendly_id Unique Slug FormattingRails4 Friendly_id 独特的 Slug 格式
【发布时间】:2014-06-19 12:15:05
【问题描述】:

我正在使用friendly_id gem 来打击我的模型。因为当我输入相同的数据来检查时,slug 必须是唯一的,所以我在 slug 中得到一个长散列附加。

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

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

版本: friendly_id 5.0.4

【问题讨论】:

  • 您使用的是哪个版本的friendly_id?
  • @tirdadcfriendly_id 5.0.4
  • 是的,这看起来像是 5 中引入的新 UUID 功能。
  • @tirdadc 知道如何获得我想要的格式吗?

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


【解决方案1】:

同意,这似乎是相当粗暴的行为。

如果你看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 不是唯一的时,您可以使用 slug 候选者附加模型的id

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

此外,在某些用例中,根本不需要独特的 slugs 解决方案。例如,如果我们只想依赖validates_uniqueness_of :slug 或候选人的唯一性验证。

【讨论】:

  • 怪胎。我不敢相信他认为最后有这么大的随机字符串是可以的。它只是一个蛞蝓。
  • 非常感谢:D 你能告诉我如何给猴子打补丁吗?
  • 只需创建一个像 friendly_id.rb 这样的初始化器: module FriendlyId module Slugged def resolve_friendly_id_conflict(candidates) my_cool_unique_suffix = ... Candidates.first +friendly_id_config.sequence_separator + my_cool_unique_suffix end end end
  • 你能粘贴bin.com的内容吗?我收到奇怪的错误
  • pastebin.com/rz2VHLFZ 你可以阅读更多关于猴子补丁的信息:runtime-era.com/2012/12/… 但不要滥用猴子补丁。一旦您需要进行许多更改,最好分叉一个 gem。
【解决方案2】:

因此,如果有人在某个时候遇到此问题,我希望将更新作为评论放在 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 清零并执行保存,这将重新分配 slug(现在 id 完好无损)。在 after_create 调用中保存对象是否危险?可能,但它似乎对我有用。

【讨论】:

    【解决方案3】:

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

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

    尝试使用:scope =&gt; :id,因为无论如何每个 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
    

    【讨论】:

    • 未定义方法 `scope=' == Line == friendly_id :name, use: :slugged, :scope => :id
    • 你需要use: :scoped,而不是use: :slugged。看例子。
    • 你试过slug_candidates吗?
    • 不,这可能行不通。因为ID不是在保存之前创建的,所以在explore之后很难获得唯一的小号-$
    【解决方案4】:

    今天我遇到了这个问题,虽然其他答案帮助我入门,但我并不满意,因为和你一样,我想让这些 slug 按exploreexplore-2explore-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
    

    我对@9​​87654325@ 使用了唯一性验证,以防万一此模型用于并发代码中。

    在这里你可以看到它的工作原理:

    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 代码)。

    【讨论】:

    • 应该是唯一可以接受的答案,就好像你使用uuids而不是默认ids(整数)一样,[:name, [:name, :id] ]不会对结果有太大的改变
    【解决方案5】:

    Almir's 解决方案对我帮助很大,但 id_for_slug 方法对我不起作用。

    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

    【讨论】:

      猜你喜欢
      • 2018-10-12
      • 1970-01-01
      • 2016-06-30
      • 2011-04-21
      • 2012-02-28
      • 2013-01-06
      • 2015-07-30
      • 2014-03-30
      • 2015-02-26
      相关资源
      最近更新 更多