-
-
Save mamantoha/9c0aec7958c7636cebef to your computer and use it in GitHub Desktop.
# app/models/experience.rb | |
# | |
# == Schema Information | |
# | |
# Table name: experiences | |
# | |
# id :integer not null, primary key | |
# title :string | |
# description :text | |
# created_at :datetime not null | |
# updated_at :datetime not null | |
# city_id :integer | |
# price :integer default(0) | |
# distance :integer | |
# duration :integer | |
# | |
class Experience < ActiveRecord::Base | |
belongs_to :city | |
has_many :categorizations | |
has_many :categories, through: :categorizations | |
scope :by_city, -> (city_ids) { where(city_id: city_ids) } | |
scope :by_price, -> (from, to) { where("price >= ? AND price <= ?", from, to) } | |
scope :by_duration, -> (from, to) { where("duration >= ? AND duration <= ?", from, to) } | |
scope :by_distance, -> (from, to) { where("distance >= ? AND distance <= ?", from, to) } | |
scope :by_category, -> (category_ids) { joins(:categories).where(categories: { id: category_ids }) } | |
end |
# app/serializers/experience_serializer.rb | |
class ExperienceSerializer < ActiveModel::Serializer | |
attribute :id | |
attribute :title | |
attribute :description | |
attribute :price | |
attribute :distance | |
attribute :duration | |
belongs_to :city | |
has_many :categories | |
end |
# app/controllers/api/v1/experiences_controller.rb | |
class Api::V1::ExperiencesController < Api::V1::BaseController | |
include Orderable | |
before_filter :authenticate_user! | |
# Filters: | |
# /api/v1/experiences?by_price[from]=100&by_price[to]=999 | |
# /api/v1/experiences?by_category=1,2,3 | |
# /api/v1/experiences?by_city=1,2,3 | |
# /api/v1/experiences?by_duration[from]=10&by_duration[to]=60 | |
# | |
has_scope :by_category, only: :index | |
has_scope :by_city, only: :index | |
has_scope :by_price, using: [:from, :to], only: :index | |
has_scope :by_duration, using: [:from, :to], only: :index | |
has_scope :by_distance, using: [:from, :to], only: :index | |
# GET /api/v1/experiences | |
def index | |
@experiences = | |
apply_scopes(Experience) | |
.order(ordering_params(params)) | |
.includes(:city, :user, :categories) | |
.all | |
render json: @experiences | |
end | |
end | |
# app/controllers/concerns/orderable.rb | |
module Orderable | |
extend ActiveSupport::Concern | |
module ClassMethods | |
end | |
# A list of the param names that can be used for ordering the model list | |
def ordering_params(params) | |
# For example it retrieves a list of experiences in descending order of price. | |
# Within a specific price, older experiences are ordered first | |
# | |
# GET /api/v1/experiences?sort=-price,created_at | |
# ordering_params(params) # => { price: :desc, created_at: :asc } | |
# Experience.order(price: :desc, created_at: :asc) | |
# | |
ordering = {} | |
if params[:sort] | |
sort_order = { '+' => :asc, '-' => :desc } | |
sorted_params = params[:sort].split(',') | |
sorted_params.each do |attr| | |
sort_sign = (attr =~ /\A[+-]/) ? attr.slice!(0) : '+' | |
model = controller_name.classify.constantize | |
if model.attribute_names.include?(attr) | |
ordering[attr] = sort_order[sort_sign] | |
end | |
end | |
end | |
ordering | |
end | |
end |
Looks great. I think your symbols are backwards.
sort_order = { '-' => :asc, '+' => :desc }
should be changed to:
sort_order = { '+' => :asc, '-' => :desc }
really nice man! exactly what I was searching for!
Hi. Using the structure you have, if you want to include the name of the city in the json and use the name as a parameter to order the result, that method does not work or yes?
There is an edge-case in the Orderable#ordering_params
processing. If a client submits a URL with a literal plus sign +
(not encoded to %2B
) then it will be decoded as a space " "
. If you wish to catch that case, here's the diff.
# A list of the param names that can be used for ordering the model list
def ordering_params(params)
ordering = {}
if params[:sort]
- sort_order = { "+" => :asc, "-" => :desc }
+ sort_order = { " " => :asc, "+" => :asc, "-" => :desc }
sorted_params = params[:sort].split(",")
sorted_params.each do |attr|
- sort_sign = attr =~ /\A[+-]/ ? attr.slice!(0) : "+"
+ sort_sign = attr =~ /\A[ +-]/ ? attr.slice!(0) : "+"
model = controller_name.classify.constantize
if model.attribute_names.include?(attr)
ordering[attr] = sort_order[sort_sign]
end
end
end
ordering
end
@beporter it doesn't needed. sort_sign
will be "+" by default
@mamantoha Yes, the sign will be +
by default, but my diff covers the case where the client sends an unencoded +
(instead of a URL encoded %2B
). Consider these two request URLs:
http://my.server.com/route/action?sort=%2Bcreated_at
(The%2B
gets decoded as a plus+
)http://my.server.com/route/action?sort=+created_at
(The+
gets decoded as a space" "
)
My diff covers the second case.
@mamantoha please replace line number 24 of orderable.rb to fix model class retrieving when controller_name is like this: image_types
model = controller_name.classify.constantize