rails: count and group with joined tables - ruby-on-rails

I have the following models:
Car:
has_many :car_classes
CarClass:
belongs_to :Car
belongs_to :CarMainClass
CarMainClass:
has_many :car_classes
What I want to do is to count the amount of cars in CarClass grouped by the car_main_class_id but then linked to the main_class_symbol which is in CarMainClass.
The query I have now is:
CarClass.group(:car_main_class_id).count(:car_id) => {43=>79, 45=>4 ...}
Which is almost what I want, except that I end up only with the :car_main_class_id which I to be the :main_class_symbol from CarMainClass:
{"A1"=>79, "A2"=>4 ...}
I tried joining the tables and custom select options, but they didn't work.
Can this be done in a query in which I don't have to iterate through the main classes again?
Many thanks for your help!

Instead of having a SQL approach and using a "count/group by", you should look to a very simple feature of Rails ActiveRecords : the counter_cache column.
For example, you can add a column "car_classes_count" in the CarMainClass, and in CarClass class, you do like this :
CarClass:
belongs_to :car
belongs_to :car_main_class, :counter_cache => true
You can do the same with a column "car_class_count" in Car.
I don't know if it can help, but I had the same kind of problems when I started to develop with Rails. I tried to do some unsuccessful crazy SQL queries (queries that worked w/ sqlite, but did not w/ postgres) and I finally choose an other approach.

Try this:
CarClass.includes(:car_main_class => :car_classes)
.group(:car_main_class_id).map { |cc|
{ cc.car_main_class.main_class_symbol => cc.car_main_class.cars.size }
}
Although this is quite ugly - I agree with #Tom that you should try to think of more meaningful class names.

Related

Ruby multiple joins

Here are the models I created in my Rails app:
class Pet < ActiveRecord::Base
belongs_to :shelter
belongs_to :type
end
class Shelter < ActiveRecord::Base
has_many :pets
end
class Type < ActiveRecord::Base
has_many :pets
end
I'm trying to find shelters that don't have any exotic pets in them but am stuck joining the tables in the way where I can retrieve that information! Here is my latest attempt where I believe I'm at least reaching the Types table. Any help and explanation on joins would be much appreciated!
Shelter.joins(:pet => :type).where(:types => {exotic => false})
I believe it is impossible to get the results you want using just JOINS. Instead you need to find which shelters do have exotic pets and then negate that.
One way to accomplish that is through a subquery:
Shelter.where(<<~SQL)
NOT EXISTS (
SELECT 1 FROM pets
INNER JOIN types ON types.id = pets.type_id
WHERE shelters.id = pets.shelter_id
AND types.exotic IS TRUE
)
SQL
Of course that involves a lot of explicit SQL, something I don't mind, but others do not like it.
You can also do something similar using just the ActiveRecord query interface.
shelters_with_exotics = Shelter.joins(pets: :type).where(types: { exotic: true })
Shelter.where.not(id: shelters_with_exotics)
NOTE: The queries for the two examples are different. If it mattered you would need to benchmark both of them to determine which one performed best.

Get children of different models in one single query

class Category
has_many :images
has_many :articles
end
class Image
belongs_to :category
end
class Article
belongs_to :category
end
I'm trying to understand what solutions there are in Rails for children of different models to be queried by the same parent?
E.g. I'd like to get all images and articles that belong to the same category and sort them all by created_at.
You can try 'includes' in rails
Article.includes(:Category)
As I said it seems to me you can use eager loading multiple associations. In your case it could be something like this:
Category.where(id: 2).includes(:images, :articles).sort_by(&:created_at)
Basically you pass your desired Category ID and get :images, :articles which belongs_to Category with particular ID. sort_byprobably should do the sorting thing.
This blog post on eager loading could help you as well.
You can't simply force Active Record to bring all their dependences in a single query (afaik), regardless if is lazy/eager loading. I think your best bet is:
class Category
has_many :images, -> { order(:created_at) }
has_many :articles, -> { order(:created_at) }
end
categories = Category.includes(:images, :articles)
As long as you iterate categories and get their images and articles, this will make three queries, one for each table categories, images and articles, which is a good tradeoff for the ease of use of an ORM.
Now, if you insist to bring all that info in just one query, for sure it must be a way using Arel, but think twice if it worths. The last choice I see is the good old SQL with:
query = <<-SQL
SELECT *, images.*, articles.*
FROM categories
-- and so on with joins, orders, etc...
SQL
result = ActiveRecord::Base.connection.execute(query)
I really discourage this option as it will bring A LOT of duplicated info as you will joining three tables and it really would be a pain to sort them for your use.

Rails Advanced Sorting

