README.md in active_fields-1.0.0 vs README.md in active_fields-1.1.0

- old
+ new

@@ -7,13 +7,13 @@ **ActiveFields** is a Rails plugin that implements the Entity-Attribute-Value (EAV) pattern, enabling the addition of custom fields to any model at runtime without requiring changes to the database schema. ## Key Concepts -- **Active Field**: A record with the definition of a custom field. -- **Active Value**: A record that stores the value of an _Active Field_ for a specific _Customizable_. -- **Customizable**: A record that has custom fields. +- **Customizable**: A record that has custom fields (_Entity_). +- **Active Field**: A record with the definition of a custom field (_Attribute_). +- **Active Value**: A record that stores the value of an _Active Field_ for a specific _Customizable_ (_Value_). ## Models Structure ```mermaid classDiagram @@ -22,15 +22,15 @@ class ActiveField { + string name + string type + string customizable_type - + json default_value + + json default_value_meta + json options } class ActiveValue { - + json value + + json value_meta } class Customizable { // This is your model } ``` @@ -54,82 +54,94 @@ ``` 3. Add the `has_active_fields` method to any models where you want to enable custom fields: ```ruby - class Author < ApplicationRecord + class Post < ApplicationRecord has_active_fields end ``` -4. Implement the necessary code to work with _Active Fields_. +4. Run scaffold generator. - This plugin provides a convenient API and helpers, allowing you to write code that meets your specific needs + This plugin provides a convenient API, allowing you to write code that meets your specific needs without being forced to use predefined implementations that is hard to extend. - Generally, you should: - - Implement a controller and UI for managing _Active Fields_. - - Add inputs for _Active Values_ in _Customizable_ forms and permit their params in the controller. + However, for a quick start, you can generate a scaffold by running the following command: - To set _Active Values_ for your _Customizable_, use the `active_fields_attributes=` method, - that integrates with Rails `fields_for` to generate appropriate form fields. - Alternatively, the alias `active_fields=` can be used in contexts without `fields_for`, such as APIs. + ```shell + bin/rails generate active_fields:scaffold + ``` - To prepare a collection of _Active Values_ for use with the `fields_for` builder, - call the `initialize_active_values` method. + This command generates a controller, routes and views for managing _Active Fields_, + along with form inputs for _Active Values_ and some useful helper methods. - **Note:** By default, Rails form fields insert an empty string into array (multiple) parameters. - You’ll need to handle the removal of these empty strings. + **Note:** Don't forget to add available _Customizable_ types in generated _Active Fields_ forms. - ```ruby - # app/controllers/posts_controller.rb - # ... + **Note:** The array field helper uses _Stimulus_ for interactivity. + If your app doesn't already include _Stimulus_, you can [easily add it](https://github.com/hotwired/stimulus-rails). + Alternatively, if you prefer not to use _Stimulus_, you should implement your own JavaScript code. - def new - @post = Post.new - @post.initialize_active_values - end +5. Add _Active Fields_ inputs in _Customizables_ forms and permit their params in controllers. - def edit - @post.initialize_active_values - end + There are two methods available on _Customizable_ models for retrieving _Active Values_: + - `active_values` returns collection of only existing _Active Values_. + - `initialize_active_values` builds any missing _Active Values_ and returns the full collection. - def post_params - permitted_params = params.require(:post).permit( - # ... - active_fields_attributes: [:name, :value, :_destroy, value: []], - ) - permitted_params[:active_fields_attributes]&.each do |_index, value_attrs| - value_attrs[:value] = compact_array_param(value_attrs[:value]) if value_attrs[:value].is_a?(Array) - end + Choose the method that suits your requirements. + In most cases, however, `initialize_active_values` is the more suitable option. - permitted_params - end + ```erb + # app/views/posts/_form.html.erb + # ... - def compact_array_param(value) - if value.first == "" - value[1..-1] - else - value - end - end - ``` + <%= form.fields_for :active_fields, post.initialize_active_values.sort_by(&:active_field_id), include_id: false do |active_fields_form| %> + <%= active_fields_form.hidden_field :name %> + <%= render_active_value_input(form: active_fields_form, active_value: active_fields_form.object) %> + <% end %> - ```erb - # app/views/posts/_form.html.erb - # ... + # ... + ``` - <%= form.fields_for :active_fields, post.active_values.sort_by(&:active_field_id), include_id: false do |active_fields_form| %> - <%= active_fields_form.hidden_field :name %> - # Render appropriate Active Value input and (optionally) destroy flag here - <% end %> + Finally, permit the _Active Fields_ attributes in your _Customizables_ controllers: - # ... - ``` + ```ruby + # app/controllers/posts_controller.rb + # ... - You can find a detailed [example](https://github.com/lassoid/active_fields/blob/main/spec/dummy) - of how to implement this in a full-stack Rails application. + def post_params + permitted_params = params.require(:post).permit( + # ... + active_fields_attributes: [:name, :value, :_destroy, value: []], + ) + permitted_params[:active_fields_attributes]&.each do |_index, value_attrs| + value_attrs[:value] = compact_array_param(value_attrs[:value]) if value_attrs[:value].is_a?(Array) + end + + permitted_params + end + + # Removes an empty string from the beginning of the array parameter + def compact_array_param(value) + if value.first == "" + value[1..-1] + else + value + end + end + ``` + + **Note:** Here we use the `active_fields_attributes=` method (as a permitted parameter), + that integrates well with Rails `fields_for` to generate appropriate form fields. + Alternatively, the alias `active_fields=` can be used in contexts without `fields_for`, such as API controllers. + + That's it! + You can now add _Active Fields_ to _Customizables_ at `http://localhost:3000/active_fields` + and fill in _Active Values_ within _Customizable_ forms. + + You can also explore the [Demo app](https://github.com/lassoid/active_fields/blob/main/spec/dummy) + where the plugin is fully integrated into a full-stack Rails application. Feel free to explore the source code and run it locally: ```shell spec/dummy/bin/setup bin/rails s @@ -249,11 +261,11 @@ ### Fields Base Attributes - `name`(`string`) - `type`(`string`) - `customizable_type`(`string`) -- `default_value` (`json`) +- `default_value_meta` (`json`) ### Field Types Summary All _Active Field_ model names start with `ActiveFields::Field`. We replace it with `**` for conciseness. @@ -493,10 +505,12 @@ This could cause existing _Active Values_ to become invalid, leading to the associated _Customizables_ also becoming invalid, which could potentially result in update failures. +3. Only _Zeitwerk_ autoloading mode is supported. + ## API Overview ### Fields API ```ruby @@ -508,11 +522,11 @@ # Attributes: active_field.type # Class name of this Active Field (utilizing STI) active_field.customizable_type # Name of the Customizable model this Active Field is registered to active_field.name # Identifier of this Active Field, it should be unique in scope of customizable_type active_field.default_value_meta # JSON column declaring the default value. Consider using `default_value` instead -active_field.options # A hash (json) containing type-specific attributes for this Active Field +active_field.options # JSON column containing type-specific attributes for this Active Field # Methods: active_field.default_value # Default value for all Active Values associated with this Active Field active_field.array? # Returns whether the Active Field type is an array active_field.value_validator_class # Class used for values validation @@ -521,11 +535,11 @@ active_field.value_caster # Caster object that performs values casting active_field.customizable_model # Customizable model class active_field.type_name # Identifier of the type of this Active Field (instead of class name) # Scopes: -ActiveFields::Field::Boolean.for("Author") # Collection of Active Fields registered for the specified Customizable type +ActiveFields::Field::Boolean.for("Post") # Collection of Active Fields registered for the specified Customizable type ``` ### Values API ```ruby @@ -544,11 +558,11 @@ ``` ### Customizable API ```ruby -customizable = Author.take +customizable = Post.take # Associations: customizable.active_values # `has_many` association with Active Values linked to this Customizable # Methods: @@ -575,13 +589,14 @@ # Create, update or destroy Active Values. # Implemented by `accepts_nested_attributes_for`. # Please use `active_fields_attributes=`/`active_fields=` instead. customizable.active_values_attributes = attributes -# Build an Active Value, if it doesn't exist, with the default value for each Active Field. +# Build not existing Active Values, with the default value for each Active Field. +# Returns full collection of Active Values. # This method is useful with `fields_for`, allowing you to pass the collection as an argument to render new Active Values: -# `form.fields_for :active_values, customizable.active_values`. +# `form.fields_for :active_fields, customizable.initialize_active_values`. customizable.initialize_active_values ``` ### Global Config @@ -600,10 +615,10 @@ ``` ### Customizable Config ```ruby -customizable_model = Author +customizable_model = Post customizable_model.active_fields_config # Access the Customizable's configuration customizable_model.active_fields_config.customizable_model # The Customizable model itself customizable_model.active_fields_config.types # Allowed Active Field types (e.g., `[:boolean]`) customizable_model.active_fields_config.types_class_names # Allowed Active Field class names (e.g., `[ActiveFields::Field::Boolean]`) ```