Rails4 Friendly_id独特的Slug格式

我正在使用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/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的作者添加了一个配置选项来处理独特的slugs解析(方法符号或proc将生成器和候选者作为params)或者只是检查模型是否响应某种方法,那将是很好的。

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

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

如果你想在处理碰撞时避免你的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 

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

我使用了唯一性validation: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 => # 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 => # 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 => # 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 => # irb(main):005:0> 

此外,如果您使用sqlite3适配器,您将需要安装sqlite3_ar_regexp gem(它不会非常快,因为SQLite没有REGEXP()而它会评估Ruby代码)。