Graphql-ruby: Extending relay connection

Created on 15 May 2017  Â·  16Comments  Â·  Source: rmosolgo/graphql-ruby

Hi, first big thanks for the gem.

I wanted to extend relay connection and I did as guides states:

module Graph
   module Types
     MessagesConnection = Graph::Types::Message.define_connection do
       name 'MessagesConnection'
       field :totalCount do
         type types.Int
         resolve ->(obj, _args, _ctx) { obj.nodes.count }
       end
     end
   end
 end

Still I need to define new connection for every collection. Can I just extend all connections with custom attributes?

There is probably chance I could write

 connection :messages, CustomConnectionBuilder.build(Graph::Types::Message)

Is there a better way?

Most helpful comment

I would also love for this to be easier to set up. I have the exact same use case as @piotrze's example, wanting to add totalCount to all connections. I'm not sure what the best way is to implement this but wanted to voice my support for this feature as it's probably fairly common. GitHub's API has a few examples of extended attributes in connections that I think show off the value.

One way I can think of to implement this would be to make it part of the type definition DSL. That would make it easy to ensure that the i.e. RepositoryConnection had the totalCount field everywhere that Repositories were available as a connection, rather than having to augment every part of the schema that uses Repository.connection_type.

All 16 comments

Hi, sorry for the slow response! No, there's not a built-in way to extend the default connection. I think a method like you posted above is the best option right now!

I would also love for this to be easier to set up. I have the exact same use case as @piotrze's example, wanting to add totalCount to all connections. I'm not sure what the best way is to implement this but wanted to voice my support for this feature as it's probably fairly common. GitHub's API has a few examples of extended attributes in connections that I think show off the value.

One way I can think of to implement this would be to make it part of the type definition DSL. That would make it easy to ensure that the i.e. RepositoryConnection had the totalCount field everywhere that Repositories were available as a connection, rather than having to augment every part of the schema that uses Repository.connection_type.

Thanks for the suggestion, I think a way to do it down the line would be:

  • Migrate GraphQL::Relay code to use the class-based API, then
  • Reopen the built-in classes and extend them

In the long view, I have hopes for a more flexible pagination system (#1359)

(see also "Porting Relay Types" in http://rmosolgo.github.io/blog/2018/04/09/updating-github-to-graphql-1-dot-8-0/)

@rmosolgo I actually did that yesterday! There were quite a few things in the Relay examples that were broken and/or needed tweaking but I did ultimately get it working. I can post that somewhere if you think it’d be helpful to have another (working) example, lmk where if so.

Once I got the class-based Relay system going it was trivial to add fields to my BaseConnection and have them appear in all other connections.

Hi @bgentry, same use case and same issue here.

If you can gist an example somewhere, it would be very very helpful ;-).
Thanks a lot

@zuuno: posted a gist https://gist.github.com/bgentry/e0a06b06aa7261a085807c3d4ca1d22f

FWIW I couldn't get this working with a gradual migration so I just bit the bullet and converted all my edges/connections in a single PR (one commit per type).

@rmosolgo feel free to share, it might help others w/ an upgrade. It's a slightly less complicated scenario than GitHub's (my relay types all end with Connection or Edge). But in particular there were certain fixes that were required to get the blog post code working, such as calls to super. Hopefully this can all get rolled upstream at some point 😄

Thanks a lot ;-), very helpful!

Tried it and it seems to not work on my side.

I did what @bgentry did but it seems the field :edges isn't valid anymore. While looking into the source code, it seems linkdoesn't accept edge_class anymore.

I will most probably resolve myself to create single files connections with using .define.

@sayduck-daniel what I posted is still working as of v1.8.2. However, v1.8.3 was just released and there are some new commits to master which seem to include a bunch of built-in class-based Relay stuff. The guides were updated in this commit: https://github.com/rmosolgo/graphql-ruby/commit/44d17186893c8a58377747131db64836b33a94d1

Please share if you're able to get a solution working using this new code :smile:

I can confirm the field initialize doesn't take an edge_class anymore

def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, resolve: nil, introspection: false, hash_key: nil, camelize: true, complexity: 1, extras: [], resolver_class: nil, subscription_scope: nil, arguments: {}, &definition_block)

