Comments (3)
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 myconnection
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 aUserType
.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.
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.
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)
- Support GraphOS Tracing for Apollo Studio HOT 7
- Executing mutations outside of the Graph HOT 2
- No such type XXXX, so it can't be a fragment condition HOT 2
- C-Parser coerces zero-padded integers into floats HOT 1
- Current relay v15 compiler doesn't support `--persist-output` option HOT 1
- edge_class is populating as nil after 2.x upgrade HOT 2
- undefined method `to_type_signature' for #<LateBoundType @name=__Field> HOT 4
- [ENTERPRISE] Changesets don't see new input objects for inputs of mutations HOT 2
- Improve "field doesn't exist on type" errors by suggesting camelcasing HOT 3
- Invisible object class makes interfaces ivislbe for all objects that extend it HOT 1
- Extensions only on requests? HOT 4
- [PRO] Configure scalar behavior with schema directives HOT 13
- Default register_connection_implementation should not overwrite HOT 1
- "Errors as data" from within `prepare` blocks HOT 1
- `GraphQL::Schema.orphan_types` does not work for scalars HOT 2
- LocalJumpError: no block given (yield) (LocalJumpError) HOT 5
- Fields validation - only one error at a time, the other errors are ignored HOT 2
- Argument name "null" seems to cause parse error HOT 4
- Orphan types are exposed when using interfaces HOT 2
- Allow field complexity calculation procs access to a lookahead instance HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from graphql-ruby.