Skip to content
  • Kasper Timm Hansen's avatar
    cea392eb
    Polymorphic has_one touch: Reset association cache result after create transaction · cea392eb
    Kasper Timm Hansen authored
    In case of a polymorphic association there's no automatic inverse_of to assign the
    inverse record. So to get the record there needs to be a query executed,
    however, if the query fires within the transaction that's trying to create
    the associated record, no record can be found. And worse, the nil result is cached
    on the association so after the transaction commits the record can't be found.
    
    That's what happens if touch is enabled on a polymorphic has_one association.
    
    Consider a Comment with a commentable association that needs to be touched.
    
    For `Comment.create(commentable: Post.new)`, the existing code essentially
    does `commentable.send(:comment)` within the create transaction for the comment
    and thus not finding the comment.
    
    Now we're purposefully clearing the cache in case we've tried accessing
    the association within the transaction and found no object.
    
    Before:
    
    ```
    kaspth-imac 2.6.3 ~/code/rails/activerecord master *= ARCONN=postgresql bin/test test/cases/associations/has_one_associations_test.rb -n /commit/
    Using postgresql
    Run options: -n /commit/ --seed 46022
    
    D, [2019-07-19T03:30:37.864537 #96022] DEBUG -- :   Chef Load (0.2ms)  SELECT "chefs".* FROM "chefs" WHERE "chefs"."employable_id" = $1 AND "chefs"."employable_type" = $2 LIMIT $3  [["employable_id", 1], ["employable_type", "DrinkDesignerWithPolymorphicTouchChef"], ["LIMIT", 1]]
    D, [2019-07-19T03:30:37.865013 #96022] DEBUG -- :   Chef Create (0.2ms)  INSERT INTO "chefs" ("employable_id", "employable_type") VALUES ($1, $2) RETURNING "id"  [["employable_id", 1], ["employable_type", "DrinkDesignerWithPolymorphicTouchChef"]]
    D, [2019-07-19T03:30:37.865201 #96022] DEBUG -- :   TRANSACTION (0.1ms)  RELEASE SAVEPOINT active_record_1
    D, [2019-07-19T03:30:37.874136 #96022] DEBUG -- :   TRANSACTION (0.1ms)  ROLLBACK
    D, [2019-07-19T03:30:37.874323 #96022] DEBUG -- :   TRANSACTION (0.1ms)  ROLLBACK
    F
    
    Failure:
    HasOneAssociationsTest#test_polymorphic_has_one_with_touch_option_on_create_wont_cache_assocation_so_fetching_after_transaction_commit_works [/Users/kaspth/code/rails/activerecord/test/cases/associations/has_one_associations_test.rb:716]:
    --- expected
    +++ actual
    @@ -1 +1 @@
    -#<Chef id: 1, employable_id: 1, employable_type: "DrinkDesignerWithPolymorphicTouchChef", department_id: nil, employable_list_type: nil, employable_list_id: nil>
    +nil
    ```
    
    After:
    
    ```
    kaspth-imac 2.6.3 ~/code/rails/activerecord master *= ARCONN=postgresql bin/test test/cases/associations/has_one_associations_test.rb -n /commit/
    Using postgresql
    Run options: -n /commit/ --seed 46022
    
    D, [2019-07-19T03:30:22.479387 #95973] DEBUG -- :   Chef Create (0.3ms)  INSERT INTO "chefs" ("employable_id", "employable_type") VALUES ($1, $2) RETURNING "id"  [["employable_id", 1], ["employable_type", "DrinkDesignerWithPolymorphicTouchChef"]]
    D, [2019-07-19T03:30:22.479574 #95973] DEBUG -- :   TRANSACTION (0.1ms)  RELEASE SAVEPOINT active_record_1
    D, [2019-07-19T03:30:22.482051 #95973] DEBUG -- :   Chef Load (0.1ms)  SELECT "chefs".* FROM "chefs" WHERE "chefs"."employable_id" = $1 AND "chefs"."employable_type" = $2 LIMIT $3  [["employable_id", 1], ["employable_type", "DrinkDesignerWithPolymorphicTouchChef"], ["LIMIT", 1]]
    D, [2019-07-19T03:30:22.482317 #95973] DEBUG -- :   TRANSACTION (0.1ms)  ROLLBACK
    D, [2019-07-19T03:30:22.482437 #95973] DEBUG -- :   TRANSACTION (0.1ms)  ROLLBACK
    .
    
    Finished in 0.088498s, 11.2997 runs/s, 22.5994 assertions/s.
    1 runs, 2 assertions, 0 failures, 0 errors, 0 skips
    ```
    
    Notice the select now fires after the commit.
    cea392eb
    Polymorphic has_one touch: Reset association cache result after create transaction
    Kasper Timm Hansen authored
    In case of a polymorphic association there's no automatic inverse_of to assign the
    inverse record. So to get the record there needs to be a query executed,
    however, if the query fires within the transaction that's trying to create
    the associated record, no record can be found. And worse, the nil result is cached
    on the association so after the transaction commits the record can't be found.
    
    That's what happens if touch is enabled on a polymorphic has_one association.
    
    Consider a Comment with a commentable association that needs to be touched.
    
    For `Comment.create(commentable: Post.new)`, the existing code essentially
    does `commentable.send(:comment)` within the create transaction for the comment
    and thus not finding the comment.
    
    Now we're purposefully clearing the cache in case we've tried accessing
    the association within the transaction and found no object.
    
    Before:
    
    ```
    kaspth-imac 2.6.3 ~/code/rails/activerecord master *= ARCONN=postgresql bin/test test/cases/associations/has_one_associations_test.rb -n /commit/
    Using postgresql
    Run options: -n /commit/ --seed 46022
    
    D, [2019-07-19T03:30:37.864537 #96022] DEBUG -- :   Chef Load (0.2ms)  SELECT "chefs".* FROM "chefs" WHERE "chefs"."employable_id" = $1 AND "chefs"."employable_type" = $2 LIMIT $3  [["employable_id", 1], ["employable_type", "DrinkDesignerWithPolymorphicTouchChef"], ["LIMIT", 1]]
    D, [2019-07-19T03:30:37.865013 #96022] DEBUG -- :   Chef Create (0.2ms)  INSERT INTO "chefs" ("employable_id", "employable_type") VALUES ($1, $2) RETURNING "id"  [["employable_id", 1], ["employable_type", "DrinkDesignerWithPolymorphicTouchChef"]]
    D, [2019-07-19T03:30:37.865201 #96022] DEBUG -- :   TRANSACTION (0.1ms)  RELEASE SAVEPOINT active_record_1
    D, [2019-07-19T03:30:37.874136 #96022] DEBUG -- :   TRANSACTION (0.1ms)  ROLLBACK
    D, [2019-07-19T03:30:37.874323 #96022] DEBUG -- :   TRANSACTION (0.1ms)  ROLLBACK
    F
    
    Failure:
    HasOneAssociationsTest#test_polymorphic_has_one_with_touch_option_on_create_wont_cache_assocation_so_fetching_after_transaction_commit_works [/Users/kaspth/code/rails/activerecord/test/cases/associations/has_one_associations_test.rb:716]:
    --- expected
    +++ actual
    @@ -1 +1 @@
    -#<Chef id: 1, employable_id: 1, employable_type: "DrinkDesignerWithPolymorphicTouchChef", department_id: nil, employable_list_type: nil, employable_list_id: nil>
    +nil
    ```
    
    After:
    
    ```
    kaspth-imac 2.6.3 ~/code/rails/activerecord master *= ARCONN=postgresql bin/test test/cases/associations/has_one_associations_test.rb -n /commit/
    Using postgresql
    Run options: -n /commit/ --seed 46022
    
    D, [2019-07-19T03:30:22.479387 #95973] DEBUG -- :   Chef Create (0.3ms)  INSERT INTO "chefs" ("employable_id", "employable_type") VALUES ($1, $2) RETURNING "id"  [["employable_id", 1], ["employable_type", "DrinkDesignerWithPolymorphicTouchChef"]]
    D, [2019-07-19T03:30:22.479574 #95973] DEBUG -- :   TRANSACTION (0.1ms)  RELEASE SAVEPOINT active_record_1
    D, [2019-07-19T03:30:22.482051 #95973] DEBUG -- :   Chef Load (0.1ms)  SELECT "chefs".* FROM "chefs" WHERE "chefs"."employable_id" = $1 AND "chefs"."employable_type" = $2 LIMIT $3  [["employable_id", 1], ["employable_type", "DrinkDesignerWithPolymorphicTouchChef"], ["LIMIT", 1]]
    D, [2019-07-19T03:30:22.482317 #95973] DEBUG -- :   TRANSACTION (0.1ms)  ROLLBACK
    D, [2019-07-19T03:30:22.482437 #95973] DEBUG -- :   TRANSACTION (0.1ms)  ROLLBACK
    .
    
    Finished in 0.088498s, 11.2997 runs/s, 22.5994 assertions/s.
    1 runs, 2 assertions, 0 failures, 0 errors, 0 skips
    ```
    
    Notice the select now fires after the commit.
Loading