Skip to content

Commit

Permalink
Fix accepts_nested_attributes_for raising error when database is not …
Browse files Browse the repository at this point in the history
…initialized or table for model doesnt exist

Fix assign_nested_attributes_for_collection_association override breaking ActiveRecord::Base#accepts_nested_attributes_for for non storemodel relations
  • Loading branch information
Arseniy Chirkov committed Sep 1, 2023
1 parent b71db75 commit 6a0a8c8
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 9 deletions.
37 changes: 30 additions & 7 deletions lib/store_model/nested_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,42 @@ module ClassMethods # :nodoc:
#
# See https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for
def accepts_nested_attributes_for(*attributes)
global_options = attributes.extract_options!
options = attributes.extract_options!

attributes.each do |attribute, options|
case attribute_types[attribute.to_s]
attributes.each do |attribute|
case nested_attribute_type(attribute)
when Types::OneBase, Types::ManyBase
define_store_model_attr_accessors(attribute, options || global_options)
options.reverse_merge!(allow_destroy: false, update_only: false)
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)

define_store_model_attr_accessors(attribute, options)
else
super(attribute, options || global_options)
super(*attribute, options)
end
end
end

private

# If attribute defined in ActiveRecord model but you dont yet have database created
# you cannot access attribute types.
# To handle this case, we can use ActiveRecord::Attributes 'attributes_to_define_after_schema_loads'
# which stores information about custom defined attributes.
# See ActiveRecord::Attributes#atribute
# If #accepts_nested_attributes_for is used inside active model instance
# schema is not required to determine attribute type so we can still use attribute_types
# If schema loaded the attribute_types already populated and we can safely use it
# See ActiveRecord::ModelSchema#load_schema!
def nested_attribute_type(attribute)
if self < ActiveRecord::Base && !schema_loaded?
attributes_to_define_after_schema_loads[attribute.to_s]&.first
else
attribute_types[attribute.to_s]
end
end

def define_store_model_attr_accessors(attribute, options)
case attribute_types[attribute.to_s]
case nested_attribute_type(attribute)
when Types::OneBase
define_association_setter_for_single(attribute, options)
alias_method "#{attribute}_attributes=", "#{attribute}="
Expand Down Expand Up @@ -88,7 +108,10 @@ def define_association_setter_for_single(association, options)
end
end

def assign_nested_attributes_for_collection_association(association, attributes, options)
# Base
def assign_nested_attributes_for_collection_association(association, attributes, options=nil)
return super(association, attributes) unless options

attributes = attributes.values if attributes.is_a?(Hash)

if options&.dig(:allow_destroy)
Expand Down
24 changes: 22 additions & 2 deletions spec/store_model/nested_attributes_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class Store
attribute :color, :string
attribute :supplier, Supplier.to_type

accepts_nested_attributes_for [:supplier, { allow_destroy: true }]
accepts_nested_attributes_for :supplier, allow_destroy: true
end
end

Expand Down Expand Up @@ -214,7 +214,7 @@ class Store
attribute :color, :string
attribute :suppliers, Supplier.to_array_type

accepts_nested_attributes_for [:suppliers, { allow_destroy: true }]
accepts_nested_attributes_for :suppliers, allow_destroy: true
end
end

Expand Down Expand Up @@ -389,6 +389,26 @@ def self.model_name
it { is_expected.to respond_to(:products_attributes=) }
it { is_expected.to respond_to(:bicycles_attributes=) }
end

context "when db is not connected" do
subject do
model_class.accepts_nested_attributes_for(:suppliers)
model_class
end

let(:model_class) do
Class.new(ActiveRecord::Base) do
include StoreModel::NestedAttributes

def self.name = "invalid_table_name"

attribute :suppliers, Supplier.to_array_type
end
end

it { expect { subject }.not_to(raise_error) }
it { expect(subject.instance_methods).to(include(:suppliers_attributes=)) }
end
end
end
end

0 comments on commit 6a0a8c8

Please sign in to comment.