warped 0.1.0 → 1.0.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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +25 -0
  3. data/Gemfile +0 -2
  4. data/Gemfile.lock +25 -19
  5. data/README.md +116 -270
  6. data/app/assets/config/warped_manifest.js +2 -0
  7. data/app/assets/javascript/warped/controllers/filter_controller.js +76 -0
  8. data/app/assets/javascript/warped/controllers/filters_controller.js +21 -0
  9. data/app/assets/javascript/warped/index.js +2 -0
  10. data/app/assets/stylesheets/warped/application.css +15 -0
  11. data/app/assets/stylesheets/warped/base.css +23 -0
  12. data/app/assets/stylesheets/warped/filters.css +115 -0
  13. data/app/assets/stylesheets/warped/pagination.css +74 -0
  14. data/app/assets/stylesheets/warped/search.css +33 -0
  15. data/app/assets/stylesheets/warped/table.css +114 -0
  16. data/app/views/warped/_actions.html.erb +9 -0
  17. data/app/views/warped/_cell.html.erb +3 -0
  18. data/app/views/warped/_column.html.erb +35 -0
  19. data/app/views/warped/_filters.html.erb +21 -0
  20. data/app/views/warped/_hidden_fields.html.erb +19 -0
  21. data/app/views/warped/_pagination.html.erb +34 -0
  22. data/app/views/warped/_row.html.erb +19 -0
  23. data/app/views/warped/_search.html.erb +21 -0
  24. data/app/views/warped/_table.html.erb +52 -0
  25. data/app/views/warped/filters/_filter.html.erb +40 -0
  26. data/config/importmap.rb +3 -0
  27. data/docs/controllers/FILTERABLE.md +193 -0
  28. data/docs/controllers/PAGEABLE.md +70 -0
  29. data/docs/controllers/README.md +8 -0
  30. data/docs/controllers/SEARCHABLE.md +95 -0
  31. data/docs/controllers/SORTABLE.md +94 -0
  32. data/docs/controllers/TABULATABLE.md +28 -0
  33. data/docs/controllers/views/PARTIALS.md +285 -0
  34. data/docs/jobs/README.md +22 -0
  35. data/docs/services/README.md +81 -0
  36. data/lib/generators/warped/install_generator.rb +1 -1
  37. data/lib/warped/api/filter/base/value.rb +52 -0
  38. data/lib/warped/api/filter/base.rb +84 -0
  39. data/lib/warped/api/filter/boolean.rb +41 -0
  40. data/lib/warped/api/filter/date.rb +26 -0
  41. data/lib/warped/api/filter/date_time.rb +32 -0
  42. data/lib/warped/api/filter/decimal.rb +31 -0
  43. data/lib/warped/api/filter/factory.rb +38 -0
  44. data/lib/warped/api/filter/integer.rb +38 -0
  45. data/lib/warped/api/filter/string.rb +25 -0
  46. data/lib/warped/api/filter/time.rb +25 -0
  47. data/lib/warped/api/filter.rb +14 -0
  48. data/lib/warped/api/sort/value.rb +40 -0
  49. data/lib/warped/api/sort.rb +65 -0
  50. data/lib/warped/controllers/filterable/ui.rb +46 -0
  51. data/lib/warped/controllers/filterable.rb +79 -42
  52. data/lib/warped/controllers/pageable/ui.rb +70 -0
  53. data/lib/warped/controllers/pageable.rb +11 -11
  54. data/lib/warped/controllers/searchable/ui.rb +37 -0
  55. data/lib/warped/controllers/searchable.rb +2 -0
  56. data/lib/warped/controllers/sortable/ui.rb +53 -0
  57. data/lib/warped/controllers/sortable.rb +53 -33
  58. data/lib/warped/controllers/tabulatable/ui.rb +54 -0
  59. data/lib/warped/controllers/tabulatable.rb +13 -27
  60. data/lib/warped/emails/components/align.rb +21 -0
  61. data/lib/warped/emails/components/base.rb +116 -0
  62. data/lib/warped/emails/components/button.rb +58 -0
  63. data/lib/warped/emails/components/divider.rb +15 -0
  64. data/lib/warped/emails/components/heading.rb +65 -0
  65. data/lib/warped/emails/components/layouts/columns.rb +36 -0
  66. data/lib/warped/emails/components/layouts/cta.rb +38 -0
  67. data/lib/warped/emails/components/layouts/main.rb +34 -0
  68. data/lib/warped/emails/components/link.rb +36 -0
  69. data/lib/warped/emails/components/spacer.rb +15 -0
  70. data/lib/warped/emails/components/stepper.rb +104 -0
  71. data/lib/warped/emails/components/table.rb +37 -0
  72. data/lib/warped/emails/components/text.rb +67 -0
  73. data/lib/warped/emails/helpers.rb +26 -0
  74. data/lib/warped/emails/slottable.rb +61 -0
  75. data/lib/warped/emails/styleable.rb +160 -0
  76. data/lib/warped/engine.rb +19 -0
  77. data/lib/warped/queries/filter.rb +32 -12
  78. data/lib/warped/table/action.rb +33 -0
  79. data/lib/warped/table/column.rb +34 -0
  80. data/lib/warped/version.rb +1 -1
  81. data/lib/warped.rb +2 -0
  82. data/warped.gemspec +1 -1
  83. metadata +73 -7
  84. data/lib/warped/emails/.keep +0 -0
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warped
4
+ module Filter
5
+ class Factory
6
+ TYPES = %i[string integer float decimal date time date_time boolean].freeze
7
+
8
+ def self.build(kind, *args, **kwargs)
9
+ new(kind).build(*args, **kwargs)
10
+ end
11
+
12
+ def initialize(kind = nil)
13
+ @kind = kind
14
+ validate_kind!
15
+ end
16
+
17
+ def build(*args, **kwargs)
18
+ filter_class.new(*args, **kwargs)
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :kind
24
+
25
+ def validate_kind!
26
+ return if kind.nil?
27
+
28
+ raise ArgumentError, "#{kind} is not a valid filter type" unless TYPES.include?(kind)
29
+ end
30
+
31
+ def filter_class
32
+ return Base if kind.nil?
33
+
34
+ Filter.const_get(kind.to_s.camelize)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/blank"
4
+
5
+ module Warped
6
+ module Filter
7
+ class Integer < Base
8
+ RELATIONS = %w[eq neq gt gte lt lte between in not_in is_null is_not_null].freeze
9
+
10
+ def cast(value)
11
+ return if value.blank?
12
+
13
+ casted_value = case value
14
+ when ::Integer
15
+ value
16
+ when ::String
17
+ value.to_i if value.match?(/\A-?\d+\z/)
18
+ when ::Float, ::BigDecimal
19
+ value.to_i
20
+ end
21
+
22
+ check_casted_value!(casted_value)
23
+ end
24
+
25
+ def html_type
26
+ "number"
27
+ end
28
+
29
+ private
30
+
31
+ def check_casted_value!(value)
32
+ raise ValueError, "#{value} cannot be casted to #{kind}" if value.nil? && strict
33
+
34
+ value
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/blank"
4
+
5
+ module Warped
6
+ module Filter
7
+ class String < Base
8
+ RELATIONS = Queries::Filter::RELATIONS
9
+
10
+ def cast(value)
11
+ return if value.blank?
12
+
13
+ value.to_s
14
+ rescue StandardError
15
+ raise ValueError, "#{value} cannot be casted to #{kind}" if strict
16
+
17
+ nil
18
+ end
19
+
20
+ def html_type
21
+ "text"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/blank"
4
+
5
+ module Warped
6
+ module Filter
7
+ class Time < Base
8
+ RELATIONS = %w[eq neq gt gte lt lte between is_null is_not_null].freeze
9
+
10
+ def cast(value)
11
+ return if value.blank?
12
+
13
+ ::Time.parse(value)
14
+ rescue StandardError
15
+ raise ValueError, "#{value} cannot be casted to #{kind}" if strict
16
+
17
+ nil
18
+ end
19
+
20
+ def html_type
21
+ "time"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/delegation"
4
+
5
+ module Warped
6
+ module Filter
7
+ class ValueError < StandardError; end
8
+ class RelationError < StandardError; end
9
+
10
+ class << self
11
+ delegate :build, to: Factory
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/delegation"
4
+
5
+ module Warped
6
+ class Sort
7
+ class Value
8
+ attr_reader :sort
9
+
10
+ delegate :name, :alias_name, :parameter_name, to: :sort
11
+
12
+ # @param sort [Warped::Sort] The sort object
13
+ # @param direction [String] The sort direction.
14
+ def initialize(sort, direction)
15
+ @sort = sort
16
+ @direction = direction
17
+ end
18
+
19
+ # @return [String] The sort direction.
20
+ def direction
21
+ sort.direction!(@direction)
22
+ end
23
+
24
+ # @return [String] The opposite sort direction.
25
+ def opposite_direction
26
+ sort.opposite_direction(direction)
27
+ end
28
+
29
+ # @return [Boolean] Whether the sort is ascending.
30
+ def asc?
31
+ %w[asc asc_nulls_first asc_nulls_last].include?(direction)
32
+ end
33
+
34
+ # @return [Boolean] Whether the sort is descending.
35
+ def desc?
36
+ %w[desc desc_nulls_first desc_nulls_last].include?(direction)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/blank"
4
+
5
+ module Warped
6
+ class Sort
7
+ class DirectionError < StandardError; end
8
+
9
+ SORT_DIRECTIONS = %w[asc desc].freeze
10
+ NULLS_SORT_DIRECTION = %w[asc_nulls_first asc_nulls_last desc_nulls_first desc_nulls_last].freeze
11
+
12
+ attr_accessor :name, :alias_name
13
+
14
+ # @return [Array<String>] The valid sort directions.
15
+ def self.directions
16
+ @directions ||= SORT_DIRECTIONS + NULLS_SORT_DIRECTION
17
+ end
18
+
19
+ # @param name [String] The name of the sort.
20
+ # @param alias_name [String] The alias name of the sort, used for renaming the sort key in the URL params
21
+ def initialize(name, alias_name: nil)
22
+ raise ArgumentError, "name cannot be nil" if name.nil?
23
+
24
+ @name = name.to_s
25
+ @alias_name = alias_name&.to_s
26
+ end
27
+
28
+ # @return [String] The name to use in the URL params.
29
+ def parameter_name
30
+ alias_name.presence || name
31
+ end
32
+
33
+ # @param direction [String] The sort direction.
34
+ # @return [String] The sort direction.
35
+ # @raise [DirectionError] If the direction is invalid.
36
+ def direction!(direction)
37
+ raise DirectionError, "Invalid direction: #{direction}" unless valid_direction?(direction.to_s)
38
+
39
+ direction.to_s
40
+ end
41
+
42
+ # @param direction [String] The sort direction.
43
+ # @return [String] The opposite sort direction.
44
+ def opposite_direction(direction)
45
+ opposite_directions[direction]
46
+ end
47
+
48
+ private
49
+
50
+ def valid_direction?(relation)
51
+ self.class.directions.include?(relation.to_s)
52
+ end
53
+
54
+ def opposite_directions
55
+ @opposite_directions ||= {
56
+ "asc" => "desc",
57
+ "desc" => "asc",
58
+ "asc_nulls_first" => "desc_nulls_last",
59
+ "asc_nulls_last" => "desc_nulls_first",
60
+ "desc_nulls_first" => "asc_nulls_last",
61
+ "desc_nulls_last" => "asc_nulls_first"
62
+ }.freeze
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Warped
6
+ module Controllers
7
+ module Filterable
8
+ module Ui
9
+ extend ActiveSupport::Concern
10
+ include Filterable
11
+
12
+ included do
13
+ helper_method :filters, :filtered?, :filter_url_params, :filterable_by
14
+ end
15
+
16
+ # @see Filterable#filter
17
+ def filter(...)
18
+ @filtered = true
19
+
20
+ super
21
+ end
22
+
23
+ # @return [Boolean] Whether the current action is filtered.
24
+ def filtered?
25
+ @filtered ||= false
26
+ end
27
+
28
+ # @return [Hash] The filters for the current action.
29
+ def filter_url_params(**options)
30
+ url_params = {}
31
+ current_action_filter_values.each_with_object(url_params) do |filter_value, hsh|
32
+ if filter_value.value.is_a?(Array)
33
+ filter_value.value.each { |value| hsh["#{filter_value.parameter_name}[]"] = value }
34
+ else
35
+ hsh[filter_value.parameter_name] = filter_value.value
36
+ end
37
+
38
+ hsh["#{filter_value.parameter_name}.rel"] = filter_value.relation
39
+ end
40
+
41
+ url_params.merge!(options)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -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
@@ -25,7 +27,7 @@ module Warped
25
27
  # GET /users?name=John
