How do I sort on a nested field (or a virtual attribute) in graphql-ruby?
ExampleType = GraphQL::ObjectType.define do
name 'Example'
description '...'
field :nested_field, NestedType, 'some nested field' do
// some result that is virtually calculated and returns
OpenStruct.new(a: 123//some random number, b: 'some string')
end
end
QueryType = GraphQL::ObjectType.define do
name 'query'
field: example, ExampleType do
resolve -> (_obj, args,_ctx) {
Example.find(args['id']) //Example is an active record
}
end
field: examples, types[ExampleType] do
resolve -> (_obj, args,_ctx) {
// NOTE: How to order by nested field here?
Example.where(args['id'])
}
end
end
And if I am trying to get a list of examples ordered by nested_field.a:
query getExamples {
examples(ids: ["1","2"], order: 'nested_field.a desc') {
nested_field {
a
}
}
}
You can not order Active record by virtual attribute, because Active record can not match this virtual attribute to SQL/NoSQL query. You can avoid limitation, by creating view at DB layer. In GraphQL, sorting/pagination should be implemented at DB layer. Without that sorting/pagination implementation queries all data from DB to application memory.
Also, I want to recommend you switching from order argument with string type to sort argument with [SearchSort!] type based on enums. GraphQL schema will looks like that:
input SearchSort {
sorting: Sorting!
order: Order = DESC
}
enum Sorting {
FieldName1
FieldName2
}
enum Order {
DESC
ASC
}
It helps you implement mapping from GraphQL subquery to DataBase query.
Related
I have a problem.
I want to create query to update some fileds on multiple model,
it should looks like this:
mutation{
updateInternalOrder( input: {
state: {
connect: 1
}
id_internal_orders: [1,2] <= here
}){
id_internal_orders
qty
state {
id_internal_orders_states,
name
}
}
}
In this query i would like to assign(update) id_internal_orders_states(in states)
in id_internal_orders that has id: 1 and 2.
How to do that?
Schema(lighthouse-php) that works only if i provide a single id, not array:
extend type Mutation {
updateInternalOrder(input: UpdateInternalOrders! #spread): InternalOrders #update
}
input UpdateInternalOrders {
id_internal_orders: Int!
state: InternalOrdersStatesHasOne
qty: Int
id_supplier: Int
}
input InternalOrdersStatesHasOne {
connect: Int
}
Instead of this
input UpdateInternalOrders {
id_internal_orders: Int!
state: InternalOrdersStatesHasOne
qty: Int
id_supplier: Int
}
Your schema should look like this
input UpdateInternalOrders {
id_internal_orders: [Int]!
state: InternalOrdersStatesHasOne
qty: Int
id_supplier: Int
}
So this way id_internal_orders will be define as an array
Solution for the error Argument 2 passed to Nuwave\\Lighthouse\\Execution\\Arguments\\ArgPartitioner::relationMethods() must be an instance of Illuminate\\Database\\Eloquent\\Model, instance of Illuminate\\Database\\Eloquent\\Collection given
The error you get is because you might be using an ORM. The data passed to the mutation is a collection, probably because you manipulate model generated by your ORM. GraphQL expect an array and not a Collection.
You must either convert the collection in array. But this is not recommended. In case there is a collection of object with collection. You’ll have to convert the collection and all the collection inside each object of the parent collection. This can get complicated very fast.
Or you can find a way to not manipulate your model in your front end and manipulate data transfer object instead. But I can’t really help you here since I don’t know where the data come from.
Let's say that I want to get a person's age using this query:
{
getUser(id: "09d14db4-be1a-49d4-a0bd-6b46cc1ceabb") {
full_name
age
}
}
I resolve my getUser query like this (I use node.js, type-graphql and knex):
async getUser(getUserArgs: GetUserArgs, fields: UserFields[]): Promise<User> {
// Return ONLY ASKED FIELDS
const response = await knex.select(this.getKnexFields(fields)).from(USER).whereRaw('id = ?', [getUserArgs.id]);
// returns { full_name: 'John Smith' }
return response[0];
}
The problem is that then I can't calculate age field, because I did not get born_at (datetime field stored in a db) in the first place:
#FieldResolver()
age(#Root() user: User, #Info() info: GraphQLResolveInfo): number {
console.log(user); // { full_name: 'John Smith' } no born_at field - no age, so error
// calculate age from born_at
return DateTime.fromJSDate(user.born_at).diff(DateTime.fromJSDate(new Date()), ['years']).years;
}
Is there some fancy graphql-build-in way / convention to predict that born_at will be needed instead of doing it manually through info / context?
You should always return full entity data from the query-level resolvers, so they are available for field resolvers.
The other solution is to manually maintain a list of required fields for field resolvers, so your "fields to knex" layer can always include them additionally".
Further improvements might be to can a list of additional columns based on the requested fields (thus the field resolvers that will be triggered).
this is my first post on stackoverflow (Long time reader). I mainly come from a Python background and am fairly new to Ruby. I'm wondering on what's the recommended way to structure a Ruby GraphQL API like the following. Each one of the keys has a resolver that requests a different websites API to fetch the related data. These are currently nested under cars in the following way (I removed the actual http sources they are hitting since you need api keys):
Types::CarsType = GraphQL::ObjectType.define do
name "Cars"
field :count, types.ID
field :contact, types.String
field :prices do
type Types::PricesType
resolve ->(obj, args, ctx) {
HTTParty.get('http://example.com').parsed_response.fetch('some_key').map { |data| OpenStruct.new(data) }
}
end
field :inquiries do
type Types::InquiriesType
resolve ->(obj, args, ctx) {
HTTParty.get('http://example1.com').parsed_response.fetch('some_key').map { |data| OpenStruct.new(data) }
}
end
end
Types::InquiriesType = GraphQL::ObjectType.define do
name "Inquiries"
field :name, types.String
field :phone, types.String
end
Types::PricesType = GraphQL::ObjectType.define do
name "Prices"
field :max, types.String
field :min, types.String
field :suggested, types.String
end
Types::QueryType = GraphQL::ObjectType.define do
name 'Query'
field :cars, types.String do
type Types::CarsType
argument :brand, !types[types.String]
resolve ->(obj, args, ctx) {
HTTParty.get('http://example2.com').parsed_response.fetch('some_key').map { |data| OpenStruct.new(data) }
}
end
end
An example query:
query {
cars(brand: "ford") {
count
contact
prices {
max
min
suggested
}
inquiries {
name
phone
}
}
}
This works fine but I feel like the approach isn't using GraphQL to the fullest. Some immediate problems when I look at it is that If you were to make a query like so, it would now make two API requests when only one is needed (the customer is not requesting any fields on cars, just inquiries). Inquiries is an attribute of cars, so I think it makes sense to be nested under cars?
query {
cars(brand: "ford") {
inquiries {
name
phone
}
}
}
I've looked into connections, but I'm not sure if this is the correct use case for them. Is someone with experience in designing solid GraphQL APIs able to weigh in? Should I be trying to leverage unions or interfaces somehow? Thank you so much.
I have a document like that:
'subject' : {
'name' :"...."
'facebookPosts':[
{
date:"14/02/2017 20:20:03" , // it is a string
text:"facebook post text here",
other stuff here
}
]
}
and I want to count the facebookPosts within a specific objects that their date field contains e.g "23/07/2016".
Now, I do that by extracting all the documents and count in the client side (spring ) , But I think that's not efficient.
You need to aggregate your results.
final Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("facebookPosts.date").regex(REGEX)),
Aggregation.unwind("facebookPosts"),
Aggregation.group().count().as("count"));
Regex might not be the best solution, just an example.
unwind will split array into separate elements you can then count.
Create a class that will hold the count, something like:
public class PostCount {
private Long count;
// getters, setters
}
And then execute it like this:
AggregationResults<PostCount> postCount = mongoTemplate.aggregate(aggregation, Subject.class, PostCount.class);
long count = postCount.getMappedResults().get(0).getCount();
I'm trying to sort a table of data. I have the following domain (paraphrased and example-ified):
class Car {
Engine engine
static constraints = {
engine nullable: true // poor example, I know
}
}
class Engine {
String name
}
Here's the controller action that's handling the sort:
def myAction = {
def list = Car.findAll(params)
render(view: 'list', model: [list: list])
}
I provision some data such that there are several Cars, some with null engines and others with engines that are not null.
I attempt the following query:
http://www.example.com/myController/myAction?sort=engine.name&order=asc
The results from the query only return Car entries whose engine is not null. This is different from the results that would be returned if I only queried the association (without its property):
http://www.example.com/myController/myAction?sort=engine&order=asc
which would return all of the Car results, grouping the ones with null engines together.
Is there any way that:
I can get the query that sorts by the association property to return the same results as the one that sorts by only the association (with the null associations grouped together)?
I can achieve those results using the built-in sorting passed to list() (i.e. without using a Criteria or HQL query)
You need to specify LEFT_JOIN in the query, try this:
import org.hibernate.criterion.CriteriaSpecification
...
def list = Car.createCriteria().list ([max:params.max?:10, offset: params.offset?:0 ]){
if (params.sort == 'engine.name') {
createAlias("engine","e", CriteriaSpecification.LEFT_JOIN)
order( "e.name",params.order)
} else {
order(params.sort, params.order)
}
}
Remember to put engine.name as the property to order by in your list.gsp
<g:sortableColumn property="engine.name" title="Engine Name" />