在Ruby on Rails中尝试在控制器中链接类方法时出错 [英] Error when trying to chain class method in controller in Ruby on Rails

查看:195
本文介绍了在Ruby on Rails中尝试在控制器中链接类方法时出错的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想从我的用户模型链接一些类方法执行分面搜索。当代码运行时,它返回以下错误

 未定义的方法`has_skill_categories'for#< Array:0x000001026d3de8& 

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

这是我的代码:



experts_controller.erb
$ b

  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


$ b b

user.erb

  class User< ActiveRecord :: Base 

#包括默认设计模块。其他可用的是:
#:confirmable,:lockable,:timeoutable和: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,通过::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
.id
FROM users
INNER JOIN marketing_assets ON users.id = marketing_assets.user_id
WHERE marketing_assets.marketing_platform_id ='
n = 0

如果平台。 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
b
$ b def self.has_skill_categories(skills)
如果skills.present?

skill_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

如果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

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

end


解决方案

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



得到上面的错误是因为 find_by_sql 返回结果数组,而不是其中的作用域查询。所以,正如你现在做的,我不认为有办法链接他们。但这可能是一件好事,因为它会强迫你重写你的查询和范围没有原始的sql语句。



我强烈建议查看活动记录查询上的Rails指南,并且避免在Rails项目中尽可能编写原始SQL语句。这可以大大简化您的方法。您应该从不将原始用户输入放入SQL查询,这看起来像是在代码中的多个位置。 Rails提供了一个高级查询接口来保护您和您的数据,并且您正在构建的SQL语句极易受到注入攻击。



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



更新



在我看来,您的查询可以大大简化使用范围和 #merge

  def self.has_skill_categories(skill_ids)
join (: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。


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

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

解决方案

First off, in order to chain your methods, you should be returning an ActiveRecord query object. Calling return without an argument will return nil, which is not chainable. You should instead return where(), which would return the current collection with no modifications.

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. 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.

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. 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. 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.

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.

Update

It looks to me like your queries could greatly be simplified using scopes and #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.

这篇关于在Ruby on Rails中尝试在控制器中链接类方法时出错的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