在RAILS API控制器索引操作中应用过滤器散列有什么干巴巴的方法吗?

时间:2021-04-26 17:02:57

According to the JSON API specification, we should use a filter query parmeter to filter our records in a controller. What the filter parameter actually is isn't really specified, but since it should be able to contain multiple criteria for searching, the obvious thing to do would be to use a hash.

根据JSON API规范,我们应该使用筛选查询解析器来筛选控制器中的记录。实际上,过滤器参数实际上并不是指定的,但是由于它应该能够包含搜索的多个标准,显然要做的事情就是使用散列。

The problem is, it seems like I'm repeating myself quite often in controller actions for different types of records.

问题是,我似乎经常在不同类型的记录的控制器操作中重复我自己。

Here's what things look like for just a filter that includes a list of ids (to get multiple specific records).

以下是一个包含ids列表(获取多个特定记录)的过滤器的情况。

def index
  if params[:filter] and params[:filter][:id]
    ids = params[:filter][:id].split(",").map(&:to_i)
    videos = Video.find(ids)
  else
    videos = Video.all
  end
  render json: videos
end

For nested property checks, I guess I could use fetch or andand but it still doesn't look dry enough and I'm still doing the same thing across different controllers.

对于嵌套属性检查,我想我可以使用fetch or andand但是它看起来还是不够干,我仍然在不同的控制器之间做同样的事情。

Is there a way I could make this look better and not repeat myself that much?

有没有一种方法可以让它看起来更好,而不会重复那么多?

3 个解决方案

#1


2  

Rather than using concerns to just include the same code in multiple places, this seems like a good use for a service object.

与使用关注点在多个地方包含相同的代码相比,这似乎是一个很好的服务对象使用。

class CollectionFilter
    def initialize(filters={})
        @filters = filters
    end

    def results
        model_class.find(ids)
    end

    def ids
        return [] unless @filters[:id]
        @filters[:id].split(",").map(&:to_i)
    end

    def model_class
        raise NotImplementedError
    end
end

You could write a generic CollectionFilter as above, then subclass to add functionality for specific use cases.

您可以像上面一样编写一个通用的集合过滤器,然后子类为特定的用例添加功能。

class VideoFilter < CollectionFilter
    def results
        super.where(name: name)
    end

    def name
        @filters[:name]
    end

    def model_class
        Video
    end
end

You would use this in your controller as below;

您将在您的控制器中使用如下所示;

def index
    videos = VideoFilter.new(params[:filter]).results
    render json: videos
end

#2


1  

you can use Rails Concerns to drying up ...

您可以使用Rails关注点来烘干……

    ##================add common in app/models/concerns/common.rb
    module Common
      extend ActiveSupport::Concern  

      #  included do
      ##add common scopes /validations
      # end

      ##NOTE:add any instance method outside this module
      module ClassMethods
        def Find_using_filters (params)
          Rails.logger.info "calling class method in concern=======#{params}=="
          ##Do whatever you want with params now
          #you can even use switch case in case there are multiple models

        end
    end
  end

##======================include the concern in model
include Common

##=======================in your controller,call it directly    
 Image.Find_using_filters params

#3


1  

Here is my take on this, somewhat adapted from Justin Weiss' method:

以下是我对这个问题的看法,有点类似于贾斯廷·韦斯的方法:

# app/models/concerns/filterable.rb
module Filterable
  extend ActiveSupport::Concern

  class_methods do
    def filter(params)
      return self.all unless params.key? :filter

      params[:filter].inject(self) do |query, (attribute, value)|
        query.where(attribute.to_sym => value) if value.present?
      end
    end
  end
end

# app/models/user.rb
class User < ActiveRecord::Base
  include Filterable
end

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  # GET /users
  # GET /users?filter[attribute]=value
  def index
    @users = User.filter(filter_params)
  end

  private
    # Define which attributes can this model be filtered by
    def filter_params
      params.permit(filter: :username)
    end
end

You would then filter the results by issuing a GET /users?filter[username]=joe. This works with no filters (returns User.all) or filters that have no value (they are simply skipped) also.

