Join counts across tables - ruby-on-rails

I have three objects (List, ListType and GlobalList) and am trying to get a count of the items in GlobalList with a specific global_id and a specific list_type_id.
This seems to work:
select count(*) from globals_lists gl, lists l where gl.global_id=946 and gl.list_id=l.id and l.list_type_id=10;
The structure is the following:
class ListType < ActiveRecord::Base
has_many: lists
end
class List < ActiveRecord::Base
has_many :global_lists
belongs_to :list_type
end
class GlobalList < ActiveRecord::Base
belongs_to :list
end
Not sure how to do AR call - at this but can seem to put a where on the join. The list_type_id will always be 10.
a=GlobalList.where("global_id=?",119).joins(:list)
How would I do this ActiveRecord call? Most of the stuff I found via Google involved named scopes, but it seems like this should be able to be done without scopes.

First approach:
result = GlobalList
.select("COUNT(*) AS total")
.where("globals_lists.global_id=? AND lists.list_type_id=?",119, 10)
.joins(:list)
Then you will account your result using result.total
Second approach is tho use count method instead of select("COUNT(*) AS total")
GlobalList
.where("globals_lists.global_id=? AND lists.list_type_id=?",119, 10)
.joins(:list)
.count
You can find more about ActiveRecord Query Interface in Rails Guides, or directly at what you are interested in
count and joins

Related

Is a double-level joins table possible?

I have three tables, Show, Episode, and Character. Each Show has_many Episodes and Characters.
class Show < ActiveRecord::Base
has_many :episodes
has_many :characters
class Episode < ActiveRecord::Base
belongs_to :show
class Character < ActiveRecord::Base
belongs_to :show
I have a list of Characters. What I want to do is order them by the date that their Show's second Episode aired on. Episode has a uniquely named attribute called :air_date
So I would have to do a two-level joins table, something like:
#characters = Character.joins(:show)
.joins(:episodes)
.where(episodes: {number: 2})
.order(:air_date)
But ActiveRecord thinks I'm looking for Episodes associated with Characters, and of course that association doesn't exist.
How can I return the Episodes associated with the Shows associated with a list of Characters? Or is that even possible?
You should use this syntax (I assume air_date is an attribute of episodes):
Character.joins(show: :episodes)
.where(episodes: {number: 2})
.order('episodes.air_date ASC')
You should also be able to use AREL for that:
Character.joins(show: :episodes)
.where(episodes: {number: 2})
.order(Episode.arel_table[:air_date].asc)
If you want to load everything to memory you can use includes, preload or eager_load instead of joins
A complete explanation and examples can be found here.
#character = Character.find x
#shows = #character.show.where(episodes: {number: 2}).order(:air_date).joins(:episodes)
Completely untested - I remembered an ActiveRecord query I saw some time back which put the joins at the end. It was a similar use case (get only the x records which met particular conditions).

group and count by nested association in rails

So I have the following associations (An Order comes from a Catch that is of a fish).
I want to, group a set of orders by the fish they are of, and then sum the quantity ordered attribute within order for each group.
I can do it for each catch with
Order.all.group(:catch_id).sum(:qty_ordered)
but not for the fish.
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :catch
end
class Catch < ActiveRecord::Base
has_many :orders
belongs_to :fish
end
class Fish < ActiveRecord::Base
has_many :catches
end
Any tips?
You are doing this: Order.all.group(:catch_id).sum(:qty_ordered)
and it works because Order has a field called catch_id, so you can group by that field.
If you want to group by fish_id you would have to have a column fish_id in Orders and Orders would have a belongs_to :fish association.
To group_by another related column it would be something like this:
Order.all.group_by {|order| order.catch.fish}
This is not the same group function, this is the Enumerable function group_by. Since your query returns an enumerable, it can be used. It will return a hash with each key being a Fish object, and the value being an array of Order objects that have that fish in them. This may not be the dataset you are looking for. Also you will not be able to just chain a .sum onto it.
You need to look at your model relations and either use a relation that exists to get the data you want, or create more associations to be able to pull the data you want. An example of the exact data set you want would help determine your needs.
An aside, using Order as a model may not be the best form. Rails has a method .order and you might find a conflict somewhere along the way.

Get records from multiple tables ordered by shared column

i'm new to rails and i don't know how to do this simple operation :
i have ,
class Section < ActiveRecord::Base
has_many :articles
has_many :questions
end
class Article < ActiveRecord::Base
belongs_to :section
end
class Question < ActiveRecord::Base
belongs_to :section
end
and i want to get all articles and questions under a certain section but sorted together by shared column 'updated_at' .
i tried including and joining them but failed to order them together .
Start by finding the section that you wish to work with. For example, you could find a particular section by ID (12 in this case):
section = Section.find(12)
You can then access the articles and questions that belong to that section and combine the results with the + operator:
articles_and_questions = section.articles + section.questions
The articles_and_questions variable is of type ActiveRecord::Associations::CollectionProxy. Because the #targets of section.articles and section.questions are both Arrays of objects, you can call any method from the Array class on articles_and_questions. In this case, you can use sort_by which comes from the Enumerable module included in Array:
sorted_articles_and_questions = articles_and_questions.sort_by &:updated_at
If you want to know why there's an ampersand in &:updated_at, read this.
Once the result is in a single array you can do:
#sorted = #records.sort_by &:updated_at

