Using nested nodes from mutation payload to update local store - relayjs

Let's say I have a typical todo application. There's a CreateTodo mutation that takes a string and returns CreateTodoPayload with the newly created edge. The schema looks like this:
type Todo implements Node {
id: String!
content: String!
user: User!
}
type User implements Node {
id: String!
todos: [Todo]
}
input CreateTodoInput {
clientMutationId: String!
content: String!
}
type CreateTodoPayload {
clientMutationId: String!
todo: Todo
}
How can I specify that this mutation will update the user node via todo using FIELDS_CHANGE? fieldIDs requires me to specify an immediate field on the payload, so I can make it work by also returning the user, but that seems counter to what Relay is trying to accomplish.
Similarly with NODE_DELETE, RANGE_ADD and RANGE_DELETE (even though this example doesn't use connections), parentName is a string for a field name on the payload and it seems one cannot specify a nested field, i.e., todo.user.

Related

Protocols and Enums in Swift with Apollo

I am using Apollo for Swift in an iOS app. I have multiple types that all represent the same object. These types are auto-generated from a schema file and look something like this.
struct CurrentUser {
var id: String
...
}
struct MyUser {
var id: String
...
}
Basically Apollo generates multiple Swift types (one for each query) for the same underlying data type.
I want to create a new struct that unifies these types.
I would like to do something like this:
protocol UserProtocol {
var id: String { get }
}
struct User {
var id: String
...
init(_ data: UserProtocol) {
self.id = data.id
...
}
}
This approach however gives me an error when I try to construct a user object, telling me that "Type MyUser does not conform to UserProtocol". If I try to coerce the type with data as! UserProtocol I get a crash.
The only solution I've found is the following:
enum UserType {
case .currentUser(CurrentUser)
case .myUser(MyUser)
}
struct User {
var id: String
...
init(_ data: UserType) {
switch data {
case .myUser(let user):
self.id = data.id
...
case .currentUser(let user):
self.id = data.id
...
}
}
}
This approach works, but it leads to a lot of duplicated code in the init function. Is there a better way to do this in Swift?
I suspect the problem is that you need to explicitly conform the Apollo generated types to your protocol:
extension CurrentUser: UserProtocol { }
extension MyUser: UserProtocol { }
Remember that Swift is not duck-typed like some other languages, so a type with member var id: String is not UserProtocol until you declare it as such.
If for some reason you need to do some transformation of the Apollo types to fit the app models in the future, those extensions are a good place to do that, too.

Mock data generation for storybook

I am trying to generate mock data using relay for storybook.
My query is
const QUERY_LIST = graphql`
query modelControllerAllUsersQuery #relay_test_operation {
allUsers {
pageInfo {
hasNextPage
}
edges {
node {
id
firstName
lastName
}
}
}
}
`
and provided RelayEnvironmentProvider as a decorator to the story. I'm trying to return some default values to my query using custom mock resolvers.
const customMockResolvers = {
...mockResolvers,
allUsers:() => ({
pageInfo:{
hasNextPage:false,
},
edges:[
{
node:{
id :'id',
firstName:'fname',
lastName :'lname',
},
},
],
}),
};
and calling it as
(operation) => MockPayloadGenerator.generate(operation, customMockResolvers)
I don't seem to be able to get the default values returned.
Currently, it is returning
{"allUsers":{"pageInfo":{"hasNextPage":false},"edges":[{"node":{"id":"<UserNode-mock-id-1>","firstName":"<mock-value-for-field-\"firstName\">","lastName":"<mock-value-for-field-\"lastName\">"}}]}}
What am I doing wrong?
When using the #relay-test-operation, the keys within your customMockResolvers object must match the type name of the fields, which can be different from the field names themselves.
For example, you could have the following in your schema:
type Foo {
id: ID!
name: String!
}
and the following query:
query FooQuery #relay_test_operation {
foo {
id
name
}
}
Then the customMockResolvers object would look like this:
const customMockResolvers = {
Foo: () => ({
id: "fooId",
name: "fooName"
})
}
Notice that I'm passing in Foo as the key instead of foo.
You can check your schema and see what the the type name of allUsers is. I suspect it would be something like AllUsers or allUsersConnection, or something similar.
Also, if you're interested in creating Storybook stories for Relay components, I created a NPM package just for that: https://www.npmjs.com/package/use-relay-mock-environment
It doesn't require adding the #relay-test-operation directive to your query, and instead relies only on resolving the String type (which is the default for all scalar properties). You can of course still add the #relay-test-operation directive and also extend the resolvers by providing customResolvers in the config.
You can also extend the the String resolver as well, by providing extendStringResolver in the config.
Feel free to review the source code here if you want to implement something similar: https://github.com/richardguerre/use-relay-mock-environment.
Note: it's still in its early days, so some things might change, but would love some feedback!

