warped 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/Gemfile.lock +2 -2
  4. data/README.md +104 -0
  5. data/app/assets/config/warped_manifest.js +2 -0
  6. data/app/assets/javascript/warped/controllers/filter_controller.js +76 -0
  7. data/app/assets/javascript/warped/controllers/filters_controller.js +21 -0
  8. data/app/assets/javascript/warped/index.js +2 -0
  9. data/app/assets/stylesheets/warped/application.css +15 -0
  10. data/app/assets/stylesheets/warped/base.css +23 -0
  11. data/app/assets/stylesheets/warped/filters.css +115 -0
  12. data/app/assets/stylesheets/warped/pagination.css +74 -0
  13. data/app/assets/stylesheets/warped/search.css +33 -0
  14. data/app/assets/stylesheets/warped/table.css +114 -0
  15. data/app/views/warped/_actions.html.erb +9 -0
  16. data/app/views/warped/_cell.html.erb +3 -0
  17. data/app/views/warped/_column.html.erb +35 -0
  18. data/app/views/warped/_filters.html.erb +21 -0
  19. data/app/views/warped/_hidden_fields.html.erb +19 -0
  20. data/app/views/warped/_pagination.html.erb +34 -0
  21. data/app/views/warped/_row.html.erb +19 -0
  22. data/app/views/warped/_search.html.erb +21 -0
  23. data/app/views/warped/_table.html.erb +52 -0
  24. data/app/views/warped/filters/_filter.html.erb +40 -0
  25. data/config/importmap.rb +3 -0
  26. data/docs/controllers/FILTERABLE.md +82 -3
  27. data/docs/controllers/views/PARTIALS.md +285 -0
  28. data/lib/warped/api/filter/base/value.rb +52 -0
  29. data/lib/warped/api/filter/base.rb +84 -0
  30. data/lib/warped/api/filter/boolean.rb +41 -0
  31. data/lib/warped/api/filter/date.rb +26 -0
  32. data/lib/warped/api/filter/date_time.rb +32 -0
  33. data/lib/warped/api/filter/decimal.rb +31 -0
  34. data/lib/warped/api/filter/factory.rb +38 -0
  35. data/lib/warped/api/filter/integer.rb +38 -0
  36. data/lib/warped/api/filter/string.rb +25 -0
  37. data/lib/warped/api/filter/time.rb +25 -0
  38. data/lib/warped/api/filter.rb +14 -0
  39. data/lib/warped/api/sort/value.rb +40 -0
  40. data/lib/warped/api/sort.rb +65 -0
  41. data/lib/warped/controllers/filterable/ui.rb +10 -32
  42. data/lib/warped/controllers/filterable.rb +75 -42
  43. data/lib/warped/controllers/pageable/ui.rb +13 -3
  44. data/lib/warped/controllers/pageable.rb +1 -1
  45. data/lib/warped/controllers/searchable/ui.rb +3 -1
  46. data/lib/warped/controllers/sortable/ui.rb +21 -26
  47. data/lib/warped/controllers/sortable.rb +53 -33
  48. data/lib/warped/controllers/tabulatable/ui.rb +4 -0
  49. data/lib/warped/controllers/tabulatable.rb +6 -9
  50. data/lib/warped/engine.rb +19 -0
  51. data/lib/warped/queries/filter.rb +3 -3
  52. data/lib/warped/table/action.rb +33 -0
  53. data/lib/warped/table/column.rb +34 -0
  54. data/lib/warped/version.rb +1 -1
  55. data/lib/warped.rb +1 -0
  56. data/warped.gemspec +1 -1
  57. metadata +44 -6
@@ -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 :filter_fields, default: []
82
- class_attribute :mapped_filter_fields, default: []
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.filter_fields = keys
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
- # @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
127
+ self.filters = keys.map do |field|
128
+ Warped::Filter.build(nil, field, strict:)
129
+ end
103
130
 
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)
131
+ complex_filters = mapped_keys.with_indifferent_access
109
132
 
