warped 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +25 -0
- data/Gemfile +0 -2
- data/Gemfile.lock +25 -19
- data/README.md +116 -270
- data/app/assets/config/warped_manifest.js +2 -0
- data/app/assets/javascript/warped/controllers/filter_controller.js +76 -0
- data/app/assets/javascript/warped/controllers/filters_controller.js +21 -0
- data/app/assets/javascript/warped/index.js +2 -0
- data/app/assets/stylesheets/warped/application.css +15 -0
- data/app/assets/stylesheets/warped/base.css +23 -0
- data/app/assets/stylesheets/warped/filters.css +115 -0
- data/app/assets/stylesheets/warped/pagination.css +74 -0
- data/app/assets/stylesheets/warped/search.css +33 -0
- data/app/assets/stylesheets/warped/table.css +114 -0
- data/app/views/warped/_actions.html.erb +9 -0
- data/app/views/warped/_cell.html.erb +3 -0
- data/app/views/warped/_column.html.erb +35 -0
- data/app/views/warped/_filters.html.erb +21 -0
- data/app/views/warped/_hidden_fields.html.erb +19 -0
- data/app/views/warped/_pagination.html.erb +34 -0
- data/app/views/warped/_row.html.erb +19 -0
- data/app/views/warped/_search.html.erb +21 -0
- data/app/views/warped/_table.html.erb +52 -0
- data/app/views/warped/filters/_filter.html.erb +40 -0
- data/config/importmap.rb +3 -0
- data/docs/controllers/FILTERABLE.md +193 -0
- data/docs/controllers/PAGEABLE.md +70 -0
- data/docs/controllers/README.md +8 -0
- data/docs/controllers/SEARCHABLE.md +95 -0
- data/docs/controllers/SORTABLE.md +94 -0
- data/docs/controllers/TABULATABLE.md +28 -0
- data/docs/controllers/views/PARTIALS.md +285 -0
- data/docs/jobs/README.md +22 -0
- data/docs/services/README.md +81 -0
- data/lib/generators/warped/install_generator.rb +1 -1
- data/lib/warped/api/filter/base/value.rb +52 -0
- data/lib/warped/api/filter/base.rb +84 -0
- data/lib/warped/api/filter/boolean.rb +41 -0
- data/lib/warped/api/filter/date.rb +26 -0
- data/lib/warped/api/filter/date_time.rb +32 -0
- data/lib/warped/api/filter/decimal.rb +31 -0
- data/lib/warped/api/filter/factory.rb +38 -0
- data/lib/warped/api/filter/integer.rb +38 -0
- data/lib/warped/api/filter/string.rb +25 -0
- data/lib/warped/api/filter/time.rb +25 -0
- data/lib/warped/api/filter.rb +14 -0
- data/lib/warped/api/sort/value.rb +40 -0
- data/lib/warped/api/sort.rb +65 -0
- data/lib/warped/controllers/filterable/ui.rb +46 -0
- data/lib/warped/controllers/filterable.rb +79 -42
- data/lib/warped/controllers/pageable/ui.rb +70 -0
- data/lib/warped/controllers/pageable.rb +11 -11
- data/lib/warped/controllers/searchable/ui.rb +37 -0
- data/lib/warped/controllers/searchable.rb +2 -0
- data/lib/warped/controllers/sortable/ui.rb +53 -0
- data/lib/warped/controllers/sortable.rb +53 -33
- data/lib/warped/controllers/tabulatable/ui.rb +54 -0
- data/lib/warped/controllers/tabulatable.rb +13 -27
- data/lib/warped/emails/components/align.rb +21 -0
- data/lib/warped/emails/components/base.rb +116 -0
- data/lib/warped/emails/components/button.rb +58 -0
- data/lib/warped/emails/components/divider.rb +15 -0
- data/lib/warped/emails/components/heading.rb +65 -0
- data/lib/warped/emails/components/layouts/columns.rb +36 -0
- data/lib/warped/emails/components/layouts/cta.rb +38 -0
- data/lib/warped/emails/components/layouts/main.rb +34 -0
- data/lib/warped/emails/components/link.rb +36 -0
- data/lib/warped/emails/components/spacer.rb +15 -0
- data/lib/warped/emails/components/stepper.rb +104 -0
- data/lib/warped/emails/components/table.rb +37 -0
- data/lib/warped/emails/components/text.rb +67 -0
- data/lib/warped/emails/helpers.rb +26 -0
- data/lib/warped/emails/slottable.rb +61 -0
- data/lib/warped/emails/styleable.rb +160 -0
- data/lib/warped/engine.rb +19 -0
- data/lib/warped/queries/filter.rb +32 -12
- data/lib/warped/table/action.rb +33 -0
- data/lib/warped/table/column.rb +34 -0
- data/lib/warped/version.rb +1 -1
- data/lib/warped.rb +2 -0
- data/warped.gemspec +1 -1
- metadata +73 -7
- 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
|
+
```
|
data/docs/jobs/README.md
ADDED
@@ -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
|
+
```
|
@@ -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
|