-
-
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%2Bgets 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