然后,您可以通过发出GET /users?filter(用户名)=joe来过滤结果。这与没有过滤器(返回用户.all)或没有任何值的过滤器(它们只是跳过)一起工作。

The filter is there to comply with JSON-API. By having a model concern you keep your code DRY and only include it in whatever models you want to filter. I've also used strong params to enforce some kind protection against "the scary internet".

该过滤器是为了遵从JSON-API。通过关注模型,您可以使代码保持干燥,并且只将其包含在您想要筛选的任何模型中。我还使用了强大的params来加强对“恐怖网络”的保护。

Of course you can customize this concern and make it support arrays as values for filters.

当然,您可以定制这个关注点,并使它支持数组作为过滤器的值。

#1


2  

Rather than using concerns to just include the same code in multiple places, this seems like a good use for a service object.

与使用关注点在多个地方包含相同的代码相比,这似乎是一个很好的服务对象使用。

class CollectionFilter
    def initialize(filters={})
        @filters = filters
    end

    def results
        model_class.find(ids)
    end

    def ids
        return [] unless @filters[:id]
        @filters[:id].split(",").map(&:to_i)
    end

    def model_class
        raise NotImplementedError
    end
end

You could write a generic CollectionFilter as above, then subclass to add functionality for specific use cases.

您可以像上面一样编写一个通用的集合过滤器,然后子类为特定的用例添加功能。

class VideoFilter < CollectionFilter
    def results
        super.where(name: name)
    end

    def name
        @filters[:name]
    end

    def model_class
        Video
    end
end

You would use this in your controller as below;

您将在您的控制器中使用如下所示;

def index
    videos = VideoFilter.new(params[:filter]).results
    render json: videos
end

#2


1  

you can use Rails Concerns to drying up ...

您可以使用Rails关注点来烘干……

    ##================add common in app/models/concerns/common.rb
    module Common
      extend ActiveSupport::Concern  

      #  included do
      ##add common scopes /validations
      # end

      ##NOTE:add any instance method outside this module
      module ClassMethods
        def Find_using_filters (params)
          Rails.logger.info "calling class method in concern=======#{params}=="
          ##Do whatever you want with params now
          #you can even use switch case in case there are multiple models

        end
    end
  end

##======================include the concern in model
include Common

##=======================in your controller,call it directly    
 Image.Find_using_filters params

#3


1  

Here is my take on this, somewhat adapted from Justin Weiss' method:

以下是我对这个问题的看法,有点类似于贾斯廷·韦斯的方法:

# app/models/concerns/filterable.rb
module Filterable
  extend ActiveSupport::Concern

  class_methods do
    def filter(params)
      return self.all unless params.key? :filter

      params[:filter].inject(self) do |query, (attribute, value)|
        query.where(attribute.to_sym => value) if value.present?
      end
    end
  end
end

# app/models/user.rb
class User < ActiveRecord::Base
  include Filterable
end

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  # GET /users
  # GET /users?filter[attribute]=value
  def index
    @users = User.filter(filter_params)
  end

  private
    # Define which attributes can this model be filtered by
    def filter_params
      params.permit(filter: :username)
    end
end

You would then filter the results by issuing a GET /users?filter[username]=joe. This works with no filters (returns User.all) or filters that have no value (they are simply skipped) also.

然后,您可以通过发出GET /users?filter(用户名)=joe来过滤结果。这与没有过滤器(返回用户.all)或没有任何值的过滤器(它们只是跳过)一起工作。

The filter is there to comply with JSON-API. By having a model concern you keep your code DRY and only include it in whatever models you want to filter. I've also used strong params to enforce some kind protection against "the scary internet".

该过滤器是为了遵从JSON-API。通过关注模型,您可以使代码保持干燥,并且只将其包含在您想要筛选的任何模型中。我还使用了强大的params来加强对“恐怖网络”的保护。

Of course you can customize this concern and make it support arrays as values for filters.

当然,您可以定制这个关注点,并使它支持数组作为过滤器的值。