retrieve reverse multiple records of rails association - ruby-on-rails

I have two models product and category.
I am able to make successful queries like Category.products etc.
Product.rb
belongs_to :category
Category.rb
has_many :products
Now I want to retrieve only those categories that has at least one existing product.
I tried like this :
#categories = Category.where(Category.products.present?)
# returned error undefined method `products' also changing to product didn't work.

Getting your comment that you need Categories with products and that the product property with_operator to be true, you can do that query in "rails style" using joins and merge:
#categories = Category.joins(:products).merge(Product.where(with_operator: true)).uniq
Which will generate the following SQL:
SELECT DISTINCT "categories".* FROM "categories" INNER JOIN "products" ON "products"."category_id" = "categories"."id" WHERE "products"."with_operator" = 't'
You could also use the rails 4 syntax, as pointed by #yukke:
Category.joins(:products).where(products: { with_operator: true }).uniq

All you need is inner join. It will skip those categories, that has no products. And to add a condition on joined table you can use rails 4 where's syntax:
#categories = Category.joins(:products).where(products: { with_operator: true }).uniq
It will produce next sql query:
SELECT DISTINCT "categories".*
FROM "categories" INNER JOIN "products" ON "products"."category_id" = "categories"."id"
WHERE "products"."with_operator" = 't'

Related

StatementInvalid Rails Query

I've got the following query that works:
jobs = current_location.jobs.includes(:customer).all.where(complete: complete)
However, when I add a where clause to query the first name of the customer table, I get an error.
jobs = current_location.jobs.includes(:customer).all.where(complete: complete).where("customers.fist_name = ?", "Bob")
Here is the error:
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "customers"
LINE 1: ...bs"."complete" = $2 AND "jobs"."status" = $3 AND (customers....
^
: SELECT "jobs".* FROM "jobs" INNER JOIN "jobs_users" ON "jobs"."id" = "jobs_users"."job_id" WHERE "jobs_users"."user_id" = $1 AND "jobs"."complete" = $2 AND "jobs"."status" = $3 AND (customers.last_name = 'Bob') ORDER BY "jobs"."start" DESC LIMIT $4 OFFSET $5
The current_location method:
def current_location
return current_user.locations.find_by(id: cookies[:current_location])
end
Location Model
has_many :jobs
has_and_belongs_to_many :customers
Job Model
belongs_to :location
belongs_to :customer
Customer Model
has_many :jobs
has_and_belongs_to_many :locations
How can I fix this issue?
includes will only join the table if you set a reference to the association.
When using includes you ensure a reference to the association in 2 fashions:
You can use the references method this will join the table whether or not there are any query conditions (If you MUST use raw SQL as shown in your question then this is the method you would need to use) e.g.
current_location.jobs
.includes(:customer)
.references(:customer)
Or you can use the hash finder version of where: (Please note that when using an associative reference in the where clause you must reference the table name, in this case customers and not the association name customer)
current_location.jobs
.includes(:customer)
.where(customers: {first_name: "Bob" })
Both of these will eager load the customer for the jobs referenced.
The first option (references) will OUTER JOIN the customers table so that all the jobs are loaded even if they have no customers as long as no query conditions reference the customers table.
The second option (using where) will OUTER JOIN the customers table but given the query parameter against the customers table it will act very much like an INNER JOIN.
If you only need to search the jobs based on customer information then joins is a better choice as this will create an INNER JOIN with the customers table but will not try to load any of the customer data in the query e.g.
current_location.jobs.joins(:customer).where(customers: {first_name: "Bob" })
joins will always include the associated table regardless of a reference in the query.
Sidenote: the all in both your queries is completely unnecessary
includes(:customer) does not necessarily join the customers table into the SQL query. You need to use joins(:customer) to force Rails to join the customers table into the SQL query and make it available to query conditions.
jobs = current_location.jobs
.joins(:customer)
.includes(:customer)
.where(complete: complete)
.where(customers: { first_name: 'Bob' })

Joins Great Grandparent Model - Ruby On Rails

I'm sure this is very simple but I cannot get it to work. I have the following associations.
model Category
has_many :category_brands
end
model CategoryBrand
has_many :category_models
belongs_to :category
end
model CategoryModel
has_many :products
belongs_to :category_brand
end
model Product
belongs_to :category_model
end
In theory, I want to query all D records that have an A record with the name equal to "x". So like this:
#products = Product.joins(category_model: {category_brand: :category}).where("category.name like ?", "%Incline Motors%")
But I cannot get this to work. Any help would be appreciated.
Current Error:
G::UndefinedTable: ERROR: missing FROM-clause entry for table "category" LINE 1: ...es"."id" = "category_brands"."category_id" WHERE (category.n... ^ : SELECT COUNT(*) FROM "products" INNER JOIN "category_models" ON "category_models"."id" = "products"."category_model_id" INNER JOIN "category_brands" ON "category_brands"."id" = "category_models"."category_brand_id" INNER JOIN "categories" ON "categories"."id" = "category_brands"."category_id" WHERE (category.name like '%Incline Motors%')
The table name should be pluralised -- note the SQL statement text INNER JOIN "categories"
#products = Product.joins(category_model: {category_brand: :category}).where("categories.name like ?", "%Incline Motors%")

Create scope with .where on both collection and grand-parent

