【问题标题】:Rails scope with array as argument Rails 4以数组为参数的 Rails 范围 Rails 4
【发布时间】:2016-02-25 19:23:41
【问题描述】:

我的Member 模型中有两个属性first_namelast_name。我的要求是给我一个全名数组:

["Jane Doe", "John Doe", "Foo Bar"]

而且,对于每个元素我需要:

  1. 将每个元素解析为名字和姓氏
  2. 通过first_namelast_name(如果存在)查找每个成员记录

我尝试在模型范围内创建它,如下所示:

class Member < ActiveRecord::Base

  scope :find_by_full_name, -> (full_name) { |full_name|
      first_name = full_name.split(' ').first
      last_name = full_name.split(' ').last
      where(first_name: first_name, last_name: last_name)
  }

它适用于字符串,但不适用于数组。我回顾了这篇文章——https://stackoverflow.com/a/8155276/4379077。看起来 Rails 应该检测它是数组还是字符串,并相应地处理每种类型。经过一堆或控制台尝试(包括将元素转换为字符串)后,我不知道自己做错了什么,或者除了创建范围之外,我应该以不同的方式进行。任何帮助表示赞赏。

【问题讨论】:

    标签: ruby-on-rails ruby ruby-on-rails-4 rails-activerecord


    【解决方案1】:

    由于作用域只是 Class 方法的语法糖,我建议将其创建为类方法,整个事情变得更容易/更清晰:

    class Member < ActiveRecord::Base
      
      self.find_by_full_name(array_of_names)
        members = []
    
        array_of_names.each do |name|
          split_names = name.split(' ')
          members.push(Member.find_by_first_name_and_last_name(split_names.first, split_names.last))
        end
    
        members.compact
      end
    
    end
    

    更新

    如果您希望它能够接受单个名称或名称数组,您甚至可以轻松扩展它的功能(增加一行):

    class Member < ActiveRecord::Base
      
      self.find_by_full_name(names)
        members = []
        names = [names] if names.is_a?(String)
    
        names.each do |name|
          split_names = name.split(' ')
          members.push(Member.find_by_first_name_and_last_name(split_names.first, split_names.last))
        end
    
        members.compact
      end
    
    end
    

    注意

    不确定您是否熟悉.compact,但这将完成您问题的"if it exists" 部分。如果没有匹配项,Member.find_by... 会将 nil 值推送到 members,然后 .compact 将从 members 中删除 nil 值。

    【讨论】:

    • 很好的答案@jeffdill2。我感谢额外的行!
    • 这种方式好像引入了N+1查询
    • @tobmatth 是的,它肯定会。我会看看我能做些什么来提高效率。当然,取决于他们的用例,对于 OP 来说可能没有实际意义,但无论如何这将是一个有趣的练习。 :-)
    【解决方案2】:

    如果我正确理解您的问题,您想通过这组名称查找您的成员吗?

    你也可以使用类似的东西

    full_names.each do |name|
      first = name.split(" ").first
      last = name.split(" ").last
      variable = Member.find_by_first_name_and_last_name(:first_name => first,
                                                        :last_name => last)
      # member_records[] << variable 
      # or whatever you're doing with them next.
    
    end
    

    您可以使用多个参数请求“find_by” 像这样 Rails - Find By with 2 fields?

    【讨论】:

      【解决方案3】:

      如果您想避免 N+1 查询(我建议您这样做),可以使用一次性解决方案。您可以连接 SQL 查询中的名字和姓氏,并将其与给定的参数进行比较。

      class Member < ActiveRecord::Base
        space = Arel::Nodes.build_quoted(' ')
        concat_op = '||' # check which operator your database uses
        first_name_space = Arel::Nodes::InfixOperation.new(concat_op, arel_table[:first_name], space)
        first_name_space_last_name = Arel::Nodes::InfixOperation.new(concat_op, first_name_space, arel_table[:last_name])
      
        scope :find_by_full_name, -> (full_name) { where(first_name_space_last_name.in(full_name)) }
      end
      

      这适用于单个字符串和数组作为参数。

      注意检查您的数据库使用什么运算符作为连接运算符并进行相应调整。此示例假设 ||,它适用于 PostgreSQL 和 SQLite 数据库。

      【讨论】:

        【解决方案4】:

        不是很优雅,但是怎么样

        class Member < ActiveRecord::Base
        
          scope :find_by_full_name, -> (*args) {
            conds = []
            conds << args.count.times.map { "first_name = ? AND last_name = ?" }.join(' OR ')
            args.map { |arg| arg.split(' ') }.flatten.each { |e| conds << e }
            where(conds)
          }
        
        end
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-01-16
          • 1970-01-01
          • 2016-10-17
          • 2016-07-11
          • 2016-08-23
          • 1970-01-01
          • 1970-01-01
          • 2016-03-21
          相关资源
          最近更新 更多