How can I get graphql-ruby to use joins when accessing subfields? - ruby-on-rails

I have a model like this
class Foo
has_many :bars
end
and a query like this
query foos(
$offset: Int
$sort_by: String
$should_paginate: Boolean
) {
foos(
offset: $offset
sort_by: $sort_by
should_paginate: $should_paginate
) {
id
name
bars {
When I fetch this query, I get one select * from "foos" for each bar that's in the collection.
How can I have this all be smarter and do fewer SQL queries?

Look at https://github.com/Shopify/graphql-batch
It uses allows you to lazy-load your associations at once on demand.

Related

In Rails, simple graphQL-ruby queries are ignoring sort order

The following simple query is not returning Posts ordered by created_at -
According to the terminal, Rails is ordering by id, no matter what i put in ::Post.order(column: :direction)
Post Load (0.9ms) SELECT `posts`.* FROM `posts `
ORDER BY `posts `.`id` ASC LIMIT 24
I can't imagine what could be reordering the query by :id, or blocking sort order.
Here's the resolver:
module Resolvers
class Posts < Resolvers::BaseResolver
type Types::Models::PostType.connection_type, null: false
description "List or filter all observations"
def resolve
::Post.order(created_at: :desc)
end
end
end
Base resolver
module Resolvers
class BaseResolver < GraphQL::Schema::Resolver
end
end
Query type
# graphql/types/query_type.rb
require("graphql/batch")
require("loaders/record_loader")
require("search_object")
require("search_object/plugin/graphql")
module Types
class QueryType < Types::BaseObject
field :posts, Types::Models::PostType.connection_type, null: false, resolver: Resolvers::Posts
Query (using Relay style cursor pagination)
query getPosts {
posts {
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
edges {
cursor
node {
id
thumbImageId
textName
where
when
createdAt
updatedAt
}
}
}
}
The app schema:
class MyAppSchema < GraphQL::Schema
query(Types::QueryType)
mutation(Types::MutationType)
default_max_page_size 24
connections.add(ActiveRecord::Relation, GraphQL::Connections::Stable)
# GraphQL::Batch setup:
use GraphQL::Batch
end
Background: I'm planning on using a resolver for this model query because the eventual version will have a lot of filters built with search_object_graphql, and while testing the sorting filters i noticed it was ignoring the sort order. So I stripped the resolver back down to the graphql schema inheritance, hardcoded the query scope with an orderby clause, and it's still not ordering the query.
Figured it out.
This is a documented behavior of the Search Object plugin for graphql-ruby.
I redid my query filters without this dependency, and sort order is working.

RubyOnRails multiple scopes composition

I got a Product model with has_many Types table and several scopes:
class Product < ActiveRecord::Base
has_many :product_types
has_many :types, through: :product_types
scope :type1, -> { joins(:types).where(types: { name: "Type1" }) }
scope :type2, -> { joins(:types).where(types: { name: "Type2" }) }
end
When I try to use one scope (Product.type1 for example) all goes well, but two scopes at a time (Product.type1.type2) returns an empty query. Yes, one product may have multiple types.
Final goal is to create a filter of products by type with checkboxes form. When I check type1 and type2 I want to see all my products that have Type1 and Type1 at the same time.
UPD 1
So I've tried to do several queries and then & them as #aaron.v suggested. I wanted to do the logic inside of the function so:
def products_by_type_name(types)
all_types = types.each { |type| Product.joins(:types).where(types: { name: type }).distinct }
...
end
My point was to iterate through each type, collect all products and then & them inside the function.
The problem is when I'm iterating, each loop returns string instead of array of hashes.
Product.joins(:types).where(types: { name: types }).distinct # returns array of hashes, it's okay.
types.each { |type| Product.joins(:types).where(types: { name: type }).distinct } # each loop returns string (Type name) instead of array of hashes.
What am I doing wrong?
SOLUTION 1
Suggested by #aaron.v, explained below
def self.by_type_name(types)
product_ids = []
types.each do |t|
product_ids << (joins(:types).where(types: { name: t }).distinct.select(:id).map(&:id))
end
find(product_ids.inject(:&))
end
SOLUTION 2
Found on reddit.
In this function you are fetching all products with at least one required type and than grouping only ones that have required count of types. Thus, you get only those products that belongs to every type at the same time.
def self.by_type_name(types)
joins(:types).where(types: { name: types }).distinct.group('products.id').having('count(*) = ?', types.each.count)
end
If you have a database background, this would be pretty obvious as to why you wouldn't be able to find products with multiple types based off how you are writing your scopes.
Database queries that are written from these scopes will multiply the rows to ensure that a product that has many types, will have a distinct row for each type. Your query when you combine both scopes is writing
where `types`.name = "Type1" AND `types`.name = "Type2"
This will never happen since columns aren't added with multiples of the same row on a join.
What you want to do is have a method that you can pass an array of type names to find it
def self.by_type_name(types)
joins(:types).where(types: { name: types }).distinct
end
This method can accept one name by itself or an array of names for the types you want to find
If you pass an array of type names like ["type1", "type2"], this will result in a query like
where `types`.name in ("type1", "type2")
And then you should see the results you expect
UPDATE
To revise what you need in finding products that have all types given to your method, I decided to do this
def self.by_type_name(types)
product_ids = []
types.each do |t|
product_ids << (joins(:types).where(types: { name: t }).distinct.select(:id).map(&:id))
end
find(product_ids.inject(:&))
end
This method requires an array of type names(even if it is one type, pass an array with one type in it). What it will do is find all products with one specific type name, only select the product id(which will be lighter on your DB) and map it into an array which will get dumped into the product_ids array. after it has looped over each type name, it will find all products that intersect in each of the arrays resulting with products that have all types passed in.

HQL Join in Grails

I am trying to execute a query to gather specific data but encountering problems in the query on the on portion of the query. To start off this is my class:
class TimeSlot {
String timeslot_id
String time_chunk_id
String uid
String exam_id
String start_time
String is_special_arrangement
static mapping = {
table 'timeslot'
id name: "timeslot_id", column: "timeslot_id"
version false
}
}
This is the query I'm trying to get working:
TimeSlot.executeQuery("Select t.time_chunk_id, t.uid, t.start_time, t.timeslot_id, t.is_special_arrangement, e.length from TimeSlot t inner join Exams e on t.exam_id = e.exam_id where t.exam_id = ? and t.time_chunk_id = ?", [testArray[i], timeChunkArray[x]])
It's throwing an error on the on portion because it's expecting a clause, but I need the data to specifically pertain to the exam.id comparison of both tables. Is there another way around this or a different way to set up the query so it will work like it does in any SQL editor?
It would be easier if you alter the domain class and add one to many relationship
class TimeSlot {
static hasMany = [examinations:Exams]
Then HQL can be
select ... from TimeSlot t join t.examinations e

Preserving order or array in has_many relationship

I have a model CardSet which has_many :cards, :order => "cards.order". When I update some_card_set.cards = cards_in_a_particular_order, and then some_card_set.save, I want it so some_card_set.cards returns the next time with the cards in the same order I entered them in. How can I achieve this?
Either do it manually, or use one of a ton of acts_as_ordered plugins, like this or this etc.
Assuming a table design similar to
cards {
id INT PRIMARY KEY,
value VARCHAR(5),
suite VARCHAR(10)
}
card_sets {
id INT PRIMARY KEY,
name VARCHAR(30),
}
Then I would have a join table something like:
card_set_orders {
card_set_id INT,
card_id INT,
order_index INT
}
You would have to change your has many to reflect the join syntax. Then you could have an 'on load' function 'order' for the set. So you'll need:
1) a new migration
2) a new activerecord model
3) ordering method that you can invoke on find, or manually
4) updated has_many

