【问题标题】:Check if Array contains true in Rails检查 Array 在 Rails 中是否包含 true
【发布时间】:2019-07-19 09:50:51
【问题描述】:

我正在尝试限制每个 ip 的失败登录尝试。

我有以下几点:

  def validate(email, context)
    attempt = insert_into_attempts(email, context)
    return nil unless allow_login_by_ip(context.ip_address)
    flag_successful_attempt(attempt, context.ip_address)
    load_data
  end

  def allow_login_by_ip(ip_address)
    limit = LoginLimits.new(ip_address).limit
    last_5_attempts = AuthenticationAttempt.select("id","successful").where(ip: ip_address).last(5)
    last_5_attempts.include?("true")
  end 

  def insert_into_attempts(email, context)
    attempt = AuthenticationAttempt.new(
      :email => email,
      :ip => context.ip_address)
    attempt.save
  end 

  def flag_successful_attempt(attempt, ip_address)
    AuthenticationAttempt.where(ip: ip_address).last.update(successful: '1')
  end

我遇到的问题是它总是返回fasle。我一定是错误地搜索了array,但我不知道为什么。 last_5_attempts 是:

#<AuthenticationAttempt id: 1, successful: false>, 
#<AuthenticationAttempt id: 2, successful: false>, 
#<AuthenticationAttempt id: 3, successful: true>,
#<AuthenticationAttempt id: 4, successful: false>, 
#<AuthenticationAttempt id: 5, successful: false>]

【问题讨论】:

  • 您可能需要考虑使用rack-attack gem 而不是自己实现此功能。

标签: sql ruby-on-rails arrays ruby activerecord


【解决方案1】:
AuthenticationAttempt.where(ip: ip_address)<del>.last(5)</del>.exists?(successful: true)
<del>AuthenticationAttempt.where(ip: ip_address).order(id: :desc).limit(5).exists?(successful: true)</del>

您可以使用ActiveRecord::FinderMethods#exists? 来检查尝试是否成功,而无需检索任何数据或实例化任何记录。

更新:我们需要使用.order(id: :desc).limit(5) 代替.last(5) 以确保我们有一个ActiveRecord::Relation 实例来调用exists?

更新 2exists? 将任何limit 替换为limit(1)

AuthenticationAttempt.limit(5).exists?
=> SELECT 1 AS one FROM "authentication_attempts" LIMIT $1  [["LIMIT", 1]]

因此我们需要将子查询包装在外部存在查询中:

AuthenticationAttempt.exists?(AuthenticationAttempt.limit(5))
=> SELECT  1 AS one FROM "authentication_attmepts" WHERE "authentication_attmepts"."id" IN (SELECT  "authentication_attmepts"."id" FROM "authentication_attmepts" LIMIT $1) LIMIT $2  [["LIMIT", 5], ["LIMIT", 1]]

这是一个稍微复杂的查询,但仍然具有不从数据库加载任何内容的性能优势。内部子查询为我们提供了最后 5 次尝试,外部查询检查是否存在成功尝试:

 AuthenticationAttempt
   .where(successful: true)
   .exists?(AuthenticationAttempt.where(ip: ip_address).order(id: :desc).limit(5))

【讨论】:

  • 我不确定我是否可以在阵列上运行exists??它给出了一个错误undefined method 'exists?' for []:Array
  • @A.J 尝试使用order(id: :desc).limit(5) 而不是last(5)。我以为#last 返回了一个关联,但看起来我错了,它返回了一个数组。让我知道这是否有效,我会更新我的答案。
  • @A.J 我更新了查询。 exists? 将覆盖任何限制,因此您需要构造查询以创建正确记录的子查询,然后对其进行存在性检查。
【解决方案2】:

如果你的意思是true,那么你的意思是:

last_5_attempts.include?(true)

因为:

true == "true"
# => false

但这还不够,因为您要询问[id, successful] 值数组是否有任何条目字面上 只是true, ([1,true] != true) 所以你想要:

last_5_attempts.any? |id, successful|
  successful
end

您也可以在列提取中省略 id,因为您不使用它,而是:

AuthenticationAttempt.where(ip: ip_address).pluck(:successful).last(5).any?

其中 pluck 带有单个参数会返回一个“平面”数组而不是数组数组。

要检查过去 5 次中至少有一次成功登录或没有登录历史记录:

attempts = AuthenticationAttempt.where(ip: ip_address)
!attempts.any? or attempts.pluck(:successful).last(5).any?

【讨论】:

  • AuthenticationAttempt.where(ip: ip_address).pluck(:successful).last(5).any? 让一切变得更干净、更轻松。谢谢!
  • 有没有办法添加一个条件,如果ip 不存在或者还没有5 次尝试,它会返回true?我之所以问,是因为我试图弄清楚如何与新客户打交道,但未能提出对该方法的补充。
  • 这有点复杂,但我已经添加了一个例子。
  • 我也允许用户登录,如果他们有 5 次以下的尝试,所以我增加了它的大小 !attempts.any? or attempts.size &lt; 5 or attempts.pluck(:successful).last(5).any?。我猜any? 不再相关,因为我正在检查尺寸?
  • 当然。您可以选择无或小于 5,因为 0 小于 5。
【解决方案3】:

试试

last_5_attempts.map(&:to_s).include?("true")

而不是

last_5_attempts.include?("true")

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-11-19
    • 1970-01-01
    • 2012-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-24
    相关资源
    最近更新 更多