26
28
  # GET /users?created_at=2020-01-01
27
29
  # GET /users?accounts.kind=premium
28
- # GET /users?accounts.kind=premium&accounts.kind.rel=not_eq
30
+ # GET /users?accounts.kind=premium&accounts.kind.rel=neq
29
31
  #
30
32
  # Filters can be combined:
31
33
  # GET /users?name=John&created_at=2020-01-01
@@ -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))
@@ -64,9 +66,9 @@ module Warped
64
66
  # To use the operands, you must pass a parameter appended with `.rel`, and the value of a valid operand.
65
67
  #
66
68
  # 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
69
+ # GET /users?created_at=2020-01-01&created_at.rel=gt
70
+ # GET /users?created_at=2020-01-01&created_at.rel=lt
71
+ # GET /users?created_at=2020-01-01&created_at.rel=neq
70
72
  #
71
73
  # When the operand relation requires multiple values, like +in+, +not_in+, or +between+,
72
74
  # you can pass an array of values.
@@ -74,67 +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
124
+ def filterable_by(*keys, strict: nil, **mapped_keys)
125
+ self.strict_filtering = strict unless strict.nil?
126
+
127
+ self.filters = keys.map do |field|
128
+ Warped::Filter.build(nil, field, strict:)
129
+ end
130
+
131
+ complex_filters = mapped_keys.with_indifferent_access
132
+
133
+ self.filters += complex_filters.map do |field_name, opts|
134
+ kind = opts[:kind]
135
+ alias_name = opts[:alias_name]
136
+
137
+ Warped::Filter.build(kind, field_name, alias_name:, strict:)
138
+ end
91
139
  end