How to replace/update dictionary in Array of Dictionary based on Type

I'm getting below JSON response from server, and displaying phone number on screen.
Now user can change/update any of phone number, so we have to update particular mobile number in same object and send it to server.
"phone_numbers": [
{
"type": "MOBILE",
"number": "8091212121"
},
{
"type": "HOME",
"number": "4161212943"
},
{
"type": "BUSINESS",
"number": "8091212344"
}
]
My model class is looks like this:
public struct Contact: Decodable {
public let phone_numbers: [Phone]?
}
public struct Phone: Decodable {
public let type: PhoneType?
public let number: String?
}
I'm struggling to update this JSON object for particular phone number.
For example, if I want to update BUSINESS number only in above array, What's best way to do it.
I'm using XCode 11 and Swift 5.
Because all your properties are defined as constants (let), nothing can be updated. You have to initialize and return a new Contact object with the updated phone numbers.
If you change the properties to var, then you can update:
public enum PhoneType: String, Decodable {
case mobile = "MOBILE"
case home = "HOME"
case business = "BUSINESS"
}
public struct Contact: Decodable {
public var phone_numbers: [Phone]?
mutating func update(phoneNumber: String, for type: PhoneType) {
guard let phone_numbers = self.phone_numbers else { return }
for (i, number) in phone_numbers.enumerated() {
if number.type == type {
self.phone_numbers![i].number = phoneNumber
}
}
}
}
public struct Phone: Decodable {
public var type: PhoneType?
public var number: String?
}
var contact = try! JSONDecoder().decode(Contact.self, from: jsonData)
contact.update(phoneNumber: "123456", for: .business)
I'm struggling to update this JSON object for particular phone number.
It shouldn't be a JSON object when you update it. Think of JSON as just a format for transferring data. Once transferred, you should parse it into something that you can work with, like an array of dictionaries or whatever. If you've done that, then more specific questions you might ask are:
How can I find a specific entry in an array?
How can I modify the fields of a struct?
How can I replace one entry in an array with another?
After looking at the definitions of your structures, I think the problem you're having probably has to do with how you've declared them:
public struct Phone: Decodable {
public let type: PhoneType?
public let number: String?
}
Because you used let to declare type and number, those fields cannot be changed after initialization. If you want the fields of a Phone struct to be modifiable, you need to declare them with var instead of let.
The same thing is true for your Contact struct:
public struct Contact: Decodable {
public let phone_numbers: [Phone]?
}
You've declared phone_numbers as an immutable array because you used let instead of var. If you want to be able to add, remove, or modify the array in phone_numbers, you need to use var instead.
The struct declarations you have right now work fine for reading the data from JSON because all the components of the JSON data are constructed using the values from the JSON. But again, you'll need to make those structs modifiable by switching to var declarations if you want to be able to make changes.
There are a couple of ways to approach this (I'm assuming PhoneType is an enum you have somewhere)
You can iterate over the array and guard for only business numbers, like so
for phone in phone_numbers{
guard phone.type == .MOBILE else { continue }
// Code that modifies phone
}
You can filter and iterate over the array, like so
phone_numbers.filter {$0.type == .BUSINESS }.forEach { phone in
// Modify phone here
}
You can then modify the right value in the array with it's index, like this
for (phoneIndex, phone) in phone_numbers.enumerated() {
guard phone.type == .BUSINESS else { continue }
phone_numbers[phoneIndex].type = ANOTHER_TYPE
}
Some can argue that the second is preferred over the first, because it is an higher order function, but in my day to day activities, I tend to use both and believe that this is a matter of taste

Creating mock instances of GIDGoogleUser

I'm writing some Unit Tests and I need to create a mock instance of GIDGoogleUser to make sure my API returns a correct instance of my model User class which is subset of the fields in GIDGoogleUser.
Since GIDGoogleUser does not expose an initializer, and all it's properties are read only, I can't create a mock instance and inject it in to my converter. Is there any way I can do this?
For simplicity, this is what I'm doing:
struct User {
let name: String
init(googleUser: GIDGoogleUser) {
name = googleUser.profile.name
}
}
I'm not sure what you mean when you say you can't mock GIDGoogleUser. Here is a mock of GIDGoogleUser I made just now:
First, declare the protocols that GIDGoogleUser and GIDProfileData will conform to, as well as our mocks we'll make in a bit:
protocol GoogleUserProtocol {
associatedtype Profile: ProfileDataProtocol
var profile: Profile! { get }
}
protocol ProfileDataProtocol {
var name: String! { get }
}
Then, have GIDGoogleUser and GIDProfileData conform to these protocols:
extension GIDGoogleUser: GoogleUserProtocol {}
extension GIDProfileData: ProfileDataProtocol {}
Then, create our mock classes (or structs as I opted for in this case), and have them conform to the above protocols:
struct MockGoogleUser: GoogleUserProtocol {
let profile: MockProfileData!
}
struct MockProfileData: ProfileDataProtocol {
let name: String!
}
Finally, adjust User's initializer to take not a GIDGoogleUser, but instead anything that conforms to GoogleUserProtocol:
struct User {
let name: String
init<G>(googleUser: G) where G: GoogleUserProtocol {
name = googleUser.profile.name
}
}
This will let you create mock Google User instances and inject them into your User, like so:
let mockProfileData = MockProfileData(name: "Mock User Name")
let mockGoogleUser = MockGoogleUser(profile: mockProfileData)
let mockUser = User(googleUser: mockGoogleUser)
print(mockUser.name) // prints "Mock User Name"
And you can of course still init your User with "real" Google User objects too:
let realGoogleUser: GIDGoogleUser = ... // get a GIDGoogleUser after signing in
let realUser = User(googleUser: realGoogleUser)
print(realUser.name) // prints whatever the real GIDGoogleUser's name is

How to have columns in table that don't have to be specified in post request

I have a Vapor app where I want some values to be specified by the user in a POST request, and other values to be computed based on the user-specified values.
For example, suppose the user patches in some new values, and each time that happens the table should automatically update a column with the current time.
I was looking at trying to store the computed properties in the database, but when I modified the model to know about the computed properties, all my POST requests began expecting those to be specified.
What's the most idiomatic way to have columns in a table that don't have to be specified by the post requests?
If you are only looking to update a modified or created timestamp then there are two other ways. In your model, put:
static let createdAtKey: TimestampKey? = \.createdAt
static let updatedAtKey: TimestampKey? = \.updatedAt
var createdAt:Date?
var updatedAt:Date?
And let vapor do it for you, see here. Alternatively, you can make use of the methods willCreate, willUpdate, etc. as described in the docs here if you are updating fields that do not need the user's input.
extension User
{
func willUpdate(on connection: Database.Connection) throws -> Future<User>
{
modifiedCount += 1
return Future.map(on: connection) { self }
}
}
Finally, if you need a bit more flexibility than your own solution, consider using this in your controller:
struct EditUserForm:Content
{
let id:Int
let surname:String
let initials:String
}
func save(_ request:Request) throws -> Future<View>
{
return try request.content.decode(EditUserForm.self).flatMap
{
newUserData in
return try request.parameters.next(User.self).flatMap
{
originalUser in
// update fields as required, EditUserForm only has a subset
return originalUser.save(on:request).transform(to:try self.index(request))
}
}
}
You will need the usual route:
router.post(User.parameter, "save", use:userController.save)
I found that I need to make the computed fields be optional in the model, and then compute them in the route function before saving.
For example:
Making modified_date be optional in the model:
final class MyContentType: PostgreSQLModel {
var id: Int?
var name: String
var modified_date: Date?
}
Setting modified_date to the computed value:
func create(_ request: Request, content: MyContentType) throws -> Future< MyContentType > {
content.modified_date = Date()
return content.save(on: request)
}

Resources