warped 0.2.0 → 1.0.1
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 +4 -4
- data/.rubocop.yml +6 -0
- data/Gemfile.lock +2 -2
- data/README.md +104 -0
- data/app/assets/config/warped_manifest.js +2 -0
- data/app/assets/javascript/warped/controllers/filter_controller.js +76 -0
- data/app/assets/javascript/warped/controllers/filters_controller.js +21 -0
- data/app/assets/javascript/warped/index.js +2 -0
- data/app/assets/stylesheets/warped/application.css +15 -0
- data/app/assets/stylesheets/warped/base.css +23 -0
- data/app/assets/stylesheets/warped/filters.css +115 -0
- data/app/assets/stylesheets/warped/pagination.css +74 -0
- data/app/assets/stylesheets/warped/search.css +33 -0
- data/app/assets/stylesheets/warped/table.css +114 -0
- data/app/views/warped/_actions.html.erb +9 -0
- data/app/views/warped/_cell.html.erb +3 -0
- data/app/views/warped/_column.html.erb +35 -0
- data/app/views/warped/_filters.html.erb +21 -0
- data/app/views/warped/_hidden_fields.html.erb +19 -0
- data/app/views/warped/_pagination.html.erb +34 -0
- data/app/views/warped/_row.html.erb +19 -0
- data/app/views/warped/_search.html.erb +21 -0
- data/app/views/warped/_table.html.erb +52 -0
- data/app/views/warped/filters/_filter.html.erb +40 -0
- data/config/importmap.rb +3 -0
- data/docs/controllers/FILTERABLE.md +82 -3
- data/docs/controllers/views/PARTIALS.md +285 -0
- data/lib/warped/api/filter/base/value.rb +52 -0
- data/lib/warped/api/filter/base.rb +84 -0
- data/lib/warped/api/filter/boolean.rb +41 -0
- data/lib/warped/api/filter/date.rb +26 -0
- data/lib/warped/api/filter/date_time.rb +32 -0
- data/lib/warped/api/filter/decimal.rb +31 -0
- data/lib/warped/api/filter/factory.rb +38 -0
- data/lib/warped/api/filter/integer.rb +38 -0
- data/lib/warped/api/filter/string.rb +25 -0
- data/lib/warped/api/filter/time.rb +25 -0
- data/lib/warped/api/filter.rb +14 -0
- data/lib/warped/api/sort/value.rb +40 -0
- data/lib/warped/api/sort.rb +65 -0
- data/lib/warped/controllers/filterable/ui.rb +10 -32
- data/lib/warped/controllers/filterable.rb +75 -42
- data/lib/warped/controllers/pageable/ui.rb +13 -3
- data/lib/warped/controllers/pageable.rb +1 -1
- data/lib/warped/controllers/searchable/ui.rb +3 -1
- data/lib/warped/controllers/sortable/ui.rb +21 -26
- data/lib/warped/controllers/sortable.rb +53 -33
- data/lib/warped/controllers/tabulatable/ui.rb +4 -0
- data/lib/warped/controllers/tabulatable.rb +6 -9
- data/lib/warped/engine.rb +19 -0
- data/lib/warped/queries/filter.rb +3 -3
- data/lib/warped/table/action.rb +33 -0
- data/lib/warped/table/column.rb +34 -0
- data/lib/warped/version.rb +1 -1
- data/lib/warped.rb +1 -0
- data/warped.gemspec +1 -1
- metadata +43 -11
@@ -3,6 +3,8 @@
|
|
3
3
|
require "active_support/concern"
|
4
4
|
require "active_support/core_ext/class/attribute"
|
5
5
|
require "active_support/core_ext/enumerable"
|
6
|
+
require "active_support/core_ext/hash/indifferent_access"
|
7
|
+
require "active_support/core_ext/object/blank"
|
6
8
|
|
7
9
|
module Warped
|
8
10
|
module Controllers
|
@@ -40,7 +42,7 @@ module Warped
|
|
40
42
|
# class UsersController < ApplicationController
|
41
43
|
# include Filterable
|
42
44
|
#
|
43
|
-
# filterable_by :name, :created_at, 'accounts.kind' => 'kind'
|
45
|
+
# filterable_by :name, :created_at, 'accounts.kind' => { alias_name: 'kind' }
|
44
46
|
#
|
45
47
|
# def index
|
46
48
|
# scope = filter(User.joins(:account))
|
@@ -74,71 +76,102 @@ module Warped
|
|
74
76
|
# Example requests:
|
75
77
|
# GET /users?created_at[]=2020-01-01&created_at[]=2020-01-03&created_at.rel=in
|
76
78
|
# GET /users?created_at[]=2020-01-01&created_at[]=2020-01-03&created_at.rel=between
|
79
|
+
#
|
80
|
+
# Setting types and casting:
|
81
|
+
# By default, the filter values are cast to strings. If you want to cast the values to a specific type,
|
82
|
+
# and validate that the values are of the correct type, you can pass the kind of the filter to
|
83
|
+
# the +filterable_by+ method.
|
84
|
+
#
|
85
|
+
# Example:
|
86
|
+
#
|
87
|
+
# class UsersController < ApplicationController
|
88
|
+
# include Filterable
|
89
|
+
#
|
90
|
+
# filterable_by :name, :created_at, 'accounts.active' => { kind: :integer, alias_name: 'active' }
|
91
|
+
#
|
92
|
+
# def index
|
93
|
+
# scope = filter(User.joins(:account))
|
94
|
+
# render json: scope
|
95
|
+
# end
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# Example requests:
|
99
|
+
# GET /users?active=1
|
100
|
+
#
|
101
|
+
# The +kind+ parameter will be cast to an integer.
|
102
|
+
# If the value is not an integer, an error will be raised, and the response will be a 400 Bad Request.
|
103
|
+
#
|
104
|
+
# In order to change the error message, you can rescue from the +Filter::ValueError+ exception, or
|
105
|
+
# override the +render_invalid_filter_value+ method.
|
106
|
+
#
|
107
|
+
# def render_invalid_filter_value(exception)
|
108
|
+
# render action_name, status: :bad_request
|
109
|
+
# end
|
110
|
+
#
|
77
111
|
module Filterable
|
78
112
|
extend ActiveSupport::Concern
|
79
113
|
|
80
114
|
included do
|
81
|
-
class_attribute :
|
82
|
-
class_attribute :
|
115
|
+
class_attribute :filters, default: []
|
116
|
+
class_attribute :strict_filtering, default: false
|
117
|
+
|
118
|
+
helper_method :current_action_filters, :current_action_filter_values
|
83
119
|
end
|
84
120
|
|
85
121
|
class_methods do
|
86
122
|
# @param keys [Array<Symbol,String,Hash>]
|
87
123
|
# @param mapped_keys [Hash<Symbol,String>]
|
88
|
-
def filterable_by(*keys, **mapped_keys)
|
89
|
-
self.
|
90
|
-
self.mapped_filter_fields = mapped_keys.to_a
|
91
|
-
end
|
92
|
-
end
|
124
|
+
def filterable_by(*keys, strict: nil, **mapped_keys)
|
125
|
+
self.strict_filtering = strict unless strict.nil?
|
93
126
|
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
127
|
+
self.filters = keys.map do |field|
|
128
|
+
Warped::Filter.build(nil, field, strict:)
|
129
|
+
end
|
103
130
|
|
104
|
-
|
105
|
-
# @return [Array<Hash>]
|
106
|
-
def filter_conditions(*fields)
|
107
|
-
fields.filter_map do |filter_opt|
|
108
|
-
field = filter_name(filter_opt)
|
131
|
+
complex_filters = mapped_keys.with_indifferent_access
|
109
132
|
|
110
|
-
|
133
|
+
self.filters += complex_filters.map do |field_name, opts|
|
134
|
+
kind = opts[:kind]
|
135
|
+
alias_name = opts[:alias_name]
|
111
136
|
|
112
|
-
|
113
|
-
|
114
|
-
value: filter_value(filter_opt),
|
115
|
-
relation: filter_rel_value(filter_opt).presence || (filter_value(filter_opt).is_a?(Array) ? "in" : "eq")
|
116
|
-
}
|
137
|
+
Warped::Filter.build(kind, field_name, alias_name:, strict:)
|
138
|
+
end
|
117
139
|
end
|
118
140
|
end
|
119
141
|
|
120
|
-
|
121
|
-
|
142
|
+
# @param scope [ActiveRecord::Relation]
|
143
|
+
# @param filter_conditions [Array<Warped::Filter::Base>|nil]
|
144
|
+
# @return [ActiveRecord::Relation]
|
145
|
+
def filter(scope, filter_conditions: nil)
|
146
|
+
action_filters = filter_conditions.presence || filters
|
147
|
+
@current_action_filters = action_filters
|
148
|
+
@current_action_filter_values = parse_filter_params
|
149
|
+
|
150
|
+
Warped::Queries::Filter.call(scope, filter_conditions: current_action_filter_values.map(&:to_h))
|
122
151
|
end
|
123
152
|
|
124
|
-
|
153
|
+
# @return [Array<Hash>]
|
154
|
+
def parse_filter_params
|
155
|
+
current_action_filters.filter_map do |filter|
|
156
|
+
raw_value = params[filter.parameter_name]
|
157
|
+
raw_relation = params["#{filter.parameter_name}.rel"]
|
125
158
|
|
126
|
-
|
127
|
-
filter.is_a?(Array) ? filter.first : filter
|
128
|
-
end
|
159
|
+
filter_value = filter.class::Value.new(filter, raw_relation.presence || "eq", raw_value.presence)
|
129
160
|
|
130
|
-
|
131
|
-
|
161
|
+
next if filter_value.empty?
|
162
|
+
|
163
|
+
filter_value
|
164
|
+
end
|
132
165
|
end
|
133
166
|
|
134
|
-
|
135
|
-
|
136
|
-
|
167
|
+
# @return [Array<Warped::Filter::Base>]
|
168
|
+
def current_action_filters
|
169
|
+
@current_action_filters ||= []
|
137
170
|
end
|
138
171
|
|
139
|
-
|
140
|
-
|
141
|
-
|
172
|
+
# @return [Array<Warped::Filter::Base::Value>]
|
173
|
+
def current_action_filter_values
|
174
|
+
@current_action_filter_values ||= []
|
142
175
|
end
|
143
176
|
end
|
144
177
|
end
|
@@ -14,24 +14,28 @@ module Warped
|
|
14
14
|
helper_method :pagination, :paginated?, :paginate_url_params
|
15
15
|
end
|
16
16
|
|
17
|
+
# @return [Hash] The paginate_url_params
|
17
18
|
def paginate_url_params(**options)
|
18
19
|
url_params = { page:, per_page: }
|
19
20
|
url_params.merge!(options)
|
20
|
-
url_params
|
21
21
|
end
|
22
22
|
|
23
|
+
# @see Pageable#pagination
|
24
|
+
# @return [Hash]
|
23
25
|
def pagination
|
24
26
|
super.tap do |hsh|
|
25
27
|
hsh[:series] = series(hsh[:page], hsh[:total_pages])
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
31
|
+
# @see Pageable#paginate
|
29
32
|
def paginate(...)
|
30
33
|
@paginated = true
|
31
34
|
|
32
35
|
super
|
33
36
|
end
|
34
37
|
|
38
|
+
# @return [Boolean] Whether the current action is paginated.
|
35
39
|
def paginated?
|
36
40
|
@paginated ||= false
|
37
41
|
end
|
@@ -45,13 +49,19 @@ module Warped
|
|
45
49
|
# the current page is a string, the others are integers
|
46
50
|
# series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
|
47
51
|
def series(page, total_pages)
|
52
|
+
current_page = [page, total_pages].min
|
48
53
|
return [] if total_pages.zero?
|
49
54
|
return ["1"] if total_pages == 1
|
50
55
|
|
51
56
|
if total_pages <= 9
|
52
|
-
(1..(
|
57
|
+
(1..(current_page - 1)).to_a + [current_page.to_s] + ((current_page + 1)..total_pages).to_a
|
58
|
+
elsif current_page <= 5
|
59
|
+
[*(1..6).to_a.map { |i| i == page ? i.to_s : i }, :gap, total_pages]
|
60
|
+
elsif current_page >= total_pages - 4
|
61
|
+
[1, :gap, *(total_pages - 5..total_pages).to_a.map { |i| i == current_page ? i.to_s : i }]
|
53
62
|
else
|
54
|
-
[1, :gap,
|
63
|
+
[1, :gap, current_page - 2, current_page - 1, current_page.to_s, current_page + 1, current_page + 2, :gap,
|
64
|
+
total_pages]
|
55
65
|
end
|
56
66
|
end
|
57
67
|
end
|
@@ -112,7 +112,7 @@ module Warped
|
|
112
112
|
#
|
113
113
|
# @return [String,Integer] The number of records per page.
|
114
114
|
def per_page
|
115
|
-
params[:per_page].presence ||
|
115
|
+
params[:per_page].presence || default_per_page
|
116
116
|
end
|
117
117
|
|
118
118
|
# Retrieves pagination metadata.
|
@@ -14,20 +14,22 @@ module Warped
|
|
14
14
|
helper_method :searched?, :search_url_params
|
15
15
|
end
|
16
16
|
|
17
|
+
# @see Searchable#search
|
17
18
|
def search(...)
|
18
19
|
@searched = true
|
19
20
|
|
20
21
|
super
|
21
22
|
end
|
22
23
|
|
24
|
+
# @return [Boolean] Whether the current action is searched.
|
23
25
|
def searched?
|
24
26
|
@searched ||= false
|
25
27
|
end
|
26
28
|
|
29
|
+
# @return [Hash] The search_url_params
|
27
30
|
def search_url_params(**options)
|
28
31
|
url_params = { search_param => search_term }
|
29
32
|
url_params.merge!(options)
|
30
|
-
url_params
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/concern"
|
4
|
+
require "active_support/core_ext/object/blank"
|
4
5
|
|
5
6
|
module Warped
|
6
7
|
module Controllers
|
@@ -11,46 +12,40 @@ module Warped
|
|
11
12
|
include Sortable
|
12
13
|
|
13
14
|
included do
|
14
|
-
helper_method :sorted?, :
|
15
|
+
helper_method :attribute_name, :sorted?, :sorted_field?, :sortable_field?, :sort_url_params
|
15
16
|
end
|
16
17
|
|
18
|
+
# @see Sortable#sort
|
17
19
|
def sort(...)
|
18
20
|
@sorted = true
|
19
21
|
|
20
22
|
super
|
21
23
|
end
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
+
# @param parameter_name [String]
|
26
|
+
# @return [Boolean]
|
27
|
+
def sorted_field?(parameter_name)
|
28
|
+
current_action_sort_value.parameter_name == parameter_name.to_s
|
25
29
|
end
|
26
30
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
+
# @param parameter_name [String]
|
32
|
+
# @return [Boolean] Whether the parameter_name is sortable.
|
33
|
+
def sortable_field?(parameter_name)
|
34
|
+
current_action_sorts.any? { |sort| sort.parameter_name == parameter_name.to_s }
|
31
35
|
end
|
32
36
|
|
33
|
-
|
34
|
-
|
37
|
+
# @return [Boolean] Whether the current action is sorted.
|
38
|
+
def sorted?
|
39
|
+
@sorted ||= false
|
35
40
|
end
|
36
41
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
param_sort_key = mapped_sort_field_param.presence || sort_field_param.presence
|
45
|
-
|
46
|
-
return [] unless param_sort_key.present?
|
47
|
-
|
48
|
-
[
|
49
|
-
{
|
50
|
-
key: param_sort_key,
|
51
|
-
value: sort_direction
|
52
|
-
}
|
53
|
-
]
|
42
|
+
# @return [Hash] The sort_url_params
|
43
|
+
def sort_url_params(**options)
|
44
|
+
url_params = {
|
45
|
+
sort_key: current_action_sort_value.parameter_name,
|
46
|
+
sort_direction: current_action_sort_value.direction
|
47
|
+
}
|
48
|
+
url_params.merge!(options)
|
54
49
|
end
|
55
50
|
end
|
56
51
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "active_support/concern"
|
4
4
|
require "active_support/core_ext/class/attribute"
|
5
|
+
require "active_support/core_ext/object/blank"
|
5
6
|
|
6
7
|
module Warped
|
7
8
|
module Controllers
|
@@ -35,7 +36,7 @@ module Warped
|
|
35
36
|
# class UsersController < ApplicationController
|
36
37
|
# include Sortable
|
37
38
|
#
|
38
|
-
# sortable_by :name, :created_at, 'accounts.referrals_count' => 'referrals'
|
39
|
+
# sortable_by :name, :created_at, 'accounts.referrals_count' => { alias_name: 'referrals' }
|
39
40
|
#
|
40
41
|
# def index
|
41
42
|
# scope = sort(User.joins(:account))
|
@@ -55,60 +56,79 @@ module Warped
|
|
55
56
|
extend ActiveSupport::Concern
|
56
57
|
|
57
58
|
included do
|
58
|
-
class_attribute :
|
59
|
-
class_attribute :
|
60
|
-
class_attribute :
|
61
|
-
|
59
|
+
class_attribute :sorts, default: []
|
60
|
+
class_attribute :default_sort, default: Sort.new("id")
|
61
|
+
class_attribute :default_sort_direction, default: "desc"
|
62
|
+
|
63
|
+
attr_reader :current_action_sorts
|
64
|
+
|
65
|
+
helper_method :current_action_sorts, :current_action_sort_value, :default_sort, :default_sort_direction
|
66
|
+
|
67
|
+
rescue_from Sort::DirectionError, with: :render_invalid_sort_direction
|
62
68
|
end
|
63
69
|
|
64
70
|
class_methods do
|
65
71
|
# @param keys [Array<Symbol,String>]
|
66
72
|
# @param mapped_keys [Hash<Symbol,String>]
|
67
73
|
def sortable_by(*keys, **mapped_keys)
|
68
|
-
self.
|
69
|
-
|
74
|
+
self.sorts = keys.map do |field|
|
75
|
+
Warped::Sort.new(field)
|
76
|
+
end
|
77
|
+
|
78
|
+
self.sorts += mapped_keys.map do |field, opts|
|
79
|
+
Warped::Sort.new(field, alias_name: opts[:alias_name])
|
80
|
+
end
|
81
|
+
|
82
|
+
return if self.sorts.any? { |sort| sort.name == default_sort.name }
|
83
|
+
|
84
|
+
self.sorts.push(default_sort)
|
70
85
|
end
|
71
86
|
end
|
72
87
|
|
73
88
|
# @param scope [ActiveRecord::Relation] The scope to sort.
|
74
|
-
# @param
|
75
|
-
# @param sort_direction [String, Symbol] The sort direction.
|
89
|
+
# @param sort_conditions [Array<Warped::Sort::Base>|nil] The sort conditions.
|
76
90
|
# @return [ActiveRecord::Relation]
|
77
|
-
def sort(scope,
|
78
|
-
|
79
|
-
|
80
|
-
validate_sort_key!
|
91
|
+
def sort(scope, sort_conditions: nil)
|
92
|
+
action_sorts = sort_conditions.presence || sorts
|
93
|
+
@current_action_sorts = action_sorts
|
81
94
|
|
82
|
-
Queries::Sort.call(scope, sort_key
|
95
|
+
Queries::Sort.call(scope, sort_key: current_action_sort_value.name,
|
96
|
+
sort_direction: current_action_sort_value.direction)
|
83
97
|
end
|
84
98
|
|
85
|
-
|
99
|
+
# @return [Warped::Sort::Value] The current sort value.
|
100
|
+
def current_action_sort_value
|
101
|
+
@current_action_sort_value ||= begin
|
102
|
+
sort_obj = current_action_sorts.find do |sort|
|
103
|
+
params[:sort_key] == sort.parameter_name
|
104
|
+
end
|
86
105
|
|
87
|
-
|
88
|
-
|
89
|
-
|
106
|
+
if sort_obj.present?
|
107
|
+
Sort::Value.new(sort_obj, params[:sort_direction] || default_sort_direction)
|
108
|
+
else
|
109
|
+
Sort::Value.new(default_sort, default_sort_direction)
|
110
|
+
end
|
111
|
+
end
|
90
112
|
end
|
91
113
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
114
|
+
protected
|
115
|
+
|
116
|
+
# @param exception [Sort::DirectionError]
|
117
|
+
def render_invalid_sort_direction(exception)
|
118
|
+
render json: { error: exception.message }, status: :bad_request
|
96
119
|
end
|
97
120
|
|
98
121
|
private
|
99
122
|
|
100
|
-
|
101
|
-
|
123
|
+
# @return [Warped::Sort] The current sort object.
|
124
|
+
def current_sort
|
125
|
+
@current_sort ||= begin
|
126
|
+
sort_obj = sorts.find do |sort|
|
127
|
+
params[:sort_key] == sort.parameter_name
|
128
|
+
end
|
102
129
|
|
103
|
-
|
104
|
-
|
105
|
-
raise ActionController::BadRequest, message
|
106
|
-
end
|
107
|
-
|
108
|
-
def valid_sort_key?
|
109
|
-
sort_key == default_sort_key.to_s ||
|
110
|
-
sort_fields.include?(sort_key) ||
|
111
|
-
mapped_sort_fields[sort_key].present?
|
130
|
+
sort_obj.presence || Warped::Sort.new(default_sort_key)
|
131
|
+
end
|
112
132
|
end
|
113
133
|
end
|
114
134
|
end
|
@@ -31,6 +31,8 @@ module Warped
|
|
31
31
|
}
|
32
32
|
end
|
33
33
|
|
34
|
+
# @param options [Hash] Additional hash of options to include in the tabulation url_params
|
35
|
+
# @return [Hash] The tabulation url_params
|
34
36
|
def tabulate_url_params(**options)
|
35
37
|
base = paginate_url_params
|
36
38
|
base.merge!(search_url_params)
|
@@ -41,6 +43,8 @@ module Warped
|
|
41
43
|
base.tap(&:compact_blank!)
|
42
44
|
end
|
43
45
|
|
46
|
+
# @param options [Hash] Additional hash of options to include in the tabulation query
|
47
|
+
# @return [String] The tabulation query string
|
44
48
|
def tabulate_query(**options)
|
45
49
|
tabulate_url_params(**options).to_query
|
46
50
|
end
|
@@ -13,7 +13,7 @@ module Warped
|
|
13
13
|
# class UsersController < ApplicationController
|
14
14
|
# include Tabulatable
|
15
15
|
#
|
16
|
-
# tabulatable_by :
|
16
|
+
# tabulatable_by :email, :age, 'posts.created_at', 'posts.id' => { alias_name: 'post_id', kind: :integer }
|
17
17
|
#
|
18
18
|
# def index
|
19
19
|
# users = User.left_joins(:posts).group(:id)
|
@@ -31,8 +31,8 @@ module Warped
|
|
31
31
|
# class PostsController < ApplicationController
|
32
32
|
# include Tabulatable
|
33
33
|
#
|
34
|
-
# tabulatable_by :title, :content, :created_at, user: 'users.name'
|
35
|
-
# filterable_by :created_at, user: 'users.name'
|
34
|
+
# tabulatable_by :title, :content, :created_at, user: { alias_name: 'users.name' }
|
35
|
+
# filterable_by :created_at, user: { alias_name: 'users.name' }
|
36
36
|
#
|
37
37
|
# def index
|
38
38
|
# posts = Post.left_joins(:user).group(:id)
|
@@ -50,16 +50,13 @@ module Warped
|
|
50
50
|
|
51
51
|
included do
|
52
52
|
class_attribute :tabulate_fields, default: []
|
53
|
-
class_attribute :mapped_tabulate_fields, default:
|
53
|
+
class_attribute :mapped_tabulate_fields, default: {}
|
54
54
|
end
|
55
55
|
|
56
56
|
class_methods do
|
57
57
|
def tabulatable_by(*keys, **mapped_keys)
|
58
|
-
|
59
|
-
|
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?
|
58
|
+
filterable_by(*keys, **mapped_keys) if filters.empty?
|
59
|
+
sortable_by(*keys, **mapped_keys) if sorts.empty?
|
63
60
|
end
|
64
61
|
end
|
65
62
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails"
|
4
|
+
|
5
|
+
module Warped
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
isolate_namespace Warped
|
8
|
+
|
9
|
+
initializer "warped.assets" do
|
10
|
+
if Rails.application.config.respond_to?(:assets)
|
11
|
+
Rails.application.config.assets.precompile += %w[warped_manifest.js]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
initializer "warped.importmap", before: "importmap" do |app|
|
16
|
+
app.config.importmap.paths << Engine.root.join("config/importmap.rb") if Rails.application.respond_to?(:importmap)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -25,7 +25,7 @@ module Warped
|
|
25
25
|
# @see RELATIONS
|
26
26
|
# To see the list of available relations, check the +RELATIONS+ constant.
|
27
27
|
class Filter
|
28
|
-
RELATIONS =
|
28
|
+
RELATIONS = ::Warped::Filter::Base::RELATIONS.freeze
|
29
29
|
|
30
30
|
# @param scope [ActiveRecord::Relation] the scope to filter
|
31
31
|
# @param filter_conditions [Array<Hash>] the conditions to filter by
|
@@ -85,11 +85,11 @@ module Warped
|
|
85
85
|
when "is_not_null"
|
86
86
|
scope.where.not(field => nil)
|
87
87
|
when "gt"
|
88
|
-
scope.where.not(field =>
|
88
|
+
scope.where.not(field => ..value)
|
89
89
|
when "gte"
|
90
90
|
scope.where(field => value..)
|
91
91
|
when "lt"
|
92
|
-
scope.where(field =>
|
92
|
+
scope.where.not(field => value..)
|
93
93
|
when "lte"
|
94
94
|
scope.where(field => ..value)
|
95
95
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/module/delegation"
|
4
|
+
|
5
|
+
module Warped
|
6
|
+
module Table
|
7
|
+
class Action
|
8
|
+
attr_reader :options
|
9
|
+
|
10
|
+
delegate :[], to: :options
|
11
|
+
|
12
|
+
# @param name [String | Symbol | Proc] The name of the action
|
13
|
+
# @param path [String | Symbol | Proc] The path of the action
|
14
|
+
def initialize(name, path, **options)
|
15
|
+
@name = name
|
16
|
+
@path = path
|
17
|
+
@options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
def path(...)
|
21
|
+
return @path.call(...) if @path.is_a?(Proc)
|
22
|
+
|
23
|
+
@path
|
24
|
+
end
|
25
|
+
|
26
|
+
def name(...)
|
27
|
+
return @name.call(...) if @name.is_a?(Proc)
|
28
|
+
|
29
|
+
@name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/blank"
|
4
|
+
require "active_support/core_ext/string/inflections"
|
5
|
+
|
6
|
+
module Warped
|
7
|
+
module Table
|
8
|
+
class Column
|
9
|
+
attr_reader :parameter_name, :method, :options
|
10
|
+
|
11
|
+
# @param parameter_name [String] The parameter name to be used by the column
|
12
|
+
# @param display_name [String] The display name of the column
|
13
|
+
# @param method [String, Symbol, Proc] The method to be called on the record to get the content of the column
|
14
|
+
def initialize(parameter_name, display_name = nil, method: nil, **options)
|
15
|
+
@parameter_name = parameter_name
|
16
|
+
@display_name = display_name
|
17
|
+
@options = options
|
18
|
+
@method = method.presence || parameter_name
|
19
|
+
end
|
20
|
+
|
21
|
+
def display_name
|
22
|
+
@display_name.presence || parameter_name.to_s.humanize
|
23
|
+
end
|
24
|
+
|
25
|
+
def content_for(record)
|
26
|
+
if method.is_a?(Proc)
|
27
|
+
method.call(record)
|
28
|
+
else
|
29
|
+
record.public_send(method)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/warped/version.rb
CHANGED
data/lib/warped.rb
CHANGED
@@ -28,5 +28,6 @@ loader = Zeitwerk::Loader.for_gem
|
|
28
28
|
loader.ignore("#{__dir__}/generators")
|
29
29
|
loader.ignore("lib/warped/railtie.rb") unless defined?(Rails::Railtie)
|
30
30
|
loader.collapse("#{__dir__}/warped/emails/components")
|
31
|
+
loader.collapse("#{__dir__}/warped/api")
|
31
32
|
loader.setup
|
32
33
|
loader.eager_load
|
data/warped.gemspec
CHANGED
@@ -28,6 +28,6 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
29
|
spec.require_paths = ["lib"]
|
30
30
|
|
31
|
-
spec.add_dependency "rails", ">=
|
31
|
+
spec.add_dependency "rails", ">= 7.1.0"
|
32
32
|
spec.add_dependency "zeitwerk", ">= 2.4"
|
33
33
|
end
|