Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LevelConceptDifficulty keeps attributes on create #24034

Merged
merged 3 commits into from Aug 1, 2018

Conversation

islemaster
Copy link
Contributor

@islemaster islemaster commented Aug 1, 2018

While trying to full validate the changes in #23805 I ran across an existing, subtle bug:

  • Given a Level that has no LevelConceptDifficulty
  • When a LevelConceptDifficulty is created by passing a level_concept_difficulty hash to Level's assign_attributes method
    (via ActiveRecord's assign_nested_attributes_for_one_to_one_association method)
  • Then I expect the created LevelConceptDifficulty to have the properties provided in the hash I passed to Level
  • But actually all of the concept properties are nil after creation!

The root cause is that assign_nested_attributes_for_one_to_one_association calls assign_attributes twice when it causes a new LevelConceptDifficulty to be created - once when it initially builds the model instance, and a second time to set the id of the associated Level. This second call would trigger our custom logic in LevelConceptDifficulty's assign_attributes method that unsets any concepts not included in the provided attributes... but since the provided attributes in this second call are only {level_id: 123} all concepts would be unset, and we'd end up missing properties.

As a solution, I'm skipping the custom behavior when creating a new LevelConceptDifficulty record, because in that case we shouldn't have to worry about unsetting properties.

Impact of bug

The implications of this bug are probably small, but nonzero: On production, any levels with concept difficulty properties that have been created but never updated in a subsequent DTP are probably missing concept difficulty properties.

As of August 1st, 2018 only three levels on production are currently affected (don't match the LevelConceptDifficulty defined in their .level file):

  • 2-3 Artist 9 REPLACEMENT
  • KTest2
  • Overworld Free Play 20x20

Fixes a subtle bug we didn't notice for a long time:

  Given a Level that has no LevelConceptDifficulty
  When a LevelConceptDifficulty is created by passing a `level_concept_difficulty` hash to Level's `assign_attributes` method (via ActiveRecord's [`assign_nested_attributes_for_one_to_one_association`](https://apidock.com/rails/ActiveRecord/NestedAttributes/assign_nested_attributes_for_one_to_one_association) method)
  Then I expect the created LevelConceptDifficulty to have the properties provided in the hash I passed to Level
  But actually all of the concept properties are nil after creation!

The root cause is that `assign_nested_attributes_for_one_to_one_association` calls `assign_attributes` twice when it causes a new LevelConceptDifficulty to be created - once when it initially builds the model instance, and a second time to set the id of the associated Level.  This second call would trigger our custom logic in LevelConceptDifficulty's `assign_attributes` method that unsets any concepts not included in the provided attributes... but since the provided attributes in this second call are only `{level_id: 123}` _all_ concepts would be unset, and we'd end up missing properties.

As a solution, I'm skipping the custom behavior when creating a new LevelConceptDifficulty record, because in that case we shouldn't have to worry about unsetting properties.
'level_concept_difficulty' => {'sequencing' => 3}
)
refute_nil level.level_concept_difficulty
assert_equal 3, level.level_concept_difficulty.sequencing
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of these two tests, this is the only assertion that was failing prior to my fix.

level = LevelLoader.load_custom_level(LevelLoader.level_file_path('K-1 Bee 2'))
refute_nil Level.find_by_name('K-1 Bee 2')

assert_equal 3, level.level_concept_difficulty.sequencing
Copy link
Contributor Author

@islemaster islemaster Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrote this test to see if the seeding process somehow sidestepped this issue; it doesn't. This assertion also failed before my fix.

@islemaster islemaster requested a review from Hamms August 1, 2018 17:00
@islemaster
Copy link
Contributor Author

@Hamms anyone else you'd like on this review? And anyone on the product team we should notify if we can confirm that any levels on prod have incorrect concept difficulty props? (maybe Ben?)

@Hamms
Copy link
Contributor

Hamms commented Aug 1, 2018

Definitely Ben, probably also Kiki and Ryan

@@ -212,6 +212,15 @@ def raise_unless_specifies_ideal_level_source(level_class)
level.properties['initial_dirt']
end

test 'creating custom level from file sets level_concept_difficulty' do
Level.find_by_name('K-1 Bee 2')&.destroy
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, we already have a dependency on K-1 Bee 2.level in this test file (see the test above this one) so I stuck with that for now; I'm setting up proper level file fixtures in my seeding tests on the other PR though.

@islemaster
Copy link
Contributor Author

Update: I wrote a oneoff script to check production for mismatched LCDs. There are exactly three right now:

ubuntu@production-console:~/production$ bin/oneoff/find_unseeded_level_concept_difficulty.rb 
Found mismatch: 2-3 Artist 9 REPLACEMENT
Expected: {:sequencing=>3}
Actual: {}
Found mismatch: KTest2
Expected: {:sequencing=>3}
Actual: {}
Found mismatch: Overworld Free Play 20x20
Expected: {:sequencing=>1, :debugging=>1, :repeat_loops=>1, :conditionals=>1}
Actual: {}
3602 level files didn't specify LevelConceptDifficulty
5536 level files matched the seeded LevelConceptDifficulty
3 level files DID NOT match the seeded LevelConceptDifficulty
/home/ubuntu/production/dashboard/config/scripts/levels/2-3 Artist 9 REPLACEMENT.level
/home/ubuntu/production/dashboard/config/scripts/levels/KTest2.level
/home/ubuntu/production/dashboard/config/scripts/levels/Overworld Free Play 20x20.level

I'll add the oneoff script to this PR.

Copy link
Contributor

@Hamms Hamms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Great testing and a simple fix; love me a quality bugfix.

Thanks for validating the current data, too! Three levels doesn't worry me overmuch, but we should still make sure the CSF data folks are made aware of the discrepancy.

@islemaster
Copy link
Contributor Author

I've emailed the stakeholders you mentioned.

After this fix ships, we can ensure the three affected levels get fixed with a small manual change to their audit logs, which will change their checksums and cause them to be updated next time production seeds levels.

Thanks @aoby for your help with the oneoff script and spotting me while I ran it on prod!

@islemaster islemaster merged commit 0606354 into staging Aug 1, 2018
@islemaster islemaster deleted the fix-level_concept_difficulty-creation branch August 1, 2018 19:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants