active_fields 1.1.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -4
- data/CHANGELOG.md +32 -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 -5
- data/app/models/concerns/active_fields/field_concern.rb +26 -5
- data/app/models/concerns/active_fields/value_concern.rb +0 -2
- 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: 6c94bb84092249c9db341f69ec79233da8440b8ef74c00475c5c8acb6e42a290
|
4
|
+
data.tar.gz: 7653a7859137a53d465ccc2398f055f064105949f0a3be7e1e7fc5f3636ee240
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 57eecfc4cb3cf040dfcc2daf59a804b33ea5b3f9744cde6931c8fa0f9777eeeb067d3895b33ebbf927c5b74c66290f00546986d472d038c11df700d8092631d7
|
7
|
+
data.tar.gz: 1e923ede58bd193079334b3fcb9e193fdd61b56d6d73ffdf977a626618d2e308787641c8194ebc806113141b9dd66a3e5fbea106c0d0ded699c866a10a6b8d0b
|
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,5 +1,35 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
# [2.0.1] - 2025-04-09
|
4
|
+
- Fixed search with `nil` operator
|
5
|
+
|
6
|
+
## [2.0.0] - 2025-02-22
|
7
|
+
- Drop support for _Rails_ < 7.1
|
8
|
+
- Drop support for _Ruby_ < 3.1 (EOL)
|
9
|
+
- Added search functionality
|
10
|
+
- Added registry to store relationships between _Customizable_ types and _Active Field_ types
|
11
|
+
- Added notes about the necessity of disabling reloading for custom model classes and custom _Active Field_ type models
|
12
|
+
to prevent _STI_ (_Single Table Inheritance_) issues
|
13
|
+
|
14
|
+
**Breaking changes**:
|
15
|
+
- Maximum datetime precision reduced to 6 for all _Ruby_/_Rails_ versions.
|
16
|
+
|
17
|
+
While _Ruby_ allows up to 9 fractional seconds, most databases, including _PostgreSQL_, support only 6.
|
18
|
+
To ensure compatibility and prevent potential issues,
|
19
|
+
we are standardizing the precision to the minimum supported across our technology stack.
|
20
|
+
|
21
|
+
- Maximum datetime precision constant relocated.
|
22
|
+
|
23
|
+
The maximum precision value has been moved
|
24
|
+
from `ActiveFields::Casters::DateTimeCaster::MAX_PRECISION` to `ActiveFields::MAX_DATETIME_PRECISION`.
|
25
|
+
|
26
|
+
- Maximum decimal precision set to 16383 (2**14 - 1).
|
27
|
+
|
28
|
+
While _Ruby_'s `BigDecimal` class allows extremely high precision,
|
29
|
+
PostgreSQL supports a maximum of 16383 digits after the decimal point.
|
30
|
+
To ensure compatibility, we are capping the precision at this value.
|
31
|
+
The maximum precision value is now accessible via `ActiveFields::MAX_DECIMAL_PRECISION`.
|
32
|
+
|
3
33
|
## [1.1.0] - 2024-09-10
|
4
34
|
- Added scaffold generator
|
5
35
|
- Disabled models reloading to prevent STI issues
|
@@ -16,11 +46,11 @@
|
|
16
46
|
- Added datetime and datetime array field types
|
17
47
|
- Added fields configuration DSL
|
18
48
|
- 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
|
49
|
+
to replace the default setter (`active_values_attributes=`) from _Rails_ nested attributes feature
|
20
50
|
|
21
51
|
## [0.2.0] - 2024-06-13
|
22
52
|
|
23
|
-
- Rewritten as a
|
53
|
+
- Rewritten as a _Rails_ plugin!
|
24
54
|
- Custom field types support
|
25
55
|
- Global configuration options for changing field and value classes
|
26
56
|
- 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
|