-
Notifications
You must be signed in to change notification settings - Fork 33
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
Add support for reverse relations to the ARI engine #147
Conversation
Wahoo! |
The original engine doesn’t support related resources on unions, nested records, nested lists, or anything that isn’t a top-level record. This change uses the Pegasus dataiterator to iterate through all fields of a datamap and find related resources anywhere on the object.
Refactors things a bit to make testing easier
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not quite done but have some preliminary comments / questions
@@ -13,4 +13,6 @@ record Course { | |||
description: string? | |||
|
|||
extraData: AnyData | |||
|
|||
courseMetadata: union[CourseMetadata] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why union?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just for testing that relations on unions work. i can remove this field if you want
case inlineFragment: InlineFragment => | ||
inlineFragment.selections.flatMap(selection => parseSelection(selection, document)) | ||
inlineFragment.selections.flatMap { selection => | ||
println(inlineFragment.typeCondition) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove println.
case fragmentSpread: FragmentSpread => | ||
(for { | ||
fragment <- document.fragments.get(fragmentSpread.name).toList | ||
selection <- fragment.selections | ||
} yield { | ||
parseSelection(selection, document) | ||
println(fragment.typeConditionOpt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove println.
@@ -107,3 +154,7 @@ object NaptimePaginatedResourceField { | |||
} | |||
|
|||
} | |||
|
|||
case class FieldRelation( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be a union or sealed trait. Is there a case where a field is both a forward relation and a backward relation at the same time?
s"'$name', but that field is already defined on the model") | ||
case None => | ||
val errorMessageBuilder = new StringBuilder | ||
val newField = new RecordDataSchema.Field(new ArrayDataSchema(new StringDataSchema)) // TODO(bryan): fix type here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens with this code if the key is a non-string? Do things still work reasonably?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i believe it still works, because ids are serialized to strings in naptime output. I need to investigate this more though and try cases with integers and stuff. this is why i put the key type on the reverse relation annotation (FinderReverseRelation[KeyType](...)
), but i don't know if that's necessary or if i can always assume the id will be a string.
i'm going to keep this todo in here and investigate when there's more time, but i don't think it'll break anything now.
var dataElement: DataElement = null | ||
while ( { | ||
dataElement = it.next(); dataElement | ||
} != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More idiomatic to write this as
Iterator
.continually(it.next)
.takeWhile(_ != null)
.filter(_.path.toSeq.map(_.toString) == path.dropRight(1))
.foreach(_.getValue.asInstanceOf[DataMap].put(path.last, data)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh woah. i hadn't used scala iterators before... that's really nice though :)
data.foreach { element => | ||
val it = Builder.create(element, schema, IterationOrder.PRE_ORDER).dataIterator() | ||
var dataElement: DataElement = null | ||
while ( { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do an iterator here too, as above.
While forward relations are very useful in a majority of cases (i.e. courses -> instructors, where the course model has an instructorId on it), there are some cases where a relation is necessary where the first model doesn't have any reference to the second. For instance, while a Membership model has a CourseId available on it, the Course model has no reference to all of the memberships available.
In order to create this course -> membership relationship though, we can dynamically fetch the relationship via a query to the membership resource, which, given information about the course, can return a list of memberships + ids for the relationship. This is referred to as a "reverse relationship", where the relationship data is stored on the related model.
These relationships are defined in the Naptime resource (via the
Fields
attribute), like so:The
$id
string is treated like string interpolation, and will be replaced with the value of the "id" field on the fetched course.During execution, the engine will make the API call as described on the relationship (appending all interpolated arguments listed), and will make that response available. It will also create a new dynamic field on the original model (in this case, Course), with the ids of the related resources for easier traversal / interop with forward relations.
PTAL @yifan-coursera -- let me know if you want to go over this one in person.
And if you're still interested, cc @saeta