Skip to content

Issue associations not automatically set when there is an award emoji

This issue was first discovered via import code and fixed for imports via this MR: !131958 (merged)

The fix includes saving all subrelations separately from the parent. This results in child records being persisted during imports but does not fix the underlying issue, which is unexpected behavior when persisting Issue record subrealtions.

What I expect to see

when an issue is initialized alongside many subrelations, such as award_emoji, assignees, notes, and labels, and the issue is saved and the subrelations should all save as well. This is default Rails behavior.

What I see instead

Instead, if an award emoji is present, only the award emoji will save. All other subrelations fail to save but without any errors.

i = FactoryBot.build(:issue, author: User.first, notes: [FactoryBot.build(:note, importing: true)], award_emoji: [FactoryBot.build(:award_emoji)], project: Project.last)
i.save! 

=> issue saved, award_emoji saved, note not saved

More detail

Reproduction via RSpec

One thing that is confusing me so far is that this happens on an Issue but not other "awardable" objects, such as Merge Requests or Snippets.

Here is a spec that shows how subrelation persistence failure is happening (this spec failures for the issue awardable but not the other types)

# spec/models/concerns/awardable_spec.rb

context "awardable item is persisted alongside award emoji and other relations", :aggregate_failures do
  where(awardable_type: [:issue, :merge_request, :snippet])

  with_them do
    it "saves all subrelations" do
      awardable = build(awardable_type)
      note = build(:note, importing: true)
      award_emoji = build(:award_emoji)
      awardable.award_emoji << award_emoji
      awardable.notes << note

      awardable.save!

      expect(awardable).to be_persisted
      expect(awardable.award_emoji.count).to eq 1
      expect(awardable.notes.count).to eq 1
    end
  end
end

This is how the spec fails:

Failures:

  1) Awardable Associations awardable item is persisted alongside award emoji and other relations awardable_type: :issue saves all subrelations
     Failure/Error: expect(awardable.notes.count).to eq 1

       expected: 1
            got: 0


Finished in 11.72 seconds (files took 2.2 seconds to load)
3 examples, 1 failure

So the issue and award emoji are persisted, but not the note. The same thing happens for any other has_many relations on an issue that are new records on the issue at the time that issue.save! is called. So if you try to initialize a new label and associate that with the issue, it will also not save if there is also a new award emoji being persisted on the issue at the same time.

Fix via removing inverse_of

Again, this is for issues only. The key elements here seem to be Issue + Award emoji. The specs pass for merge requests and snippets, which also have has_many relationships with award emoji and notes via the Issuable concern.

I do know how to fix this problem, but I do not understand why. Currently, Awardable, which is included in the Issuable concern, which is included in MergeRequest, Issue, and Snippet models, defines the relation like this:

 has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, inverse_of: :awardable, dependent: :destroy

If I remove inverse_of: awardable here, the spec above passes. This was the best blog post I could find about inverse_of

So the key thing might be our use of inverse_of. I've dug through the rails/rails history and here are some issues that feel related to this bug (but no exact matches):

  • "On has_many_inversing object_id has different values after create" (issue) - issue closed without a fix
  • "Rails 6.1 double assignment leads to duplicate in-memory associations" (issue) (MR with fix)
  • "collection= for a has_many association doesn't persist added records already referencing the parent when has_many_inversing is enabled" (issue) (we do not have has_many_inversing enabled but having that option enabled is just an implicit global inverse_of: for all associations, as far as I can tell, which would mean this is an error related to inverse_of: being set)
Edited by Jessie Young