Rails - find_by_sql - Querying with multiple values for one field

I'm having trouble joining the values for querying multiple values to one column. Here's what I got so far:
def self.showcars(cars)
to_query = []
if !cars.empty?
to_query.push cars
end
return self.find_by_sql(["SELECT * FROM cars WHERE car IN ( ? )"])
end
That makes the query into:
SELECT * FROM cars WHERE car IN (--- \n- \"honda\"\n- \"toyota\"\n')
It seems find_by_sql sql_injection protection adds the extra characters. How do I get this to work?
Do you really need find_by_sql? Since you're performing a SELECT *, and assuming your method resides on the Car model, a better way would be:
class Car < ActiveRecord::Base
def self.showcars(*cars)
where('car in :cars', :cars => cars)
# or
where(:car => cars)
end
end
Note the * right after the parameter name... Use it and you won't need to write code to make a single parameter into an array.
If you really need find_by_sql, try to write it this way:
def self.showcars(*cars)
find_by_sql(['SELECT * FROM cars where car in (?)', cars])
end
Try joining the to_query array into a comma separated string with all values in single quotes, and then passing this string as a parameter "?".
Problem resolve.
def self.average_time(time_init, time_end)
query = <<-SQL
SELECT COUNT(*) FROM crawler_twitters AS twitter WHERE CAST(twitter.publish AS TIME) BETWEEN '#{time_init}' AND '#{time_end}'
GROUP BY user) AS total_tweets_time;
SQL
self.find_by_sql(sanitize_sql(query))
end

Resources