【问题标题】:How to dinamically create a join in rails如何在rails中动态创建连接
【发布时间】:2021-01-02 22:55:51
【问题描述】:

我需要创建一个查询,该查询基于用户点击某些过滤器的内容动态构建。例如,用户可能想要搜索所有既有鱼又有寿司的地方。我有以下表格:

分类:

+----+-------+
| id | Name  |
+----+-------+
|  1 | pizza |
|  2 | fish  |
|  3 | sushi |
+----+-------+

地点:

+----+--------+--------------+
| id |  Name  | Description  |
+----+--------+--------------+
|  1 | place1 | good place   |
|  2 | place2 | bad place    |
|  3 | place3 | nice place   |
+----+--------+--------------+

place_categories:

+----+----------+--------------+
| id | place_id | category_id  |
+----+----------+--------------+
|  1 |        1 |            2 |
|  2 |        1 |            3 |
|  3 |        2 |            1 |
+----+----------+--------------+

他们有以下的repaionships:

class Place < ApplicationRecord

    has_many :place_categories
    has_many :categories, through: :place_categories, foreign_key: 'place_id'
end


class Category < ApplicationRecord

      has_many :place_categories
      has_many :places, through: :place_categories

end

class PlaceCategory < ApplicationRecord

  belongs_to :place
  belongs_to :category

end

在我的控制器中,我收到了内部不同类别的 params 数组,例如:

Parameters: {"category_cusin"=>["fish", "sushi"]}

我想做的是为 params 数组中的每个类别创建一个连接,以一种方式为每个连接类别表都有一个“category.name”属性。通过这种方式,我应该能够找到所有同时具有“鱼”和“寿司”类别的地方(在示例中)。 我在控制器中编写的代码如下:

    filtered_places = Place.joins("INNER JOIN place_categories ON place.id = place_categories.place_id ").joins("INNER JOIN categories ON categories.id = place_categories.category_id").select("places .*")

    if(!params['category_cusin'].blank?)
      filtered_places = filtered_place .select("categories.name AS category_name")
      params['category_cusin'].each do |category|
        raw_query = "INNER JOIN categories ON categories.id = place_categories.category_id"
        filtered_places = filtered_places.joins(raw_query)
        filtered_places = filtered_places.where(categories: {name: category})
      end
    end

问题是连接总是发生在同名的表上。我应该为 params 数组中存在的每个项目使用别名(类别 AS ....),但考虑到这一事实,我不知道如何在 Rails 中动态创建查询。我试图以这种方式构建一个字符串:

if(!params['category_cusin'].blank?)
  filtered_places = filtered_places.select("categories.name AS category_name")
  count = 0
  params['category_cusin'].each do |category|
    raw_query = "INNER JOIN categories AS cat_" + count.to_s + " ON " + "cat_" + count.to_s + ".id = place_categories.category_id"
    filtered_places = filtered_places.joins(raw_query)
    filtered_places = filtered_places.where("cat_" + count.to_s +".name = " + category)
    count +=1
  end
end

但我的服务器似乎处于循环状态并且无法正常运行。解决这个问题的最佳方法是什么?

【问题讨论】:

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


    【解决方案1】:
    class Place < ApplicationRecord
      has_many :place_categories
      has_many :categories, through: :place_categories
        
      def self.with_categories(categories)
        # limits the result to rows that have a least one match in the join table.
        joins(:categories)
          .where(categories: { name: categories })
          # By applying a GROUP and HAVING to the query you limit the result 
          # per group so it must have at least N number of matches
          .group(:id)
          .having(
             # Arel for COUNT(categories.*) >= ?
             Category.arel_table[Arel.star].count.gte(categories.length)
          )
      end 
    end
    

    您的控制器不适合进行复杂查询或基于复杂参数的过滤,因为它们很难测试。最好将其移动到模型层或其他可以直接测试的对象中,而不是通过 HTTP。

    相反,您的控制器实际上应该只包含最低限度:

    def index
      @places = Place.all
      if params['category_cusin'].present?
        @places = @places.with_categories(params['category_cusin']) 
      end
    end
    

    如果您想创建一组搜索过滤器,您应该将其移至一个单独的对象,该对象采用基本范围和参数散列并返回一个范围。

    【讨论】:

    • 如果您真的想以编程方式构建无法使用ActiveRecord Query Interface 构建的SQL,答案是Arel。但我在这个问题中看不到任何真正值得考虑的地方。
    【解决方案2】:

    您可以在 Place 模型上创建一个范围

    scope :by_category, ->(*cats) { joins(:categories).where(categories: { name: cats }) }
    

    然后调用你的控制器:

    @places = Place.by_category(params[:category_cusin])
    

    如果您只是想查找具有这些类别的地点,那么范围就可以了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-18
      • 1970-01-01
      相关资源
      最近更新 更多