ORDER BY and DISTINCT ON (...) in Rails

I am trying to ORDER by created_at and then get a DISTINCT set based on a foreign key.
The other part is to somehow use this is ActiveModelSerializer. Specifically I want to be able to declare:
has_many :somethings
In the serializer. Let me explain further...
I am able to get the results I need with this custom sql:
def latest_product_levels
sql = "SELECT DISTINCT ON (product_id) client_product_levels.product_id,
client_product_levels.* FROM client_product_levels WHERE client_product_levels.client_id = #{id} ORDER BY product_id,
client_product_levels.created_at DESC";
results = ActiveRecord::Base.connection.execute(sql)
end
Is there any possible way to get this result but as a condition on a has_many relationship so that I can use it in AMS?
In pseudo code: #client.products_levels
Would do something like: #client.order(created_at: :desc).select(:product_id).distinct
That of course fails for reasons that are beyond me.
Any help would be great.
Thank you.
A good way to structure this is to split your query into two parts: the first part manages the filtering of rows so that you get only your latest client product levels. The second part uses a standard has_many association to connect Client with ClientProductLevel.
Starting with your ClientProductLevel model, you can create a scope to do the latest filtering:
class ClientProductLevel < ActiveRecord::Base
scope :latest, -> {
select("distinct on(product_id) client_product_levels.product_id,
client_product_levels.*").
order("product_id, created_at desc")
}
end
You can use this scope anywhere that you have a query that returns a list of ClientProductLevel objects, e.g., ClientProductLevel.latest or ClientProductLevel.where("created_at < ?", 1.week.ago).latest, etc.
If you haven't already done so, set up your Client class with a has_many relationship:
class Client < ActiveRecord::Base
has_many :client_product_levels
end
Then in your ActiveModelSerializer try this:
class ClientSerializer < ActiveModel::Serializer
has_many :client_product_levels
def client_product_levels
object.client_product_levels.latest
end
end
When you invoke the ClientSerializer to serialize a Client object, the serializer sees the has_many declaration, which it would ordinarily forward to your Client object, but since we've got a locally defined method by that name, it invokes that method instead. (Note that this has_many declaration is not the same as an ActiveRecord has_many, which specifies a relationship between tables: in this case, it's just saying that the serializer should present an array of serialized objects under the key `client_product_levels'.)
The ClientSerializer#client_product_levels method in turn invokes the has_many association from the client object, and then applies the latest scope to it. The most powerful thing about ActiveRecord is the way it allows you to chain together disparate components into a single query. Here, the has_many generates the `where client_id = $X' portion, and the scope generates the rest of the query. Et voila!
In terms of simplification: ActiveRecord doesn't have native support for distinct on, so you're stuck with that part of the custom sql. I don't know whether you need to include client_product_levels.product_id explicitly in your select clause, as it's already being included by the *. You might try dumping it.

Query that joins child model results item erroneously shown multiple times

I have the following models, each a related child of the previous one (I excluded other model methods and declarations for brevity):
class Course < ActiveRecord::Base
has_many :questions
scope :most_answered, joins(:questions).order('questions.answers_count DESC') #this is the query causing issues
end
class Question < ActiveRecord::Base
belongs_to :course, :counter_cache => true
has_many: :answers
end
class Answer < ActiveRecord::Base
belongs_to :question, :counter_cache => true
end
Right now I only have one Course populated (so when I run in console Course.all.count, I get 1). The first Course currently has three questions populated, but when I run Course.most_answered.count (most_answered is my scope method written in Course as seen above), I get 3 as the result in console, which is incorrect. I have tried various iterations of the query, as well as consulting the Rails guide on queries, but can't seem to figure out what Im doing wrong. Thanks in advance.
From what I can gather, your most_answered scope is attempting to order by the sum of questions.answer_count.
As it is there is no sum, and since there are three answers for the first course, your join on to that table will produce three results.
What you will need to do is something like the following:
scope :most_answered, joins(:questions).order('questions.answers_count DESC')
.select("courses.id, courses.name, ..., SUM(questions.answers_count) as answers_count")
.group("courses.id, courses.name, ...")
.order("answers_count DESC")
You'll need to explicitely specify the courses fields you want to select so that you can use them in the group by clause.
Edit:
Both places where I mention courses.id, courses.name, ... (in the select and the group), you'll need to replace this with the actual columns you want to select. Since this is a scope it would be best to select all fields in the courses table, but you will need to specify them individually.

Resources