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

fix: add targetName to hasOne relationships #926

Merged
merged 7 commits into from
Dec 6, 2020
Merged

Conversation

lawmicha
Copy link
Contributor

@lawmicha lawmicha commented Dec 1, 2020

Issue #, if available:
#920

Description of changes:
The fix in this PR is enabled by the CLI codegen changes which adds the targetName to hasOne relationships so that this becomes an additive, non-breaking change for developers.

The fix targets making the following schema usable by API, and thus DataStore when sync is enabled:

# 2 - Project with explicit field for team’s id
type Project2 @model {
  id: ID!
  name: String
  teamID: ID!
  team: Team2 @connection(fields: ["teamID"])
}

type Team2 @model {
  id: ID!
  name: String!
}

See #920 for more details

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@codecov
Copy link

codecov bot commented Dec 2, 2020

Codecov Report

Merging #926 (58449ea) into main (d49854f) will decrease coverage by 0.23%.
The diff coverage is 10.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #926      +/-   ##
==========================================
- Coverage   67.29%   67.05%   -0.24%     
==========================================
  Files         895      917      +22     
  Lines       35389    35532     +143     
==========================================
+ Hits        23814    23826      +12     
- Misses      11575    11706     +131     
Flag Coverage Δ
API_plugin_unit_test 62.59% <80.00%> (+0.03%) ⬆️
Analytics_plugin_unit_test 72.38% <ø> (ø)
Auth_plugin_unit_test 48.62% <ø> (ø)
DataStore_plugin_unit_test 83.03% <ø> (ø)
Predictions_plugin_unit_test 49.69% <ø> (ø)
Storage_plugin_unit_test 74.74% <ø> (ø)
build_test_amplify 62.31% <7.58%> (-0.81%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...Model/Internal/Schema/ModelSchema+Definition.swift 61.40% <0.00%> (ø)
...stCommon/Models/Collection/1/Project1+Schema.swift 0.00% <0.00%> (ø)
...plifyTestCommon/Models/Collection/1/Project1.swift 0.00% <0.00%> (ø)
...yTestCommon/Models/Collection/1/Team1+Schema.swift 0.00% <0.00%> (ø)
AmplifyTestCommon/Models/Collection/1/Team1.swift 0.00% <0.00%> (ø)
...stCommon/Models/Collection/2/Project2+Schema.swift 0.00% <0.00%> (ø)
...plifyTestCommon/Models/Collection/2/Project2.swift 0.00% <0.00%> (ø)
...yTestCommon/Models/Collection/2/Team2+Schema.swift 0.00% <0.00%> (ø)
AmplifyTestCommon/Models/Collection/2/Team2.swift 0.00% <0.00%> (ø)
...stCommon/Models/Collection/3/Comment3+Schema.swift 0.00% <0.00%> (ø)
... and 42 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d49854f...58449ea. Read the comment docs.

@lawmicha lawmicha requested a review from palpatim December 2, 2020 22:27
@lawmicha lawmicha marked this pull request as ready for review December 2, 2020 22:27
Comment on lines 85 to 86
// When the associated object is found, take this value over the explicit field's value by replacing the correct
// entry for the field name of the associated model.
Copy link
Member

Choose a reason for hiding this comment

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

I'm not necessarily disagreeing, but why is it right to take the object rather than the specified field? How do we let the customer know which one will be used?

Copy link
Contributor Author

@lawmicha lawmicha Dec 3, 2020

Choose a reason for hiding this comment

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

If they do any of the below, the correct team identifier will be used

let project = Project2(teamID: team.id, team: team)
let project = Project2(teamID: team.id) 
// note `teamID` is always required

So the decision to take the object over the specified field only matters when the developer sets their values differently, ie.

let project = Project2(teamID: "nonExistentTeamID", team: team)

If the developer started off with

let project = Project2(teamID: "nonExistentTeamID")

This will be persisted but the association points to an non existent team. They'll eventually end up with one of the three scenarios once they get working code, by persisting/retrieving the team object before using it in the project object

let project = Project2(teamID: team.id) // 1. they updated their code to use team.id, and it works without passing in an optional `team`
let project = Project2(teamID: team.id, team: team) // 2. they updated it in both places, because it's confusing where to add it to
let project = Project2(teamID: "nonExistentTeamID", team: team) // 3. they only passed it into `team` parameter, but it starts working

Another thought I had for this was thinking that we will omit the specific field in the future with a breaking codegen change, so that our documentation always promotes the use of passing the object (since belongsTo associations already omit the specific field). So where it's not omitted, we would continue to document it as let project = Project2(teamID: team.id, team: team) to communicate that developers should work with the model, while satisfying requirement that teamID is a required field


return nil
}

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for this refactor!

