Code Monkey home page Code Monkey logo

Comments (3)

rmosolgo avatar rmosolgo commented on May 21, 2024 6

I though of a few options, do any of them strike your fancy?

  • Don't decorate, use function objects instead. If you need a value which is a function of a user and some other data, create an object which takes those inputs and returns the value. Eg,

    prof_pic_url = ProfilePictureUrl.new(user, args[:size)
    prof_pic_url.to_s # returns the URL 

    I like this pattern because the objects have small, testable interfaces (unlike decorators, which often take an already-large public API and make it larger). But of course, if you already have a bunch of decorators, this doesn't do ya much good

  • Wrap the field's resolve procs. This is how my connection helper works in my Relay library.

    # Wrap each of `type`'s fields to inject a decorated object 
    def decorate_type(decorator_class, type)
      type.fields.each do |name, field_defn| 
        inner_resolve = field_defn.resolve_proc
        outer_resolve = wrap_resolve_with_decorator(decorator_class, inner_resolve)
        field_defn.resolve_proc = outer_resolve 
      end 
      type 
    end 
    
    # This new proc makes a decorated object and passes it to the inner proc
    def wrap_resolve_with_decorator(decorator_class, inner_resolve)
      -> (obj, args, ctx) {
        decorated_obj = decorator_class.new(obj)
        inner_resolve.call(decorated_obj, args, ctx)
       }
    end
    
    # After defining a type, pass it to `decorate_type` with a decorator
    UserType = decorate_type UserDecorator, GraphQL::ObjectType.define { ... } 

    This works with minimal boilerplate, but the implementation is so wasteful! It makes a new instance of the decorator for each resolved field.

  • Decorate in the parent type. Instead of decorating in UserType, decorate in any field which returns a UserType.

    TeamType = GraphQL::ObjectType.define do 
       field :leader, UserType do 
         resolve -> (obj, args, ctx) { UserDecorator.new(obj) } 
       end 
    
       field :members, types[UserType] do 
         resolve -> (obj, args, ctx) { obj.map { |user| UserDecorator.new(obj) }
       end 
    end 

    Again, not too much code. But you might lose some power in ListType fields. Now, it returns an Array instead of an ActiveRecord::Relation, so pagination & filtering is much less efficient.

  • Decorate with middleware. After resolving a a field, check if the value should be decorated.

    class DecorationMiddleware 
      # decorator_map matches object classes to their decorators. 
      # not sure if keys should be classes or strings, depends on Rails reloading stuffs
      #
      # You might not need this if you can fetch decorators by name, eg `"#{record.class.name}Decorator".constantize` 
      def initialize(decorator_map) 
         @decorator_map = decorator_map 
      end 
    
      def call(parent_type, parent_object, field_definition, field_args, query_context, next_middleware)
        # let the field resolve: 
        result = next_middleware.call 
        # fetch & apply decorator 
        decorator_class = @decorator_map[result.class]
        if decorator_class
          result = decorator_class.new(result)
        end 
        # return the maybe-decorated result 
        result 
      end
    end

    Add the middleware to your schema:

    MySchema = GraphQL::Schema.new # ...
    MySchema.middleware << DecorationMiddleware.new({
      User => UserDecorator,
      Team => TeamDecorator
    })

    That's not too much code and stays out of the way. I've never tried a middleware like that but it seems like it would work!

from graphql-ruby.

theodorton avatar theodorton commented on May 21, 2024

Thanks for the thorough and insightful response :) I was hoping I could use the middleware approach, just wasn't sure how.

I'll try and apply these to my project and see which approach fits me best.

from graphql-ruby.

choznerol avatar choznerol commented on May 21, 2024

I tried the middleware approach but couldn't make it work. From #2479 (comment) I found Field Extension is an alternative.

The Field Extension approach would be something like:

# decoration_extension.rb
class DecorationExtension < GraphQL::Schema::FieldExtension
  def after_resolve(value:, **options)
    decorator_class = options[:decorator_class] || value.decorator_class

    # If you're using Draper. Use `comments.map(&:decorate)` instead of the `comments.decorate` shorthand otherwise AssociationLoader won't work properly.
    if value.respond_to? :map
      value.map{|v| decorator_class.new(v) }
    else
      decorator_class.new(value)
    end
  end
end


# types/article_type.rb
module Types
  class ArticleType < Types::BaseObject
    field :comments, [Types::CommentType], 'All comments of this article', extensions: [DecorationExtension]
    field :likes, [Types::LikeType], 'All comments of this article' do
      extension(DecorationExtension, decorator_class: LikeDecorator)
    end

    # if you are using graphql-batch
    def comments
      Dataloader::AssociationLoader.for(Article, :comments).load(object)
    end
  end
end


# types/comment_type.rb
module Types
  class ArticleType < Types::BaseObject
    field :a_method_of_comment, ...
    field :a_medhot_of_comment_decorator, ...
  end
end

from graphql-ruby.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.