Skip to content

Commit

Permalink
Nested attributes extension
Browse files Browse the repository at this point in the history
  • Loading branch information
antulik committed Jul 9, 2024
1 parent c1a0654 commit 5cd7380
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 11 deletions.
1 change: 1 addition & 0 deletions lib/active_interaction/extras.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module Jobs
autoload(:FilterAlias, "active_interaction/extras/filter_alias")
autoload(:Halt, "active_interaction/extras/halt")
autoload(:ModelFields, "active_interaction/extras/model_fields")
autoload(:NestedAttributes, "active_interaction/extras/nested_attributes")
autoload(:RunCallback, "active_interaction/extras/run_callback")
autoload(:StrongParams, "active_interaction/extras/strong_params")
autoload(:Transaction, "active_interaction/extras/transaction")
Expand Down
1 change: 1 addition & 0 deletions lib/active_interaction/extras/all.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module ActiveInteraction::Extras::All
include ActiveInteraction::Extras::FilterAlias
include ActiveInteraction::Extras::Halt
include ActiveInteraction::Extras::ModelFields
include ActiveInteraction::Extras::NestedAttributes
include ActiveInteraction::Extras::RunCallback
include ActiveInteraction::Extras::StrongParams

Expand Down
76 changes: 76 additions & 0 deletions lib/active_interaction/extras/nested_attributes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# inspired by store_model/nested_attributes.rb
module ActiveInteraction::Extras::NestedAttributes
extend ActiveSupport::Concern

concern :InputsExtension do
def normalize(inputs)
@base.class.nested_attribute_options.each do |attribute, options|
alias_name = "#{attribute}_attributes"
next if !inputs.key?(alias_name) && !inputs.key?(alias_name.to_sym)

value = inputs[alias_name] || inputs[alias_name.to_sym]
value = @base.class.process_nested_collection(value, options)

inputs[attribute.to_s] = value
end

super
end
end

included do
ActiveInteraction::Inputs.prepend InputsExtension

class_attribute :nested_attribute_options, default: {}
end

class_methods do
def accepts_nested_attributes_for(*attributes)
options = attributes.extract_options!
options.reverse_merge!(allow_destroy: false, update_only: false)

attributes.each do |attribute|
nested_attribute_options[attribute] = options

case filters[attribute]
when ActiveInteraction::ArrayFilter
define_association_setter_for_many attribute, options
else
raise "Nested attributes are not supported for single object"
end
end
end

def define_association_setter_for_many(association, options)
define_method "#{association}_attributes=" do |attributes|
attributes = self.class.process_nested_collection(attributes, options)
send("#{association}=", attributes)
end
end

def process_nested_collection(attributes, options = nil)
attributes = attributes.values if attributes.is_a?(Hash)

if options&.dig(:allow_destroy)
attributes.reject! do |attribute|
ActiveRecord::Type::Boolean.new.cast(attribute.stringify_keys.dig("_destroy"))
end
end

attributes.reject! { |attribute| call_reject_if(attribute, options[:reject_if]) } if options&.dig(:reject_if)

attributes
end

def call_reject_if(attributes, callback)
callback = ActiveRecord::NestedAttributes::ClassMethods::REJECT_ALL_BLANK_PROC if callback == :all_blank

case callback
when Symbol
method(callback).arity.zero? ? send(callback) : send(callback, attributes)
when Proc
callback.call(attributes)
end
end
end
end
51 changes: 40 additions & 11 deletions lib/active_interaction/extras/strong_params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,47 @@ def initialize(inputs = {})

class_methods do
def permitted_params
filters.map do |filter_name, filter|
next unless filter.options[:permit]

case filter
when ActiveInteraction::ArrayFilter
{ filter_name => [] }
when ActiveInteraction::HashFilter, ActiveInteraction::ObjectFilter
{ filter_name => {} }
else
filter_name
permissions = filters.map do |filter_name, filter|
[
permission_for_filter(filter, filter_name),
# (permission_for_filter(filter, filter.options[:as]) if filter.options[:as])
]
end.flatten(1).compact

if respond_to?(:nested_attribute_options)
nested_attribute_options.each do |attribute, _|
permissions << {"#{attribute}_attributes": {}}
end
end.compact
end

permissions
end

def permission_for_filter(filter, name = filter.name)
permit = filter.options[:permit]
return unless permit

case filter
when ActiveInteraction::ArrayFilter
value =
if permit == true
nested_type = filter.filters.values.first
case nested_type
when ActiveInteraction::HashFilter, ActiveInteraction::ObjectFilter
{}
else
[]
end
else
permit
end

{ name => value }
when ActiveInteraction::HashFilter, ActiveInteraction::ObjectFilter
{ name => {} }
else
name
end
end
end
end

0 comments on commit 5cd7380

Please sign in to comment.