微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何验证列组合是否已存在于数据库、rails 6、postgres

如何解决如何验证列组合是否已存在于数据库、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'

Error message


我想要实现的是:


| 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 举报,一经查实,本站将立刻删除。