active_fields 1.1.0 → 2.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 +5 -4
- data/CHANGELOG.md +27 -2
- data/README.md +411 -38
- data/app/models/active_fields/field/boolean.rb +3 -0
- data/app/models/active_fields/field/date.rb +3 -0
- data/app/models/active_fields/field/date_array.rb +3 -0
- data/app/models/active_fields/field/date_time.rb +4 -1
- data/app/models/active_fields/field/date_time_array.rb +4 -1
- data/app/models/active_fields/field/decimal.rb +6 -1
- data/app/models/active_fields/field/decimal_array.rb +6 -1
- data/app/models/active_fields/field/enum.rb +3 -0
- data/app/models/active_fields/field/enum_array.rb +3 -0
- data/app/models/active_fields/field/integer.rb +3 -0
- data/app/models/active_fields/field/integer_array.rb +3 -0
- data/app/models/active_fields/field/text.rb +3 -0
- data/app/models/active_fields/field/text_array.rb +3 -0
- data/app/models/active_fields/field.rb +5 -0
- data/app/models/concerns/active_fields/customizable_concern.rb +89 -3
- data/app/models/concerns/active_fields/field_concern.rb +26 -3
- data/db/migrate/20240229230000_create_active_fields_tables.rb +1 -1
- data/lib/active_fields/casters/date_time_caster.rb +1 -3
- data/lib/active_fields/casters/decimal_caster.rb +2 -5
- data/lib/active_fields/constants.rb +55 -0
- data/lib/active_fields/engine.rb +2 -1
- data/lib/active_fields/finders/array_finder.rb +112 -0
- data/lib/active_fields/finders/base_finder.rb +73 -0
- data/lib/active_fields/finders/boolean_finder.rb +20 -0
- data/lib/active_fields/finders/date_array_finder.rb +65 -0
- data/lib/active_fields/finders/date_finder.rb +32 -0
- data/lib/active_fields/finders/date_time_array_finder.rb +65 -0
- data/lib/active_fields/finders/date_time_finder.rb +32 -0
- data/lib/active_fields/finders/decimal_array_finder.rb +65 -0
- data/lib/active_fields/finders/decimal_finder.rb +32 -0
- data/lib/active_fields/finders/enum_array_finder.rb +41 -0
- data/lib/active_fields/finders/enum_finder.rb +20 -0
- data/lib/active_fields/finders/integer_array_finder.rb +65 -0
- data/lib/active_fields/finders/integer_finder.rb +32 -0
- data/lib/active_fields/finders/singular_finder.rb +66 -0
- data/lib/active_fields/finders/text_array_finder.rb +47 -0
- data/lib/active_fields/finders/text_finder.rb +81 -0
- data/lib/active_fields/has_active_fields.rb +3 -4
- data/lib/active_fields/registry.rb +38 -0
- data/lib/active_fields/version.rb +1 -1
- data/lib/active_fields.rb +29 -1
- data/lib/generators/active_fields/scaffold/scaffold_generator.rb +9 -0
- data/lib/generators/active_fields/scaffold/templates/controllers/active_fields_controller.rb +0 -10
- data/lib/generators/active_fields/scaffold/templates/controllers/concerns/active_fields_controller_concern.rb +33 -0
- data/lib/generators/active_fields/scaffold/templates/helpers/active_fields_helper.rb +67 -0
- data/lib/generators/active_fields/scaffold/templates/javascript/controllers/active_fields_finders_form_controller.js +59 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/finders/_form.html.erb +42 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/finders/inputs/_array_size.html.erb +16 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/finders/inputs/_boolean.html.erb +21 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/finders/inputs/_date.html.erb +16 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/finders/inputs/_date_array.html.erb +16 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/finders/inputs/_datetime.html.erb +16 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/finders/inputs/_datetime_array.html.erb +16 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/finders/inputs/_decimal.html.erb +16 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/finders/inputs/_decimal_array.html.erb +16 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/finders/inputs/_enum.html.erb +16 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/finders/inputs/_enum_array.html.erb +16 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/finders/inputs/_integer.html.erb +16 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/finders/inputs/_integer_array.html.erb +16 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/finders/inputs/_text.html.erb +16 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/finders/inputs/_text_array.html.erb +16 -0
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_boolean.html.erb +1 -1
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_date.html.erb +1 -1
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_date_array.html.erb +1 -1
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_datetime.html.erb +2 -2
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_datetime_array.html.erb +2 -2
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_decimal.html.erb +2 -2
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_decimal_array.html.erb +2 -2
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_enum.html.erb +1 -1
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_enum_array.html.erb +1 -1
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_integer.html.erb +1 -1
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_integer_array.html.erb +1 -1
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_text.html.erb +1 -1
- data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_text_array.html.erb +1 -1
- data/lib/generators/active_fields/scaffold/templates/views/shared/_array_field.html.erb +1 -1
- metadata +42 -10
- data/lib/active_fields/customizable_config.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d32fccb9d7af7eab0db169ea67d7046e09d6f8eace84e0a2c49f32574377543
|
4
|
+
data.tar.gz: 0fa74f41f48702355d9fc73f64d49389433f03e94b62e5d3e2552fcff01ec7f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e76f8e6e24e1b65a8d16480b06ce348c0d777dae4be55ec4fd908d82b17752d69866d9e73cee2847dafa1b04800977d34048eac8f0e7efc61644ddc2f30ae07
|
7
|
+
data.tar.gz: 673b3a37fcd8f2a1e0ee0d40bb3f80192d2a24933e097aae4d76d1a34d9fb04850a2b5504d2301b70cdbf2226a43bd96fd0bda0e58314aaf6ff3f54d2b7e0237
|
data/.rubocop.yml
CHANGED
@@ -1,17 +1,15 @@
|
|
1
|
-
|
2
|
-
# - rubocop-factory_bot
|
1
|
+
plugins:
|
3
2
|
- rubocop-performance
|
4
3
|
- rubocop-rails
|
5
4
|
- rubocop-rake
|
6
5
|
- rubocop-rspec
|
7
|
-
# - rubocop-rspec_rails
|
8
6
|
|
9
7
|
inherit_gem:
|
10
8
|
rubocop-shopify: rubocop.yml
|
11
9
|
|
12
10
|
AllCops:
|
13
11
|
TargetRubyVersion: 3.3
|
14
|
-
TargetRailsVersion:
|
12
|
+
TargetRailsVersion: 8.0
|
15
13
|
NewCops: enable
|
16
14
|
Exclude:
|
17
15
|
- "spec/dummy/db/schema.rb"
|
@@ -31,6 +29,9 @@ Style/WordArray:
|
|
31
29
|
Style/MethodCallWithArgsParentheses:
|
32
30
|
Enabled: false
|
33
31
|
|
32
|
+
Style/SafeNavigationChainLength:
|
33
|
+
Enabled: false
|
34
|
+
|
34
35
|
Metrics/BlockLength:
|
35
36
|
Exclude:
|
36
37
|
- "spec/**/*"
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,29 @@
|
|
1
1
|
## [Unreleased]
|
2
|
+
- Drop support for _Rails_ < 7.1
|
3
|
+
- Drop support for _Ruby_ < 3.1 (EOL)
|
4
|
+
- Added search functionality
|
5
|
+
- Added registry to store relationships between _Customizable_ types and _Active Field_ types
|
6
|
+
- Added notes about the necessity of disabling reloading for custom model classes and custom _Active Field_ type models
|
7
|
+
to prevent _STI_ (_Single Table Inheritance_) issues
|
8
|
+
|
9
|
+
**Breaking changes**:
|
10
|
+
- Maximum datetime precision reduced to 6 for all _Ruby_/_Rails_ versions.
|
11
|
+
|
12
|
+
While _Ruby_ allows up to 9 fractional seconds, most databases, including _PostgreSQL_, support only 6.
|
13
|
+
To ensure compatibility and prevent potential issues,
|
14
|
+
we are standardizing the precision to the minimum supported across our technology stack.
|
15
|
+
|
16
|
+
- Maximum datetime precision constant relocated.
|
17
|
+
|
18
|
+
The maximum precision value has been moved
|
19
|
+
from `ActiveFields::Casters::DateTimeCaster::MAX_PRECISION` to `ActiveFields::MAX_DATETIME_PRECISION`.
|
20
|
+
|
21
|
+
- Maximum decimal precision set to 16383 (2**14 - 1).
|
22
|
+
|
23
|
+
While _Ruby_'s `BigDecimal` class allows extremely high precision,
|
24
|
+
PostgreSQL supports a maximum of 16383 digits after the decimal point.
|
25
|
+
To ensure compatibility, we are capping the precision at this value.
|
26
|
+
The maximum precision value is now accessible via `ActiveFields::MAX_DECIMAL_PRECISION`.
|
2
27
|
|
3
28
|
## [1.1.0] - 2024-09-10
|
4
29
|
- Added scaffold generator
|
@@ -16,11 +41,11 @@
|
|
16
41
|
- Added datetime and datetime array field types
|
17
42
|
- Added fields configuration DSL
|
18
43
|
- Introduced new _Customizable_ setter for _Active Values_ (`active_fields_attributes=`) with a more convenient syntax
|
19
|
-
to replace the default setter (`active_values_attributes=`) from
|
44
|
+
to replace the default setter (`active_values_attributes=`) from _Rails_ nested attributes feature
|
20
45
|
|
21
46
|
## [0.2.0] - 2024-06-13
|
22
47
|
|
23
|
-
- Rewritten as a
|
48
|
+
- Rewritten as a _Rails_ plugin!
|
24
49
|
- Custom field types support
|
25
50
|
- Global configuration options for changing field and value classes
|
26
51
|
- Per-model configuration option for enabling specific field types only
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
[](https://rubygems.org/gems/active_fields)
|
5
5
|
[](https://github.com/lassoid/active_fields/actions/workflows/main.yml)
|
6
6
|
|
7
|
-
**ActiveFields** is a
|
7
|
+
**ActiveFields** is a _Rails_ plugin that implements the _Entity-Attribute-Value_ (_EAV_) pattern,
|
8
8
|
enabling the addition of custom fields to any model at runtime without requiring changes to the database schema.
|
9
9
|
|
10
10
|
## Key Concepts
|
@@ -72,12 +72,10 @@ such as booleans, strings, numbers, arrays, etc.
|
|
72
72
|
bin/rails generate active_fields:scaffold
|
73
73
|
```
|
74
74
|
|
75
|
-
This command generates a controller, routes
|
76
|
-
along with form inputs for _Active Values_ and some useful helper methods.
|
75
|
+
This command generates a controller, routes, views for managing _Active Fields_,
|
76
|
+
along with form inputs for _Active Values_, search form and some useful helper methods that will be used in next steps.
|
77
77
|
|
78
|
-
**Note:**
|
79
|
-
|
80
|
-
**Note:** The array field helper uses _Stimulus_ for interactivity.
|
78
|
+
**Note:** The array field helper and search form use _Stimulus_ for interactivity.
|
81
79
|
If your app doesn't already include _Stimulus_, you can [easily add it](https://github.com/hotwired/stimulus-rails).
|
82
80
|
Alternatively, if you prefer not to use _Stimulus_, you should implement your own JavaScript code.
|
83
81
|
|
@@ -102,7 +100,7 @@ such as booleans, strings, numbers, arrays, etc.
|
|
102
100
|
# ...
|
103
101
|
```
|
104
102
|
|
105
|
-
|
103
|
+
Permit the _Active Fields_ attributes in your _Customizables_ controllers:
|
106
104
|
|
107
105
|
```ruby
|
108
106
|
# app/controllers/posts_controller.rb
|
@@ -119,34 +117,52 @@ such as booleans, strings, numbers, arrays, etc.
|
|
119
117
|
|
120
118
|
permitted_params
|
121
119
|
end
|
122
|
-
|
123
|
-
# Removes an empty string from the beginning of the array parameter
|
124
|
-
def compact_array_param(value)
|
125
|
-
if value.first == ""
|
126
|
-
value[1..-1]
|
127
|
-
else
|
128
|
-
value
|
129
|
-
end
|
130
|
-
end
|
131
120
|
```
|
132
121
|
|
133
122
|
**Note:** Here we use the `active_fields_attributes=` method (as a permitted parameter),
|
134
|
-
that integrates well with
|
123
|
+
that integrates well with _Rails_ `fields_for` to generate appropriate form fields.
|
135
124
|
Alternatively, the alias `active_fields=` can be used in contexts without `fields_for`, such as API controllers.
|
136
125
|
|
137
|
-
|
138
|
-
|
139
|
-
and fill in _Active Values_ within _Customizable_ forms.
|
126
|
+
**Note:** `compact_array_param` is a helper method, that was added by scaffold generator.
|
127
|
+
It removes an empty string from the beginning of the array parameter.
|
140
128
|
|
141
|
-
|
142
|
-
where the plugin is fully integrated into a full-stack Rails application.
|
143
|
-
Feel free to explore the source code and run it locally:
|
129
|
+
6. Use the `where_active_fields` query method to filter records and add a search form in _Customizables_ index actions.
|
144
130
|
|
145
|
-
```
|
146
|
-
|
147
|
-
|
131
|
+
```ruby
|
132
|
+
# app/controllers/posts_controller.rb
|
133
|
+
# ...
|
134
|
+
|
135
|
+
def index
|
136
|
+
@posts = Post.where_active_fields(active_fields_finders_params)
|
137
|
+
end
|
148
138
|
```
|
149
139
|
|
140
|
+
**Note:** `active_fields_finders_params` is a helper method, that was added by scaffold generator.
|
141
|
+
It permits params from search form.
|
142
|
+
|
143
|
+
```erb
|
144
|
+
# app/views/posts/index.html.erb
|
145
|
+
# ...
|
146
|
+
|
147
|
+
<%= render_active_fields_finders_form(active_fields: Post.active_fields, url: posts_path) %>
|
148
|
+
|
149
|
+
# ...
|
150
|
+
```
|
151
|
+
|
152
|
+
That's it!
|
153
|
+
You can now add _Active Fields_ to _Customizables_ at `http://localhost:3000/active_fields`,
|
154
|
+
fill in _Active Values_ within _Customizable_ forms
|
155
|
+
and search _Customizables_ using their index actions.
|
156
|
+
|
157
|
+
You can also explore the [Demo app](https://github.com/lassoid/active_fields/blob/main/spec/dummy)
|
158
|
+
where the plugin is fully integrated into a full-stack _Rails_ application.
|
159
|
+
Feel free to explore the source code and run it locally:
|
160
|
+
|
161
|
+
```shell
|
162
|
+
spec/dummy/bin/setup
|
163
|
+
bin/rails s
|
164
|
+
```
|
165
|
+
|
150
166
|
## Field Types
|
151
167
|
|
152
168
|
The plugin comes with a structured set of _Active Fields_ types:
|
@@ -289,6 +305,238 @@ We replace it with `**` for conciseness.
|
|
289
305
|
|
290
306
|
**Note:** Options marked with **\*** are mandatory.
|
291
307
|
|
308
|
+
### Search Functionality
|
309
|
+
|
310
|
+
**Note:** This feature is compatible with _PostgreSQL_ 17 and above.
|
311
|
+
|
312
|
+
The gem provides a built-in search capability. Like _Rails_ nested attributes functionality, it accepts the following argument types:
|
313
|
+
|
314
|
+
- An array of hashes.
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
Post.where_active_fields(
|
318
|
+
[
|
319
|
+
{ name: "integer_array", operator: "any_gteq", value: 5 }, # symbol keys
|
320
|
+
{ "name" => "text", operator: "=", "value" => "Lasso" }, # string keys
|
321
|
+
{ n: "boolean", op: "!=", v: false }, # compact form (string or symbol keys)
|
322
|
+
],
|
323
|
+
)
|
324
|
+
```
|
325
|
+
|
326
|
+
- A hash of hashes (typically generated by _Rails_ `fields_for` form helper).
|
327
|
+
|
328
|
+
```ruby
|
329
|
+
Post.where_active_fields(
|
330
|
+
{
|
331
|
+
"0" => { name: "integer_array", operator: "any_gteq", value: 5 },
|
332
|
+
"1" => { "name" => "text", operator: "=", "value" => "Lasso" },
|
333
|
+
"2" => { n: "boolean", op: "!=", v: false },
|
334
|
+
},
|
335
|
+
)
|
336
|
+
```
|
337
|
+
|
338
|
+
- Permitted parameters (can contain either an array of hashes or a hash of hashes).
|
339
|
+
|
340
|
+
```ruby
|
341
|
+
Post.where_active_fields(permitted_params)
|
342
|
+
```
|
343
|
+
|
344
|
+
Key details:
|
345
|
+
- `n`/`name` argument must specify the name of an _Active Field_.
|
346
|
+
- `v`/`value` argument will be automatically cast to the appropriate type.
|
347
|
+
- `op`/`operator` argument can contain either _operation_ or _operator_.
|
348
|
+
|
349
|
+
Supported _operations_ and _operators_ for each _Active Field_ type are listed below.
|
350
|
+
|
351
|
+
#### Boolean
|
352
|
+
|
353
|
+
| Operation | Operator | Description |
|
354
|
+
|------------|----------|-----------------------------|
|
355
|
+
| `eq` | `=` | Value is equal to given |
|
356
|
+
| `not_eq` | `!=` | Value is not equal to given |
|
357
|
+
|
358
|
+
#### Date
|
359
|
+
|
360
|
+
| Operation | Operator | Description |
|
361
|
+
|------------|----------|-----------------------------------------|
|
362
|
+
| `eq` | `=` | Value is equal to given |
|
363
|
+
| `not_eq` | `!=` | Value is not equal to given |
|
364
|
+
| `gt` | `>` | Value is greater than given |
|
365
|
+
| `gteq` | `>=` | Value is greater than or equal to given |
|
366
|
+
| `lt` | `<` | Value is less than given |
|
367
|
+
| `lteq` | `<=` | Value is less than or equal to given |
|
368
|
+
|
369
|
+
#### DateArray
|
370
|
+
|
371
|
+
| Operation | Operator | Description |
|
372
|
+
|---------------|----------|----------------------------------------------------------------|
|
373
|
+
| `include` | `\|=` | Array value includes given element |
|
374
|
+
| `not_include` | `!\|=` | Array value doesn't include given element |
|
375
|
+
| `any_gt` | `\|>` | Array value contains an element greater than given |
|
376
|
+
| `any_gteq` | `\|>=` | Array value contains an element greater than or equal to given |
|
377
|
+
| `any_lt` | `\|<` | Array value contains an element less than given |
|
378
|
+
| `any_lteq` | `\|<=` | Array value contains an element less than or equal to given |
|
379
|
+
| `all_gt` | `&>` | All elements of array value are greater than given |
|
380
|
+
| `all_gteq` | `&>=` | All elements of array value are greater than or equal to given |
|
381
|
+
| `all_lt` | `&<` | All elements of array value are less than given |
|
382
|
+
| `all_lteq` | `&<=` | All elements of array value are less than or equal to given |
|
383
|
+
| `size_eq` | `#=` | Array value size is equal to given |
|
384
|
+
| `size_not_eq` | `#!=` | Array value size is not equal to given |
|
385
|
+
| `size_gt` | `#>` | Array value size is greater than given |
|
386
|
+
| `size_gteq` | `#>=` | Array value size is greater than or equal to given |
|
387
|
+
| `size_lt` | `#<` | Array value size is less than given |
|
388
|
+
| `size_lteq` | `#<=` | Array value size is less than or equal to given |
|
389
|
+
|
390
|
+
#### DateTime
|
391
|
+
|
392
|
+
| Operation | Operator | Description |
|
393
|
+
|------------|----------|-----------------------------------------|
|
394
|
+
| `eq` | `=` | Value is equal to given |
|
395
|
+
| `not_eq` | `!=` | Value is not equal to given |
|
396
|
+
| `gt` | `>` | Value is greater than given |
|
397
|
+
| `gteq` | `>=` | Value is greater than or equal to given |
|
398
|
+
| `lt` | `<` | Value is less than given |
|
399
|
+
| `lteq` | `<=` | Value is greater than or equal to given |
|
400
|
+
|
401
|
+
#### DateTimeArray
|
402
|
+
|
403
|
+
| Operation | Operator | Description |
|
404
|
+
|---------------|----------|----------------------------------------------------------------|
|
405
|
+
| `include` | `\|=` | Array value includes given element |
|
406
|
+
| `not_include` | `!\|=` | Array value doesn't include given element |
|
407
|
+
| `any_gt` | `\|>` | Array value contains an element greater than given |
|
408
|
+
| `any_gteq` | `\|>=` | Array value contains an element greater than or equal to given |
|
409
|
+
| `any_lt` | `\|<` | Array value contains an element less than given |
|
410
|
+
| `any_lteq` | `\|<=` | Array value contains an element less than or equal to given |
|
411
|
+
| `all_gt` | `&>` | All elements of array value are greater than given |
|
412
|
+
| `all_gteq` | `&>=` | All elements of array value are greater than or equal to given |
|
413
|
+
| `all_lt` | `&<` | All elements of array value are less than given |
|
414
|
+
| `all_lteq` | `&<=` | All elements of array value are less than or equal to given |
|
415
|
+
| `size_eq` | `#=` | Array value size is equal to given |
|
416
|
+
| `size_not_eq` | `#!=` | Array value size is not equal to given |
|
417
|
+
| `size_gt` | `#>` | Array value size is greater than given |
|
418
|
+
| `size_gteq` | `#>=` | Array value size is greater than or equal to given |
|
419
|
+
| `size_lt` | `#<` | Array value size is less than given |
|
420
|
+
| `size_lteq` | `#<=` | Array value size is less than or equal to given |
|
421
|
+
|
422
|
+
#### Decimal
|
423
|
+
|
424
|
+
| Operation | Operator | Description |
|
425
|
+
|------------|----------|-----------------------------------------|
|
426
|
+
| `eq` | `=` | Value is equal to given |
|
427
|
+
| `not_eq` | `!=` | Value is not equal to given |
|
428
|
+
| `gt` | `>` | Value is greater than given |
|
429
|
+
| `gteq` | `>=` | Value is greater than or equal to given |
|
430
|
+
| `lt` | `<` | Value is less than given |
|
431
|
+
| `lteq` | `<=` | Value is greater than or equal to given |
|
432
|
+
|
433
|
+
#### DecimalArray
|
434
|
+
|
435
|
+
| Operation | Operator | Description |
|
436
|
+
|---------------|----------|----------------------------------------------------------------|
|
437
|
+
| `include` | `\|=` | Array value includes given element |
|
438
|
+
| `not_include` | `!\|=` | Array value doesn't include given element |
|
439
|
+
| `any_gt` | `\|>` | Array value contains an element greater than given |
|
440
|
+
| `any_gteq` | `\|>=` | Array value contains an element greater than or equal to given |
|
441
|
+
| `any_lt` | `\|<` | Array value contains an element less than given |
|
442
|
+
| `any_lteq` | `\|<=` | Array value contains an element less than or equal to given |
|
443
|
+
| `all_gt` | `&>` | All elements of array value are greater than given |
|
444
|
+
| `all_gteq` | `&>=` | All elements of array value are greater than or equal to given |
|
445
|
+
| `all_lt` | `&<` | All elements of array value are less than given |
|
446
|
+
| `all_lteq` | `&<=` | All elements of array value are less than or equal to given |
|
447
|
+
| `size_eq` | `#=` | Array value size is equal to given |
|
448
|
+
| `size_not_eq` | `#!=` | Array value size is not equal to given |
|
449
|
+
| `size_gt` | `#>` | Array value size is greater than given |
|
450
|
+
| `size_gteq` | `#>=` | Array value size is greater than or equal to given |
|
451
|
+
| `size_lt` | `#<` | Array value size is less than given |
|
452
|
+
| `size_lteq` | `#<=` | Array value size is less than or equal to given |
|
453
|
+
|
454
|
+
#### Enum
|
455
|
+
|
456
|
+
| Operation | Operator | Description |
|
457
|
+
|------------|----------|-----------------------------|
|
458
|
+
| `eq` | `=` | Value is equal to given |
|
459
|
+
| `not_eq` | `!=` | Value is not equal to given |
|
460
|
+
|
461
|
+
#### EnumArray
|
462
|
+
|
463
|
+
| Operation | Operator | Description |
|
464
|
+
|---------------|----------|----------------------------------------------------|
|
465
|
+
| `include` | `\|=` | Array value includes given element |
|
466
|
+
| `not_include` | `!\|=` | Array value doesn't include given element |
|
467
|
+
| `size_eq` | `#=` | Array value size is equal to given |
|
468
|
+
| `size_not_eq` | `#!=` | Array value size is not equal to given |
|
469
|
+
| `size_gt` | `#>` | Array value size is greater than given |
|
470
|
+
| `size_gteq` | `#>=` | Array value size is greater than or equal to given |
|
471
|
+
| `size_lt` | `#<` | Array value size is less than given |
|
472
|
+
| `size_lteq` | `#<=` | Array value size is less than or equal to given |
|
473
|
+
|
474
|
+
#### Integer
|
475
|
+
|
476
|
+
| Operation | Operator | Description |
|
477
|
+
|------------|----------|-----------------------------------------|
|
478
|
+
| `eq` | `=` | Value is equal to given |
|
479
|
+
| `not_eq` | `!=` | Value is not equal to given |
|
480
|
+
| `gt` | `>` | Value is greater than given |
|
481
|
+
| `gteq` | `>=` | Value is greater than or equal to given |
|
482
|
+
| `lt` | `<` | Value is less than given |
|
483
|
+
| `lteq` | `<=` | Value is greater than or equal to given |
|
484
|
+
|
485
|
+
#### IntegerArray
|
486
|
+
|
487
|
+
| Operation | Operator | Description |
|
488
|
+
|---------------|----------|----------------------------------------------------------------|
|
489
|
+
| `include` | `\|=` | Array value includes given element |
|
490
|
+
| `not_include` | `!\|=` | Array value doesn't include given element |
|
491
|
+
| `any_gt` | `\|>` | Array value contains an element greater than given |
|
492
|
+
| `any_gteq` | `\|>=` | Array value contains an element greater than or equal to given |
|
493
|
+
| `any_lt` | `\|<` | Array value contains an element less than given |
|
494
|
+
| `any_lteq` | `\|<=` | Array value contains an element less than or equal to given |
|
495
|
+
| `all_gt` | `&>` | All elements of array value are greater than given |
|
496
|
+
| `all_gteq` | `&>=` | All elements of array value are greater than or equal to given |
|
497
|
+
| `all_lt` | `&<` | All elements of array value are less than given |
|
498
|
+
| `all_lteq` | `&<=` | All elements of array value are less than or equal to given |
|
499
|
+
| `size_eq` | `#=` | Array value size is equal to given |
|
500
|
+
| `size_not_eq` | `#!=` | Array value size is not equal to given |
|
501
|
+
| `size_gt` | `#>` | Array value size is greater than given |
|
502
|
+
| `size_gteq` | `#>=` | Array value size is greater than or equal to given |
|
503
|
+
| `size_lt` | `#<` | Array value size is less than given |
|
504
|
+
| `size_lteq` | `#<=` | Array value size is less than or equal to given |
|
505
|
+
|
506
|
+
#### Text
|
507
|
+
|
508
|
+
| Operation | Operator | Description |
|
509
|
+
|-------------------|----------|-------------------------------------------------------------|
|
510
|
+
| `eq` | `=` | Value is equal to given |
|
511
|
+
| `not_eq` | `!=` | Value is not equal to given |
|
512
|
+
| `start_with` | `^` | Value starts with given substring |
|
513
|
+
| `end_with` | `$` | Value ends with given substring |
|
514
|
+
| `contain` | `~` | Value contains given substring |
|
515
|
+
| `not_start_with` | `!^` | Value doesn't start with given substring |
|
516
|
+
| `not_end_with` | `!$` | Value doesn't end with given substring |
|
517
|
+
| `not_contain` | `!~` | Value doesn't contain given substring |
|
518
|
+
| `istart_with` | `^*` | Value starts with given substring (case-insensitive) |
|
519
|
+
| `iend_with` | `$*` | Value ends with given substring (case-insensitive) |
|
520
|
+
| `icontain` | `~*` | Value contains given substring (case-insensitive) |
|
521
|
+
| `not_istart_with` | `!^*` | Value doesn't start with given substring (case-insensitive) |
|
522
|
+
| `not_iend_with` | `!$*` | Value doesn't end with given substring (case-insensitive) |
|
523
|
+
| `not_icontain` | `!~*` | Value doesn't contain given substring (case-insensitive) |
|
524
|
+
|
525
|
+
#### TextArray
|
526
|
+
|
527
|
+
| Operation | Operator | Description |
|
528
|
+
|------------------|----------|-------------------------------------------------------------|
|
529
|
+
| `include` | `\|=` | Array value includes given element |
|
530
|
+
| `not_include` | `!\|=` | Array value doesn't include given element |
|
531
|
+
| `any_start_with` | `\|^` | Array value contains an element starts with given substring |
|
532
|
+
| `all_start_with` | `&^` | All elements of array value starts with given substring |
|
533
|
+
| `size_eq` | `#=` | Array value size is equal to given |
|
534
|
+
| `size_not_eq` | `#!=` | Array value size is not equal to given |
|
535
|
+
| `size_gt` | `#>` | Array value size is greater than given |
|
536
|
+
| `size_gteq` | `#>=` | Array value size is greater than or equal to given |
|
537
|
+
| `size_lt` | `#<` | Array value size is less than given |
|
538
|
+
| `size_lteq` | `#<=` | Array value size is less than or equal to given |
|
539
|
+
|
292
540
|
## Configuration
|
293
541
|
|
294
542
|
### Limiting Field Types for a Customizable
|
@@ -348,6 +596,25 @@ class CustomValue < ApplicationRecord
|
|
348
596
|
end
|
349
597
|
```
|
350
598
|
|
599
|
+
**Note:** To avoid _STI_ (_Single Table Inheritance_) issues in environments with code reloading (`config.enable_reloading = true`),
|
600
|
+
you should ensure that your custom model classes, along with all their superclasses and mix-ins, are non-reloadable.
|
601
|
+
Follow these steps:
|
602
|
+
- Move your custom model classes to a separate folder, such as `app/models/active_fields`.
|
603
|
+
- If your custom model classes subclass `ApplicationRecord` (or other reloadable class) or mix-in reloadable modules,
|
604
|
+
move those superclasses and modules to another folder, such as `app/models/core`.
|
605
|
+
- After organizing your files, add the following code to your `config/application.rb`:
|
606
|
+
```ruby
|
607
|
+
# Disable custom models reloading to avoid STI issues.
|
608
|
+
custom_models_dir = "#{root}/app/models/active_fields"
|
609
|
+
models_core_dir = "#{root}/app/models/core"
|
610
|
+
Rails.autoloaders.main.ignore(custom_models_dir, models_core_dir)
|
611
|
+
Rails.autoloaders.once.collapse(custom_models_dir, models_core_dir)
|
612
|
+
config.autoload_once_paths += [custom_models_dir, models_core_dir]
|
613
|
+
config.eager_load_paths += [custom_models_dir, models_core_dir]
|
614
|
+
```
|
615
|
+
This configuration disables namespaces for these folders
|
616
|
+
and adds them to `autoload_once_paths`, ensuring they are not reloaded.
|
617
|
+
|
351
618
|
### Adding Custom Field Types
|
352
619
|
|
353
620
|
To add a custom _Active Field_ type, create a subclass of the `ActiveFields.config.field_base_class`,
|
@@ -372,6 +639,9 @@ class IpField < ActiveFields.config.field_base_class
|
|
372
639
|
class_name: "IpCaster",
|
373
640
|
options: -> { { strip: strip? } }, # options that will be passed to the caster
|
374
641
|
},
|
642
|
+
finder: { # Optional
|
643
|
+
class_name: "IpFinder",
|
644
|
+
},
|
375
645
|
)
|
376
646
|
|
377
647
|
# Store specific attributes in `options`
|
@@ -423,12 +693,18 @@ class IpArrayField < ActiveFields.config.field_base_class
|
|
423
693
|
caster: {
|
424
694
|
class_name: "IpArrayCaster",
|
425
695
|
},
|
696
|
+
finder: { # Optional
|
697
|
+
class_name: "IpArrayFinder",
|
698
|
+
},
|
426
699
|
)
|
427
700
|
# ...
|
428
701
|
end
|
429
702
|
```
|
430
703
|
|
431
|
-
|
704
|
+
**Note:** Similar to custom model classes, you should disable code reloading for custom _Active Field_ type models.
|
705
|
+
Place them in the `app/models/active_fields` folder too.
|
706
|
+
|
707
|
+
For each custom _Active Field_ type, you must define a **validator**, a **caster** and optionally a **finder**:
|
432
708
|
|
433
709
|
#### Validator
|
434
710
|
|
@@ -483,6 +759,96 @@ class IpCaster < ActiveFields::Casters::BaseCaster
|
|
483
759
|
end
|
484
760
|
```
|
485
761
|
|
762
|
+
#### Finder
|
763
|
+
|
764
|
+
To create your custom finder, you should define a class that inherits from one of the following base classes:
|
765
|
+
- `ActiveFields::Finders::SingularFinder` - for singular values,
|
766
|
+
- `ActiveFields::Finders::ArrayFinder` - for array values,
|
767
|
+
- `ActiveFields::Finders::BaseCaster` - if you don’t need built-in helper methods.
|
768
|
+
|
769
|
+
Finder classes include a DSL for defining search operations and provide helper methods to simplify query building.
|
770
|
+
Explore the source code to discover all these methods.
|
771
|
+
|
772
|
+
```ruby
|
773
|
+
# lib/ip_finder.rb (or anywhere you want)
|
774
|
+
class IpFinder < ActiveFields::Finders::SingularFinder
|
775
|
+
operation :eq, operator: "=" do |value|
|
776
|
+
scope.where(eq(casted_value_field("text"), cast(value)))
|
777
|
+
# Equivalent to:
|
778
|
+
# if value.is_a?(TrueClass) || value.is_a?(FalseClass) || value.is_a?(NilClass)
|
779
|
+
# scope.where("CAST(active_fields_values.value_meta ->> 'const' AS text) IS ?)", cast(value))
|
780
|
+
# else
|
781
|
+
# scope.where("CAST(active_fields_values.value_meta ->> 'const' AS text) = ?)", cast(value))
|
782
|
+
# end
|
783
|
+
end
|
784
|
+
operation :not_eq, operator: "!=" do |value|
|
785
|
+
scope.where(not_eq(casted_value_field("text"), cast(value)))
|
786
|
+
end
|
787
|
+
|
788
|
+
def cast(value)
|
789
|
+
IpCaster.new.deserialize(value)
|
790
|
+
end
|
791
|
+
end
|
792
|
+
|
793
|
+
# lib/ip_array_finder.rb (or anywhere you want)
|
794
|
+
class IpArrayFinder < ActiveFields::Finders::ArrayFinder
|
795
|
+
operation :include, operator: "|=" do |value|
|
796
|
+
scope.where(value_match_any("==", cast(value)))
|
797
|
+
# Equivalent to:
|
798
|
+
# scope.where("jsonb_path_exists(active_fields_values.value_meta -> 'const', ?, ?)", "$[*] ? (@ == $value)", { value: cast(value) }.to_json)
|
799
|
+
end
|
800
|
+
operation :not_include, operator: "!|=" do |value|
|
801
|
+
scope.where.not(value_match_any("==", cast(value)))
|
802
|
+
end
|
803
|
+
operation :size_eq, operator: "#=" do |value|
|
804
|
+
scope.where(value_size_eq(value))
|
805
|
+
# Equivalent to:
|
806
|
+
# scope.where("jsonb_array_length(active_fields_values.value_meta -> 'const') = ?", value&.to_i)
|
807
|
+
end
|
808
|
+
operation :size_not_eq, operator: "#!=" do |value|
|
809
|
+
scope.where(value_size_not_eq(value))
|
810
|
+
end
|
811
|
+
operation :size_gt, operator: "#>" do |value|
|
812
|
+
scope.where(value_size_gt(value))
|
813
|
+
end
|
814
|
+
operation :size_gteq, operator: "#>=" do |value|
|
815
|
+
scope.where(value_size_gteq(value))
|
816
|
+
end
|
817
|
+
operation :size_lt, operator: "#<" do |value|
|
818
|
+
scope.where(value_size_lt(value))
|
819
|
+
end
|
820
|
+
operation :size_lteq, operator: "#<=" do |value|
|
821
|
+
scope.where(value_size_lteq(value))
|
822
|
+
end
|
823
|
+
|
824
|
+
private
|
825
|
+
|
826
|
+
def cast(value)
|
827
|
+
caster = IpCaster.new
|
828
|
+
caster.serialize(caster.deserialize(value))
|
829
|
+
end
|
830
|
+
|
831
|
+
# This method must be defined to utilize the `value_match_any` and `value_match_all` helper methods in your class.
|
832
|
+
# It should return a valid JSONPath expression for use in PostgreSQL jsonb query functions.
|
833
|
+
def jsonpath(operator) = "$[*] ? (@ #{operator} $value)"
|
834
|
+
end
|
835
|
+
```
|
836
|
+
|
837
|
+
Once defined, every _Active Value_ of this type will support the specified search operations!
|
838
|
+
|
839
|
+
```ruby
|
840
|
+
# Find customizables
|
841
|
+
Author.where_active_fields([
|
842
|
+
{ name: "main_ip", operator: "eq", value: "127.0.0.1" },
|
843
|
+
{ n: "all_ips", op: "#>=", v: 5 },
|
844
|
+
{ name: "all_ips", operator: "|=", value: "0.0.0.0" },
|
845
|
+
])
|
846
|
+
|
847
|
+
# Find Active Values
|
848
|
+
IpFinder.new(active_field: ip_active_field).search(op: "eq", value: "127.0.0.1")
|
849
|
+
IpArrayFinder.new(active_field: ip_array_active_field).search(op: "#>=", value: 5)
|
850
|
+
```
|
851
|
+
|
486
852
|
### Localization (I18n)
|
487
853
|
|
488
854
|
The built-in _validators_ primarily use _Rails_ default error types.
|
@@ -495,20 +861,16 @@ For an example, refer to the [locale file](https://github.com/lassoid/active_fie
|
|
495
861
|
|
496
862
|
## Current Restrictions
|
497
863
|
|
498
|
-
1. Only _PostgreSQL_ is fully supported.
|
864
|
+
1. Only _PostgreSQL_ 17+ is fully supported.
|
499
865
|
|
500
866
|
The gem is tested exclusively with _PostgreSQL_. Support for other databases is not guaranteed.
|
501
867
|
|
502
|
-
However, you can give it a try! :)
|
503
|
-
|
504
868
|
2. Updating some _Active Fields_ options may be unsafe.
|
505
869
|
|
506
870
|
This could cause existing _Active Values_ to become invalid,
|
507
871
|
leading to the associated _Customizables_ also becoming invalid,
|
508
872
|
which could potentially result in update failures.
|
509
873
|
|
510
|
-
3. Only _Zeitwerk_ autoloading mode is supported.
|
511
|
-
|
512
874
|
## API Overview
|
513
875
|
|
514
876
|
### Fields API
|
@@ -535,6 +897,7 @@ active_field.value_caster_class # Class used for values casting
|
|
535
897
|
active_field.value_caster # Caster object that performs values casting
|
536
898
|
active_field.customizable_model # Customizable model class
|
537
899
|
active_field.type_name # Identifier of the type of this Active Field (instead of class name)
|
900
|
+
active_field.available_customizable_types # Available Customizable types for this Active Field
|
538
901
|
|
539
902
|
# Scopes:
|
540
903
|
ActiveFields::Field::Boolean.for("Post") # Collection of Active Fields registered for the specified Customizable type
|
@@ -567,6 +930,9 @@ customizable.active_values # `has_many` association with Active Values linked to
|
|
567
930
|
|
568
931
|
# Methods:
|
569
932
|
customizable.active_fields # Collection of Active Fields registered for this record
|
933
|
+
Post.active_fields # Collection of Active Fields registered for this model
|
934
|
+
Post.allowed_active_fields_type_names # Active Fields type names allowed for this Customizable model
|
935
|
+
Post.allowed_active_fields_class_names # Active Fields class names allowed for this Customizable model
|
570
936
|
|
571
937
|
# Create, update or destroy Active Values.
|
572
938
|
customizable.active_fields_attributes = [
|
@@ -596,6 +962,15 @@ customizable.active_values_attributes = attributes
|
|
596
962
|
# This method is useful with `fields_for`, allowing you to pass the collection as an argument to render new Active Values:
|
597
963
|
# `form.fields_for :active_fields, customizable.initialize_active_values`.
|
598
964
|
customizable.initialize_active_values
|
965
|
+
|
966
|
+
# Query Customizables by Active Values.
|
967
|
+
Post.where_active_fields(
|
968
|
+
[
|
969
|
+
{ name: "integer_array", operator: "any_gteq", value: 5 }, # symbol keys
|
970
|
+
{ "name" => "text", operator: "=", "value" => "Lasso" }, # string keys
|
971
|
+
{ n: "boolean", op: "!=", v: false }, # compact form (string or symbol keys)
|
972
|
+
],
|
973
|
+
)
|
599
974
|
```
|
600
975
|
|
601
976
|
### Global Config
|
@@ -614,14 +989,12 @@ ActiveFields.config.type_class_names # Registered Active Fields class names
|
|
614
989
|
ActiveFields.config.register_field(:ip, "IpField") # Register a custom Active Field type
|
615
990
|
```
|
616
991
|
|
617
|
-
###
|
992
|
+
### Registry
|
618
993
|
|
619
994
|
```ruby
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
customizable_model.active_fields_config.types # Allowed Active Field types (e.g., `[:boolean]`)
|
624
|
-
customizable_model.active_fields_config.types_class_names # Allowed Active Field class names (e.g., `[ActiveFields::Field::Boolean]`)
|
995
|
+
ActiveFields.registry.add(:boolean, "Post") # Stores relation between Active Field type and customizable type. Please do not use directly.
|
996
|
+
ActiveFields.registry.customizable_types_for(:boolean) # Returns Customizable types that allow provided Active Field type name
|
997
|
+
ActiveFields.registry.field_type_names_for("Post") # Returns Active Field type names, allowed for given Customizable type
|
625
998
|
```
|
626
999
|
|
627
1000
|
## Development
|