可排序的UUID和覆盖ActiveRecord::Base

10

我想在正在构建的应用程序中使用UUID,但遇到了一些问题。由于UUID(v4)是随机生成的,因此它们不能排序,我尝试覆盖ActiveRecord :: Base#first方法,但Rails不太满意。它向我大喊大叫,说:ArgumentError:您尝试在模型“Item”上定义一个名为“first”的作用域,但Active Record已经定义了一个同名的类方法。 如果我想正确排序并使其正确排序,我必须使用不同的方法吗?

这就是全部内容:

# lib/sortable_uuid.rb
module SortableUUID
  def self.included(base)
    base.class_eval do
      scope :first, -> { order("created_at").first }
      scope :last, -> { order("created_at DESC").first }
    end
  end
end


# app/models/item.rb
class Item < ActiveRecord::Base
  include SortableUUID
end

Rails 4.2,Ruby 2.2.2

参考资料:

4个回答

6
Rails 6(目前版本为6.0.0rc1)通过implicit_order_column挽救了情况!
created_at排序并使.first, .last, .second等方法尊重它就像这样简单:
class ApplicationRecord < ActiveRecord::Base
  self.implicit_order_column = :created_at
end

5
首先,firstlast 并不像你认为的那么简单:你完全忽略了这两种方法都支持的 limit 参数。
其次,scope 只不过是一种添加类方法的花式方法,这些方法旨在返回查询。你的作用域滥用了 scope ,因为它们返回单个模型实例而不是查询。你根本不想使用 scope ,你只是想替换 firstlast 类方法,那么为什么不直接覆盖它们呢?不过你需要正确地进行覆盖,这将需要阅读和理解 Rails 源代码,以便正确地模仿 find_nth_with_limit 的功能。你需要同时覆盖 second, third 等其他愚蠢的方法。

如果你觉得用默认作用域对事物进行排序更合适(我认为这是一个好主意),那么你可以添加一个默认作用域:

default_scope -> { order(:created_at) }

当然, 默认作用域会带来自己的一系列问题,像这样将东西潜入 ORDER BY 中可能会迫使你在实际想要指定 ORDER BY 时调用 reorder;请记住,对 order 的多次调用会添加新的排序条件,而不是替换已经存在的条件。

或者,如果您使用的是 Rails6+,可以使用 Markus's implicit_order_column solution 来避免默认作用域可能引起的所有问题。


我认为你的做法完全错误。每当我看到 M.first,我就会认为有些事情被遗忘了。按照 id 排序几乎没有任何用处,所以在使用 firstlast 等方法之前,你应该始终手动指定你想要的顺序。

1
请注意,这篇文章中提到:“默认作用域可能会导致各种奇怪的错误和混乱的行为”。这可能是更好的解决方案:https://github.com/rails/rails/pull/34480#issue-231798255 - stevec
1
@stevec 我并不反对,我不喜欢默认作用域,我曾经骂过它们。如果你在Rails6+中,Markus的implicit_order_column方法是一个很好的解决方案。 - mu is too short

0

在将id替换为uuid后,我发现关联分配外键的方式有些奇怪,并不是因为.last.first,而是因为我简单地忘记了在使用uuid的一个表中添加default: 'gen_random_uuid()'。一旦我解决了这个问题,问题就得到了解决。

create_table :appointments, id: :uuid, default: 'gen_random_uuid()' do |t|

0
根据Rails文档这里提供的最佳方法是确保为您想要使用的UUID添加默认选项。关于选择UUID的良好写作可在这里找到。我使用ULID作为它们是可排序的,并且通过将我的UUID选项指定为ULID,我的记录也是可排序的!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接