Joins Great Grandparent Model - Ruby On Rails - 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%")

Related

Active Record doesn't update collection when there is joins and includes in query

Hello I've a problem with my query.
There are my models below:
class Owner
has_many :busiensses
has_many :adverts
end
class Business
belongs_to :owner
end
class Advert
belongs_to :owner
end
When I make this query everything is okay and it returns right collection full of needed objects:
Owner.joins(:adverts).includes(:businesses)
.where(businesses: {owner_id: nil})
But when I add to query update it raises error
Owner.joins(:adverts).includes(:businesses)
.where(businesses: {owner_id: nil})
.update_all(status: 'sth')
Error:
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "businesses"
Where is the problem? I bet this error from SQL and it raised when you forget add sth to FROM statement and that sth needed in further conditions, but where in AR i forgot to add it?
Owner.joins(:adverts)
.includes(:businesses)
.where(businesses: {owner_id: 1})
.update_all(name: "blaaaaa")
This statement translates into this query:
UPDATE "owners"
SET "name" = 'blaaaaa'
FROM "businesses" /* missed this */
WHERE "owners"."id" IN
(SELECT "owners"."id" FROM "owners"
INNER JOIN "adverts"
ON "adverts"."owner_id" = "owners"."id"
WHERE "businesses"."owner_id" = 1)
You miss the "FROM 'bussinesses'" which causes the error:
missing FROM-clause entry for table "businesses"
My solution is to use joins instead of using includes. It works fine in my machine.
Owner.joins(:adverts)
.joins(:businesses)
.where(businesses: {owner_id: 1})
.update_all(name: "blaaaaa")
=> 1

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})

Order an association in Active Record to override default sort order defined in association

So I have a class called agency and in that class I have the following:
class Agency
has_many :users, order: 'last_name ASC, first_name ASC'
end
And, when I do the following:
irb(main):004:0> agency.users.order('active desc').pluck(:active)
and that generates the following
SQL (22.0ms) SELECT "users"."active" FROM "users" WHERE "users"."agency_id" = 4040 ORDER BY last_name ASC, first_name ASC, active desc
So, what I want is to override the order in the agency class and not have it sort by last_name or first_name. How can I do that?
Use reorder
agency.users.reorder('active desc').pluck(:active)
The SQL generated would be
SELECT "users"."active" FROM "users" WHERE "users"."agency_id" = 4040 ORDER BY active desc
From the Guides,
The reorder method overrides the default scope order, for example:
class Article < ActiveRecord::Base
has_many :comments, -> { order('posted_at DESC') }
end
Article.find(10).comments.reorder('name')
The SQL that would be executed:
SELECT * FROM articles WHERE id = 10
SELECT * FROM comments WHERE article_id = 10 ORDER BY name
In case the reorder clause is not used, the SQL executed would be:
SELECT * FROM articles WHERE id = 10
SELECT * FROM comments WHERE article_id = 10 ORDER BY posted_at DESC

retrieve reverse multiple records of rails association

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'

Specifying conditions on eager loaded associations returns ActiveRecord::RecordNotFound

The problem is that when a Restaurant does not have any MenuItems that match the condition, ActiveRecord says it can't find the Restaurant. Here's the relevant code:
class Restaurant < ActiveRecord::Base
has_many :menu_items, dependent: :destroy
has_many :meals, through: :menu_items
def self.with_meals_of_the_week
includes({menu_items: :meal}).where(:'menu_items.date' => Time.now.beginning_of_week..Time.now.end_of_week)
end
end
And the sql code generated:
Restaurant Load (0.0ms)←[0m ←[1mSELECT DISTINCT "restaurants".id FROM "restaurants"
LEFT OUTER JOIN "menu_items" ON "menu_items"."restaurant_id" = "restaurants"."id"
LEFT OUTER JOIN "meals" ON "meals"."id" = "menu_items"."meal_id" WHERE
"restaurants"."id" = ? AND ("menu_items"."date" BETWEEN '2012-10-14 23:00:00.000000'
AND '2012-10-21 22:59:59.999999') LIMIT 1←[0m [["id", "1"]]
However, according to this part of the Rails Guides, this shouldn't be happening:
Post.includes(:comments).where("comments.visible", true)
If, in the case of this includes query, there were no comments for any posts, all the posts would still be loaded.
The SQL generated is a correct translation of your query. But look at it,
just at the SQL level (i shortened it a bit):
SELECT *
FROM
"restaurants"
LEFT OUTER JOIN
"menu_items" ON "menu_items"."restaurant_id" = "restaurants"."id"
LEFT OUTER JOIN
"meals" ON "meals"."id" = "menu_items"."meal_id"
WHERE
"restaurants"."id" = ?
AND
("menu_items"."date" BETWEEN '2012-10-14' AND '2012-10-21')
the left outer joins do the work you expect them to do: restaurants
are combined with menu_items and meals; if there is no menu_item to
go with a restaurant, the restaurant is still kept in the result, with
all the missing pieces (menu_items.id, menu_items.date, ...) filled in with NULL
now look aht the second part of the where: the BETWEEN operator demands,
that menu_items.date is not null! and this
is where you filter out all the restaurants without meals.
so we need to change the query in a way that makes having null-dates ok.
going back to ruby, you can write:
def self.with_meals_of_the_week
includes({menu_items: :meal})
.where('menu_items.date is NULL or menu_items.date between ? and ?',
Time.now.beginning_of_week,
Time.now.end_of_week
)
end
The resulting SQL is now
.... WHERE (menu_items.date is NULL or menu_items.date between '2012-10-21' and '2012-10-28')
and the restaurants without meals stay in.
As it is said in Rails Guide, all Posts in your query will be returned only if you will not use "where" clause with "includes", cause using "where" clause generates OUTER JOIN request to DB with WHERE by right outer table so DB will return nothing.
Such implementation is very helpful when you need some objects (all, or some of them - using where by base model) and if there are related models just get all of them, but if not - ok just get list of base models.
On other hand if you trying to use conditions on including tables then in most cases you want to select objects only with this conditions it means you want to select Restaurants only which has meals_items.
So in your case, if you still want to use only 2 queries (and not N+1) I would probably do something like this:
class Restaurant < ActiveRecord::Base
has_many :menu_items, dependent: :destroy
has_many :meals, through: :menu_items
cattr_accessor :meals_of_the_week
def self.with_meals_of_the_week
restaurants = Restaurant.all
meals_of_the_week = {}
MenuItems.includes(:meal).where(date: Time.now.beginning_of_week..Time.now.end_of_week, restaurant_id => restaurants).each do |menu_item|
meals_of_the_week[menu_item.restaurant_id] = menu_item
end
restaurants.each { |r| r.meals_of_the_week = meals_of_the_week[r.id] }
restaurants
end
end
Update: Rails 4 will raise Deprecation warning when you simply try to do conditions on models
Sorry for possible typo.
I think there is some misunderstanding of this
If there was no where condition, this would generate the normal set of two queries.
If, in the case of this includes query, there were no comments for any
posts, all the posts would still be loaded. By using joins (an INNER
JOIN), the join conditions must match, otherwise no records will be
returned.
[from guides]
I think this statements doesn't refer to the example Post.includes(:comments).where("comments.visible", true)
but refer to one without where statement Post.includes(:comments)
So all work right! This is the way LEFT OUTER JOIN work.
So... you wrote: "If, in the case of this includes query, there were no comments for any posts, all the posts would still be loaded." Ok! But this is true ONLY when there is NO where clause! You missed the context of the phrase.

Resources