{"id":19536209,"url":"https://github.com/lassoid/active_fields","last_synced_at":"2026-01-02T02:33:53.907Z","repository":{"id":219417091,"uuid":"739878057","full_name":"lassoid/active_fields","owner":"lassoid","description":"Add custom fields to ActiveRecord models at runtime.","archived":false,"fork":false,"pushed_at":"2024-05-22T20:49:51.000Z","size":147,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2024-05-23T01:15:04.679Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lassoid.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-06T20:20:31.000Z","updated_at":"2024-05-28T00:28:36.055Z","dependencies_parsed_at":"2024-02-10T11:29:23.395Z","dependency_job_id":"eebdd200-8007-4785-acf1-544bb56a32ed","html_url":"https://github.com/lassoid/active_fields","commit_stats":null,"previous_names":["lassoid/active_fields"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lassoid%2Factive_fields","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lassoid%2Factive_fields/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lassoid%2Factive_fields/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lassoid%2Factive_fields/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lassoid","download_url":"https://codeload.github.com/lassoid/active_fields/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243573495,"owners_count":20312883,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-11T02:21:39.528Z","updated_at":"2026-01-02T02:33:53.900Z","avatar_url":"https://github.com/lassoid.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ActiveFields\n\n[![Gem Version](https://img.shields.io/gem/v/active_fields?color=blue\u0026label=version)](https://rubygems.org/gems/active_fields)\n[![Gem downloads count](https://img.shields.io/gem/dt/active_fields)](https://rubygems.org/gems/active_fields)\n[![Github Actions CI](https://github.com/lassoid/active_fields/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/lassoid/active_fields/actions/workflows/main.yml)\n\n**ActiveFields** is a _Rails_ plugin that implements the _Entity-Attribute-Value_ (_EAV_) pattern,\nenabling the addition of custom fields to any model at runtime without requiring changes to the database schema or application code.\n\nIt may look similar to other gems like [attr_json](https://github.com/jrochkind/attr_json) or [store_attribute](https://github.com/palkan/store_attribute), but it solves a fundamentally different problem. While those tools allow you to add fields without migrations, they still require developer work - you must write code to define each field.\n\nThe main use case of _EAV_ in general, and _ActiveFields_ in particular, is to enable **any application user** (not just developers) to add their own fields. Not just without migrations, but without touching the source code at all. These are truly data-driven fields that can be created, modified, and managed entirely through your application's interface.\n\n## Table of Contents\n\n- [Key Concepts](#key-concepts)\n- [Models Structure](#models-structure)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Field Types](#field-types)\n  - [Fields Base Attributes](#fields-base-attributes)\n  - [Field Types Summary](#field-types-summary)\n  - [Search Functionality](#search-functionality)\n- [Configuration](#configuration)\n  - [Limiting Field Types for a Customizable](#limiting-field-types-for-a-customizable)\n  - [Customizing Internal Model Classes](#customizing-internal-model-classes)\n  - [Adding Custom Field Types](#adding-custom-field-types)\n  - [Multi-tenancy (scoping)](#multi-tenancy-scoping)\n  - [Localization (I18n)](#localization-i18n)\n- [Current Restrictions](#current-restrictions)\n- [API Overview](#api-overview)\n  - [Fields API](#fields-api)\n  - [Values API](#values-api)\n  - [Customizable API](#customizable-api)\n  - [Global Config](#global-config)\n  - [Registry](#registry)\n- [Development](#development)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Key Concepts\n\n- **Customizable**: A record that has custom fields (_Entity_).\n- **Active Field**: A record with the definition of a custom field (_Attribute_).\n- **Active Value**: A record that stores the value of an _Active Field_ for a specific _Customizable_ (_Value_).\n\n## Models Structure\n\n```mermaid\nclassDiagram\n    ActiveValue \"*\" --\u003e \"1\" ActiveField\n    ActiveValue \"*\" --\u003e \"1\" Customizable\n\n    class ActiveField {\n        + string name\n        + string type\n        + string customizable_type\n        + json default_value_meta\n        + json options\n    }\n    class ActiveValue {\n        + json value_meta\n    }\n    class Customizable {\n        // This is your model\n    }\n```\n\nAll values are stored in a JSON (jsonb) field, which is a highly flexible column type capable of storing various data types,\nsuch as booleans, strings, numbers, arrays, etc.\n\n## Requirements\n\n- Ruby 3.1+\n- Rails 7.1+\n- Postgres 15+ (17+ for search functionality)\n\n## Installation\n\n1. Install the gem and add it to your application's Gemfile by running:\n\n    ```shell\n    bundle add active_fields\n    ```\n\n2. Run install generator, then run migrations:\n\n    ```shell\n    bin/rails generate active_fields:install\n    bin/rails db:migrate\n    ```\n\n3. Add the `has_active_fields` method to any models where you want to enable custom fields:\n\n    ```ruby\n    class Post \u003c ApplicationRecord\n      has_active_fields\n    end\n    ```\n\n4. Run scaffold generator.\n\n    This plugin provides a convenient API, allowing you to write code that meets your specific needs\n    without being forced to use predefined implementations that is hard to extend.\n\n    However, for a quick start, you can generate a scaffold by running the following command:\n\n    ```shell\n    bin/rails generate active_fields:scaffold\n    ```\n\n    This command generates a controller, routes, views for managing _Active Fields_,\n    along with form inputs for _Active Values_, search form and some useful helper methods that will be used in next steps.\n\n    **Note:** The array field helper and search form use _Stimulus_ for interactivity.\n    If your app doesn't already include _Stimulus_, you can [easily add it](https://github.com/hotwired/stimulus-rails).\n    Alternatively, if you prefer not to use _Stimulus_, you should implement your own JavaScript code.\n\n5. Add _Active Fields_ inputs in _Customizables_ forms and permit their params in controllers.\n\n    There are two methods available on _Customizable_ models for retrieving _Active Values_:\n    - `active_values` returns collection of only existing _Active Values_.\n    - `initialize_active_values` builds any missing _Active Values_ and returns the full collection.\n\n    Choose the method that suits your requirements.\n    In most cases, however, `initialize_active_values` is the more suitable option.\n\n    ```erb\n    # app/views/posts/_form.html.erb\n    # ...\n\n    \u003c%= form.fields_for :active_fields, post.initialize_active_values.sort_by(\u0026:active_field_id), include_id: false do |active_fields_form| %\u003e\n      \u003c%= active_fields_form.hidden_field :name %\u003e\n      \u003c%= render_active_value_input(form: active_fields_form, active_value: active_fields_form.object) %\u003e\n    \u003c% end %\u003e\n\n    # ...\n    ```\n\n    Permit the _Active Fields_ attributes in your _Customizables_ controllers:\n\n    ```ruby\n    # app/controllers/posts_controller.rb\n    # ...\n\n    def post_params\n      permitted_params = params.require(:post).permit(\n        # ...\n        active_fields_attributes: [:name, :value, :_destroy, value: []],\n      )\n      permitted_params[:active_fields_attributes]\u0026.each do |_index, value_attrs|\n        value_attrs[:value] = compact_array_param(value_attrs[:value]) if value_attrs[:value].is_a?(Array)\n      end\n    \n      permitted_params\n    end\n    ```\n\n    **Note:** Here we use the `active_fields_attributes=` method (as a permitted parameter),\n    that integrates well with _Rails_ `fields_for` to generate appropriate form fields.\n    Alternatively, the alias `active_fields=` can be used in contexts without `fields_for`, such as API controllers.\n\n    **Note:** `compact_array_param` is a helper method, that was added by scaffold generator.\n    It removes an empty string from the beginning of the array parameter.\n\n6. Use the `where_active_fields` query method to filter records and add a search form in _Customizables_ index actions.\n\n    ```ruby\n    # app/controllers/posts_controller.rb\n    # ...\n    \n    def index\n      @posts = Post.where_active_fields(active_fields_finders_params)\n    end\n    ```\n\n    **Note:** `active_fields_finders_params` is a helper method, that was added by scaffold generator. \n    It permits params from search form.\n\n    ```erb\n    # app/views/posts/index.html.erb\n    # ...\n\n    \u003c%= render_active_fields_finders_form(active_fields: Post.active_fields, url: posts_path) %\u003e\n\n    # ...\n    ```\n\nThat's it!\nYou can now add _Active Fields_ to _Customizables_ at `http://localhost:3000/active_fields`, \nfill in _Active Values_ within _Customizable_ forms\nand search _Customizables_ using their index actions.\n\nYou can also explore the [Demo app](https://github.com/lassoid/active_fields/blob/main/spec/dummy)\nwhere the plugin is fully integrated into a full-stack _Rails_ application.\nFeel free to explore the source code and run it locally:\n\n```shell\nspec/dummy/bin/setup\nbin/rails s\n```\n\n## Field Types\n\nThe plugin comes with a structured set of _Active Fields_ types:\n\n```mermaid\nclassDiagram\n    class ActiveField {\n        + string name\n        + string type\n        + string customizable_type\n    }\n    class Boolean {\n        + boolean default_value\n        + boolean required\n        + boolean nullable\n    }\n    class Date {\n        + date default_value\n        + boolean required\n        + date min\n        + date max\n    }\n    class DateArray {\n        + array~date~ default_value\n        + date min\n        + date max\n        + integer min_size\n        + integer max_size\n    }\n    class DateTime {\n        + datetime default_value\n        + boolean required\n        + datetime min\n        + datetime max\n        + integer precision\n    }\n    class DateTimeArray {\n        + array~datetime~ default_value\n        + datetime min\n        + datetime max\n        + integer precision\n        + integer min_size\n        + integer max_size\n    }\n    class Decimal {\n        + decimal default_value\n        + boolean required\n        + decimal min\n        + decimal max\n        + integer precision\n    }\n    class DecimalArray {\n        + array~decimal~ default_value\n        + decimal min\n        + decimal max\n        + integer precision\n        + integer min_size\n        + integer max_size\n    }\n    class Enum {\n        + string default_value\n        + boolean required\n        + array~string~ allowed_values\n    }\n    class EnumArray {\n        + array~string~ default_value\n        + array~string~ allowed_values\n        + integer min_size\n        + integer max_size\n    }\n    class Integer {\n        + integer default_value\n        + boolean required\n        + integer min\n        + integer max\n    }\n    class IntegerArray {\n        + array~integer~ default_value\n        + integer min\n        + integer max\n        + integer min_size\n        + integer max_size\n    }\n    class Text {\n        + string default_value\n        + boolean required\n        + integer min_length\n        + integer max_length\n    }\n    class TextArray {\n        + array~string~ default_value\n        + integer min_length\n        + integer max_length\n        + integer min_size\n        + integer max_size\n    }\n    \n    ActiveField \u003c|-- Boolean\n    ActiveField \u003c|-- Date\n    ActiveField \u003c|-- DateArray\n    ActiveField \u003c|-- DateTime\n    ActiveField \u003c|-- DateTimeArray\n    ActiveField \u003c|-- Decimal\n    ActiveField \u003c|-- DecimalArray\n    ActiveField \u003c|-- Enum\n    ActiveField \u003c|-- EnumArray\n    ActiveField \u003c|-- Integer\n    ActiveField \u003c|-- IntegerArray\n    ActiveField \u003c|-- Text\n    ActiveField \u003c|-- TextArray\n```\n\n### Fields Base Attributes\n- `name`(`string`)\n- `type`(`string`)\n- `customizable_type`(`string`)\n- `default_value_meta` (`json`)\n\n### Field Types Summary\n\nAll _Active Field_ model names start with `ActiveFields::Field`.\nWe replace it with `**` for conciseness.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eTable\u003c/strong\u003e\u003c/summary\u003e\n\n| Active Field model              | Type name        | Attributes                               | Options                                                                                                                                                                                                                                                                                                         |\n|---------------------------------|------------------|------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `**::Boolean`                   | `boolean`        | `default_value`\u003cbr\u003e(`boolean` or `nil`)  | `required`(`boolean`) - the value must not be `false`\u003cbr\u003e`nullable`(`boolean`) - the value could be `nil`                                                                                                                                                                                                       |\n| `**::Date`                      | `date`           | `default_value`\u003cbr\u003e(`date` or `nil`)     | `required`(`boolean`) - the value must not be `nil`\u003cbr\u003e`min`(`date`) - minimum value allowed\u003cbr\u003e`max`(`date`) - maximum value allowed                                                                                                                                                                           |\n| `**::DateArray`                 | `date_array`     | `default_value`\u003cbr\u003e(`array[date]`)       | `min`(`date`) - minimum value allowed, for each element\u003cbr\u003e`max`(`date`) - maximum value allowed, for each element\u003cbr\u003e`min_size`(`integer`) - minimum value size\u003cbr\u003e`max_size`(`integer`) - maximum value size                                                                                                  |\n| `**::DateTime`                  | `datetime`       | `default_value`\u003cbr\u003e(`datetime` or `nil`) | `required`(`boolean`) - the value must not be `nil`\u003cbr\u003e`min`(`datetime`) - minimum value allowed\u003cbr\u003e`max`(`datetime`) - maximum value allowed\u003cbr\u003e`precision`(`integer`) - the number of digits in fractional seconds                                                                                            |\n| `**::DateTimeArray`             | `datetime_array` | `default_value`\u003cbr\u003e(`array[datetime]`)   | `min`(`datetime`) - minimum value allowed, for each element\u003cbr\u003e`max`(`datetime`) - maximum value allowed, for each element\u003cbr\u003e`precision`(`integer`) - the number of digits in fractional seconds, for each element\u003cbr\u003e`min_size`(`integer`) - minimum value size\u003cbr\u003e`max_size`(`integer`) - maximum value size |\n| `**::Decimal`                   | `decimal`        | `default_value`\u003cbr\u003e(`decimal` or `nil`)  | `required`(`boolean`) - the value must not be `nil`\u003cbr\u003e`min`(`decimal`) - minimum value allowed\u003cbr\u003e`max`(`decimal`) - maximum value allowed\u003cbr\u003e`precision`(`integer`) - the number of digits after the decimal point                                                                                            |\n| `**::DecimalArray`              | `decimal_array`  | `default_value`\u003cbr\u003e(`array[decimal]`)    | `min`(`decimal`) - minimum value allowed, for each element\u003cbr\u003e`max`(`decimal`) - maximum value allowed, for each element\u003cbr\u003e`precision`(`integer`) - the number of digits after the decimal point, for each element\u003cbr\u003e`min_size`(`integer`) - minimum value size\u003cbr\u003e`max_size`(`integer`) - maximum value size |\n| `**::Enum`                      | `enum`           | `default_value`\u003cbr\u003e(`string` or `nil`)   | `required`(`boolean`) - the value must not be `nil`\u003cbr\u003e**\\***`allowed_values`(`array[string]`) - a list of allowed values                                                                                                                                                                                       |\n| `**::EnumArray`                 | `enum_array`     | `default_value`\u003cbr\u003e(`array[string]`)     | **\\***`allowed_values`(`array[string]`) - a list of allowed values\u003cbr\u003e`min_size`(`integer`) - minimum value size\u003cbr\u003e`max_size`(`integer`) - maximum value size                                                                                                                                                  |\n| `**::Integer`                   | `integer`        | `default_value`\u003cbr\u003e(`integer` or `nil`)  | `required`(`boolean`) - the value must not be `nil`\u003cbr\u003e`min`(`integer`) - minimum value allowed\u003cbr\u003e`max`(`integer`) - maximum value allowed                                                                                                                                                                     |\n| `**::IntegerArray`              | `integer_array`  | `default_value`\u003cbr\u003e(`array[integer]`)    | `min`(`integer`) - minimum value allowed, for each element\u003cbr\u003e`max`(`integer`) - maximum value allowed, for each element\u003cbr\u003e`min_size`(`integer`) - minimum value size\u003cbr\u003e`max_size`(`integer`) - maximum value size                                                                                            |\n| `**::Text`                      | `text`           | `default_value`\u003cbr\u003e(`string` or `nil`)   | `required`(`boolean`) - the value must not be `nil`\u003cbr\u003e`min_length`(`integer`) - minimum value length allowed\u003cbr\u003e`max_length`(`integer`) - maximum value length allowed                                                                                                                                         |\n| `**::TextArray`                 | `text_array`     | `default_value`\u003cbr\u003e(`array[string]`)     | `min_length`(`integer`) - minimum value length allowed, for each element\u003cbr\u003e`max_length`(`integer`) - maximum value length allowed, for each element\u003cbr\u003e`min_size`(`integer`) - minimum value size\u003cbr\u003e`max_size`(`integer`) - maximum value size                                                                |\n| _Your custom class can be here_ | _..._            | _..._                                    | _..._                                                                                                                                                                                                                                                                                                           |\n\n\u003c/details\u003e\n\n**Note:** Options marked with **\\*** are mandatory.\n\n### Search Functionality\n\n**Note:** This feature is compatible with _PostgreSQL_ 17 and above.\n\nThe gem provides a built-in search capability. Like _Rails_ nested attributes functionality, it accepts the following argument types:\n\n- An array of hashes.\n\n    ```ruby\n    Post.where_active_fields(\n      [\n        { name: \"integer_array\", operator: \"any_gteq\", value: 5 }, # symbol keys\n        { \"name\" =\u003e \"text\", operator: \"=\", \"value\" =\u003e \"Lasso\" }, # string keys\n        { n: \"boolean\", op: \"!=\", v: false }, # compact form (string or symbol keys)\n      ],\n    )\n    ```\n\n- A hash of hashes (typically generated by _Rails_ `fields_for` form helper).\n\n    ```ruby\n    Post.where_active_fields(\n      {\n        \"0\" =\u003e { name: \"integer_array\", operator: \"any_gteq\", value: 5 },\n        \"1\" =\u003e { \"name\" =\u003e \"text\", operator: \"=\", \"value\" =\u003e \"Lasso\" },\n        \"2\" =\u003e { n: \"boolean\", op: \"!=\", v: false },\n      },\n    )\n    ```\n\n- Permitted parameters (can contain either an array of hashes or a hash of hashes).\n\n    ```ruby\n    Post.where_active_fields(permitted_params)\n    ```\n\nKey details:\n- `n`/`name` argument must specify the name of an _Active Field_.\n- `v`/`value` argument will be automatically cast to the appropriate type.\n- `op`/`operator` argument can contain either _operation_ or _operator_.\n\nSupported _operations_ and _operators_ for each _Active Field_ type are listed below.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eBoolean\u003c/strong\u003e\u003c/summary\u003e\n\n| Operation  | Operator | Description                 |\n|------------|----------|-----------------------------|\n| `eq`       | `=`      | Value is equal to given     |\n| `not_eq`   | `!=`     | Value is not equal to given |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eDate\u003c/strong\u003e\u003c/summary\u003e\n\n| Operation  | Operator | Description                             |\n|------------|----------|-----------------------------------------|\n| `eq`       | `=`      | Value is equal to given                 |\n| `not_eq`   | `!=`     | Value is not equal to given             |\n| `gt`       | `\u003e`      | Value is greater than given             |\n| `gteq`     | `\u003e=`     | Value is greater than or equal to given |\n| `lt`       | `\u003c`      | Value is less than given                |\n| `lteq`     | `\u003c=`     | Value is less than or equal to given    |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eDateArray\u003c/strong\u003e\u003c/summary\u003e\n\n| Operation     | Operator | Description                                                    |\n|---------------|----------|----------------------------------------------------------------|\n| `include`     | `\\|=`    | Array value includes given element                             |\n| `not_include` | `!\\|=`   | Array value doesn't include given element                      |\n| `any_gt`      | `\\|\u003e`    | Array value contains an element greater than given             |\n| `any_gteq`    | `\\|\u003e=`   | Array value contains an element greater than or equal to given |\n| `any_lt`      | `\\|\u003c`    | Array value contains an element less than given                |\n| `any_lteq`    | `\\|\u003c=`   | Array value contains an element less than or equal to given    |\n| `all_gt`      | `\u0026\u003e`     | All elements of array value are greater than given             |\n| `all_gteq`    | `\u0026\u003e=`    | All elements of array value are greater than or equal to given |\n| `all_lt`      | `\u0026\u003c`     | All elements of array value are less than given                |\n| `all_lteq`    | `\u0026\u003c=`    | All elements of array value are less than or equal to given    |\n| `size_eq`     | `#=`     | Array value size is equal to given                             |\n| `size_not_eq` | `#!=`    | Array value size is not equal to given                         |\n| `size_gt`     | `#\u003e`     | Array value size is greater than given                         |\n| `size_gteq`   | `#\u003e=`    | Array value size is greater than or equal to given             |\n| `size_lt`     | `#\u003c`     | Array value size is less than given                            |\n| `size_lteq`   | `#\u003c=`    | Array value size is less than or equal to given                |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eDateTime\u003c/strong\u003e\u003c/summary\u003e\n\n| Operation  | Operator | Description                             |\n|------------|----------|-----------------------------------------|\n| `eq`       | `=`      | Value is equal to given                 |\n| `not_eq`   | `!=`     | Value is not equal to given             |\n| `gt`       | `\u003e`      | Value is greater than given             |\n| `gteq`     | `\u003e=`     | Value is greater than or equal to given |\n| `lt`       | `\u003c`      | Value is less than given                |\n| `lteq`     | `\u003c=`     | Value is greater than or equal to given |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eDateTimeArray\u003c/strong\u003e\u003c/summary\u003e\n\n| Operation     | Operator | Description                                                    |\n|---------------|----------|----------------------------------------------------------------|\n| `include`     | `\\|=`    | Array value includes given element                             |\n| `not_include` | `!\\|=`   | Array value doesn't include given element                      |\n| `any_gt`      | `\\|\u003e`    | Array value contains an element greater than given             |\n| `any_gteq`    | `\\|\u003e=`   | Array value contains an element greater than or equal to given |\n| `any_lt`      | `\\|\u003c`    | Array value contains an element less than given                |\n| `any_lteq`    | `\\|\u003c=`   | Array value contains an element less than or equal to given    |\n| `all_gt`      | `\u0026\u003e`     | All elements of array value are greater than given             |\n| `all_gteq`    | `\u0026\u003e=`    | All elements of array value are greater than or equal to given |\n| `all_lt`      | `\u0026\u003c`     | All elements of array value are less than given                |\n| `all_lteq`    | `\u0026\u003c=`    | All elements of array value are less than or equal to given    |\n| `size_eq`     | `#=`     | Array value size is equal to given                             |\n| `size_not_eq` | `#!=`    | Array value size is not equal to given                         |\n| `size_gt`     | `#\u003e`     | Array value size is greater than given                         |\n| `size_gteq`   | `#\u003e=`    | Array value size is greater than or equal to given             |\n| `size_lt`     | `#\u003c`     | Array value size is less than given                            |\n| `size_lteq`   | `#\u003c=`    | Array value size is less than or equal to given                |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eDecimal\u003c/strong\u003e\u003c/summary\u003e\n\n| Operation  | Operator | Description                             |\n|------------|----------|-----------------------------------------|\n| `eq`       | `=`      | Value is equal to given                 |\n| `not_eq`   | `!=`     | Value is not equal to given             |\n| `gt`       | `\u003e`      | Value is greater than given             |\n| `gteq`     | `\u003e=`     | Value is greater than or equal to given |\n| `lt`       | `\u003c`      | Value is less than given                |\n| `lteq`     | `\u003c=`     | Value is greater than or equal to given |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eDecimalArray\u003c/strong\u003e\u003c/summary\u003e\n\n| Operation     | Operator | Description                                                    |\n|---------------|----------|----------------------------------------------------------------|\n| `include`     | `\\|=`    | Array value includes given element                             |\n| `not_include` | `!\\|=`   | Array value doesn't include given element                      |\n| `any_gt`      | `\\|\u003e`    | Array value contains an element greater than given             |\n| `any_gteq`    | `\\|\u003e=`   | Array value contains an element greater than or equal to given |\n| `any_lt`      | `\\|\u003c`    | Array value contains an element less than given                |\n| `any_lteq`    | `\\|\u003c=`   | Array value contains an element less than or equal to given    |\n| `all_gt`      | `\u0026\u003e`     | All elements of array value are greater than given             |\n| `all_gteq`    | `\u0026\u003e=`    | All elements of array value are greater than or equal to given |\n| `all_lt`      | `\u0026\u003c`     | All elements of array value are less than given                |\n| `all_lteq`    | `\u0026\u003c=`    | All elements of array value are less than or equal to given    |\n| `size_eq`     | `#=`     | Array value size is equal to given                             |\n| `size_not_eq` | `#!=`    | Array value size is not equal to given                         |\n| `size_gt`     | `#\u003e`     | Array value size is greater than given                         |\n| `size_gteq`   | `#\u003e=`    | Array value size is greater than or equal to given             |\n| `size_lt`     | `#\u003c`     | Array value size is less than given                            |\n| `size_lteq`   | `#\u003c=`    | Array value size is less than or equal to given                |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eEnum\u003c/strong\u003e\u003c/summary\u003e\n\n| Operation  | Operator | Description                 |\n|------------|----------|-----------------------------|\n| `eq`       | `=`      | Value is equal to given     |\n| `not_eq`   | `!=`     | Value is not equal to given |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eEnumArray\u003c/strong\u003e\u003c/summary\u003e\n\n| Operation     | Operator | Description                                        |\n|---------------|----------|----------------------------------------------------|\n| `include`     | `\\|=`    | Array value includes given element                 |\n| `not_include` | `!\\|=`   | Array value doesn't include given element          |\n| `size_eq`     | `#=`     | Array value size is equal to given                 |\n| `size_not_eq` | `#!=`    | Array value size is not equal to given             |\n| `size_gt`     | `#\u003e`     | Array value size is greater than given             |\n| `size_gteq`   | `#\u003e=`    | Array value size is greater than or equal to given |\n| `size_lt`     | `#\u003c`     | Array value size is less than given                |\n| `size_lteq`   | `#\u003c=`    | Array value size is less than or equal to given    |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eInteger\u003c/strong\u003e\u003c/summary\u003e\n\n| Operation  | Operator | Description                             |\n|------------|----------|-----------------------------------------|\n| `eq`       | `=`      | Value is equal to given                 |\n| `not_eq`   | `!=`     | Value is not equal to given             |\n| `gt`       | `\u003e`      | Value is greater than given             |\n| `gteq`     | `\u003e=`     | Value is greater than or equal to given |\n| `lt`       | `\u003c`      | Value is less than given                |\n| `lteq`     | `\u003c=`     | Value is greater than or equal to given |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eIntegerArray\u003c/strong\u003e\u003c/summary\u003e\n\n| Operation     | Operator | Description                                                    |\n|---------------|----------|----------------------------------------------------------------|\n| `include`     | `\\|=`    | Array value includes given element                             |\n| `not_include` | `!\\|=`   | Array value doesn't include given element                      |\n| `any_gt`      | `\\|\u003e`    | Array value contains an element greater than given             |\n| `any_gteq`    | `\\|\u003e=`   | Array value contains an element greater than or equal to given |\n| `any_lt`      | `\\|\u003c`    | Array value contains an element less than given                |\n| `any_lteq`    | `\\|\u003c=`   | Array value contains an element less than or equal to given    |\n| `all_gt`      | `\u0026\u003e`     | All elements of array value are greater than given             |\n| `all_gteq`    | `\u0026\u003e=`    | All elements of array value are greater than or equal to given |\n| `all_lt`      | `\u0026\u003c`     | All elements of array value are less than given                |\n| `all_lteq`    | `\u0026\u003c=`    | All elements of array value are less than or equal to given    |\n| `size_eq`     | `#=`     | Array value size is equal to given                             |\n| `size_not_eq` | `#!=`    | Array value size is not equal to given                         |\n| `size_gt`     | `#\u003e`     | Array value size is greater than given                         |\n| `size_gteq`   | `#\u003e=`    | Array value size is greater than or equal to given             |\n| `size_lt`     | `#\u003c`     | Array value size is less than given                            |\n| `size_lteq`   | `#\u003c=`    | Array value size is less than or equal to given                |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eText\u003c/strong\u003e\u003c/summary\u003e\n\n| Operation         | Operator | Description                                                 |\n|-------------------|----------|-------------------------------------------------------------|\n| `eq`              | `=`      | Value is equal to given                                     |\n| `not_eq`          | `!=`     | Value is not equal to given                                 |\n| `start_with`      | `^`      | Value starts with given substring                           |\n| `end_with`        | `$`      | Value ends with given substring                             |\n| `contain`         | `~`      | Value contains given substring                              |\n| `not_start_with`  | `!^`     | Value doesn't start with given substring                    |\n| `not_end_with`    | `!$`     | Value doesn't end with given substring                      |\n| `not_contain`     | `!~`     | Value doesn't contain given substring                       |\n| `istart_with`     | `^*`     | Value starts with given substring (case-insensitive)        |\n| `iend_with`       | `$*`     | Value ends with given substring (case-insensitive)          |\n| `icontain`        | `~*`     | Value contains given substring (case-insensitive)           |\n| `not_istart_with` | `!^*`    | Value doesn't start with given substring (case-insensitive) |\n| `not_iend_with`   | `!$*`    | Value doesn't end with given substring (case-insensitive)   |\n| `not_icontain`    | `!~*`    | Value doesn't contain given substring (case-insensitive)    |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eTextArray\u003c/strong\u003e\u003c/summary\u003e\n\n| Operation        | Operator | Description                                                 |\n|------------------|----------|-------------------------------------------------------------|\n| `include`        | `\\|=`    | Array value includes given element                          |\n| `not_include`    | `!\\|=`   | Array value doesn't include given element                   |\n| `any_start_with` | `\\|^`    | Array value contains an element starts with given substring |\n| `all_start_with` | `\u0026^`     | All elements of array value starts with given substring     |\n| `size_eq`        | `#=`     | Array value size is equal to given                          |\n| `size_not_eq`    | `#!=`    | Array value size is not equal to given                      |\n| `size_gt`        | `#\u003e`     | Array value size is greater than given                      |\n| `size_gteq`      | `#\u003e=`    | Array value size is greater than or equal to given          |\n| `size_lt`        | `#\u003c`     | Array value size is less than given                         |\n| `size_lteq`      | `#\u003c=`    | Array value size is less than or equal to given             |\n\n\u003c/details\u003e\n\n## Configuration\n\n### Limiting Field Types for a Customizable\n\nYou can restrict the allowed _Active Field_ types for a _Customizable_ by passing _type names_ to the `types` argument in the `has_active_fields` method:\n\n```ruby\nclass Post \u003c ApplicationRecord\n  has_active_fields types: %i[boolean date_array integer your_custom_field_type_name]\n  # ...\nend\n```\n\nAttempting to save an _Active Field_ with a disallowed type will result in a validation error:\n\n```ruby\nactive_field = ActiveFields::Field::Date.new(name: \"date\", customizable_type: \"Post\")\nactive_field.valid? #=\u003e false\nactive_field.errors.messages #=\u003e {:customizable_type=\u003e[\"is not included in the list\"]}\n```\n\n### Customizing Internal Model Classes\n\nYou can extend the functionality of _Active Fields_ and _Active Values_ by changing their classes.\nBy default, _Active Fields_ inherit from `ActiveFields::Field::Base` (utilizing STI),\nand _Active Values_ class is `ActiveFields::Value`.\nYou should include the mix-ins `ActiveFields::FieldConcern` and `ActiveFields::ValueConcern`\nin your custom models to add the necessary functionality.\n\n```ruby\n# config/initializers/active_fields.rb\nActiveFields.configure do |config|\n  config.field_base_class_name = \"CustomField\"\n  config.value_class_name = \"CustomValue\"\nend\n\n# app/models/custom_field.rb\nclass CustomField \u003c ApplicationRecord\n  self.table_name = \"active_fields\" # Ensure the model uses the correct table\n\n  include ActiveFields::FieldConcern\n\n  # Your custom code to extend Active Fields\n  def label = name.titleize\n  # ...\nend\n\n# app/models/custom_value.rb\nclass CustomValue \u003c ApplicationRecord\n  self.table_name = \"active_fields_values\" # Ensure the model uses the correct table\n\n  include ActiveFields::ValueConcern\n\n  # Your custom code to extend Active Values\n  def label = active_field.label\n  # ...\nend\n```\n\n**Note:** To avoid _STI_ (_Single Table Inheritance_) issues in environments with code reloading (`config.enable_reloading = true`),\nyou should ensure that your custom model classes, along with all their superclasses and mix-ins, are non-reloadable.\nFollow these steps:\n- Move your custom model classes to a separate folder, such as `app/models/active_fields`.\n- If your custom model classes subclass `ApplicationRecord` (or other reloadable class) or mix-in reloadable modules,\nmove those superclasses and modules to another folder, such as `app/models/core`.\n- After organizing your files, add the following code to your `config/application.rb`:\n    ```ruby\n    # Disable custom models reloading to avoid STI issues.\n    custom_models_dir = \"#{root}/app/models/active_fields\"\n    models_core_dir = \"#{root}/app/models/core\"\n    Rails.autoloaders.main.ignore(custom_models_dir, models_core_dir)\n    Rails.autoloaders.once.collapse(custom_models_dir, models_core_dir)\n    config.autoload_once_paths += [custom_models_dir, models_core_dir]\n    config.eager_load_paths += [custom_models_dir, models_core_dir]\n    ```\n    This configuration disables namespaces for these folders\n    and adds them to `autoload_once_paths`, ensuring they are not reloaded.\n\n### Adding Custom Field Types\n\nTo add a custom _Active Field_ type, create a subclass of the `ActiveFields.config.field_base_class`,\nregister it in the global configuration and configure the field by calling `acts_as_active_field`.\n\n```ruby\n# config/initializers/active_fields.rb\nActiveFields.configure do |config|\n  # The first argument - field type name, the second - field class name\n  config.register_field :ip, \"IpField\"\nend\n\n# app/models/ip_field.rb\nclass IpField \u003c ActiveFields.config.field_base_class\n  # Configure the field\n  acts_as_active_field(\n    validator: {\n      class_name: \"IpValidator\",\n      options: -\u003e { { required: required? } }, # options that will be passed to the validator\n    },\n    caster: {\n      class_name: \"IpCaster\",\n      options: -\u003e { { strip: strip? } }, # options that will be passed to the caster\n    },\n    finder: { # Optional\n      class_name: \"IpFinder\",\n    },\n  )\n\n  # Store specific attributes in `options`\n  store_accessor :options, :required, :strip\n\n  # You can use built-in casters to cast your options\n  %i[required strip].each do |column|\n    define_method(column) do\n      ActiveFields::Casters::BooleanCaster.new.deserialize(super())\n    end\n\n    define_method(:\"#{column}?\") do\n      !!public_send(column)\n    end\n\n    define_method(:\"#{column}=\") do |other|\n      super(ActiveFields::Casters::BooleanCaster.new.serialize(other))\n    end\n  end\n\n  private\n\n  # This method allows you to assign default values to your options.\n  # It is automatically executed within the `after_initialize` callback.\n  def set_defaults\n    self.required ||= false\n    self.strip ||= true\n  end\nend\n```\n\nTo create an array _Active Field_ type, pass the `array: true` option to `acts_as_active_field`.\nThis will add `min_size` and `max_size` options, as well as some important internal methods such as `array?`.\n\n```ruby\n# config/initializers/active_fields.rb\nActiveFields.configure do |config|\n  config.register_field :ip_array, \"IpArrayField\"\nend\n\n# app/models/ip_array_field.rb\nclass IpArrayField \u003c ActiveFields.config.field_base_class\n  acts_as_active_field(\n    array: true,\n    validator: {\n      class_name: \"IpArrayValidator\",\n      options: -\u003e { { min_size: min_size, max_size: max_size } },\n    },\n    caster: {\n      class_name: \"IpArrayCaster\",\n    },\n    finder: { # Optional\n      class_name: \"IpArrayFinder\",\n    },\n  )\n  # ...\nend\n```\n\n**Note:** Similar to custom model classes, you should disable code reloading for custom _Active Field_ type models.\nPlace them in the `app/models/active_fields` folder too.\n\nFor each custom _Active Field_ type, you must define a **validator**, a **caster** and optionally a **finder**:\n\n#### Validator\n\nCreate a class that inherits from `ActiveFields::Validators::BaseValidator` and implements the `perform_validation` method.\nThis method is responsible for validating `active_field.default_value` and `active_value.value`, and adding any errors to the `errors` set.\nThese errors will then propagate to the corresponding record.\nEach error should match the arguments format of the _ActiveModel_ `errors.add` method.\n\n```ruby\n# lib/ip_validator.rb (or anywhere you want)\nclass IpValidator \u003c ActiveFields::Validators::BaseValidator\n  private\n\n  def perform_validation(value)\n    if value.nil?\n      if options[:required]\n        errors \u003c\u003c :required # type only\n      end\n    elsif value.is_a?(String)\n      unless value.match?(Resolv::IPv4::Regex)\n        errors \u003c\u003c [:invalid, message: \"doesn't match the IPv4 format\"] # type with options    \n      end\n    else\n      errors \u003c\u003c :invalid\n    end\n  end\nend                                                               \n```\n\n#### Caster\n\nCreate a class that inherits from `ActiveFields::Casters::BaseCaster` \nand implements methods `serialize` (used when setting a value) and `deserialize` (used when retrieving a value).\nThese methods handle the conversion of `active_field.default_value` and `active_value.value`.\n\n```ruby\n# lib/ip_caster.rb (or anywhere you want)\nclass IpCaster \u003c ActiveFields::Casters::BaseCaster\n  def serialize(value)\n    value = value\u0026.to_s\n    value = value\u0026.strip if options[:strip]\n\n    value\n  end\n\n  def deserialize(value)\n    value = value\u0026.to_s\n    value = value\u0026.strip if options[:strip]\n\n    value\n  end\nend\n```\n\n#### Finder\n\nTo create your custom finder, you should define a class that inherits from one of the following base classes:\n- `ActiveFields::Finders::SingularFinder` - for singular values,\n- `ActiveFields::Finders::ArrayFinder` - for array values,\n- `ActiveFields::Finders::BaseCaster` - if you don’t need built-in helper methods.\n\nFinder classes include a DSL for defining search operations and provide helper methods to simplify query building.\nExplore the source code to discover all these methods.\n\n```ruby\n# lib/ip_finder.rb (or anywhere you want)\nclass IpFinder \u003c ActiveFields::Finders::SingularFinder\n  operation :eq, operator: \"=\" do |value|\n    scope.where(eq(casted_value_field(\"text\"), cast(value)))\n    # Equivalent to:\n    # if value.is_a?(TrueClass) || value.is_a?(FalseClass) || value.is_a?(NilClass)\n    #   scope.where(\"CAST(active_fields_values.value_meta -\u003e\u003e 'const' AS text) IS ?)\", cast(value))\n    # else\n    #   scope.where(\"CAST(active_fields_values.value_meta -\u003e\u003e 'const' AS text) = ?)\", cast(value))\n    # end\n  end\n  operation :not_eq, operator: \"!=\" do |value|\n    scope.where(not_eq(casted_value_field(\"text\"), cast(value)))\n  end\n\n  def cast(value)\n    IpCaster.new.deserialize(value)\n  end\nend\n\n# lib/ip_array_finder.rb (or anywhere you want)\nclass IpArrayFinder \u003c ActiveFields::Finders::ArrayFinder\n  operation :include, operator: \"|=\" do |value|\n    scope.where(value_match_any(\"==\", cast(value)))\n    # Equivalent to:\n    # scope.where(\"jsonb_path_exists(active_fields_values.value_meta -\u003e 'const', ?, ?)\", \"$[*] ? (@ == $value)\", { value: cast(value) }.to_json)\n  end\n  operation :not_include, operator: \"!|=\" do |value|\n    scope.where.not(value_match_any(\"==\", cast(value)))\n  end\n  operation :size_eq, operator: \"#=\" do |value|\n    scope.where(value_size_eq(value))\n    # Equivalent to:\n    # scope.where(\"jsonb_array_length(active_fields_values.value_meta -\u003e 'const') = ?\", value\u0026.to_i)\n  end\n  operation :size_not_eq, operator: \"#!=\" do |value|\n    scope.where(value_size_not_eq(value))\n  end\n  operation :size_gt, operator: \"#\u003e\" do |value|\n    scope.where(value_size_gt(value))\n  end\n  operation :size_gteq, operator: \"#\u003e=\" do |value|\n    scope.where(value_size_gteq(value))\n  end\n  operation :size_lt, operator: \"#\u003c\" do |value|\n    scope.where(value_size_lt(value))\n  end\n  operation :size_lteq, operator: \"#\u003c=\" do |value|\n    scope.where(value_size_lteq(value))\n  end\n\n  private\n\n  def cast(value)\n    caster = IpCaster.new\n    caster.serialize(caster.deserialize(value))\n  end\n\n  # This method must be defined to utilize the `value_match_any` and `value_match_all` helper methods in your class.\n  # It should return a valid JSONPath expression for use in PostgreSQL jsonb query functions.\n  def jsonpath(operator) = \"$[*] ? (@ #{operator} $value)\"\nend\n```\n\nOnce defined, every _Active Value_ of this type will support the specified search operations!\n\n```ruby\n# Find customizables\nAuthor.where_active_fields([\n  { name: \"main_ip\", operator: \"eq\", value: \"127.0.0.1\" },\n  { n: \"all_ips\", op: \"#\u003e=\", v: 5 },\n  { name: \"all_ips\", operator: \"|=\", value: \"0.0.0.0\" },\n])\n\n# Find Active Values\nIpFinder.new(active_field: ip_active_field).search(op: \"eq\", value: \"127.0.0.1\")\nIpArrayFinder.new(active_field: ip_array_active_field).search(op: \"#\u003e=\", value: 5)\n```\n\n### Multi-tenancy (scoping)\n\nThe scoping feature enables multi-tenancy or context-based field definitions per model.\nIt allows you to define different sets of _Active Fields_ for different scopes (e.g., different tenants, organizations, or contexts).\n\n**How it works:**\n- Pass a `scope_method` parameter to `has_active_fields` to enable scoping for a _Customizable_ model. The method should return a value that identifies the scope (e.g., `tenant_id`, `organization_id`).\n- The scope method's return value is automatically converted to a string and exposed as `active_fields_scope` on each _Customizable_ record. This value is used to match against _Active Field_ `scope` values.\n- When an _Active Field_ has `scope` = `nil` (_global field_), it is available to all _Customizable_ records, regardless of their scope value.\n- When an _Active Field_ has a `scope` != `nil` (_scope field_), it is only available to _Customizable_ records where `active_fields_scope` matches the `scope`.\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_active_fields scope_method: :tenant_id\nend\n\n# Global active field (available to all users)\nActiveFields::Field::Text.create!(\n  name: \"note\",\n  customizable_type: \"User\",\n  scope: nil,\n)\n\n# Scoped active field (only available to users with tenant_id = \"tenant_1\")\nActiveFields::Field::Integer.create!(\n  name: \"age\",\n  customizable_type: \"User\",\n  scope: \"tenant_1\",\n)\n\n# Scoped active field (only available to users with tenant_id = \"tenant_2\")\nActiveFields::Field::Date.create!(\n  name: \"registered_on\",\n  customizable_type: \"User\",\n  scope: \"tenant_2\",\n)\n\n# Usage\nuser_1 = User.create!(tenant_id: \"tenant_1\")\nuser_1.active_fields # Returns `note` and `age`\n\nuser_2 = User.create!(tenant_id: \"tenant_2\")\nuser_2.active_fields # Returns `note` and `registered_on`\n\nuser_3 = User.create!(tenant_id: nil)\nuser_3.active_fields # Returns only `note`\n\n# Query with scope\nUser.active_fields # Returns only `note`\nUser.active_fields(scope: \"tenant_1\") # Returns `note` and `age`\nUser.where_active_fields(filters) # Search by global fields only (`note`)\nUser.where_active_fields(filters, scope: \"tenant_1\") # Search by `note` and `registered_on`\n```\n\n**Handling scope changes:**\n\nIf you change the scope value of a _Customizable_ record (e.g., changing `tenant_id`), you must manually destroy _Active Values_ that are no longer available for that record.\nThe gem does not automatically handle this because the `scope_method` implementation is up to you, and therefore its change tracking is your responsibility.\nHowever, there is a helper method that you could use to clear the _Active Values_ list: `clear_unavailable_active_values`.\n\n**Example 1:** `scope_method` is a single database column.\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_active_fields scope_method: :tenant_id\n\n  after_update :clear_unavailable_active_values, if: :saved_change_to_tenant_id?\nend\n```\n\n**Example 2:** `scope_method` is a computed value from multiple columns.\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_active_fields scope_method: :tenant_and_department_scope\n\n  # The scope method should return a string\n  def tenant_and_department_scope\n    \"#{tenant_id}-#{department_id}\"\n  end\n\n  after_update :clear_unavailable_active_values, if: :tenant_and_department_scope_changed?\n\n  private\n\n  def tenant_and_department_scope_changed?\n    saved_change_to_tenant_id? || saved_change_to_department_id?\n  end\nend\n```\n\n### Localization (I18n)\n\nThe built-in _validators_ primarily use _Rails_ default error types.\nHowever, there are some custom error types that you’ll need to handle in your locale files:\n- `size_too_short` (args: `count`): Triggered when the size of an array _Active Field_ value is smaller than the allowed minimum.\n- `size_too_long` (args: `count`): Triggered when the size of an array _Active Field_ value exceeds the allowed maximum.\n- `duplicate`: Triggered when an enum array _Active Field_ contains duplicate elements.\n\nFor an example, refer to the [locale file](https://github.com/lassoid/active_fields/blob/main/spec/dummy/config/locales/en.yml).\n\n## Current Restrictions\n\n1. This gem requires _PostgreSQL_ and is not designed to support other database systems.\n\n2. Updating some _Active Fields_ options may be unsafe.\n\n    This could cause existing _Active Values_ to become invalid,\n    leading to the associated _Customizables_ also becoming invalid,\n    which could potentially result in update failures.\n\n## API Overview\n\n### Fields API\n\n```ruby\nactive_field = ActiveFields::Field::Boolean.take\n\n# Associations:\nactive_field.active_values # `has_many` association with Active Values associated with this Active Field\n\n# Attributes:\nactive_field.type # Class name of this Active Field (utilizing STI)\nactive_field.customizable_type # Name of the Customizable model this Active Field is registered to\nactive_field.name # Identifier of this Active Field, it should be unique in scope of customizable_type\nactive_field.default_value_meta # JSON column declaring the default value. Consider using `default_value` instead\nactive_field.options # JSON column containing type-specific attributes for this Active Field\n\n# Methods:\nactive_field.default_value # Default value for all Active Values associated with this Active Field\nactive_field.array? # Returns whether the Active Field type is an array\nactive_field.value_validator_class # Class used for values validation\nactive_field.value_validator # Validator object that performs values validation\nactive_field.value_caster_class # Class used for values casting\nactive_field.value_caster # Caster object that performs values casting\nactive_field.customizable_model # Customizable model class\nactive_field.type_name # Identifier of the type of this Active Field (instead of class name)\nactive_field.available_customizable_types # Available Customizable types for this Active Field\n\n# Scopes:\nActiveFields::Field::Boolean.for(\"Post\") # Collection of Active Fields registered for the specified Customizable type\nActiveFields::Field::Integer.for(\"User\", scope: \"main_tenant\") # Collection of Active Fields available for the specified Customizable type with given scope\n```\n\n### Values API\n\n```ruby\nactive_value = ActiveFields::Value.take\n\n# Associations:\nactive_value.active_field # `belongs_to` association with the associated Active Field\nactive_value.customizable # `belongs_to` association with the associated Customizable\n\n# Attributes:\nactive_value.value_meta # JSON column declaring the value. Consider using `value` instead\n\n# Methods:\nactive_value.value # The value of this Active Value\nactive_value.name # Name of the associated Active Field\n```\n\n### Customizable API\n\n```ruby\ncustomizable = Post.take\n\n# Associations:\ncustomizable.active_values # `has_many` association with Active Values linked to this Customizable\n\n# Methods:\ncustomizable.active_fields # Collection of Active Fields available for this record\ncustomizable.active_fields_scope # Scope value for this record\nPost.active_fields # Collection of Active Fields available for this model\nUser.active_fields(scope: \"main_tenant\") # Collection of Active Fields available for this model and given scope\nPost.allowed_active_fields_type_names # Active Fields type names allowed for this Customizable model\nPost.allowed_active_fields_class_names # Active Fields class names allowed for this Customizable model\nUser.active_fields_scope_method # Scope method for this model\n\n# Create, update or destroy Active Values.\ncustomizable.active_fields_attributes = [\n  { name: \"integer_array\", value: [1, 4, 5, 5, 0] }, # create or update (symbol keys)\n  { \"name\" =\u003e \"text\", \"value\" =\u003e \"Lasso\" }, # create or update (string keys)\n  { name: \"date\", _destroy: true }, # destroy (symbol keys)\n  { \"name\" =\u003e \"boolean\", \"_destroy\" =\u003e true }, # destroy (string keys)\n  permitted_params, # params could be passed, but they must be permitted\n]\n\n# Alias of `#active_fields_attributes=`.\ncustomizable.active_fields = [\n  { name: \"integer_array\", value: [1, 4, 5, 5, 0] }, # create or update (symbol keys)\n  { \"name\" =\u003e \"text\", \"value\" =\u003e \"Lasso\" }, # create or update (string keys)\n  { name: \"date\", _destroy: true }, # destroy (symbol keys)\n  { \"name\" =\u003e \"boolean\", \"_destroy\" =\u003e true }, # destroy (string keys)\n  permitted_params, # params could be passed, but they must be permitted\n]\n\n# Create, update or destroy Active Values.\n# Implemented by `accepts_nested_attributes_for`.\n# Please use `active_fields_attributes=`/`active_fields=` instead.\ncustomizable.active_values_attributes = attributes\n\n# Build not existing Active Values, with the default value for each Active Field.\n# Returns full collection of Active Values.\n# This method is useful with `fields_for`, allowing you to pass the collection as an argument to render new Active Values:\n# `form.fields_for :active_fields, customizable.initialize_active_values`.\ncustomizable.initialize_active_values\n\n# Destroys Active Values that are no longer associated with Active Fields available for this record.\n# Call this method after changing the scope value to ensure all Active Values are valid.\ncustomizable.clear_unavailable_active_values\n\n# Query Customizables by Active Values.\nPost.where_active_fields(\n  [\n    { name: \"integer_array\", operator: \"any_gteq\", value: 5 }, # symbol keys\n    { \"name\" =\u003e \"text\", operator: \"=\", \"value\" =\u003e \"Lasso\" }, # string keys\n    { n: \"boolean\", op: \"!=\", v: false }, # compact form (string or symbol keys)\n  ],\n)\n# Search with given scope.\nUser.where_active_fields(\n  filters,\n  scope: \"main_tenant\",\n)\n```\n\n### Global Config\n\n```ruby\nActiveFields.config # Access the plugin's global configuration\nActiveFields.config.fields # Registered Active Fields types (type_name =\u003e field_class)\nActiveFields.config.field_base_class # Base class for all Active Fields\nActiveFields.config.field_base_class_name # Name of the Active Fields base class\nActiveFields.config.value_class # Active Values class\nActiveFields.config.value_class_name # Name of the Active Values class\nActiveFields.config.field_base_class_changed? # Check if the Active Fields base class has changed\nActiveFields.config.value_class_changed? # Check if the Active Values class has changed\nActiveFields.config.type_names # Registered Active Fields type names\nActiveFields.config.type_class_names # Registered Active Fields class names\nActiveFields.config.register_field(:ip, \"IpField\") # Register a custom Active Field type\n```\n\n### Registry\n\n```ruby\nActiveFields.registry.add(:boolean, \"Post\") # Stores relation between Active Field type and customizable type. Please do not use directly.\nActiveFields.registry.customizable_types_for(:boolean) # Returns Customizable types that allow provided Active Field type name\nActiveFields.registry.field_type_names_for(\"Post\") # Returns Active Field type names, allowed for given Customizable type\n```\n\n## Development\n\nAfter checking out the repo, run `spec/dummy/bin/setup` to setup the environment.\nThen, run `bin/rspec` to run the tests.\nYou can also run `bin/rubocop` to lint the source code,\n`bin/rails c` for an interactive prompt that will allow you to experiment\nand `bin/rails s` to start the Dummy app with plugin already enabled and configured.\n\nTo install this gem onto your local machine, run `bin/rake install`.\nTo release a new version, update the version number in `version.rb`, and then run `bin/rake release`,\nwhich will create a git tag for the version, push git commits and the created tag,\nand push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/lassoid/active_fields.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flassoid%2Factive_fields","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flassoid%2Factive_fields","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flassoid%2Factive_fields/lists"}