110
- next if filter_value(filter_opt).blank? && %w[is_null is_not_null].exclude?(filter_rel_value(filter_opt))
133
+ self.filters += complex_filters.map do |field_name, opts|
134
+ kind = opts[:kind]
135
+ alias_name = opts[:alias_name]
111
136
 
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" : "eq")
116
- }
137
+ Warped::Filter.build(kind, field_name, alias_name:, strict:)
138
+ end
117
139
  end
118
140
  end
119
141
 
120
- def filterable_by
121
- @filterable_by ||= self.class.filter_fields.concat(self.class.mapped_filter_fields)
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
- private
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
- def filter_name(filter)
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
- def filter_mapped_name(filter)
131
- filter.is_a?(Array) ? filter.last : filter
161
+ next if filter_value.empty?
162
+
163
+ filter_value
164
+ end
132
165
  end
133
166
 
134
- def filter_value(filter)
135
- param_key = filter_mapped_name(filter)
136
- params[param_key]
167
+ # @return [Array<Warped::Filter::Base>]
168
+ def current_action_filters
169
+ @current_action_filters ||= []
137
170
  end
138
171
 
139
- def filter_rel_value(filter)
140
- param_key = filter_mapped_name(filter)
141
- params["#{param_key}.rel"]
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..(page - 1)).to_a + [page.to_s] + ((page + 1)..total_pages).to_a
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, page - 2, page - 1, page.to_s, page + 1, page + 2, :gap, total_pages]
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 || self.class.default_per_page
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?, :sort_url_params, :sort_key, :sort_direction
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
- def sorted?
24
- @sorted ||= false
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
- def sort_url_params(**options)
28
- url_params = { sort_key:, sort_direction: }
29
- url_params.merge!(options)
30
- url_params
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
- def sorts
34
- sort_fields + mapped_sort_fields.keys
37
+ # @return [Boolean] Whether the current action is sorted.
38
+ def sorted?
39
+ @sorted ||= false
35
40
  end
36
41
 
37
- def current_sorts
38
- return [] unless params[:sort_key].present?
39
-
40
- mapped_sort_field_param = mapped_sort_fields.value?(params[:sort_key]) ? params[:sort_key] : nil
41
- sort_field_param = sort_fields.find do |field|
42
- field == params[:sort_key]
43
- end
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 :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
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.sort_fields = keys.map(&:to_s)
69
- self.mapped_sort_fields = mapped_keys.with_indifferent_access
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 sort_key [String, Symbol] The sort key.
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, sort_key: self.sort_key, sort_direction: self.sort_direction)
78
- return scope unless sort_key && sort_direction
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:, sort_direction:)
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
- protected
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
- # @return [Symbol] The sort direction.
88
- def sort_direction
89
- @sort_direction ||= params[:sort_direction] || default_sort_direction
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
- def sort_key
93
- @sort_key ||= mapped_sort_fields.key(params[:sort_key]).presence ||
94
- params[:sort_key] ||
95
- default_sort_key.to_s
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
- def validate_sort_key!
101
- return if valid_sort_key?
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
- 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.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 :name, :email, :age, 'posts.created_at', 'posts.id' => 'post_id'
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
- 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?
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 = %w[eq neq gt gte lt lte between in not_in starts_with ends_with contains is_null is_not_null].freeze
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 => ...value)
88
+ scope.where.not(field => ..value)
89
89
  when "gte"
90
90
  scope.where(field => value..)
91
91
  when "lt"
92
- scope.where(field => ...value)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Warped
4
- VERSION = "0.2.0"
4
+ VERSION = "1.0.0"
5
5
  end
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", ">= 6.0", "< 8.0"
31
+ spec.add_dependency "rails", ">= 7.1.0", "<= 8.0"
32
32
  spec.add_dependency "zeitwerk", ">= 2.4"
33
33
  end