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,285 @@
1
+ # Using Warped built-in view partials
2
+
3
+ In order to use the built in Warped view partials, you need to include the `Warped::Controllers::<WarpedConcern>::Ui` module in your controller.
4
+
5
+ ## Warped::Controllers::Filterable::Ui
6
+ ```ruby
7
+ # app/controllers/users_controller.rb
8
+ class UsersController < ApplicationController
9
+ include Warped::Controllers::Filterable::Ui
10
+
11
+ def index
12
+ @users = filter(User.all)
13
+ end
14
+ end
15
+ ```
16
+
17
+ ```erb
18
+ <!-- app/views/users/index.html.erb -->
19
+
20
+ <%= render "warped/filters", path: users_path, turbo_action: "replace" %>
21
+
22
+ <% @users.each do |user| %>
23
+ <p><%= user.first_name %>, <%= user.last_name %></p>
24
+ <% end %>
25
+ ```
26
+
27
+ The `warped/_filters` partial uses [strict locals](https://edgeguides.rubyonrails.org/action_view_overview.html#strict-locals), and it accepts:
28
+ - `path` - the path to the controller action to which the filters will be applied
29
+ - `turbo_action` - the Turbo action to be used when submitting the form (replace/advance)
30
+
31
+ The partial also accepts the following optional locals:
32
+ - `class`: the class to be applied to the form
33
+ - `data`: a hash of data attributes to be applied to the form
34
+ Any other locals that are passed to the partial will be passed to the `form_with` helper.
35
+
36
+ The `warped/_filters` partial will also render a set of hidden fields to store the sortable and searchable values, if the controller action called `#search` or `#sort`
37
+
38
+ ## Warped::Controllers::Pageable::Ui
39
+ ```ruby
40
+ # app/controllers/users_controller.rb
41
+ class UsersController < ApplicationController
42
+ include Warped::Controllers::Pageable::Ui
43
+
44
+ def index
45
+ @users = paginate(User.all)
46
+ end
47
+ end
48
+ ```
49
+
50
+ ```erb
51
+ <!-- app/views/users/index.html.erb -->
52
+
53
+ <% @users.each do |user| %>
54
+ <p><%= user.first_name %>, <%= user.last_name %></p>
55
+ <% end %>
56
+
57
+ <%= render "warped/pagination", path: users_path, turbo_action: "replace" %>
58
+ ```
59
+
60
+ The `warped/_pagination` partial uses [strict locals](https://edgeguides.rubyonrails.org/action_view_overview.html#strict-locals), and it accepts:
61
+ - `path` - the path to the controller action to which the pagination will be applied
62
+ - `turbo_action` - the Turbo action to be used when submitting the form (replace/advance)
63
+
64
+ Any other locals that are passed to the partial will be passed to the pagination <nav> tag.
65
+
66
+ The `warped/_pagination` partial will also render a set of hidden fields for each button, to store the sortable, searchable and filterable values, if the controller action called `#search`, `#sort` or `#filter`.
67
+
68
+ ## Warped::Controllers::Searchable::Ui
69
+ ```ruby
70
+ # app/models/user.rb
71
+
72
+ class User < ApplicationRecord
73
+
74
+ scope :search, ->(query) {
75
+ where("first_name ILIKE :query OR last_name ILIKE :query", query: "%#{query}%")
76
+ }
77
+ end
78
+ ```
79
+
80
+ ```ruby
81
+ # app/controllers/users_controller.rb
82
+ class UsersController < ApplicationController
83
+ include Warped::Controllers::Searchable::Ui
84
+
85
+ def index
86
+ @users = search(User.all)
87
+ end
88
+ end
89
+ ```
90
+
91
+ ```erb
92
+ <!-- app/views/users/index.html.erb -->
93
+
94
+ <%= render "warped/search", path: users_path, turbo_action: "replace" %>
95
+
96
+ <% @users.each do |user| %>
97
+ <p><%= user.first_name %>, <%= user.last_name %></p>
98
+ <% end %>
99
+ ```
100
+
101
+ The `warped/_search` partial uses [strict locals](https://edgeguides.rubyonrails.org/action_view_overview.html#strict-locals), and it accepts:
102
+ - `path` - the path to the controller action to which the search will be applied
103
+ - `turbo_action` - the Turbo action to be used when submitting the form (replace/advance)
104
+ Any other locals that are passed to the partial will be passed to the search `form_with` helper.
105
+
106
+ The `warped/_search` partial will also render a set of hidden fields to store the sortable, filterable and pagination values, if the controller action called `#sort`, `#filter` or `#paginate`.
107
+
108
+ ## Warped::Controllers::Tabulatable::Ui
109
+ ```ruby
110
+ # app/controllers/users_controller.rb
111
+ class UsersController < ApplicationController
112
+ include Warped::Controllers::Tabulatable::Ui
113
+
114
+ def index
115
+ @users = tabulate(User.all)
116
+ end
117
+
118
+ def show
119
+ @user = User.find(params[:id])
120
+ end
121
+
122
+ def destroy
123
+ User.find(params[:id]).destroy
124
+ redirect_to users_path
125
+ end
126
+ end
127
+ ```
128
+
129
+ ```erb
130
+ <!-- app/views/users/index.html.erb -->
131
+
132
+ <%= render "warped/table", collection: @users, path: users_path, turbo_action: "replace",
133
+ columns: [
134
+ Warped::Table::Column.new(:first_name, "First Name"),
135
+ Warped::Table::Column.new(:last_name, "Last Name")
136
+ ],
137
+ actions: [
138
+ Warped::Table::Action.new(:show, ->(user) { user_path(user) }),
139
+ Warped::Table::Action.new(:destroy, ->(user) { user_path(user) }, data: { turbo_method: "delete", turbo_confirm: "Are you sure?" })
140
+ ] %>
141
+ ```
142
+
143
+ The `warped/_table` partial uses [strict locals](https://edgeguides.rubyonrails.org/action_view_overview.html#strict-locals), and it accepts:
144
+ - `collection` - the collection of records to be displayed
145
+ - `path` - the path to the controller action to which the table filtering/sorting/searching/pagination will be applied
146
+ - `turbo_action` - the Turbo action to be used when submitting the form (replace/advance)
147
+ - `columns` - an array of `Warped::Table::Column` objects that define the columns to be displayed
148
+ - `actions` - an array of `Warped::Table::Action` objects that define the actions to be displayed for each record
149
+ Any other locals under the keys `:table`, `:filters`, `:search` or `:pagination` are passed to the respective partials.
150
+ Example:
151
+ ```erb
152
+ <%= render "warped/table", collection: @users, path: users_path, turbo_action: "replace",
153
+ columns: [
154
+ Warped::Table::Column.new(:first_name, "First Name"),
155
+ Warped::Table::Column.new(:last_name, "Last Name")
156
+ ],
157
+ actions: [
158
+ Warped::Table::Action.new(:show, ->(user) { user_path(user) }),
159
+ Warped::Table::Action.new(:destroy, ->(user) { user_path(user) }, data: { turbo_method: "delete", turbo_confirm: "Are you sure?" })
160
+ ],
161
+ table: { class: "table" },
162
+ filters: { class: "filters" },
163
+ search: { class: "search" },
164
+ pagination: { class: "pagination" } %>
165
+ ```
166
+ This will render the table with the class `table`, and it will forward the locals:
167
+ - `class: "filters"` to the `warped/_filters` partial
168
+ - `class: "search"` to the `warped/_search` partial
169
+ - `class: "pagination"` to the `warped/_pagination` partial
170
+
171
+ ### Using the `Warped::Table::Column` class
172
+ The `Warped::Table::Column` class is used to define the columns to be displayed in the table. The initializer accepts the following arguments:
173
+ - `parameter_name` - the name of the parameter/alias_name passed to the `filterable_by`/`sortable_by`/`tabulatable_by` methods
174
+ - `display_name`(optional) - the name to be displayed in the table header
175
+ - `method`(optional) - this can be a symbol or a lambda. If the method is a symbol, it will be called on the record to get the value. If the method is a lambda, it will be called with the record as an argument.
176
+
177
+ ### Using the `Warped::Table::Action` class
178
+ The `Warped::Table::Action` class is used to define the actions to be displayed for each record in the table. The initializer accepts the following arguments:
179
+ - `name` - the name of the action. If the name is a symbol or a string, it will be used as the action name. If the name is a lambda, it will be called with the record as an argument.
180
+ - `path` - the path to the controller action to which the action will be applied. If the path is a lambda, it will be called with the record as an argument.
181
+
182
+
183
+ ## Using turbo-frames with the partials
184
+ The partials can be used with turbo-frames to provide a seamless experience, regardless of the action used in the controller.
185
+
186
+ ```ruby
187
+ # app/controllers/user_controller.rb
188
+ class UserController < ApplicationController
189
+ include Warped::Controllers::Tabulatable::Ui
190
+
191
+ tabulatable_by title: { kind: :string }, published_at: { kind: :date_time },
192
+
193
+ def show
194
+ @user = current_user
195
+ @posts = tabulate(@user.posts)
196
+ end
197
+ end
198
+ ```
199
+
200
+ ```erb
201
+ <!-- app/views/users/show.html.erb -->
202
+ <h1><%= @user.name %></h1>
203
+
204
+ <%= turbo_frame_tag dom_id(@posts) do %>
205
+ <%= render "warped/table", collection: @posts, path: user_path, turbo_action: "advance",
206
+ columns: [
207
+ Warped::Table::Column.new(:title, "Title"),
208
+ Warped::Table::Column.new(:published_at, "Published At")
209
+ ],
210
+ actions: [
211
+ <%# pass target: "_top" to the action, to escape from the turbo_frame navigation %>
212
+ Warped::Table::Action.new(:show, ->(post) { post_path(post) }, target: "_top")
213
+ ] %>
214
+ <% end %>
215
+ ```
216
+
217
+ ## Styling the partials
218
+ The partials are designed to be as unobtrusive as possible, and they can be styled using the classes passed as locals to the partials, or by modifying the partials css classes.
219
+
220
+ The css classes are in the following files:
221
+ – `app/assets/stylesheets/warped/base.css` - the base css variables
222
+ - `app/assets/stylesheets/warped/filters.css` - the css classes for the filters partial
223
+ - `app/assets/stylesheets/warped/pagination.css` - the css classes for the pagination partial
224
+ - `app/assets/stylesheets/warped/search.css` - the css classes for the search partial
225
+ - `app/assets/stylesheets/warped/table.css` - the css classes for the table partial
226
+
227
+ You can override the css classes by adding the following to your css file:
228
+ ```css
229
+ @import "warped/base.css";
230
+ @import "warped/filters.css";
231
+ @import "warped/pagination.css";
232
+ @import "warped/search.css";
233
+ @import "warped/table.css";
234
+ ```
235
+
236
+ ## Moving logic from the view to the controller
237
+ Using the "warped/_table" can introduce a lot of logic in the view. To move the logic to the controller, you can instantiate the Columns and Actions in the controller and use them in the view as instance variables.
238
+
239
+ ```ruby
240
+ # app/controllers/users_controller.rb
241
+ class UsersController < ApplicationController
242
+ include Warped::Controllers::Tabulatable::Ui
243
+
244
+ helper_method :columns_for_index, :actions_for_index
245
+
246
+ tabulatable_by first_name: { kind: :string },
247
+ last_name: { kind: :string },
248
+ email: { kind: :string },
249
+ created_at: { kind: :date_time, alias_name: :registered_at },
250
+ status: { kind: :date_time }
251
+
252
+ def index
253
+ users_query = User.all.select("users.*, CASE WHEN confirmed_at IS NULL THEN 'Inactive' ELSE 'Active' END AS status")
254
+ @users = tabulate(users_query)
255
+ end
256
+
257
+ ...
258
+
259
+ private
260
+
261
+ def columns_for_index
262
+ [
263
+ Warped::Table::Column.new(:first_name, "First Name"),
264
+ Warped::Table::Column.new(:last_name, "Last Name"),
265
+ Warped::Table::Column.new(:email, "Email"),
266
+ Warped::Table::Column.new(:registered_at, "Registered At"),
267
+ Warped::Table::Column.new(:status, "Status")
268
+ ]
269
+ end
270
+
271
+ def actions_for_index
272
+ [
273
+ Warped::Table::Action.new(:show, ->(user) { user_path(user) }),
274
+ Warped::Table::Action.new(:edit, ->(user) { edit_user_path(user) }),
275
+ Warped::Table::Action.new(:destroy, ->(user) { user_path(user) }, data: { turbo_method: "delete", turbo_confirm: "Are you sure?" })
276
+ ]
277
+ end
278
+ ```
279
+
280
+ ```erb
281
+ <!-- app/views/users/index.html.erb -->
282
+ <%= render "warped/table", collection: @users, path: users_path, turbo_action: "replace",
283
+ columns: columns_for_index,
284
+ actions: actions_for_index %>
285
+ ```
@@ -0,0 +1,22 @@
1
+ # Warped::Jobs
2
+
3
+ The gem provides a `Warped::Jobs::Base` class that can be used to create background jobs in a rails application.
4
+
5
+ ```ruby
6
+ class PrintJob < Warped::Jobs::Base
7
+ def perform
8
+ puts 'Hello, world!'
9
+ end
10
+ end
11
+ ```
12
+
13
+ Warped::Jobs::Base is a subclass of ActiveJob::Base, and can be used as a regular ActiveJob job.
14
+
15
+ The superclass can be overriden to inherit from a different job class, by changing it in the `config/initializers/warped.rb` file.
16
+
17
+ ```ruby
18
+ # config/initializers/warped.rb
19
+ Warped.configure do |config|
20
+ config.job_superclass = 'ApplicationJob'
21
+ end
22
+ ```
@@ -0,0 +1,81 @@
1
+ # Warped::Services
2
+
3
+ The gem provides a `Warped::Service::Base` class that can be used to create services in a rails application.
4
+
5
+ ```ruby
6
+ class PrintService < Warped::Service::Base
7
+ def call
8
+ puts 'Hello, world!'
9
+ end
10
+ end
11
+ ```
12
+
13
+ The `call` method is the entry point for the service. It can be overridden to provide the service's functionality.
14
+
15
+ ```ruby
16
+ class PrintService < Warped::Service::Base
17
+ def call
18
+ puts "Hello, #{name}!"
19
+ end
20
+
21
+ private
22
+
23
+ def name
24
+ 'world'
25
+ end
26
+ end
27
+ ```
28
+
29
+ The way of calling the service is by calling the `call` method on the service class.
30
+ The .call method is a class method that creates a new instance of the service, passing the arguments to the `initialize` method, and then calls the `call` method on the new instance.
31
+
32
+ ```ruby
33
+ PrintService.call # Executes new.call
34
+ ```
35
+
36
+ If you want to pass arguments to the service, you can so by defining the `initialize` method in the service class.
37
+
38
+ ```ruby
39
+ class PrintService < Warped::Service::Base
40
+ def initialize(name = 'John')
41
+ @name = name
42
+ end
43
+
44
+ def call
45
+ puts "Hello, #{@name}!"
46
+ end
47
+ end
48
+ ```
49
+
50
+ ```ruby
51
+ PrintService.call # Executes new.call, prints "Hello, John!"
52
+ PrintService.call('world') # Executes new('world').call, prints "Hello, world!"
53
+ ```
54
+
55
+ ## Using services as job classes in the background
56
+
57
+ The `Warped::Service::Base` class provides a class method `.enable_job!` that can be used to enable the service to be used as a job class.
58
+
59
+ ```ruby
60
+ class PrintService < Warped::Service::Base
61
+ enable_job!
62
+
63
+ def call
64
+ puts 'Hello, world!'
65
+ end
66
+ end
67
+ ```
68
+
69
+ The `enable_job!` method will define a `PrintService::Job` class that inherits from `Warped::Jobs::Base` and calls the `call` method on the service instance.
70
+
71
+ ```ruby
72
+ PrintService.call_later # Executes PrintService::Job.perform_later
73
+ PrintService::Job.perform_later # Executes PrintService.new.call in the background
74
+ ```
75
+
76
+ call_later and perform_later will pass the arguments to the `initialize` method of the service class, and then call the `call` method on the new instance.
77
+
78
+ ```ruby
79
+ PrintService.call_later('world') # Executes PrintService::Job.perform_later('world')
80
+ PrintService::Job.perform_later('world') # Executes PrintService.new('world').call in the background
81
+ ```
@@ -7,7 +7,7 @@ module Warped
7
7
  class InstallGenerator < Rails::Generators::Base
8
8
  source_root File.expand_path("templates", __dir__)
9
9
 
10
- def say_hello
10
+ def install
11
11
  template "initializer.rb.tt", "config/initializers/warped.rb"
12
12
  end
13
13
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/blank"
4
+ require "active_support/core_ext/module/delegation"
5
+
6
+ module Warped
7
+ module Filter
8
+ class Base
9
+ class Value
10
+ attr_reader :filter
11
+
12
+ delegate :name, :alias_name, :parameter_name, :kind, to: :filter
13
+
14
+ # @param filter [Warped::Filter::Base] The filter object
15
+ # @param relation [String] The filter relation.
16
+ # @param value [String] The filter value.
17
+ def initialize(filter, relation, value)
18
+ @filter = filter
19
+ @relation = relation
20
+ @value = value
21
+ end
22
+
23
+ # @return [String] The casted filter value.
24
+ def value
25
+ filter.cast(@value)
26
+ end
27
+
28
+ # @return [String] The validated filter relation.
29
+ def relation
30
+ filter.relation(@relation)
31
+ end
32
+
33
+ # @return [Boolean] Whether the filter is empty.
34
+ def empty?
35
+ value.nil? && !%w[is_null is_not_null].include?(relation)
36
+ end
37
+
38
+ def to_h
39
+ {
40
+ field: filter.name,
41
+ relation:,
42
+ value:
43
+ }
44
+ end
45
+
46
+ # Some filters may need to be parsed/formatted differently for the HTML input value.
47
+ # This method can be overridden in the filter value class to provide a different value.
48
+ alias html_value value
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/delegation"
4
+ require "active_support/core_ext/object/blank"
5
+ require "active_support/core_ext/string/inflections"
6
+
7
+ module Warped
8
+ module Filter
9
+ class Base
10
+ RELATIONS = %w[eq neq gt gte lt lte between in not_in starts_with ends_with contains is_null is_not_null].freeze
11
+
12
+ attr_reader :name, :strict, :alias_name, :options
13
+
14
+ delegate :[], to: :options
15
+
16
+ # @return [Symbol, nil] The filter kind.
17
+ def self.kind
18
+ filter_type = name.demodulize.underscore.to_sym
19
+
20
+ filter_type == :filter ? nil : filter_type
21
+ end
22
+
23
+ # @param name [String] The name of the filter.
24
+ # @param alias_name [String] The alias name of the filter, used for renaming the filter key in the URL params
25
+ # @param options [Hash] The filter options.
26
+ def initialize(name, strict:, alias_name: nil, **options)
27
+ raise ArgumentError, "name cannot be nil" if name.nil?
28
+
29
+ @name = name.to_s
30
+ @strict = strict
31
+ @alias_name = alias_name&.to_s
32
+ @options = options
33
+ end
34
+
35
+ # @return [Symbol, nil] The filter kind.
36
+ def kind
37
+ self.class.kind
38
+ end
39
+
40
+ # @return [Array<String>] The valid filter relations.
41
+ def relations
42
+ self.class::RELATIONS
43
+ end
44
+
45
+ # @param relation [Object] The filter relation.
46
+ # @return [Object] The casted value.
47
+ # @raise [Warped::Filter::RelationError] If the relation is invalid and strict is true.
48
+ def cast(value)
49
+ return if value.nil?
50
+
51
+ value
52
+ end
53
+
54
+ # @param relation [String] The validated filter relation.
55
+ # @return [String] The validated filter relation.
56
+ # @raise [Warped::Filter::RelationError] If the relation is invalid and strict is true.
57
+ def relation(relation)
58
+ if valid_relation?(relation)
59
+ relation
60
+ else
61
+ raise RelationError, "Invalid relation: #{relation}" unless strict
62
+
63
+ "eq"
64
+ end
65
+ end
66
+
67
+ # @return [String] The name to use in the URL params.
68
+ def parameter_name
69
+ alias_name.presence || name
70
+ end
71
+
72
+ # @return [String] The HTML input type.
73
+ def html_type
74
+ raise NotImplementedError, "#{self.class.name}#html_type not implemented"
75
+ end
76
+
77
+ private
78
+
79
+ def valid_relation?(relation)
80
+ relations.include?(relation.to_s)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warped
4
+ module Filter
5
+ class Boolean < Base
6
+ RELATIONS = %w[eq neq is_null is_not_null].freeze
7
+
8
+ def cast(value)
9
+ return if value.nil?
10
+
11
+ casted_value = case value
12
+ when true, false
13
+ value
14
+ when "true", "1", "t", 1
15
+ true
16
+ when "false", "0", "f", 0
17
+ false
18
+ end
19
+
20
+ casted_value.tap do |casted|
21
+ raise ValueError, "#{value} cannot be casted to #{kind}" if casted.nil? && strict
22
+ end
23
+ end
24
+
25
+ def html_type
26
+ "text"
27
+ end
28
+
29
+ class Value < Value
30
+ def html_value
31
+ case value.class
32
+ when TrueClass
33
+ "true"
34
+ when FalseClass
35
+ "false"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "active_support/core_ext/object/blank"
5
+
6
+ module Warped
7
+ module Filter
8
+ class Date < Base
9
+ RELATIONS = %w[eq neq gt gte lt lte between is_null is_not_null].freeze
10
+
11
+ def cast(value)
12
+ return if value.blank?
13
+
14
+ ::Date.parse(value)
15
+ rescue ::Date::Error
16
+ raise ValueError, "#{value} cannot be casted to #{kind}" if strict
17
+
18
+ nil
19
+ end
20
+
21
+ def html_type
22
+ "date"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "active_support/core_ext/object/blank"
5
+
6
+ module Warped
7
+ module Filter
8
+ class DateTime < Base
9
+ RELATIONS = %w[eq neq gt gte lt lte between is_null is_not_null].freeze
10
+
11
+ def cast(value)
12
+ return if value.blank?
13
+
14
+ ::DateTime.parse(value)
15
+ rescue ::Date::Error
16
+ raise ValueError, "#{value} cannot be casted to #{kind}" if strict
17
+
18
+ nil
19
+ end
20
+
21
+ def html_type
22
+ "datetime-local"
23
+ end
24
+
25
+ class Value < Value
26
+ def html_value
27
+ value.strftime("%Y-%m-%dT%H:%M:%S")
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal"
4
+ require "active_support/core_ext/object/blank"
5
+
6
+ module Warped
7
+ module Filter
8
+ class Decimal < Base
9
+ RELATIONS = %w[eq neq gt gte lt lte between in not_in is_null is_not_null].freeze
10
+
11
+ def cast(value)
12
+ return if value.blank?
13
+
14
+ casted_value = case value
15
+ when ::BigDecimal
16
+ value
17
+ when ::Integer, ::Float, ::String
18
+ value.to_d
19
+ end
20
+
21
+ casted_value.tap do |casted|
22
+ raise ValueError, "#{value} cannot be casted to #{kind}" if casted.nil? && strict
23
+ end
24
+ end
25
+
26
+ def html_type
27
+ "number"
28
+ end
29
+ end
30
+ end
31
+ end