如何在 Rails 中使用 ICU 排序规则作为我的默认排序规则?

How do I use an ICU collation as my default in Rails?

我正在开发 Rails / Postgres 应用程序。我在 Mac 上开发。其他人使用 Linux。产品在 Heroku 上。

排序规则在 Mac 上被破坏,所以我得到的排序与 Linux 和 Heroku 略有不同。这会导致涉及排序的测试偶尔会失败或行为不一致。解决方法是使用 ICU 归类来获得一致的排序,但我不知道如何将其设为默认值。

Postgres will not create a database with an ICU collation。如果我将排序规则设置为 en-US-x-icu in database.yml...

default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  host: <%= ENV['DB_HOST'] || 'localhost' %>
  username: <%= ENV['DB_USER'] || 'postgres' %>
  password: <%= ENV['DB_PASSWORD'] || '' %>
  collation: en-US-x-icu

我收到一个错误,invalid locale name: "en-US-x-icu",尽管它出现在 pg_collation

$ rails db:migrate:reset
Dropped database 'email_integrator_development'
Dropped database 'email_integrator_test'
PG::WrongObjectType: ERROR:  invalid locale name: "en-US-x-icu"
Couldn't create 'email_integrator_development' database. Please check your configuration.
rails aborted!
ActiveRecord::StatementInvalid: PG::WrongObjectType: ERROR:  invalid locale name: "en-US-x-icu"

也许有一种方法可以让 Rails 设置每个连接或每个 table 的集合?

我正在使用 Rails 6 和 Postgres 11,但如果需要我可以转移到 Postgres 12。

恐怕您必须将排序规则添加到每个字符串列定义中。

目前,您不能将 ICU 归类用作数据库默认值。 There have been efforts to improve that,但还没有任何可提交的结果。

但之后修复数据库很容易。使用 psql,您可以 运行

SELECT format(
          'ALTER TABLE %I.%I ALTER %I TYPE %I%s;',
          table_schema,
          table_name,
          column_name,
          data_type,
          '(' || character_maximum_length || ')'
       )
FROM information_schema.columns
WHERE data_type IN ('character', 'character varying', 'text')
  AND table_schema NOT IN ('pg_catalog', 'information_schema', 'pg_toast') \gexec

您可以对 ActiveRecord 进行猴子修补:

module ActiveRecord
  module ConnectionAdapters
    module PostgreSQL
      module ColumnMethods
        def new_column_definition(name, type, **options) # :nodoc:
          if integer_like_primary_key?(type, options)
            type = integer_like_primary_key_type(type, options)
          end
          type = aliased_types(type.to_s, type)
          options[:primary_key] ||= type == :primary_key
          options[:null] = false if options[:primary_key]
          # set default collation for a column if not set explicitly
          options[:collation] = "en-US-x-icu" if options[:collation].blank? && (type == :string || type == :text)
          create_column_definition(name, type, options)
        end
      end
    end
  end
end