92
140
  end
93
141
 
94
142
  # @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
143
+ # @param filter_conditions [Array<Warped::Filter::Base>|nil]
99
144
  # @return [ActiveRecord::Relation]
100
- def filter(scope, filter_conditions: filter_conditions(*filter_fields, *mapped_filter_fields))
101
- Warped::Queries::Filter.call(scope, filter_conditions:)
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))
102
151
  end
103
152
 
104
- # @param fields [Array<Symbol,String>]
105
153
  # @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
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"]
119
158
 
120
- private
159
+ filter_value = filter.class::Value.new(filter, raw_relation.presence || "eq", raw_value.presence)
121
160
 
122
- def filter_name(filter)
123
- filter.is_a?(Array) ? filter.first : filter
124
- end
161
+ next if filter_value.empty?
125
162
 
126
- def filter_mapped_name(filter)
127
- filter.is_a?(Array) ? filter.last : filter
163
+ filter_value
164
+ end
128
165
  end
129
166
 
130
- def filter_value(filter)
131
- param_key = filter_mapped_name(filter)
132
- params[param_key]
167
+ # @return [Array<Warped::Filter::Base>]
168
+ def current_action_filters
169
+ @current_action_filters ||= []
133
170
  end
134
171
 
135
- def filter_rel_value(filter)
136
- param_key = filter_mapped_name(filter)
137
- params["#{param_key}.rel"]
172
+ # @return [Array<Warped::Filter::Base::Value>]
173
+ def current_action_filter_values
174
+ @current_action_filter_values ||= []
138
175
  end
