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.
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 +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 :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.1"
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"
32
32
  spec.add_dependency "zeitwerk", ">= 2.4"
33
33
  end