【问题标题】:Error when trying to chain class method in controller in Ruby on Rails尝试在 Ruby on Rails 的控制器中链接类方法时出错
【发布时间】:2014-02-01 23:28:53
【问题描述】:

我试图从我的用户模型中链接一些类方法来执行分面搜索。代码运行时返回以下错误

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

你能告诉我如何通过将它们链接在一起从控制器中的模型调用这些方法吗?

这是我的代码:

experts_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

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

【问题讨论】:

    标签: ruby-on-rails model ruby-on-rails-4 controller


    【解决方案1】:

    首先,为了链接您的方法,您应该返回一个 ActiveRecord 查询对象。不带参数调用 return 将返回 nil,这是不可链接的。您应该返回 where(),这将返回当前集合而不进行任何修改。

    您收到上述错误的原因是因为find_by_sqlreturns results as an array,而不是像where 这样的范围查询。所以,正如你现在所做的那样,我认为没有办法将它们链接起来。但这可能是一件好事,因为它会迫使您在没有原始 sql 语句的情况下重写查询和范围。

    我强烈建议您查看Rails Guides on Active Record Querying,并尽可能避免在 Rails 项目中编写原始 SQL 语句。这可以大大简化您的方法。您应该绝不将原始用户输入放入 SQL 查询中,这看起来就像您在代码中的多个位置执行的操作。 Rails 提供了一个高级查询接口来保护您和您的数据,而您在上面构建的 SQL 语句极易受到注入攻击。

    通过正确组合scope 和关联调用(可以使用关联模型上定义的范围),您可能会清理大量代码并大大提高应用程序的安全性。

    更新

    在我看来,您的查询可以使用范围和#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
    

    这些可能无法让您准确了解您的目标,但据我所知,它应该很接近,并向您展示如何使用内置的 ActiveRecord 查询接口来构建复杂的查询,而无需编写任何代码原始 SQL。

    【讨论】:

    • 感谢您抽出宝贵时间帮助我了解我做错了什么。您是否建议使用 Squeel gem 来编写这样的复杂 SQL。我认为不可能使用开箱即用的 Rails 解决方案。
    • 如果有人能帮助我使用 Tails 活动记录重写此 SQL 语句,我将不胜感激。甚至可能像我使用 Rails 4 一样使用 Squeel Gem。 SELECT "users".* 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= 3 GROUP BY users.id HAVING COUNT(DISTINCT marketing_assets.marketing_platform_id) = 1)
    • 看起来您可能正在尝试搜索某物在 id 集合中的位置。 ActiveRecord 可以通过几种不同的方式做到这一点。 MyModel.where(product_id: [1,4,5])。您可能还会从 #merge 方法中受益。在这篇文章中查看#1:blog.mitchcrowe.com/blog/2012/04/14/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-01
    • 1970-01-01
    • 2023-01-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多