warped 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +30 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +240 -0
- data/LICENSE.txt +21 -0
- data/README.md +563 -0
- data/Rakefile +12 -0
- data/lib/generators/warped/install_generator.rb +15 -0
- data/lib/generators/warped/templates/initializer.rb.tt +8 -0
- data/lib/warped/controllers/filterable.rb +141 -0
- data/lib/warped/controllers/pageable.rb +130 -0
- data/lib/warped/controllers/searchable.rb +162 -0
- data/lib/warped/controllers/sortable.rb +115 -0
- data/lib/warped/controllers/tabulatable.rb +91 -0
- data/lib/warped/emails/.keep +0 -0
- data/lib/warped/jobs/base.rb +23 -0
- data/lib/warped/queries/filter.rb +91 -0
- data/lib/warped/queries/paginate.rb +100 -0
- data/lib/warped/queries/search.rb +62 -0
- data/lib/warped/queries/sort.rb +90 -0
- data/lib/warped/railtie.rb +12 -0
- data/lib/warped/services/base.rb +94 -0
- data/lib/warped/version.rb +5 -0
- data/lib/warped.rb +31 -0
- data/sig/warped.rbs +4 -0
- data/warped.gemspec +33 -0
- metadata +108 -0
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require "active_support/core_ext/class/attribute"
|
5
|
+
require "active_support/core_ext/enumerable"
|
6
|
+
|
7
|
+
module Warped
|
8
|
+
module Controllers
|
9
|
+
# Provides functionality for filtering records from an +ActiveRecord::Relation+ in a controller.
|
10
|
+
#
|
11
|
+
# Example usage:
|
12
|
+
#
|
13
|
+
# class UsersController < ApplicationController
|
14
|
+
# include Filterable
|
15
|
+
#
|
16
|
+
# filterable_by :name, :created_at, 'accounts.kind'
|
17
|
+
#
|
18
|
+
# def index
|
19
|
+
# scope = filter(User.joins(:account))
|
20
|
+
# render json: scope
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# Example requests:
|
25
|
+
# GET /users?name=John
|
26
|
+
# GET /users?created_at=2020-01-01
|
27
|
+
# GET /users?accounts.kind=premium
|
28
|
+
# GET /users?accounts.kind=premium&accounts.kind.rel=not_eq
|
29
|
+
#
|
30
|
+
# Filters can be combined:
|
31
|
+
# GET /users?name=John&created_at=2020-01-01
|
32
|
+
#
|
33
|
+
# Renaming filter keys:
|
34
|
+
#
|
35
|
+
# In some cases, you may not want to expose the actual column names to the client.
|
36
|
+
# In such cases, you can rename the filter keys by passing a hash to the +filterable_by+ method.
|
37
|
+
#
|
38
|
+
# Example:
|
39
|
+
#
|
40
|
+
# class UsersController < ApplicationController
|
41
|
+
# include Filterable
|
42
|
+
#
|
43
|
+
# filterable_by :name, :created_at, 'accounts.kind' => 'kind'
|
44
|
+
#
|
45
|
+
# def index
|
46
|
+
# scope = filter(User.joins(:account))
|
47
|
+
# render json: scope
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# Example requests:
|
52
|
+
# GET /users?kind=premium
|
53
|
+
#
|
54
|
+
# Using relations:
|
55
|
+
#
|
56
|
+
# In some cases, you may want to filter records based on a relation.
|
57
|
+
# For example, you may want to filter users based on operands like:
|
58
|
+
# - greater than
|
59
|
+
# - less than
|
60
|
+
# - not equal
|
61
|
+
# @see Warped::Queries::Filter::RELATIONS
|
62
|
+
# To see the full list of operands, check the +Warped::Queries::Filter::RELATIONS+ constant.
|
63
|
+
#
|
64
|
+
# To use the operands, you must pass a parameter appended with `.rel`, and the value of a valid operand.
|
65
|
+
#
|
66
|
+
# Example requests:
|
67
|
+
# GET /users?created_at=2020-01-01&created_at.rel=>
|
68
|
+
# GET /users?created_at=2020-01-01&created_at.rel=<
|
69
|
+
# GET /users?created_at=2020-01-01&created_at.rel=not_eq
|
70
|
+
#
|
71
|
+
# When the operand relation requires multiple values, like +in+, +not_in+, or +between+,
|
72
|
+
# you can pass an array of values.
|
73
|
+
#
|
74
|
+
# Example requests:
|
75
|
+
# GET /users?created_at[]=2020-01-01&created_at[]=2020-01-03&created_at.rel=in
|
76
|
+
# GET /users?created_at[]=2020-01-01&created_at[]=2020-01-03&created_at.rel=between
|
77
|
+
module Filterable
|
78
|
+
extend ActiveSupport::Concern
|
79
|
+
|
80
|
+
included do
|
81
|
+
class_attribute :filter_fields, default: []
|
82
|
+
class_attribute :mapped_filter_fields, default: []
|
83
|
+
end
|
84
|
+
|
85
|
+
class_methods do
|
86
|
+
# @param keys [Array<Symbol,String,Hash>]
|
87
|
+
# @param mapped_keys [Hash<Symbol,String>]
|
88
|
+
def filterable_by(*keys, **mapped_keys)
|
89
|
+
self.filter_fields = keys
|
90
|
+
self.mapped_filter_fields = mapped_keys.to_a
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# @param scope [ActiveRecord::Relation]
|
95
|
+
# @param filter_conditions [Array<Hash>]
|
96
|
+
# @option filter_conditions [Symbol,String] :field
|
97
|
+
# @option filter_conditions [String,Integer,Array<String,Integer>] :value
|
98
|
+
# @option filter_conditions [String] :relation
|
99
|
+
# @return [ActiveRecord::Relation]
|
100
|
+
def filter(scope, filter_conditions: filter_conditions(*filter_fields, *mapped_filter_fields))
|
101
|
+
Warped::Queries::Filter.call(scope, filter_conditions:)
|
102
|
+
end
|
103
|
+
|
104
|
+
# @param fields [Array<Symbol,String>]
|
105
|
+
# @return [Array<Hash>]
|
106
|
+
def filter_conditions(*fields)
|
107
|
+
fields.filter_map do |filter_opt|
|
108
|
+
field = filter_name(filter_opt)
|
109
|
+
|
110
|
+
next if filter_value(filter_opt).blank? && %w[is_null is_not_null].exclude?(filter_rel_value(filter_opt))
|
111
|
+
|
112
|
+
{
|
113
|
+
field:,
|
114
|
+
value: filter_value(filter_opt),
|
115
|
+
relation: filter_rel_value(filter_opt).presence || (filter_value(filter_opt).is_a?(Array) ? "in" : "=")
|
116
|
+
}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def filter_name(filter)
|
123
|
+
filter.is_a?(Array) ? filter.first : filter
|
124
|
+
end
|
125
|
+
|
126
|
+
def filter_mapped_name(filter)
|
127
|
+
filter.is_a?(Array) ? filter.last : filter
|
128
|
+
end
|
129
|
+
|
130
|
+
def filter_value(filter)
|
131
|
+
param_key = filter_mapped_name(filter)
|
132
|
+
params[param_key]
|
133
|
+
end
|
134
|
+
|
135
|
+
def filter_rel_value(filter)
|
136
|
+
param_key = filter_mapped_name(filter)
|
137
|
+
params["#{param_key}.rel"]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require "active_support/core_ext/class/attribute"
|
5
|
+
|
6
|
+
module Warped
|
7
|
+
module Controllers
|
8
|
+
# Provides functionality for paginating records from an +ActiveRecord::Relation+ in a controller.
|
9
|
+
#
|
10
|
+
# Example usage:
|
11
|
+
#
|
12
|
+
# class UsersController < ApplicationController
|
13
|
+
# include Pageable
|
14
|
+
#
|
15
|
+
# def index
|
16
|
+
# scope = paginate(User.all)
|
17
|
+
# render json: scope, root: :users, meta: page_info
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# Example requests:
|
22
|
+
# GET /users?page=1&per_page=10
|
23
|
+
# GET /users?page=2&per_page=50
|
24
|
+
#
|
25
|
+
# The +per_page+ parameter is optional. If not provided, the default value will be used.
|
26
|
+
#
|
27
|
+
# The default value can be set at a controller level using the +default_per_page+ class attribute.
|
28
|
+
# Example:
|
29
|
+
# class UsersController < ApplicationController
|
30
|
+
# include Pageable
|
31
|
+
#
|
32
|
+
# self.default_per_page = 50
|
33
|
+
#
|
34
|
+
# def index
|
35
|
+
# scope = paginate(User.all)
|
36
|
+
# render json: scope, root: :users, meta: page_info
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# Or by overriding the +default_per_page+ controller instance method.
|
41
|
+
# Example:
|
42
|
+
# class UsersController < ApplicationController
|
43
|
+
# include Pageable
|
44
|
+
#
|
45
|
+
# def index
|
46
|
+
# scope = paginate(User.all)
|
47
|
+
# render json: scope, root: :users, meta: page_info
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# private
|
51
|
+
#
|
52
|
+
# def default_per_page
|
53
|
+
# 50
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# The +per_page+ value can also be set at action level, by passing the +per_page+ parameter to
|
58
|
+
# the +paginate+ method.
|
59
|
+
# Example:
|
60
|
+
# class UsersController < ApplicationController
|
61
|
+
# include Pageable
|
62
|
+
#
|
63
|
+
# def index
|
64
|
+
# scope = paginate(User.all, per_page: 50)
|
65
|
+
# render json: scope, root: :users, meta: page_info
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# def other_index
|
69
|
+
# # The default per_page value is used.
|
70
|
+
# scope = paginate(User.all)
|
71
|
+
# render json: scope, root: :users, meta: page_info
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# The pagination metadata can be accessed by calling the +page_info+ method.
|
76
|
+
# It includes the following keys:
|
77
|
+
# - +total_count+: The total number of records in the collection.
|
78
|
+
# - +total_pages+: The total number of pages.
|
79
|
+
# - +next_page+: The next page number.
|
80
|
+
# - +prev_page+: The previous page number.
|
81
|
+
# - +page+: The current page number.
|
82
|
+
# - +per_page+: The number of records per page.
|
83
|
+
# *Warning*: The +page_info+ method will raise an +ArgumentError+ if the method +paginate+ was not
|
84
|
+
# called within the action.
|
85
|
+
module Pageable
|
86
|
+
extend ActiveSupport::Concern
|
87
|
+
|
88
|
+
included do
|
89
|
+
class_attribute :default_per_page, default: Queries::Paginate::DEFAULT_PER_PAGE
|
90
|
+
end
|
91
|
+
|
92
|
+
# Paginates the given scope.
|
93
|
+
#
|
94
|
+
# @param scope [ActiveRecord::Relation] The scope to be paginated.
|
95
|
+
# @param page [String,Integer,nil] The page number.
|
96
|
+
# @param per_page [String,Integer,nil] The number of records per page.
|
97
|
+
# @return [ActiveRecord::Relation] The paginated scope.
|
98
|
+
def paginate(scope, page: self.page, per_page: self.per_page)
|
99
|
+
@page_info, paginated_scope = Queries::Paginate.call(scope, page:, per_page:)
|
100
|
+
paginated_scope
|
101
|
+
end
|
102
|
+
|
103
|
+
# Retrieves the page number from the request parameters.
|
104
|
+
#
|
105
|
+
# @return [String,nil] The page number if present, otherwise nil.
|
106
|
+
def page
|
107
|
+
params[:page]&.to_i || 1
|
108
|
+
end
|
109
|
+
|
110
|
+
# Retrieves the number of records per page from the request parameters or defaults to the
|
111
|
+
# controller's default value.
|
112
|
+
#
|
113
|
+
# @return [String,Integer] The number of records per page.
|
114
|
+
def per_page
|
115
|
+
params[:per_page].presence || self.class.default_per_page
|
116
|
+
end
|
117
|
+
|
118
|
+
# Retrieves pagination metadata.
|
119
|
+
#
|
120
|
+
# @return [Hash] Metadata about the pagination.
|
121
|
+
# @raise [ArgumentError] If pagination was not performed.
|
122
|
+
# @see Warped::Queries::Paginate#metadata
|
123
|
+
def page_info
|
124
|
+
return @page_info if @page_info.present?
|
125
|
+
|
126
|
+
raise ActionController::BadRequest, "Pagination was not performed"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require "active_support/core_ext/class/attribute"
|
5
|
+
|
6
|
+
module Warped
|
7
|
+
module Controllers
|
8
|
+
# Provides functionality for searching records from an +ActiveRecord::Relation+ in a controller.
|
9
|
+
#
|
10
|
+
# Example usage:
|
11
|
+
# class User < ApplicationRecord
|
12
|
+
# scope :search, ->(term) { where('name ILIKE ?', "%#{term}%") }
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# class UsersController < ApplicationController
|
16
|
+
# include Searchable
|
17
|
+
#
|
18
|
+
# def index
|
19
|
+
# scope = search(User.all)
|
20
|
+
# render json: scope
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# Example requests:
|
25
|
+
# GET /users?q=John
|
26
|
+
# GET /users?q=John%20Doe
|
27
|
+
#
|
28
|
+
# There are cases where the search scope is not called +search+.
|
29
|
+
# In such cases, you can use the +searchable_by+ method to override the search scope.
|
30
|
+
#
|
31
|
+
# Example:
|
32
|
+
# class User < ApplicationRecord
|
33
|
+
# scope :search_case_sensitive, ->(term) { where('name LIKE ?', "%#{term}%") }
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# class UsersController < ApplicationController
|
37
|
+
# include Searchable
|
38
|
+
#
|
39
|
+
# searchable_by :search_case_sensitive
|
40
|
+
#
|
41
|
+
# def index
|
42
|
+
# scope = search(User.all)
|
43
|
+
# render json: scope
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# When only overriding the search scope for a single action, you can pass the scope name as an argument to
|
48
|
+
# the +search+ method.
|
49
|
+
# Example:
|
50
|
+
# class UsersController < ApplicationController
|
51
|
+
# include Searchable
|
52
|
+
#
|
53
|
+
# def index
|
54
|
+
# # The default search scope name is overridden.
|
55
|
+
# # runs User.all.search_case_sensitive(search_term)
|
56
|
+
# scope = search(User.all, model_search_scope: :search_case_sensitive)
|
57
|
+
# render json: scope
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# def other_index
|
61
|
+
# # The default search scope name is used.
|
62
|
+
# # runs User.all.search(search_term)
|
63
|
+
# scope = search(User.all)
|
64
|
+
# render json: scope
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# In addition, you can override the search term parameter name for the entire controller by implementing
|
69
|
+
# the +search_param+ method.
|
70
|
+
#
|
71
|
+
# Example:
|
72
|
+
# class UsersController < ApplicationController
|
73
|
+
# include Searchable
|
74
|
+
#
|
75
|
+
# def index
|
76
|
+
# scope = search(User.all)
|
77
|
+
# render json: scope
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# private
|
81
|
+
#
|
82
|
+
# def search_param
|
83
|
+
# :term
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# Or you can override the search term parameter name for a single action by passing the parameter name as
|
88
|
+
# an argument to the +search_term+ method.
|
89
|
+
#
|
90
|
+
# Example:
|
91
|
+
# class UsersController < ApplicationController
|
92
|
+
# include Searchable
|
93
|
+
#
|
94
|
+
# def index
|
95
|
+
# # The default search term parameter name (+q+) is overridden.
|
96
|
+
# # GET /users?term=John
|
97
|
+
# scope = search(User.all, search_term: params[:term])
|
98
|
+
# render json: scope
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# def other_index
|
102
|
+
# # The default search term parameter name (+q+) is used.
|
103
|
+
# # GET /other_users?q=John
|
104
|
+
# scope = search(User.all)
|
105
|
+
# render json: scope
|
106
|
+
# end
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# Example requests:
|
110
|
+
# GET /users?term=John
|
111
|
+
# GET /other_users?q=John%20Doe
|
112
|
+
module Searchable
|
113
|
+
extend ActiveSupport::Concern
|
114
|
+
|
115
|
+
included do
|
116
|
+
class_attribute :model_search_scope, default: :search
|
117
|
+
class_attribute :search_param, default: :q
|
118
|
+
end
|
119
|
+
|
120
|
+
class_methods do
|
121
|
+
# Sets the search scope.
|
122
|
+
#
|
123
|
+
# @param scope [Symbol] The name of the search scope.
|
124
|
+
def searchable_by(scope = model_search_scope, param: :q)
|
125
|
+
self.model_search_scope = scope
|
126
|
+
self.search_param = param
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Searches records based on the provided scope and search term.
|
131
|
+
#
|
132
|
+
# @param scope [ActiveRecord::Relation] The scope to search within.
|
133
|
+
# @param search_term [String] The search term.
|
134
|
+
# @param model_search_scope [Symbol] The name of the search scope.
|
135
|
+
# @return [ActiveRecord::Relation] The result of the search.
|
136
|
+
def search(scope, search_term: self.search_term, model_search_scope: self.model_search_scope)
|
137
|
+
Queries::Search.call(scope, search_term:, model_search_scope:)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Retrieves the search term from the request parameters.
|
141
|
+
#
|
142
|
+
# @return [String, nil] The search term if present, otherwise nil.
|
143
|
+
def search_term
|
144
|
+
params[search_param]&.strip
|
145
|
+
end
|
146
|
+
|
147
|
+
# Returns the name of the parameter used for searching.
|
148
|
+
#
|
149
|
+
# @return [Symbol] The search parameter name.
|
150
|
+
def search_param
|
151
|
+
self.class.search_param
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns the name of the model's search scope.
|
155
|
+
#
|
156
|
+
# @return [Symbol] The name of the model's search scope.
|
157
|
+
def model_search_scope
|
158
|
+
self.class.model_search_scope
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require "active_support/core_ext/class/attribute"
|
5
|
+
|
6
|
+
module Warped
|
7
|
+
module Controllers
|
8
|
+
# Provides functionality for sorting records from an +ActiveRecord::Relation+ in a controller.
|
9
|
+
#
|
10
|
+
# Example usage:
|
11
|
+
#
|
12
|
+
# class UsersController < ApplicationController
|
13
|
+
# include Sortable
|
14
|
+
#
|
15
|
+
# sortable_by :name, :created_at, 'accounts.kind'
|
16
|
+
#
|
17
|
+
# def index
|
18
|
+
# scope = sort(User.joins(:account))
|
19
|
+
# render json: scope
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# Example requests:
|
24
|
+
# GET /users?sort_key=name
|
25
|
+
# GET /users?sort_key=name&sort_direction=asc_nulls_first
|
26
|
+
# GET /users?sort_key=created_at&sort_direction=asc
|
27
|
+
#
|
28
|
+
# Renaming sort keys:
|
29
|
+
#
|
30
|
+
# In some cases, you may not want to expose the actual column names to the client.
|
31
|
+
# In such cases, you can rename the sort keys by passing a hash to the +sortable_by+ method.
|
32
|
+
#
|
33
|
+
# Example:
|
34
|
+
#
|
35
|
+
# class UsersController < ApplicationController
|
36
|
+
# include Sortable
|
37
|
+
#
|
38
|
+
# sortable_by :name, :created_at, 'accounts.referrals_count' => 'referrals'
|
39
|
+
#
|
40
|
+
# def index
|
41
|
+
# scope = sort(User.joins(:account))
|
42
|
+
# render json: scope
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# Example requests:
|
47
|
+
# GET /users?sort_key=referrals&sort_direction=asc
|
48
|
+
#
|
49
|
+
# The +sort_key+ and +sort_direction+ parameters are optional. If not provided, the default sort key and direction
|
50
|
+
# will be used.
|
51
|
+
#
|
52
|
+
# The default sort key and sort direction can be set at a controller level using the +default_sort_direction+ and
|
53
|
+
# +default_sort_key+ class attributes.
|
54
|
+
module Sortable
|
55
|
+
extend ActiveSupport::Concern
|
56
|
+
|
57
|
+
included do
|
58
|
+
class_attribute :sort_fields, default: []
|
59
|
+
class_attribute :mapped_sort_fields, default: {}
|
60
|
+
class_attribute :default_sort_key, default: :id
|
61
|
+
class_attribute :default_sort_direction, default: :desc
|
62
|
+
end
|
63
|
+
|
64
|
+
class_methods do
|
65
|
+
# @param keys [Array<Symbol,String>]
|
66
|
+
# @param mapped_keys [Hash<Symbol,String>]
|
67
|
+
def sortable_by(*keys, **mapped_keys)
|
68
|
+
self.sort_fields = keys.map(&:to_s)
|
69
|
+
self.mapped_sort_fields = mapped_keys.with_indifferent_access
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# @param scope [ActiveRecord::Relation] The scope to sort.
|
74
|
+
# @param sort_key [String, Symbol] The sort key.
|
75
|
+
# @param sort_direction [String, Symbol] The sort direction.
|
76
|
+
# @return [ActiveRecord::Relation]
|
77
|
+
def sort(scope, sort_key: self.sort_key, sort_direction: self.sort_direction)
|
78
|
+
return scope unless sort_key && sort_direction
|
79
|
+
|
80
|
+
validate_sort_key!
|
81
|
+
|
82
|
+
Queries::Sort.call(scope, sort_key:, sort_direction:)
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
# @return [Symbol] The sort direction.
|
88
|
+
def sort_direction
|
89
|
+
@sort_direction ||= params[:sort_direction] || default_sort_direction
|
90
|
+
end
|
91
|
+
|
92
|
+
def sort_key
|
93
|
+
@sort_key ||= mapped_sort_fields.key(params[:sort_key]).presence ||
|
94
|
+
params[:sort_key] ||
|
95
|
+
default_sort_key
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def validate_sort_key!
|
101
|
+
return if valid_sort_key?
|
102
|
+
|
103
|
+
possible_values = sort_fields + mapped_sort_fields.values
|
104
|
+
message = "Invalid sort key: #{sort_key}, must be one of #{possible_values}"
|
105
|
+
raise ActionController::BadRequest, message
|
106
|
+
end
|
107
|
+
|
108
|
+
def valid_sort_key?
|
109
|
+
sort_key == default_sort_key ||
|
110
|
+
sort_fields.include?(sort_key) ||
|
111
|
+
mapped_sort_fields[sort_key].present?
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require "active_support/core_ext/class/attribute"
|
5
|
+
|
6
|
+
module Warped
|
7
|
+
module Controllers
|
8
|
+
# Provides functionality for filtering, sorting, searching, and paginating records
|
9
|
+
# from an +ActiveRecord::Relation+ in a controller.
|
10
|
+
#
|
11
|
+
# Example usage:
|
12
|
+
#
|
13
|
+
# class UsersController < ApplicationController
|
14
|
+
# include Tabulatable
|
15
|
+
#
|
16
|
+
# tabulatable_by :name, :email, :age, 'posts.created_at', 'posts.id' => 'post_id'
|
17
|
+
#
|
18
|
+
# def index
|
19
|
+
# users = User.left_joins(:posts).group(:id)
|
20
|
+
# users = tabulate(users)
|
21
|
+
# render json: users, meta: tabulate_info
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# There are cases where not all fields should be filterable or sortable.
|
26
|
+
# In such cases, you can use the `filterable_by` and `sortable_by` methods to
|
27
|
+
# override the tabulate fields.
|
28
|
+
#
|
29
|
+
# Example:
|
30
|
+
#
|
31
|
+
# class PostsController < ApplicationController
|
32
|
+
# include Tabulatable
|
33
|
+
#
|
34
|
+
# tabulatable_by :title, :content, :created_at, user: 'users.name'
|
35
|
+
# filterable_by :created_at, user: 'users.name'
|
36
|
+
#
|
37
|
+
# def index
|
38
|
+
# posts = Post.left_joins(:user).group(:id)
|
39
|
+
# posts = tabulate(posts)
|
40
|
+
# render json: posts, meta: tabulate_info
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
module Tabulatable
|
44
|
+
extend ActiveSupport::Concern
|
45
|
+
|
46
|
+
included do
|
47
|
+
include Filterable
|
48
|
+
include Sortable
|
49
|
+
include Searchable
|
50
|
+
include Pageable
|
51
|
+
|
52
|
+
class_attribute :tabulate_fields, default: []
|
53
|
+
class_attribute :mapped_tabulate_fields, default: []
|
54
|
+
end
|
55
|
+
|
56
|
+
class_methods do
|
57
|
+
def tabulatable_by(*keys, **mapped_keys)
|
58
|
+
self.tabulate_fields = keys
|
59
|
+
self.mapped_tabulate_fields = mapped_keys.to_a
|
60
|
+
|
61
|
+
filterable_by(*keys, **mapped_keys) if filter_fields.empty? && mapped_filter_fields.empty?
|
62
|
+
sortable_by(*keys, **mapped_keys) if sort_fields.empty? && mapped_sort_fields.empty?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# @param scope [ActiveRecord::Relation]
|
67
|
+
# @return [ActiveRecord::Relation]
|
68
|
+
# @see Filterable#filter
|
69
|
+
# @see Sortable#sort
|
70
|
+
# @see Searchable#search
|
71
|
+
# @see Pageable#paginate
|
72
|
+
def tabulate(scope)
|
73
|
+
scope = filter(scope)
|
74
|
+
scope = search(scope)
|
75
|
+
scope = sort(scope)
|
76
|
+
paginate(scope)
|
77
|
+
end
|
78
|
+
|
79
|
+
# @return [Hash]
|
80
|
+
def tabulate_info
|
81
|
+
{
|
82
|
+
filters: filter_conditions(*filter_fields, *mapped_filter_fields),
|
83
|
+
sorts: sort_conditions(*sort_fields, *mapped_sort_fields),
|
84
|
+
search_term:,
|
85
|
+
search_param:,
|
86
|
+
page_info:
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
File without changes
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_job"
|
4
|
+
require "active_support/core_ext/string/inflections"
|
5
|
+
|
6
|
+
module Warped
|
7
|
+
module Jobs
|
8
|
+
##
|
9
|
+
# Base class for all jobs in the application.
|
10
|
+
# This class is used to provide a common interface for all jobs used by Warped
|
11
|
+
# and to allow for easy configuration of the parent class.
|
12
|
+
# By default, the parent class is set to +ActiveJob::Base+.
|
13
|
+
# @see Warped
|
14
|
+
#
|
15
|
+
# @example Change the parent class for Warped::Jobs::Base
|
16
|
+
# Warped.configure do |config|
|
17
|
+
# config.base_job_parent_class = "ApplicationJob"
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
class Base < Warped.base_job_parent_class.constantize
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|