warped 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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