diff --git a/lib/rom/factory/attributes/association.rb b/lib/rom/factory/attributes/association.rb index 772ee3b..164eca8 100644 --- a/lib/rom/factory/attributes/association.rb +++ b/lib/rom/factory/attributes/association.rb @@ -69,13 +69,15 @@ class ManyToOne < Core # @api private # rubocop:disable Metrics/AbcSize def call(attrs, persist: true) + return if attrs.key?(name) && attrs[name].nil? + assoc_data = attrs.fetch(name, EMPTY_HASH) - if assoc_data && assoc_data[assoc.source.primary_key] && !attrs[foreign_key] + if assoc_data.is_a?(Hash) && assoc_data[assoc.target.primary_key] && !attrs[foreign_key] assoc.associate(attrs, attrs[name]) elsif assoc_data.is_a?(ROM::Struct) assoc.associate(attrs, assoc_data) - elsif !(attrs.key?(name) && attrs[name].nil?) && !attrs[foreign_key] + elsif !attrs[foreign_key] parent = if persist builder.persistable.create(*parent_traits, **assoc_data) else diff --git a/lib/rom/factory/tuple_evaluator.rb b/lib/rom/factory/tuple_evaluator.rb index f2e9d29..8b3b073 100644 --- a/lib/rom/factory/tuple_evaluator.rb +++ b/lib/rom/factory/tuple_evaluator.rb @@ -15,9 +15,6 @@ class TupleEvaluator # @api private attr_reader :traits - # @api private - attr_reader :model - # @api private attr_reader :sequence @@ -26,10 +23,13 @@ def initialize(attributes, relation, traits = {}) @attributes = attributes @relation = relation.with(auto_struct: true) @traits = traits - @model = @relation.combine(*assoc_names).mapper.model @sequence = Sequences[relation] end + def model(traits) + @relation.combine(*assoc_names(traits)).mapper.model + end + # @api private def defaults(traits, attrs, **opts) mergeable_attrs = select_mergeable_attrs(traits, attrs) @@ -51,7 +51,8 @@ def struct(*traits, **attrs) attributes.merge!(materialized_callables) - associations = assoc_names + associations = assoc_names(traits) + .reject { |name| attrs.key?(name) && attrs[name].nil? } .map { |key| if (assoc = @attributes[key]) && assoc.count.positive? [key, build_assoc_attrs(key, attributes[relation.primary_key], attributes[key])] @@ -63,7 +64,7 @@ def struct(*traits, **attrs) attributes = relation.output_schema[attributes] attributes.update(associations) - model.new(attributes) + model(traits).new(attributes) end def build_assoc_attrs(key, fk, value) @@ -137,19 +138,22 @@ def evaluate_traits(trait_list, attrs, opts) traits_attrs = self.traits.select { |key, _value| traits[key] }.values.flat_map(&:elements) registry = AttributeRegistry.new(traits_attrs) + self.class.new(registry, relation).defaults([], attrs, **opts) end # @api private def evaluate_associations(traits, attrs, opts) - assocs(traits).associations.each_with_object({}) do |assoc, h| - if assoc.dependency?(relation) - h[assoc.name] = ->(parent, call_opts) do + assocs(traits).associations.each_with_object({}) do |assoc, memo| + if attrs.key?(assoc.name) && attrs[assoc.name].nil? + memo + elsif assoc.dependency?(relation) + memo[assoc.name] = ->(parent, call_opts) do assoc.call(parent, **opts, **call_opts) end else result = assoc.(attrs, **opts) - h.update(result) if result + memo.update(result) if result end end end diff --git a/spec/integration/rom/factory_spec.rb b/spec/integration/rom/factory_spec.rb index cab4db5..9588857 100644 --- a/spec/integration/rom/factory_spec.rb +++ b/spec/integration/rom/factory_spec.rb @@ -84,39 +84,90 @@ end context "many-to-one" do - before do - factories.define(:task) do |f| - f.title { "Foo" } - f.association(:user) + context "with an association that is not aliased" do + before do + factories.define(:task) do |f| + f.title { "Foo" } + f.association(:user) + end + + factories.define(:user) do |f| + f.first_name "Jane" + f.last_name "Doe" + f.email "janjiss@gmail.com" + f.timestamps + + f.association(:tasks) + end end - factories.define(:user) do |f| - f.first_name "Jane" - f.last_name "Doe" - f.email "janjiss@gmail.com" - f.timestamps + it "creates a struct with associated parent" do + task = factories.structs[:task, title: "Bar"] - f.association(:tasks) + expect(task.title).to eql("Bar") + expect(task.user.first_name).to eql("Jane") end - end - it "creates a struct with associated parent" do - task = factories.structs[:task, title: "Bar"] + it "does not build associated struct if it's set to nil explicitly" do + task = factories.structs[:task, user: nil] - expect(task.title).to eql("Bar") - expect(task.user.first_name).to eql("Jane") - end + expect(task.user).to be_nil + end + + it "does not persist associated struct if it's set to nil explicitly" do + task = factories[:task, user: nil] + + expect(task.user).to be_nil + end - it "does not build associated struct if it's set to nil explicitly" do - task = factories[:task, user: nil] + it "creates the associated record with provided attributes" do + task = factories[:task, user: {first_name: "John"}] - expect(task.user).to be_nil + expect(task.user.first_name).to eql("John") + end end - it "creates the associated record with provided attributes" do - task = factories[:task, user: {first_name: "John"}] + context "with an aliased association" do + before do + factories.define(:task) do |f| + f.title { "Foo" } + f.association(:author) + end + + factories.define(:user) do |f| + f.first_name "Jane" + f.last_name "Doe" + f.email "janjiss@gmail.com" + f.timestamps + + f.association(:tasks) + end + end + + it "creates a struct with associated parent" do + task = factories.structs[:task, title: "Bar"] + + expect(task.title).to eql("Bar") + expect(task.author.first_name).to eql("Jane") + end + + it "does not build associated struct if it's set to nil explicitly" do + task = factories.structs[:task, author: nil] + + expect(task.author).to be_nil + end + + it "does not persist associated struct if it's set to nil explicitly" do + task = factories[:task, author: nil] - expect(task.user.first_name).to eql("John") + expect(task.author).to be_nil + end + + it "creates the associated record with provided attributes" do + task = factories[:task, author: {first_name: "John"}] + + expect(task.author.first_name).to eql("John") + end end end diff --git a/spec/shared/relations.rb b/spec/shared/relations.rb index b314292..b9e5fc2 100644 --- a/spec/shared/relations.rb +++ b/spec/shared/relations.rb @@ -44,6 +44,7 @@ schema(infer: true) do associations do belongs_to :user + belongs_to :user, as: :author end end end