如何验证数据库中是否已经存在一个列组合,rails 6、postgres
How to validate if a column combination already exists in database, rails 6, postgres
这里是新手!
ruby 2.7.1, gem --version 3.1.4, Rails 6.0.3.4
我的查询出现错误
def entry_uniqueness
if Entry.where("lower(from) = ? AND lower(to) = ?",
from.downcase, to.downcase).exists?
errors.add(:base, 'Identical combination already exists. YUP.')
end
end
错误:
PG::SyntaxError: ERROR: syntax error at or near "from" LINE 1: SELECT 1 AS one FROM "entries" WHERE (lower(from) = 'here' A... ^
完全错误
app/models/entry.rb:35:in entry_uniqueness' app/controllers/entries_controller.rb:9:in
create' Started POST
"/entries" for ::1 at 2021-02-13 16:17:47 +0800 Processing by
EntriesController#create as HTML Parameters:
{"authenticity_token"=>"98sjupNso6NW5xUonE/414I7ZvJQETMPBNWS+jcN+PffHaAJ3K0pdQofVPgnrBfflYn2SDXMlB17Q2G/gzideA==",
"entry"=>{"translation"=>"1", "from"=>"here", "to"=>"there"},
"commit"=>"Post Translation"} [1m[36mEntry Exists? (10.2ms)[0m
[1m[34mSELECT 1 AS one FROM "entries" WHERE (lower(from) = 'here'
AND lower(to) = 'there') LIMIT [0m [["LIMIT", 1]] ↳
app/models/entry.rb:35:in `entry_uniqueness' Completed 500 Internal
Server Error in 38ms (ActiveRecord: 10.2ms | Allocations: 2140)
ActiveRecord::StatementInvalid (PG::SyntaxError: ERROR: syntax
error at or near "from" LINE 1: SELECT 1 AS one FROM "entries" WHERE
(lower(from) = 'here' A...
^ ): app/models/entry.rb:35:in entry_uniqueness' app/controllers/entries_controller.rb:9:in
create'
我想要实现的是:
| from | to |
-----------------
| Here | there |
=> 验证应防止用户添加 heRE | THERE
但可以添加 THERE | heRE
列 :from
和 :to
位于 table entries
。
注意:我已经尝试过范围,它们在唯一性方面起作用但在不区分大小写方面失败
validates_uniqueness_of :from, scope: :to
也试过
validates_uniqueness_of :from, :scope => :to, :case_sensitive => false
也尝试了@r4cc00n 的实现,但它不起作用
scope :get_entries_by_from_and_to, ->(from, to) { where(arel_table[:from].lower.eq(from)).where(arel_table[:to].lower.eq(to))}
validates_uniqueness_of :from, if: :entry_uniqueness?
def entry_uniqueness?
Entry.get_entries_by_from_and_to('from','to').nil?
end
这是我认为您可以解决查询的方法:
您可以在 Entry 模型中定义一个范围,然后在任何地方使用该范围,如下所示:
scope :get_entries_by_from_and_to, ->(from, to) { where(arel_table[:from].lower.eq(from)).where(arel_table[:to].lower.eq(to))}
然后你可以使用它:Entry.get_entries_by_from_and_to('your_from','your_to')
如果那个 returns nil
那么你的数据库中没有符合你条件的记录。
话虽如此,如果您想将其与您拥有的内容和验证范围结合起来,您可以像下面那样进行:
def entry_uniqueness?
Entry.get_entries_by_from_and_to('your_from','your_to').nil?
end
validates_uniqueness_of :from, if: :entry_uniqueness?
请注意 validates_uniqueness_of 不 thread/concurrency 安全,这意味着在非常奇怪的情况下,您可以 运行 进入某些场景,在这些场景中您的数据库中没有唯一数据,为避免这种情况,您应该始终在数据库中创建唯一索引,以便数据库为您避免那些 'duplicated' 场景。
希望对您有所帮助!
from
是一个 reserved name in PostgreSQL。这很有意义,因为读取查询通常包含这个词(想想 SELECT value FROM table
)。因此 Rails 根据您的条件构建的查询
SELECT 1 AS one FROM "entries" WHERE (lower(from) = 'here' A...
^^^^ ^^^^
混淆了数据库。顺便说一句 to
也是一个保留名称。
避免 运行 出现这种问题的一种方法是您应该考虑重命名该列。
另一个简单的解决方法是将列名用双引号引起来。只需将您的条件更改为:
Entry.exists?('lower("from") = ? AND lower("to") = ?', from.downcase, to.downcase)
请注意,我在列名周围使用了双引号,在整个条件周围使用了单引号。
除了@Spickermann 提到的列命名问题,您还应该考虑使用 citext (case insentive) type column。与您的方法相比,它有许多优点:
- 您不必使用冗长的查询。
WHERE foo = ?
不分大小写。
- 您可以声明一个多列唯一索引以确保数据库级别的唯一性 - 这是不区分大小写的。这可以防止潜在的 duplicates due to race conditions.
缺点是,如果您必须切换到另一个数据库,如 MySQL 或 Oracle,那么您的应用程序的可移植性将降低,这可能不是一个真正的问题。
这当然要求您能够在您的 Postgres 数据库中启用扩展,并且您还需要确保您在测试、开发和生产中使用相同的数据库(无论如何这是一个好主意)。
class EnableCitext < ActiveRecord::Migration
def change
enable_extension("citext")
end
end
class ChangeFoosToCitext < ActiveRecord::Migration
def change
change_column :foos, :bar, :citext
change_column :foos, :baz, :citext
add_index :foos, [:bar, :baz], unique: true
end
end
这将让您使用一个简单的验证范围:
validates :bar, uniqueness: { scope: :baz }
您不需要使用生成无效查询的 case_sentive: false
选项。
这里是新手!
ruby 2.7.1, gem --version 3.1.4, Rails 6.0.3.4
我的查询出现错误
def entry_uniqueness
if Entry.where("lower(from) = ? AND lower(to) = ?",
from.downcase, to.downcase).exists?
errors.add(:base, 'Identical combination already exists. YUP.')
end
end
错误:
PG::SyntaxError: ERROR: syntax error at or near "from" LINE 1: SELECT 1 AS one FROM "entries" WHERE (lower(from) = 'here' A... ^
完全错误
app/models/entry.rb:35:in
entry_uniqueness' app/controllers/entries_controller.rb:9:in
create' Started POST "/entries" for ::1 at 2021-02-13 16:17:47 +0800 Processing by EntriesController#create as HTML Parameters: {"authenticity_token"=>"98sjupNso6NW5xUonE/414I7ZvJQETMPBNWS+jcN+PffHaAJ3K0pdQofVPgnrBfflYn2SDXMlB17Q2G/gzideA==", "entry"=>{"translation"=>"1", "from"=>"here", "to"=>"there"}, "commit"=>"Post Translation"} [1m[36mEntry Exists? (10.2ms)[0m [1m[34mSELECT 1 AS one FROM "entries" WHERE (lower(from) = 'here' AND lower(to) = 'there') LIMIT [0m [["LIMIT", 1]] ↳ app/models/entry.rb:35:in `entry_uniqueness' Completed 500 Internal Server Error in 38ms (ActiveRecord: 10.2ms | Allocations: 2140)ActiveRecord::StatementInvalid (PG::SyntaxError: ERROR: syntax error at or near "from" LINE 1: SELECT 1 AS one FROM "entries" WHERE (lower(from) = 'here' A... ^ ): app/models/entry.rb:35:in
entry_uniqueness' app/controllers/entries_controller.rb:9:in
create'
我想要实现的是:
| from | to |
-----------------
| Here | there |
=> 验证应防止用户添加 heRE | THERE
但可以添加 THERE | heRE
列 :from
和 :to
位于 table entries
。
注意:我已经尝试过范围,它们在唯一性方面起作用但在不区分大小写方面失败
validates_uniqueness_of :from, scope: :to
也试过
validates_uniqueness_of :from, :scope => :to, :case_sensitive => false
也尝试了@r4cc00n 的实现,但它不起作用
scope :get_entries_by_from_and_to, ->(from, to) { where(arel_table[:from].lower.eq(from)).where(arel_table[:to].lower.eq(to))}
validates_uniqueness_of :from, if: :entry_uniqueness?
def entry_uniqueness?
Entry.get_entries_by_from_and_to('from','to').nil?
end
这是我认为您可以解决查询的方法:
您可以在 Entry 模型中定义一个范围,然后在任何地方使用该范围,如下所示:
scope :get_entries_by_from_and_to, ->(from, to) { where(arel_table[:from].lower.eq(from)).where(arel_table[:to].lower.eq(to))}
然后你可以使用它:Entry.get_entries_by_from_and_to('your_from','your_to')
如果那个 returns nil
那么你的数据库中没有符合你条件的记录。
话虽如此,如果您想将其与您拥有的内容和验证范围结合起来,您可以像下面那样进行:
def entry_uniqueness?
Entry.get_entries_by_from_and_to('your_from','your_to').nil?
end
validates_uniqueness_of :from, if: :entry_uniqueness?
请注意 validates_uniqueness_of 不 thread/concurrency 安全,这意味着在非常奇怪的情况下,您可以 运行 进入某些场景,在这些场景中您的数据库中没有唯一数据,为避免这种情况,您应该始终在数据库中创建唯一索引,以便数据库为您避免那些 'duplicated' 场景。
希望对您有所帮助!
from
是一个 reserved name in PostgreSQL。这很有意义,因为读取查询通常包含这个词(想想 SELECT value FROM table
)。因此 Rails 根据您的条件构建的查询
SELECT 1 AS one FROM "entries" WHERE (lower(from) = 'here' A...
^^^^ ^^^^
混淆了数据库。顺便说一句 to
也是一个保留名称。
避免 运行 出现这种问题的一种方法是您应该考虑重命名该列。
另一个简单的解决方法是将列名用双引号引起来。只需将您的条件更改为:
Entry.exists?('lower("from") = ? AND lower("to") = ?', from.downcase, to.downcase)
请注意,我在列名周围使用了双引号,在整个条件周围使用了单引号。
除了@Spickermann 提到的列命名问题,您还应该考虑使用 citext (case insentive) type column。与您的方法相比,它有许多优点:
- 您不必使用冗长的查询。
WHERE foo = ?
不分大小写。 - 您可以声明一个多列唯一索引以确保数据库级别的唯一性 - 这是不区分大小写的。这可以防止潜在的 duplicates due to race conditions.
缺点是,如果您必须切换到另一个数据库,如 MySQL 或 Oracle,那么您的应用程序的可移植性将降低,这可能不是一个真正的问题。
这当然要求您能够在您的 Postgres 数据库中启用扩展,并且您还需要确保您在测试、开发和生产中使用相同的数据库(无论如何这是一个好主意)。
class EnableCitext < ActiveRecord::Migration
def change
enable_extension("citext")
end
end
class ChangeFoosToCitext < ActiveRecord::Migration
def change
change_column :foos, :bar, :citext
change_column :foos, :baz, :citext
add_index :foos, [:bar, :baz], unique: true
end
end
这将让您使用一个简单的验证范围:
validates :bar, uniqueness: { scope: :baz }
您不需要使用生成无效查询的 case_sentive: false
选项。