I have three models with grand-parent, parent, child relation: Organization, Category, Post.
I'm trying to create a scope in my Post model, using where first on the passed collection and then on the grand-parent:
scope :ready, -> {
where("next_setup_at < ?", DateTime.current)
.joins(category: :organization)
.where("organizations.active = ?", true)
}
But Postgres is throwing me an error:
ActiveRecord::StatementInvalid: PG::AmbiguousColumn: ERROR: column reference "next_setup_at" is ambiguous
LINE 1: ...zations"."id" = "categories"."organization_id" WHERE (next_setup...
^
: SELECT "posts".* FROM "posts" INNER JOIN "categories" ON "categories"."id" = "posts"."category_id" INNER JOIN "organizations" ON "organizations"."id" = "categories"."organization_id" WHERE (next_setup_at < '2016-03-22 15:57:19.971887') AND (organizations.active = 't')
Take a look at your .where clauses. The second one does a good job at defining what column to query.
where("organizations.active = ?", true)
The first one doesn't.
where("next_setup_at < ?", DateTime.current)
You have to define what table the next_setup_at column references to. Leading to
where("posts.next_setup_at < ?", DateTime.current)
Further imporvement
You can easily specify what table to reference in pure ActiveRecord like so:
where(posts: {next_setup_at: DateTime.current}, categories: {active: true})

How to eager load child model's sum value for ruby on rails?

I have an Order model, it has many items, it looks like this
class Order < ActiveRecord::Base
has_many :items
def total
items.sum('price * quantity')
end
end
And I have an order index view, querying order table like this
def index
#orders = Order.includes(:items)
end
Then, in the view, I access total of order, as a result, you will see tons of SUM query like this
SELECT SUM(price * quantity) FROM "items" WHERE "items"."order_id" = $1 [["order_id", 1]]
SELECT SUM(price * quantity) FROM "items" WHERE "items"."order_id" = $1 [["order_id", 2]]
SELECT SUM(price * quantity) FROM "items" WHERE "items"."order_id" = $1 [["order_id", 3]]
...
It's pretty slow to load order.total one by one, I wonder how can I load the sum in a eager manner via single query, but still I can access order.total just like before.
Try this:
subquery = Order.joins(:items).select('orders.id, sum(items.price * items.quantity) AS total').group('orders.id')
#orders = Order.includes(:items).joins("INNER JOIN (#{subquery.to_sql}) totals ON totals.id = orders.id")
This will create a subquery that sums the total of the orders, and then you join that subquery to your other query.
I wrote up two options for this in this blog post on using find_by_sql or joins to solve this.
For your example above, using find_by_sql you could write something like this:
Order.find_by_sql("select
orders.id,
SUM(items.price * items.quantity) as total
from orders
join items
on orders.id = items.order_id
group by
order.id")
Using joins, you could rewrite as:
Order.all.select("order.id, SUM(items.price * items.quantity) as total").joins(:items).group("order.id")
Include all the fields you want in your select list in both the select clause and the group by clause. Hope that helps!

How to merge (link) 2 Relations on different tables in Rails 4

Given 2 ActiveRecord relations that generate following SQL:
relation a = SELECT comments.* FROM comments INNER JOIN attachments ON attachments.comment_id = comments.id WHERE attachment.name ILIKE '%foo%
relation b = SELECT attachments.* FROM attachments INNER JOIN users ON attachments.user_id = users.id WHERE users.other_conditions
This worked in Rails/ActiveRecord 3:
puts a.merge(b).to_sql # Rails 3
> "SELECT comments.* FROM comments INNER JOIN attachments ON attachments.comment_id = comments.id INNER JOIN users ON attachments.user_id = users.id WHERE attachment.name ILIKE '%foo% AND users.other_conditions"
I think it worked because the merge was ignoring any non-existing associations on the queries.
But Rails 4 is much more pedantic and fails with:
puts a.merge(b).to_sql # Rails 4
> ActiveRecord::ConfigurationError: Association named 'user' was not found on Comment; perhaps you misspelled it?
So the question is how can I literally merge the 2 relations without Rails being worried about the correctness (my specs take responsibility for that)?
Can you describe your models and their relations a little more?
For me it worked like this:
class User
has_many :facebook_friends
end
class FacebookFriend
belongs_to :user
end
a = User.where("users.first_name LIKE '%Sandy%'")
b = FacebookFriend.where("facebook_friends.last_name LIKE '%John%'")
a.merge(b)
=> User Load (0.5ms) SELECT users.* FROM users WHERE (users.first_name LIKE '%Sandy%') AND (facebook_friends.last_name LIKE '%John%')
=> Mysql2::Error: Unknown column 'facebook_friends.last_name' in 'where clause': SELECT users.* FROM users WHERE (users.first_name LIKE '%Sandy%') AND (facebook_friends.last_name LIKE '%John%')
a.joins(:facebook_friends).merge(b)
=> User Load (0.6ms) SELECT users.* FROM users INNER JOIN facebook_friends ON facebook_friends.user_uid = users.uid WHERE (users.first_name LIKE '%Sandy%') AND (facebook_friends.last_name LIKE '%John%')
=> []
The amazing scuttle.io transforms your sql as follows:
Comment.select(Comment.arel_table[Arel.star]).where(
Attachment.arel_table[:name].and(User.arel_table[:other_conditions])
).joins(
Comment.arel_table.join(Attachment.arel_table).on(
Attachment.arel_table[:comment_id].eq(Comment.arel_table[:id])
).join_sources
).joins(
Comment.arel_table.join(User.arel_table).on(
Attachment.arel_table[:user_id].eq(User.arel_table[:id])
).join_sources
)

Resources