139
176
  end
140
177
  end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Warped
6
+ module Controllers
7
+ module Pageable
8
+ module Ui
9
+ extend ActiveSupport::Concern
10
+
11
+ include Pageable
12
+
13
+ included do
14
+ helper_method :pagination, :paginated?, :paginate_url_params
15
+ end
16
+
17
+ # @return [Hash] The paginate_url_params
18
+ def paginate_url_params(**options)
19
+ url_params = { page:, per_page: }
20
+ url_params.merge!(options)
21
+ end
22
+
23
+ # @see Pageable#pagination
24
+ # @return [Hash]
25
+ def pagination
26
+ super.tap do |hsh|
27
+ hsh[:series] = series(hsh[:page], hsh[:total_pages])
28
+ end
29
+ end
30
+
31
+ # @see Pageable#paginate
32
+ def paginate(...)
33
+ @paginated = true
34
+
35
+ super
36
+ end
37
+
38
+ # @return [Boolean] Whether the current action is paginated.
39
+ def paginated?
40
+ @paginated ||= false
41
+ end
42
+
43
+ private
44
+
45
+ # @param page [Integer]
46
+ # @param total_pages [Integer]
47
+ # @return [Array]
48
+ # the series method returns an array of page numbers and :gap symbols
49
+ # the current page is a string, the others are integers
50
+ # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
51
+ def series(page, total_pages)
52
+ current_page = [page, total_pages].min
53
+ return [] if total_pages.zero?
54
+ return ["1"] if total_pages == 1
55
+
56
+ if total_pages <= 9
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 }]
62
+ else
63
+ [1, :gap, current_page - 2, current_page - 1, current_page.to_s, current_page + 1, current_page + 2, :gap,
64
+ total_pages]
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -14,7 +14,7 @@ module Warped
14
14
  #
