question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Best way to work with relay

See original GitHub issue

Hello, I started working with graphene recently and I decided to create a small API to learn the framework. I have an external service that expose some functions, let’s call them get_properties() and get_property_rooms(property_id). I started creating two DataLoader, one to fetch the properties of the user and another to fetch the rooms of a specific property.

class PropertiesLoader(DataLoader):
    def get_cache_key(self, key):
        return key['context'].user

    def batch_load_fn(self, keys):
        return Promise.resolve([get_properties() for key in keys])


class RoomsLoader(DataLoader):
    def get_cache_key(self, key):
        return key['context'].user + key['property_id']

    def batch_load_fn(self, keys):
        return Promise.resolve([get_property_rooms(key['property_id']) for key in keys])


properties_loader = PropertiesLoader()
rooms_loader = RoomsLoader()

Then I created two nodes and two connections, one for the properties and another for the rooms.

class Room(graphene.ObjectType):
    class Meta:
        interfaces = (relay.Node, )

    name = graphene.String(description='The room name')


class RoomConnection(relay.Connection):
    class Meta:
        node = Room


class Property(graphene.ObjectType):
    class Meta:
        interfaces = (relay.Node, )

    name = graphene.String(description='The name of the property')
    rooms = relay.ConnectionField(RoomConnection, description='The rooms of the property')

    @staticmethod
    def resolve_rooms(root, info, **kwargs):
        return rooms_loader.load(root.id)

    @classmethod
    def get_node(cls, info, id):
        return properties_loader.load(
            {'context': info.context}
        ).then(lambda properties: [
            item for item in properties if item.id == int(id)
        ][0])


class PropertyConnection(relay.Connection):
    class Meta:
        node = Property


class RelayQuery(graphene.ObjectType):
    property = relay.Node.Field(Property)
    properties = relay.ConnectionField(PropertyConnection)
    room = graphene.Field(Room,
                          id=graphene.ID(required=True),
                          property_id=graphene.ID(required=True))
    rooms = relay.ConnectionField(RoomConnection,
                                  property_id=graphene.ID(required=True))

    @staticmethod
    def resolve_properties(root, info, **kwargs):
        return properties_loader.load({'context': info.context})

    @staticmethod
    def resolve_room(root, info, id, property_id, **kwargs):
        _, _id = relay.Node.from_global_id(id)
        _, _property_id = relay.Node.from_global_id(property_id)

        return rooms_loader.load(
            {'context': info.context, 'id': int(_property_id)}
        ).then(lambda rooms: [
            item for item in rooms if item.id == int(_id)
        ][0])

    @staticmethod
    def resolve_rooms(root, info, property_id, **kwargs):
        _, id = relay.Node.from_global_id(property_id)
        return rooms_loader.load({'context': info.context, 'property_id': int(id)})


schema = graphene.Schema(query=RelayQuery)

As you may notice from the RelayQuery, I have a room field which accepts two graphene.ID(). One for the room id and another for the property id. I want to know if there is a better way to do this? It would be really nice if I could use a relay.Node.Field for the room field in the RelayQuery, pass the property id as argument and implement the get_node method in the Room node like the following example:

class Room(graphene.ObjectType):
    class Meta:
        interfaces = (relay.Node, )

    name = graphene.String(description='The room name')

    @classmethod
    def get_node(cls, info, id, property_id):
        return rooms_loader.load(
            {'context': info.context, 'property_id': int(property_id)}
        ).then(lambda rooms: [
            item for item in rooms if item.id == int(id)
        ][0])


class RelayQuery(graphene.ObjectType):
    ...
    room = relay.Node.Field(Room, property_id=graphene.ID(required=True))
    ...

Please let me know if there is a better approach to do this and if yes, could you update the documentation in the relay section? I want to be able to do the following queries:

query {
  properties {
    edges {
      node {
        id, name,
        rooms {
          edges {
            node {
              id, name
            }
          }
        }
      }
    }
  }
}

query {
  rooms(propertyId: "UHJvcGVydHk6Mjg5Mg==") {
    edges {
      node {
        id, name
      }
    }
  }
}

query {
  property(id: "UHJvcGVydHk6Mjg5Mg==") {
    id, name,
    rooms {
      edges {
        node {
          id, name
        }
      }
    }
  }
}

query {
  room(id: "Um9vbTo2", propertyId: "UHJvcGVydHk6Mjg5Mg==") {
    id, name
  }
}

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:8 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
jkimbocommented, May 28, 2018

Sure it’s totally up to you. What I would say is that the best way to architect your schema is to think about what makes the most sense for how the front end client wants to use the data. You can always improve efficiency in the backend but if your client has to work around types or fields that don’t make sense to how the data is actually being used then you might as well be using a traditional REST api.

Anyway I’m going to close this issue as it doesn’t seem like there is anything that needs to change in Graphene.

0reactions
marcelombccommented, May 28, 2018

Thanks for the idea @jkimbo but I see a problem with your approach. Since my method get_room accepts the room id and the property id, there is no need to fetch the property. All I need is to pass the property id and room id to my service to get the room details. Going through the property is an unnecessary job because it will pass in the get_node method from the Property node. Since I’m using DataLoader the data is in cache but even then I don’t want my code to go through unnecessary methods to fetch the data. I would prefer to use a room graphene field at root and translate the global id to proper id using the from_global_id method

Read more comments on GitHub >

github_iconTop Results From Across the Web

4 Ways to Control Electronic Relays - Make Magazine
There are plenty of ways to use relays, and each method can achieve several different results. Check out this step by step guide...
Read more >
Understanding Relays & Wiring Diagrams - Swe-Check
A relay is an electrically operated switch. They commonly use an electromagnet (coil) to operate their internal mechanical switching mechanism (contacts).
Read more >
How Relays Work - The Engineering Mindset
Relays are used where it is necessary to control a circuit using a low-power signal, or where several circuits must be controlled by...
Read more >
What is a Relay and Why Are They So Important? - Amperite
Relays are electric switches that use electromagnetism to convert small electrical stimuli into larger currents. ... These conversions occur when ...
Read more >
Relay Switch Circuit - Electronics Tutorials
The advantage of relays is that it takes a relatively small amount of power to operate the relay coil. However a relay switch...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found