# frozen_string_literal: true module ActiveFields # Model mix-in that adds the active fields functionality to the customizable model module CustomizableConcern extend ActiveSupport::Concern included do # rubocop:disable Rails/ReflectionClassName has_many :active_values, class_name: ActiveFields.config.value_class_name, as: :customizable, inverse_of: :customizable, autosave: true, dependent: :destroy # rubocop:enable Rails/ReflectionClassName accepts_nested_attributes_for :active_values, allow_destroy: true end def active_fields ActiveFields.config.field_base_class.for(model_name.name) end # Assigns the given attributes to the active_values association. # # Accepts an Array of Hashes (symbol/string keys) or permitted params. # Each element should contain a :name key matching an existing active_field record. # Element with a :value key will create an active_value if it doesn't exist # or update an existing active_value, with the provided value. # Element with a :_destroy key set to a truthy value will mark the # matched active_value for destruction. # # Example: # # customizable.active_fields_attributes = [ # { name: "integer_array", value: [1, 4, 5, 5, 0] }, # create or update (symbol keys) # { "name" => "text", "value" => "Lasso" }, # create or update (string keys) # { name: "date", _destroy: true }, # destroy (symbol keys) # { "name" => "boolean", "_destroy" => true }, # destroy (string keys) # permitted_params, # params could be passed, but they must be permitted # ] def active_fields_attributes=(attributes) attributes = attributes.to_h if attributes.respond_to?(:permitted?) unless attributes.is_a?(Array) || attributes.is_a?(Hash) raise ArgumentError, "Array or Hash expected for `active_fields=`, got #{attributes.class.name}" end # Handle `fields_for` params attributes = attributes.values if attributes.is_a?(Hash) active_fields_by_name = active_fields.index_by(&:name) active_values_by_field_id = active_values.index_by(&:active_field_id) nested_attributes = attributes.filter_map do |active_value_attributes| # Convert params to Hash active_value_attributes = active_value_attributes.to_h if active_value_attributes.respond_to?(:permitted?) active_value_attributes = active_value_attributes.with_indifferent_access active_field = active_fields_by_name[active_value_attributes[:name]] next if active_field.nil? active_value = active_values_by_field_id[active_field.id] if has_destroy_flag?(active_value_attributes) # Destroy { id: active_value&.id, _destroy: true } elsif active_value # Update { id: active_value.id, value: active_value_attributes[:value] } else # Create { active_field: active_field, value: active_value_attributes[:value] } end end self.active_values_attributes = nested_attributes end alias_method :active_fields=, :active_fields_attributes= # Build an active_value, if it doesn't exist, with a default value for each available active_field def initialize_active_values existing_field_ids = active_values.map(&:active_field_id) active_fields.each do |active_field| next if existing_field_ids.include?(active_field.id) active_values.new(active_field: active_field, value: active_field.default_value) end end end end