Is there a DRY approach to applying filter hash on RAILS API controller index action?

As per the JSON API specification, we have to use a filter parameter filter to filter our records in the controller. In fact, the filter parameter is not actually set, but since it must contain multiple search criteria, it is obvious that a hash must be used.

The problem is that I often repeat myself in controller actions for different post types.

This is what it looks like for a filter that includes a list of IDs (to get a few specific entries).

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 checking on nested properties, I think I could use fetch

or andand

, but it still doesn't look dry enough and I still do the same on different controllers.

Is there a way I could look better and not repeat myself so much?

+3


source to share


3 answers


Rather than using problems just to include the same code in multiple places, it 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 can write generic CollectionFilter

as above and 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 like below:

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

      

+2


source


you can use Rails Concerns to dry it out ...



    ##================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

      

+1


source


Here's my example, somewhat adapted from Justin Weiss 's 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

      

Then you filter the results by submitting GET /users?filter[username]=joe

. This works without filters (returns User.all

) or filters that don't matter (they're just skipped).

filter

must conform to JSON-API. With a model interest, you keep your DRY code and only include it in whatever models you want to filter. I also used strong parameters to provide some sort of protection from the "scary internet".

Of course, you can tweak this problem and make it support arrays as values ​​for filters.

+1


source







All Articles