Advertisement
Guest User

awesome-sauce

a guest
May 25th, 2021
292
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Ruby 4.10 KB | None | 0 0
  1. # routes.rb
  2. Rails.application.routes.draw do
  3.   namespace :api do
  4.     resources :streets, only: [:index]
  5.   end
  6. end
  7.  
  8. # app/controllers/api/streets_controller.rb
  9. module Api
  10.   class StreetsController < ApplicationController
  11.     include Pagination
  12.  
  13.     def index
  14.       render(
  15.         json: serialized_streets,
  16.         status: :ok
  17.       )
  18.     end
  19.  
  20.     private
  21.  
  22.     def serialized_streets
  23.       Rails.cache.fetch("api/streets/serialized_streets/#{cache_key}", expires_in: 1.hour) do
  24.         StreetSerializer.new(streets, serializer_options).serialized_json
  25.       end
  26.     end
  27.  
  28.     def streets
  29.       @streets ||= search_query.perform.page(current_page).per(per_page)
  30.     end
  31.  
  32.     def search_query
  33.       ::Search::StreetsQuery.new(streets_params)
  34.     end
  35.  
  36.     def serializer_options
  37.       {
  38.         meta: {
  39.           pagination: meta_pagination_for(streets)
  40.         }
  41.       }
  42.     end
  43.  
  44.     def cache_key
  45.       Digest::MD5.hexdigest(streets_params.to_s)
  46.     end
  47.  
  48.     def streets_params
  49.       params.permit(
  50.         :street_name,
  51.         :street_number,
  52.         :city,
  53.         :sort,
  54.         :page
  55.       )
  56.     end
  57.   end
  58. end
  59.  
  60. # app/controllers/api/pagination.rb
  61. module Api
  62.   module Pagination
  63.     def per_page
  64.       MyAwesomeApp.config.per_page
  65.     end
  66.  
  67.     def current_page
  68.       params[:page] || 1
  69.     end
  70.  
  71.     def meta_pagination_for(collection)
  72.       {
  73.         current_page: collection.current_page,
  74.         prev_page: collection.prev_page,
  75.         next_page: collection.next_page,
  76.         total_pages: collection.total_pages
  77.       }
  78.     end
  79.   end
  80. end
  81.  
  82. # app/serializers/street_serializer.rb
  83. class StreetSerializer
  84.   include FastJsonapi::ObjectSerializer
  85.  
  86.   attributes *Street.attribute_names.map(&:to_sym)
  87. end
  88.  
  89. # app/queries/search/streets_query.rb
  90. module Search
  91.   class StreetsQuery
  92.     attr_reader :params, :scope
  93.  
  94.     def initialize(params = {}, scope = Street.all)
  95.       @params = params
  96.       @scope = scope
  97.     end
  98.  
  99.     def perform
  100.       query = scope
  101.  
  102.       query = filter_by_street_name_like(query, params[:street_name])
  103.       query = filter_by_street_number(query, params[:street_number])
  104.       query = filter_by_city_like(query, params[:city])
  105.  
  106.       query = sort_by_street_name(query, sorts[:street_name])
  107.       query = sort_by_city(query, sorts[:city])
  108.     end
  109.  
  110.     private
  111.  
  112.     #
  113.     # Filters
  114.     #
  115.  
  116.     def filter_by_street_name_like(query, street_name)
  117.       return query unless street_name.present?
  118.  
  119.       query.where_street_name_like(street_name.downcase)
  120.     end
  121.  
  122.     def filter_by_street_number(query, street_number)
  123.       return query unless street_number.present?
  124.  
  125.       query.where(
  126.         low: -Float::INFINITY..street_number.to_i,
  127.         high: street_number.to_i..Float::INFINITY
  128.       )
  129.     end
  130.  
  131.     def filter_by_city_like(query, city)
  132.       return query unless city.present?
  133.  
  134.       query.where('city LIKE ?', "%#{city.downcase}%")
  135.     end
  136.  
  137.     #
  138.     # Sorts
  139.     #
  140.  
  141.     def sort_by_street_name(query, direction)
  142.       return query unless direction.present?
  143.  
  144.       query.order(street_name: direction)
  145.     end
  146.  
  147.     def sort_by_city(query, direction)
  148.       return query unless direction.present?
  149.  
  150.       query.order(city: direction)
  151.     end
  152.  
  153.     def sorts
  154.       @sorts ||= SortString.new(params[:sort]).to_h
  155.     end
  156.   end
  157. end
  158.  
  159. # app/services/sort_string.rb
  160. class SortString
  161.   attr_reader :sort
  162.  
  163.   def initialize(sort)
  164.     @sort = sort
  165.   end
  166.  
  167.   def to_h
  168.    sorts.to_h
  169.   end
  170.  
  171.   private
  172.  
  173.   # Example:
  174.   #
  175.   # 'name:asc,created_at:desc'
  176.   #
  177.   #  => ['name:asc', 'created_at:desc']
  178.   #
  179.   def pieces
  180.     sort&.split(',') || []
  181.   end
  182.  
  183.   # Example:
  184.   #
  185.   # ['name:asc', 'created_at:desc']
  186.   #
  187.   #  => [['name', 'asc'], ['created_at', 'desc']]
  188.   #
  189.   def sorts
  190.     @sorts ||= pieces.map do |piece|
  191.       slug, direction = piece.split(':')
  192.  
  193.       unless direction.in?(allowed_directions)
  194.         direction = default_direction
  195.       end
  196.  
  197.       [slug.to_sym, direction.to_sym]
  198.     end
  199.   end
  200.  
  201.   def allowed_directions
  202.     %w(asc desc)
  203.   end
  204.  
  205.   def default_direction
  206.     'asc'
  207.   end
  208. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement