简体   繁体   English

尝试在Ruby on Rails的控制器中链接类方法时出错

[英]Error when trying to chain class method in controller in Ruby on Rails

I am trying to chain a few class methods from my User model to perform a faceted search. 我试图从我的用户模型中链接一些类方法以执行多面搜索。 When the code runs it returns the following error 代码运行时返回以下错误

undefined method `has_skill_categories' for #<Array:0x000001026d3de8>

Can you show me how to call these methods from the model in the controller by chaining them together? 您能告诉我如何将它们链接在一起从控制器的模型中调用这些方法吗?

Here is my code: 这是我的代码:

experts_controller.erb expert_controller.erb

class ExpertsController < ApplicationController
  layout 'experts'

  def index

    @users = User.text_search(params[:query])
              .has_marketing_assets(params[:marketing_platforms])
              .has_skill_categories(params[:skills])
              .search_for_user_country(params[:user][:country])
  end

  def show
    @user = User.find(params[:id])
  end
end

user.erb user.erb

class User < ActiveRecord::Base

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  has_many :marketing_assets
  has_many :marketing_platforms, through: :marketing_assets
  has_many :my_skills
  has_many :skills, through: :my_skills
  has_many :past_works
  has_many :past_work_types, through: :past_works

  validates :first_name, :last_name, presence: true

  include PgSearch
  pg_search_scope :search, against: [:first_name, :last_name, :company, :description, :job_title, :website, :email, :country, :city, :state],
                  using: {tsearch: {dictionary: 'english'}},
                  associated_against: {:skills => :name, :past_works => [:description, :title, :url], :marketing_assets => [:platform, :description, :url], :past_work_types => :name,
                                       :marketing_platforms => :name}

  def self.text_search(query)
    if query.present?
      search(query)
    else
      User.all
    end
  end


  def self.has_marketing_assets(platforms)
    if platforms.present?
      @platforms = MarketingPlatform.all
      platforms_count = platforms.count
      where_clause_platforms = 'SELECT *
                                FROM Users
                                WHERE Users.id IN
                                (SELECT Users.id
                                FROM users
                                INNER JOIN marketing_assets ON users.id = marketing_assets.user_id
                                WHERE marketing_assets.marketing_platform_id= '
      n = 0

      if platforms.count > 0

        platforms.each do |platform|
          n += 1
          where_clause_platforms = where_clause_platforms + platform
          if n < platforms_count
            where_clause_platforms = where_clause_platforms + ' OR marketing_assets.marketing_platform_id= '
          end
        end

        where_clause_platforms = where_clause_platforms + " GROUP BY users.id
                                                          HAVING COUNT(DISTINCT marketing_assets.marketing_platform_id) = #{platforms.count})"
        find_by_sql(where_clause_platforms)

      else
        return
      end
    end
  end


  def self.has_skill_categories(skills)
    if skills.present?

      skills_count = skills.count
      where_clause_skills = 'SELECT *
                                      FROM Users
                                      WHERE Users.id IN
                                      (SELECT Users.id
                                      FROM users
                                      INNER JOIN my_skills ON users.id = my_skills.user_id
                                      WHERE my_skills.skill_id= '
      n = 0

      if skills_count > 0

        skills.each do |skill|
          n += 1
          where_clause_skills = where_clause_skills + skill
          if n < skills_count
            where_clause_skills = where_clause_skills + ' OR my_skills.skill_id= '
          end
        end

        where_clause_skills = where_clause_skills + "GROUP BY users.id
                                                        HAVING COUNT(DISTINCT my_skills.skill_id) = #{skills.count})"
        find_by_sql(where_clause_skills)


      else
        return
      end
    end
  end


  def self.search_for_user_country(country)
    if country.present?
      where('country = ?', "#{country}")
    else
      return
    end
  end

end

First off, in order to chain your methods, you should be returning an ActiveRecord query object. 首先,为了链接您的方法,您应该返回一个ActiveRecord查询对象。 Calling return without an argument will return nil, which is not chainable. 不带参数调用return将返回nil,这是不可链接的。 You should instead return where() , which would return the current collection with no modifications. 相反where()您应该返回where() ,它将不做任何修改就返回当前集合。

The reason you are getting the error above is because find_by_sql returns results as an array , not a scoped query like where does. 出现上述错误的原因是因为find_by_sql 以数组形式返回结果 ,而不是像where那样有范围的查询。 So, as you are doing it now, I don't think there's a way to chain them. 因此,正如您现在所做的那样,我认为没有办法将它们链接起来。 But that's probably a good thing because it will force you to rewrite your queries and scopes without raw sql statements. 但这可能是一件好事,因为它将迫使您在没有原始sql语句的情况下重写查询和范围。

I would highly recommend reviewing the Rails Guides on Active Record Querying , and avoid writing raw SQL statements if at all possible in a Rails project. 我强烈建议您阅读Active Record Querys上Rails指南 ,如果可能的话,最好避免在Rails项目中编写原始SQL语句。 This could greatly simplify your methodology. 这可以大大简化您的方法。 You should never put raw user input into SQL queries, which it looks like you are doing in multiple places in your code. 永远不应将原始用户输入放入SQL查询中,这看起来就像您在代码中的多个位置所做的一样。 Rails provides an advanced query interface to protect you and your data, and the SQL statement you are building above is extremely vulnerable to injection attacks. Rails提供了一个高级查询界面来保护您和您的数据,并且您在上面构建的SQL语句极易受到注入攻击的攻击。

With the correct combination of scope and association calls (which can use scopes defined on the associated model), you could probably clean a lot of that code up and greatly improve the security of your application. 使用scope和关联调用的正确组合(可以使用在关联模型上定义的范围),您可能可以清理掉很多代码,并大大提高应用程序的安全性。

Update 更新资料

It looks to me like your queries could greatly be simplified using scopes and #merge . 在我看来,使用范围和#merge可以大大简化您的查询。

def self.has_skill_categories(skill_ids)
  joins(:my_skills).merge Skill.where(id: skill_ids)
end

def self.has_marketing_assets(platform_ids)
  joins(:marketing_assets).merge MarketingAsset.where(marketing_platform_id: platform_ids)
end

Those may not get you exactly what you're going for, but from what I can tell, it should be close, and show you how you can use the built-in ActiveRecord query interface to build complex queries without ever writing any raw SQL. 这些可能无法完全满足您的需求,但是据我所知,它应该很接近,并向您展示如何使用内置的ActiveRecord查询接口来构建复杂的查询,而无需编写任何原始SQL。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM