Skip to content

Conversation

@joshheald
Copy link
Contributor

@joshheald joshheald commented Aug 29, 2025

Closes: WOOMOB-1210
Merge after: #16063

Description

This restructures the mapping/persistence tests to revolve around Products and Variations.

It also adds tests for #16063

Sorry it's so big! Hopefully, being tests-only makes it easier to manage.

Steps to reproduce

None, these are not used in the app yet, just check unit tests.


  • I have considered if this change warrants user-facing release notes and have added them to RELEASE-NOTES.txt if necessary.

@joshheald joshheald added this to the 23.2 milestone Aug 29, 2025
@joshheald joshheald added type: task An internally driven task. status: feature-flagged Behind a feature flag. Milestone is not strongly held. feature: POS labels Aug 29, 2025
@joshheald joshheald force-pushed the woomob-1210-tests-for-saving-pos-models branch from dd9ee72 to e9fc1ce Compare August 29, 2025 18:07
@joshheald joshheald changed the base branch from trunk to woomob-1210-relationship-based-mapping-and-saving-for-POS-entities August 29, 2025 18:09
@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Aug 29, 2025

App Icon📲 You can test the changes from this Pull Request in WooCommerce iOS Prototype by scanning the QR code below to install the corresponding build.

App NameWooCommerce iOS Prototype
Build Numberpr16064-604a3df
Version23.1
Bundle IDcom.automattic.alpha.woocommerce
Commit604a3df
Installation URL31esdt9l5b3qg
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@joshheald joshheald requested a review from jaclync August 29, 2025 18:18
@joshheald joshheald marked this pull request as ready for review August 29, 2025 18:22
Copy link
Contributor

@jaclync jaclync left a comment

Choose a reason for hiding this comment

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

Mostly a question about fetchOne!


// Test automatic fetching via associations
let fetchedVariation = try db.read { db in
try PersistedProductVariation.filter(PersistedProductVariation.Columns.id == 500).fetchOne(db)
Copy link
Contributor

Choose a reason for hiding this comment

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

❓ does fetchOne include all relationships by default? I recall seeing the relationships being fetched separately in toPOSProduct(db:). From the documentation, including seems to be for including the relationships which aren't fetched by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, it doesn't.

You're right, including lets us get more than one thing back at a time, via the relationships. However, we need dedicated wrapper record types to use it, I think.

For example, we could:

        struct DetailedVariation: Decodable, FetchableRecord {
            var variation: PersistedProductVariation
            var image: PersistedProductVariationImage
            var attributes: [PersistedProductVariationAttribute]
        }

        let fetchedDetailedVariation = try db.read { db in
            try PersistedProductVariation
                .including(optional: PersistedProductVariation.image)
                .including(all: PersistedProductVariation.attributes)
                .asRequest(of: DetailedVariation.self)
                .fetchAll(db)
        }

Perhaps those wrappers would be worthwhile, even though they're another small layer. WDYT? We could use them in the tests to avoid relying on our POSProduct extensions.

With that approach, we need to do things like #expect(fetchedDetailedVariation.variation.id == 500), which isn't so nice. I guess we could use computed vars to get the base properties on the root of the detailed model, but it would be best if we could auto-generate those.

I've done an example in the test for now – see what you think, as it might be more convenient to fetch things this way before converting to POS models. See a9048a2

Copy link
Contributor

Choose a reason for hiding this comment

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

This seems related to my question in another PR about why relationships are fetched separately per product/variation #16063 (comment). Now I'm connecting the dots, that our Persisted*'s relationships aren't properties but associated types, which is a difference from Core Data managed objects.

Perhaps those wrappers would be worthwhile, even though they're another small layer. WDYT?

Yea, I think the wrappers that include the relationships for the use case are nice. Could be nice outside of unit tests in our POS use cases as well.

My question about toPOSProduct(db:) in the linked thread in another PR was mainly for the POS use case, do we plan to:

  • Fetch all products/variations including relationships in one db request like in the example code above a9048a2. Then map the list of wrapper objects to POS product/variation list.
  • Fetch all products/variations without relationships, then in the same db read transaction, map each Persisted* in the list with toPOSProduct(db:) to fetch relationships for each product/variation then convert to POS product/variation.

Not sure if there's significant performance difference, the first approach feels a bit more intuitive and efficient to me, but either way is probably fine since we fetch them in a paginated way in POS like 20 at a time.


Btw I tested and it's possible to keep the properties in the FetchableRecord wrapper constant, even though the doc used variables as well:

struct DetailedVariation: Decodable, FetchableRecord {
    let variation: PersistedProductVariation
    let image: PersistedProductVariationImage
    let attributes: [PersistedProductVariationAttribute]
    
    ...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Btw I tested and it's possible to keep the properties in the FetchableRecord wrapper constant, even though the doc used variables as well

Yeah, that makes sense, I wasn't looking too closely at that, just seeing how it worked. The thing I don't like about it is having to dig around in detailedVariation.variation for stuff, nor having to keep computed vars matching the base variation model. But there we are – we can do it by hand and invest time in automation when it's worthwhile.

Yea, I think the wrappers that include the relationships for the use case are nice. Could be nice outside of unit tests in our POS use cases as well.

Yeah, the unit tests just seemed a good place to try it out. If we want to keep it, they can be promoted to Yosemite.

My question about toPOSProduct(db:) in the linked thread in another PR was mainly for the POS use case, do we plan to:

Fetch all products/variations including relationships in one db request like in the example code above a9048a2. Then map the list of wrapper objects to POS product/variation list.
Fetch all products/variations without relationships, then in the same db read transaction, map each Persisted* in the list with toPOSProduct(db:) to fetch relationships for each product/variation then convert to POS product/variation.

Yes, it'll be paged so it probably doesn't matter too much. If that turns out to be true, I think it's probably best to fetch everything in one request, with relationships, and make them into POS models from the wrapper. We may want multiple ways to read from the db for other use cases, but that'll be the main one for display.

try variation.insert(db)

// Add attributes only, no image
var attr = PersistedProductVariationAttribute(
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: could be let?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, because of the auto incremented ID making insert mutating

}

@Test("A Product's images and attributes are fetched automatically")
func test_product_with_associations_fetches_related_records() throws {
Copy link
Contributor

Choose a reason for hiding this comment

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

super nit: can add Given/When/Then for consistency (same for test_product_without_related_records_creates_empty_arrays).

}

@Test("ProductVariation with associations fetches attributes and image automatically")
func test_product_variation_with_associations_fetches_related_records() throws {
Copy link
Contributor

Choose a reason for hiding this comment

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

super nit: similar to above, can add Given/When/Then for consistency (same for test_product_variation_without_image_returns_nil).

@joshheald joshheald merged commit d0c1b2d into woomob-1210-relationship-based-mapping-and-saving-for-POS-entities Sep 1, 2025
14 checks passed
@joshheald joshheald deleted the woomob-1210-tests-for-saving-pos-models branch September 1, 2025 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: POS status: feature-flagged Behind a feature flag. Milestone is not strongly held. type: task An internally driven task.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants