On Fri, Nov 19, 2021 at 7:26 PM [email protected] <
[email protected]> wrote:

> I'm having some trouble using nested_attributes in conjunction with the
> tactical_eager_loading plugin.
>
> Specifically, I'm trying to set a default value for a one_to_one
> association in before_validation only if that association doesn't already
> have an associated object via an update of a one_to_many association also
> using nested attributes. However, it seems that the association is always
> cleared by the time we get to before_validation so we always end up with
> the default.
>
> Here is a basic setup to illustrate the problem:
>
> DB.create_table :albums do
>   primary_key :id
>   column :title, :text
> end
>
> DB.create_table :tracks do
>   primary_key :id
>   column :title, :text
>   foreign_key :album_id, :albums, null: false
> end
>
> DB.create_table :studios do
>   primary_key :id
>   column :name, :text
>   foreign_key :track_id, :tracks
> end
>
> Sequel::Model.plugin :nested_attributes
> Sequel::Model.plugin :tactical_eager_loading
>
> class Album < Sequel::Model
>   one_to_many :tracks
>   nested_attributes :tracks
> end
>
> class Track < Sequel::Model
>   many_to_one :album
>   one_to_one :studio
>   nested_attributes :studio
>
>   def before_validation
>     self.studio_attributes = { name: "Default Studio" } if !studio
>
>     super
>   end
> end
>
> class Studio < Sequel::Model
>   one_to_one :track
> end
>
> album = Album.create \
>   title: "Title",
>   tracks_attributes: [{
>     title: "First track",
>     studio_attributes: {
>       name: "MY favorite studio"
>     }
>   }]
>
> album = Album.first
> album.update \
>   tracks_attributes: [{
>     id: album.tracks.first.id,
>     title: "Renamed"
>   }, {
>     title: "A New track",
>     studio_attributes: {
>       name: "My second favorite studio"
>     }
>   }]
>
> expected = "My second favorite studio"
> actual = Track[title: "A New track"].studio.name
>
> raise "expected: #{expected}, actual: #{actual}" if expected != actual
>
>
> For the new track being added in the update, the studio= setter is being
> called twice because the studio association is nil in
> Track#before_validation. We tracked it down to the
> initialize_association_cache clearing the association via
> tactical_eager_loading after studio_attributes= is first called.
>
> Is there a more conventional way to do this you could suggest or is it
> indeed a bug?
>

First, thanks for providing a self-contained reproducible example.  That
makes things much easier.

In terms of working around the issue, you could switch the
before_validation to after_save, and don't use studio_attributes inside it:

  def after_save
    super
    self.studio ||= Studio.new(name: "Default Studio")
  end

That should work fine if you don't need to do some special validation of
the studio when saving the track.  You don't need a presence validation or
similar, since if there isn't a studio set when validating, you know one
will be set after save.

In terms of why your code doesn't work, it looks like it's due to a bug in
tactical eager loading. Tactical eager loading will try to eager load for
all objects if the current object doesn't have a cached association.  It
should probably only eager load for objects that don't have a cached
association if the current object doesn't have a cached association.  I
committed a fix for this:
https://github.com/jeremyevans/sequel/commit/288ae6f210842cbca54d3a18425117ad1d782ff4

Your example was very helpful, it helped fix this bug, which has been
present in tactical_eager_loading since it was originally introduced over
12 years ago.

Thanks,
Jeremy

-- 
You received this message because you are subscribed to the Google Groups 
"sequel-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sequel-talk/CADGZSSfY8may50tUEVZhG_8PoENLxQg3Oq18DUst0%3D3J9g-7DA%40mail.gmail.com.

Reply via email to