@lawmicha lawmicha self-assigned this Dec 3, 2020
@lawmicha lawmicha force-pushed the fix/hasone-targetname branch from a988f65 to f8b1830 Compare December 4, 2020 17:30
wait(for: [queriedCommentCompleted], timeout: networkTimeout)
}

// TODO: Issue https://github.com/aws-amplify/amplify-ios/issues/939
Copy link
Contributor Author

@lawmicha lawmicha Dec 4, 2020

Choose a reason for hiding this comment

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

Capturing the issue separately: #939

/// This information is also stored in the schema as `targetName` which is codegenerated to be the same as the
/// default or an explicit field specified by the developer.
private func getFieldNameForAssociatedModels(modelField: ModelField) -> String {
let defaultFieldName = modelName.camelCased() + modelField.name + "Id"
Copy link
Contributor

Choose a reason for hiding this comment

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

"Id" was not added before this change.. is this a bug fix ? Earlier it was modelName.camelCased() + field.graphQLName

Copy link
Contributor Author

Choose a reason for hiding this comment

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

graphQLName internally resolves to name.pascalCased() + "Id" for associations. oh shoot. this is incorrect then it should be:
modelName.camelCased() + modelField.name.pascalCased() + "Id"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated to correct association field, we always pascal case the modeField.name for the associated field

Copy link
Contributor

Choose a reason for hiding this comment

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

Cool! Wondering whether we have coverage on this logic. It would nice if you can add this or add a backlog to add this logic to unit test.

guard let fieldValue = fieldOptionalValue else {
input.updateValue(nil, forKey: name)
guard let fieldValue = fieldValueOptional else {
input.updateValue(nil, forKey: modelField.name)
Copy link
Contributor

Choose a reason for hiding this comment

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

We used field.graphQLName before? Just confirming both works?

Copy link
Contributor Author

@lawmicha lawmicha Dec 4, 2020

Choose a reason for hiding this comment

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

graphQLName returns name by default, and only in the case it's a belongsTo it will return name.pascalCase()+"Id". this value name.pascalCase() + "Id" is not valid until we've prepended the current model name, and is done in the case model scenario. which is why i've refactored this to just modelField.name and consolidated the logic down below

https://github.com/aws-amplify/amplify-ios/blob/main/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/ModelField+GraphQL.swift

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if it is belongsTo and returns targetName because targetName exists, then it may set targetName to nil here. I don't think this is ever the case because you cannot have nil belongsTo assocations, but i will revert this change and only make it if we're sure that there isn't a scenario that i've missed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

reverted this change

return primaryKeyValue
}

preconditionFailure("Could not retrieve the identifier from associated model: \(value)")
Copy link
Contributor

Choose a reason for hiding this comment

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

Hope this wont break? Previous code returned nil here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i wrote this on the assumption that it should always get an id from the value, but i'm more inclined to return nil here to introduce less changes to the code

@@ -64,13 +64,13 @@ extension GraphQLRequest: ModelSyncGraphQLRequestFactory {
decodePath: document.name)
}

public static func createMutation(of model: Model, version: Int?) -> GraphQLRequest<MutationSyncResult> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need these changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I noticed in the integration tests over in AWSAPIPlugin that tests these paths, for some reason were not compiling. until i added the default parameters here. Not sure how those were written without the nil default parameter on these methods

@lawmicha lawmicha merged commit 97b96df into main Dec 6, 2020
@lawmicha lawmicha deleted the fix/hasone-targetname branch December 6, 2020 16:20
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.

3 participants