Skip to content

Commit

Permalink
Refactor serializers, paginators, and filters into dedicated modules.
Browse files Browse the repository at this point in the history
  • Loading branch information
gregschmit committed Jul 7, 2023
1 parent c3be8fd commit ccf500d
Show file tree
Hide file tree
Showing 14 changed files with 545 additions and 503 deletions.
2 changes: 1 addition & 1 deletion .yardopts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
lib/**/*.rb
-
LICENSE
guide/*
README.md
10 changes: 5 additions & 5 deletions lib/rest_framework/filters.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module RESTFramework::Filters
end

require_relative "filters/base"
require_relative "filters/base_filter"

require_relative "filters/model_ordering"
require_relative "filters/model_query"
require_relative "filters/model_search"
require_relative "filters/ransack"
require_relative "filters/model_ordering_filter"
require_relative "filters/model_query_filter"
require_relative "filters/model_search_filter"
require_relative "filters/ransack_filter"
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class RESTFramework::BaseFilter
class RESTFramework::Filters::BaseFilter
def initialize(controller:)
@controller = controller
end
Expand All @@ -7,3 +7,6 @@ def get_filtered_data(data)
raise NotImplementedError
end
end

# Alias for convenience.
RESTFramework::BaseFilter = RESTFramework::Filters::BaseFilter
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# A filter backend which handles ordering of the recordset.
class RESTFramework::ModelOrderingFilter < RESTFramework::BaseFilter
class RESTFramework::Filters::ModelOrderingFilter < RESTFramework::Filters::BaseFilter
# Get a list of ordering fields for the current action.
def _get_fields
return @controller.ordering_fields&.map(&:to_s) || @controller.get_fields
Expand Down Expand Up @@ -46,3 +46,6 @@ def get_filtered_data(data)
return data
end
end

# Alias for convenience.
RESTFramework::ModelOrderingFilter = RESTFramework::Filters::ModelOrderingFilter
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# A simple filtering backend that supports filtering a recordset based on query parameters.
class RESTFramework::ModelQueryFilter < RESTFramework::BaseFilter
class RESTFramework::Filters::ModelQueryFilter < RESTFramework::Filters::BaseFilter
# Get a list of filterset fields for the current action.
def _get_fields
# Always return a list of strings; `@controller.get_fields` already does this.
Expand Down Expand Up @@ -49,3 +49,6 @@ def get_filtered_data(data)
return data
end
end

# Alias for convenience.
RESTFramework::ModelQueryFilter = RESTFramework::Filters::ModelQueryFilter
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Multi-field text searching on models.
class RESTFramework::ModelSearchFilter < RESTFramework::BaseFilter
class RESTFramework::Filters::ModelSearchFilter < RESTFramework::Filters::BaseFilter
# Get a list of search fields for the current action.
def _get_fields
if search_fields = @controller.search_fields
Expand Down Expand Up @@ -39,3 +39,6 @@ def get_filtered_data(data)
return data
end
end

# Alias for convenience.
RESTFramework::ModelSearchFilter = RESTFramework::Filters::ModelSearchFilter
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Adapter for the `ransack` gem.
class RESTFramework::RansackFilter < RESTFramework::BaseFilter
class RESTFramework::Filters::RansackFilter < RESTFramework::Filters::BaseFilter
# Filter data according to the request query parameters.
def get_filtered_data(data)
q = @controller.request.query_parameters[@controller.ransack_query_param]
Expand All @@ -23,3 +23,6 @@ def get_filtered_data(data)
return data
end
end

# Alias for convenience.
RESTFramework::RansackFilter = RESTFramework::Filters::RansackFilter
87 changes: 3 additions & 84 deletions lib/rest_framework/paginators.rb
Original file line number Diff line number Diff line change
@@ -1,90 +1,9 @@
class RESTFramework::BasePaginator
def initialize(data:, controller:, **kwargs)
@data = data
@controller = controller
end

# Get the page and return it so the caller can serialize it.
def get_page
raise NotImplementedError
end

# Wrap the serialized page with appropriate metadata.
def get_paginated_response(serialized_page)
raise NotImplementedError
end
module RESTFramework::Paginators
end

# A simple paginator based on page numbers.
#
# Example: http://example.com/api/users/?page=3&page_size=50
class RESTFramework::PageNumberPaginator < RESTFramework::BasePaginator
def initialize(**kwargs)
super
# Exclude any `select` clauses since that would cause `count` to fail with a SQL `SyntaxError`.
@count = @data.except(:select).count
@page_size = self._page_size

@total_pages = @count / @page_size
@total_pages += 1 if @count % @page_size != 0
end

def _page_size
page_size = nil

# Get from context, if allowed.
if @controller.page_size_query_param
if page_size = @controller.params[@controller.page_size_query_param].presence
page_size = page_size.to_i
end
end

# Otherwise, get from config.
if !page_size && @controller.page_size
page_size = @controller.page_size
end
require_relative "paginators/base_paginator"

# Ensure we don't exceed the max page size.
if @controller.max_page_size && page_size > @controller.max_page_size
page_size = @controller.max_page_size
end

# Ensure we return at least 1.
return page_size.zero? ? 1 : page_size
end

# Get the page and return it so the caller can serialize it.
def get_page(page_number=nil)
# If page number isn't provided, infer from the params or use 1 as a fallback value.
unless page_number
page_number = @controller&.params&.[](@controller.page_query_param&.to_sym)
if page_number.blank?
page_number = 1
else
page_number = page_number.to_i
if page_number.zero?
page_number = 1
end
end
end
@page_number = page_number

# Get the data page and return it so the caller can serialize the data in the proper format.
page_index = @page_number - 1
return @data.limit(@page_size).offset(page_index * @page_size)
end

# Wrap the serialized page with appropriate metadata. TODO: include links.
def get_paginated_response(serialized_page)
return {
count: @count,
page: @page_number,
page_size: @page_size,
total_pages: @total_pages,
results: serialized_page,
}
end
end
require_relative "paginators/page_number_paginator"

# TODO: implement this
# class RESTFramework::CountOffsetPaginator
Expand Down
19 changes: 19 additions & 0 deletions lib/rest_framework/paginators/base_paginator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class RESTFramework::Paginators::BasePaginator
def initialize(data:, controller:, **kwargs)
@data = data
@controller = controller
end

# Get the page and return it so the caller can serialize it.
def get_page
raise NotImplementedError
end

# Wrap the serialized page with appropriate metadata.
def get_paginated_response(serialized_page)
raise NotImplementedError
end
end

# Alias for convenience.
RESTFramework::BasePaginator = RESTFramework::Paginators::BasePaginator
73 changes: 73 additions & 0 deletions lib/rest_framework/paginators/page_number_paginator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# A simple paginator based on page numbers.
#
# Example: http://example.com/api/users/?page=3&page_size=50
class RESTFramework::Paginators::PageNumberPaginator < RESTFramework::Paginators::BasePaginator
def initialize(**kwargs)
super
# Exclude any `select` clauses since that would cause `count` to fail with a SQL `SyntaxError`.
@count = @data.except(:select).count
@page_size = self._page_size

@total_pages = @count / @page_size
@total_pages += 1 if @count % @page_size != 0
end

def _page_size
page_size = nil

# Get from context, if allowed.
if @controller.page_size_query_param
if page_size = @controller.params[@controller.page_size_query_param].presence
page_size = page_size.to_i
end
end

# Otherwise, get from config.
if !page_size && @controller.page_size
page_size = @controller.page_size
end

# Ensure we don't exceed the max page size.
if @controller.max_page_size && page_size > @controller.max_page_size
page_size = @controller.max_page_size
end

# Ensure we return at least 1.
return page_size.zero? ? 1 : page_size
end

# Get the page and return it so the caller can serialize it.
def get_page(page_number=nil)
# If page number isn't provided, infer from the params or use 1 as a fallback value.
unless page_number
page_number = @controller&.params&.[](@controller.page_query_param&.to_sym)
if page_number.blank?
page_number = 1
else
page_number = page_number.to_i
if page_number.zero?
page_number = 1
end
end
end
@page_number = page_number

# Get the data page and return it so the caller can serialize the data in the proper format.
page_index = @page_number - 1
return @data.limit(@page_size).offset(page_index * @page_size)
end

# Wrap the serialized page with appropriate metadata. TODO: include links.
def get_paginated_response(serialized_page)
return {
count: @count,
page: @page_number,
page_size: @page_size,
total_pages: @total_pages,
results: serialized_page,
}
end
end

# Alias for convenience.
RESTFramework::PageNumberPaginator = RESTFramework::Paginators::PageNumberPaginator

0 comments on commit ccf500d

Please sign in to comment.