15
15
  # def index
16
16
  # scope = paginate(User.all)
17
- # render json: scope, root: :users, meta: page_info
17
+ # render json: scope, root: :users, meta: pagination
18
18
  # end
19
19
  # end
20
20
  #
@@ -33,7 +33,7 @@ module Warped
33
33
  #
34
34
  # def index
35
35
  # scope = paginate(User.all)
36
- # render json: scope, root: :users, meta: page_info
36
+ # render json: scope, root: :users, meta: pagination
37
37
  # end
38
38
  # end
39
39
  #
@@ -44,7 +44,7 @@ module Warped
44
44
  #
45
45
  # def index
46
46
  # scope = paginate(User.all)
47
- # render json: scope, root: :users, meta: page_info
47
+ # render json: scope, root: :users, meta: pagination
48
48
  # end
49
49
  #
50
50
  # private
@@ -62,17 +62,17 @@ module Warped
62
62
  #
63
63
  # def index
64
64
  # scope = paginate(User.all, per_page: 50)
65
- # render json: scope, root: :users, meta: page_info
65
+ # render json: scope, root: :users, meta: pagination
66
66
  # end
67
67
  #
68
68
  # def other_index
69
69
  # # The default per_page value is used.
70
70
  # scope = paginate(User.all)
71
- # render json: scope, root: :users, meta: page_info
71
+ # render json: scope, root: :users, meta: pagination
72
72
  # end
73
73
  # end
74
74
  #
75
- # The pagination metadata can be accessed by calling the +page_info+ method.
75
+ # The pagination metadata can be accessed by calling the +pagination+ method.
76
76
  # It includes the following keys:
77
77
  # - +total_count+: The total number of records in the collection.
78
78
  # - +total_pages+: The total number of pages.
@@ -80,7 +80,7 @@ module Warped
80
80
  # - +prev_page+: The previous page number.
81
81
  # - +page+: The current page number.
82
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
83
+ # *Warning*: The +pagination+ method will raise an +ArgumentError+ if the method +paginate+ was not
84
84
  # called within the action.
85
85
  module Pageable
86
86
  extend ActiveSupport::Concern
@@ -96,7 +96,7 @@ module Warped
96
96
  # @param per_page [String,Integer,nil] The number of records per page.
97
97
  # @return [ActiveRecord::Relation] The paginated scope.
98
98
  def paginate(scope, page: self.page, per_page: self.per_page)
99
- @page_info, paginated_scope = Queries::Paginate.call(scope, page:, per_page:)
99
+ @pagination, paginated_scope = Queries::Paginate.call(scope, page:, per_page:)
100
100
  paginated_scope
101
101
  end
102
102
 
@@ -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.
@@ -120,8 +120,8 @@ module Warped
120
120
  # @return [Hash] Metadata about the pagination.
121
121
  # @raise [ArgumentError] If pagination was not performed.
122
122
  # @see Warped::Queries::Paginate#metadata
123
- def page_info
124
- return @page_info if @page_info.present?
123
+ def pagination
124
+ return @pagination if @pagination.present?
125
125
 
126
126
  raise ActionController::BadRequest, "Pagination was not performed"
127
127
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Warped
6
+ module Controllers
7
+ module Searchable
8
+ module Ui
9
+ extend ActiveSupport::Concern
10
+
11
+ include Searchable
12
+
13
+ included do
14
+ helper_method :searched?, :search_url_params
15
+ end
16
+
17
+ # @see Searchable#search
18
+ def search(...)
19
+ @searched = true
20
+
21
+ super
22
+ end
23
+
24
+ # @return [Boolean] Whether the current action is searched.
25
+ def searched?
26
+ @searched ||= false
27
+ end
28
+
29
+ # @return [Hash] The search_url_params
30
+ def search_url_params(**options)
31
+ url_params = { search_param => search_term }
32
+ url_params.merge!(options)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -115,6 +115,8 @@ module Warped
115
115
  included do
116
116
  class_attribute :model_search_scope, default: :search
117
117
  class_attribute :search_param, default: :q
118
+
119
+ helper_method :search_term, :search_param, :model_search_scope
118
120
  end
119
121
 
120
122
  class_methods do