Thanks for your quick response and fix! I think adding the default in 
after_save should work just fine. 

Happy to hear that the example helped fix the bug. Sequel has been a 
pleasure to work with so I'm glad to be able to contribute in any way I can!

On Saturday, November 20, 2021 at 12:04:10 AM UTC-8 Jeremy Evans wrote:

> On Fri, Nov 19, 2021 at 7:26 PM s.brimd...@gmail.com <s.brimd...@gmail.com> 
> 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 sequel-talk+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sequel-talk/c98e8910-da11-4ad5-919e-4164a62ffa35n%40googlegroups.com.

Reply via email to