When I let it there, it gives me a

unknown keyword: edge_class

When I take it off, I get this

  Designer Load (0.8ms)  SELECT  "designers".* FROM "designers" LIMIT $1  [["LIMIT", 12]]
  ↳ app/controllers/graphql_controller.rb:6
        Failed to implement BaseEdge.cursor, tried:

        - `Types::DesignerEdge#cursor`, which did not exist
        - `Designer#cursor`, which did not exist
        - Looking up hash key `:cursor` or `"cursor"` on `#<Designer:0x00007fb01c8cf158>`, but it wasn't a Hash

        To implement this field, define one of the methods above (and check for typos)

/Users/daniel/.rvm/gems/ruby-2.5.1/gems/graphql-1.8.3/lib/graphql/schema/field.rb:319:in `resolve_field_method'

I'm quite hopeless at this point. I don't see where to continue looking. Most probably, I'm downgrading to v1.8.2

FYI, in my setup I've one DesignerType, DesignerConnection, and DesignerEdge

FYI those changes were merged in #1568 _after_ v1.8.3 was released. You could try running off master and following the connections guide to see if that does the trick.

I doubt it will work as the example uses edge_class and on master the field class does not accept edge_class

Tried 1.8.2 and doesn't work.

Just to be sure, what do you have @bgentry in your BaseObject here?

Or maybe I'm missing something here...

@sayduck-daniel yes, I understand that the old example I posted no longer works on the newer version. edge_class works up through 1.8.2, which I am still running successfully using the old example code. It does not work on the newer version.

What I'm saying is: there is a new official approach to doing this that is now in master and is documented in the (unpublished) connections guide. This was merged in #1568.

My recommendation is to at least give that a shot, because all of us doing it the old way (as shown in the blog post and my example) are going to have to migrate to that soon anyway. So I'd say try installing the gem using:

gem "graphql", github: "rmosolgo/graphql-ruby"

and then follow the class-based examples in that that connections guide. Since it was merged I'm assuming it has to be pretty much ready to go, and likely no more bug-prone than the old approach.

BTW, those new Relay types accept an edge_class: option: https://github.com/rmosolgo/graphql-ruby/blob/master/lib/graphql/types/relay/base_field.rb#L8

But like @bgentry said, if you want to try it out, you'll have to bundle master using the snippet he posted.

Got you @bgentry.

Got it to work with this code:

# app/graphql/types/base_connection.rb

module Types
  class BaseConnection < GraphQL::Types::Relay::BaseConnection
    field :total_count, Integer, null: false
    def total_count
      object.nodes.size
    end

    field :count, Integer, null: false
    def count
      @object.edge_nodes.count
    end

    def self.set_edge_type
      edge_name = graphql_name.split('::').last.sub('Connection', 'Edge')
      edge_type = Types.const_get(edge_name, false)
      edge_type(edge_type)
    end
  end
end
# app/graphql/types/designer_connection.rb

module Types
  class DesignerConnection < BaseConnection
    set_edge_type
  end
end
# app/graphql/types/base_edge.rb

module Types
  class BaseEdge < GraphQL::Types::Relay::BaseEdge
    def self.set_edge_type
      node_name = graphql_name.split('::').last.sub('Edge', 'Type')
      node_type = Types.const_get(node_name, false)
      node_type(node_type)
    end
  end
end
# app/graphql/types/designer_edge.rb

module Types
  class DesignerEdge < BaseEdge
    set_edge_type
  end
end

I'm trying to figure out now how to get rid of the set_edge_type or even better, have the .connection_type to use my BaseConnection.

Yes .connection_type should use app specified BaseConnection / BaseEdge / BasePageInfo etc...

Was this page helpful?
0 / 5 - 0 ratings

Related issues

skanev picture skanev  Â·  3Comments

pareeohnos picture pareeohnos  Â·  3Comments

KevinColemanInc picture KevinColemanInc  Â·  3Comments

sayduck-daniel picture sayduck-daniel  Â·  3Comments

EAdeveloper picture EAdeveloper  Â·  3Comments