I have three models, basically:
class Vendor
has_many :items
end
class Item
has_many :sale_items
belongs_to :vendor
end
class SaleItem
belongs_to :item
end
Essentially, each sale_item points to a specific item (but has an associated quantity and sale price which might be different from the item's base price, hence the separate model), and each item is made by a specific vendor.
I'd like to sort all sale_items by vendor name, but this means going through the associated item, because that's where the association is.
My first attempt was to change SaleItem to the following:
class SaleItem
belongs_to :item
has_one :vendor, :through => :item
end
Which allows me to look for SaleItem.first.vendor, but doesn't allow me to do something like:
SaleItem.joins(:vendor).all(:order => "vendors.name")
Is there an easy way to figure out these complex associations and sorting? It would be especially great if there were a plugin that could take care of these sort of things. I have a lot of different types of tables to add sorting to in this application, and I feel like this will be a big chunk of the figuring-out work.
This could definitely be done with a more complex SQL query (possibly using find_by_sql), but you could also do it pretty easily in Ruby. Try something like the following:
SaleItem.find(:all, :include => { :items => :vendors }).sort do |first,second|
first.vendor.name <=> second.vendor.name
end
I haven't tested it, so it might not work exactly like this, but it should give you a good idea of one possible solution.
Edit: Found an old blog post that seems to have solved this issue. Hopefully this still works in the lastest version of ActiveRecord.
source: http://matthewman.net/2007/01/04/eager-loading-objects-in-a-rails-has_many-through-association/
Second Edit: Straight from the Rails documentation
To include a deep hierarchy of associations, use a hash:
for post in Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ])
That’ll grab not only all the comments but all their authors and gravatar pictures. You can mix and match symbols, arrays and hashes in any combination to describe the associations you want to load.
There's your explanation.
Do you really need your sale_items sorted by the database, or could you wait until it is presented and do the sorting client side via javascript (there are some great sorting libraries out there) - that would save server CPU and (backend) code complexity.

Eager loading for polymorphic associations

Not sure this could fall in performance section as well as model/database section, so here goes....
Let's say I have 3 models:
Movie {
has_one :interest, :as => :resource
}
Song {
has_one :interest, :as => :resource
}
Story {
has_one :interest, :as => :resource
}
and ...
Interest {
belongs_to :resource, :polymorphic => true
}
Now, if I need a list of all interests for all movies, and I want to show also the date those Movies objects were created (to say how old they were), then I use the lookup on resource_type attribute and then #some_interest.resource.created_at.
The problem with this is if I have 100 movie interests, then I will get 101 queries right ? So linear degradation. I tried using :include => [:resource] in my query call, but it says cannot use include in polymorphic associations.
How can I either eager load or optimize this problem to avoid this severe degradation ??
Any help would be greatly appreciated !!
If you are using searchlogic, there is a special syntax to deal with polymorphic relationships that may help. You can search on the has side of the relationship by specifying the name of the related class suffixed with type.
E.g. given your models, you ought to be able to do something like this to get movies created in the last 90 days:
Interest.resource_movie_type_created_at_gt(Time.now-90.days)
Searchlogic sets up the join on the related model for you, which should allay performance concerns.
Of course you can always write your own SQL using the find_by_sql method.
PS. one very helpful trick is to turn on logging in the Rails console while writing searches. This allows you to see the SQL generated right in the console without having to dig through the logs.

Querying a polymorphic association

I have a polymorphic association like this -
class Image < ActiveRecord::Base
has_one :approval, :as => :approvable
end
class Page < ActiveRecord::Base
has_one :approval, :as => :approvable
end
class Site < ActiveRecord::Base
has_one :approval, :as => :approvable
end
class Approval < ActiveRecord::Base
belongs_to :approvable, :polymorphic => true
end
I need to find approvals where approval.apporvable.deleted = false
I have tried something like this -
#approvals = Approval.find(:all,
:include => [:approvable],
:conditions => [":approvable.deleted = ?", false ])
This gives "Can not eagerly load the polymorphic association :approvable" error
How can the condition be given correctly so that I get a result set with approvals who's approvable item is not deleted ?
Thanks for any help in advance
This is not possible, since all "approvables" reside in different tables. Instead you will have to fetch all approvals, and then use the normal array methods.
#approvals = Approval.all.select { |approval| !approval.approvable.deleted? }
What your asking, in terms of SQL, is projecting data from different tables for different rows in the resultset. It is not possible to my knowledge.
So you'll have to be content with:
#approvals = Approval.all.reject{|a| a.approvable.deleted? }
# I assume you have a deleted? method in all the approvables
I would recommend either of the answers already presented here (they are the same thing) but I would also recommend putting that deleted flag into the Approval model if you really care to do it all in a single query.
With a polymorphic relationship rails can use eager fetching on the polys, but you can't join to them because yet again, the relationships are not known so the query is actually multiple queried intersected.
So in the end if you REALLY need to, drop into sql and intersect all the possible joins you can do to all the types of approvables in a single query, but you will have to do lots of joining manually. (manually meaning not using rails' built-in mechanisms...)
thanks for your answers
I was pretty sure that this couldn't be done. I wanted some more confirmation
besides that I was hoping for some other soln than looping thru the result set
to avoid performance related issues later
Although for the time being both reject/select are fine but in the long run I
will have to do those sql joins manually.
Thanks again for your help!!
M

Resources