如何解决如何验证列组合是否已存在于数据库、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: 语法错误在“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' 开始 POST
"/entries" for ::1 at 2021-02-13 16:17:47 +0800
EntriesController#create 作为 HTML 参数:
{"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 $1[0m [["LIMIT",1]] ↳
app/models/entry.rb:35:in `entry_uniqueness' 已完成 500 内部
38 毫秒内的服务器错误(ActiveRecord:10.2ms | 分配:2140)
ActiveRecord::StatementInvalid (PG::SyntaxError: ERROR: 语法
“来自”第 1 行或其附近的错误:SELECT 1 AS one FROM “entries” WHERE
(lower(from) = '这里' 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
列在表 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
解决方法
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
选项。
我认为您可以通过以下方式解决问题:
您可以在 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')
,如果返回 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 不是线程/并发安全的,这意味着在非常奇怪的情况下,您可能会遇到某些情况,即您的数据库中没有唯一数据,为避免这种情况,您应该始终在其中创建唯一索引您的数据库,以便数据库为您避免那些“重复”的场景。
希望这有帮助! ?
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。