How to count has_and_belongs_to_many in Rails? - ruby-on-rails

I am working on a simple Rails with the following structure:
Product
has_and_belongs_to_many :subscribers
Subscriber
has_and_belongs_to_many :products
How can I get all products that has subscribers which is products with subscribers > 0 ?
def self.has_subscribers
#subscribers > 0
end

well have you tried doing,
#product.subscribers.count > 0
so you can do something like
def has_subscribers?
subscribers.count > 0
end

def self.has_subscribers
Product.joins("LEFT JOIN products_subscribers ON products_subscribers.product_id = products.id").where("products_subscribers.product_id IS NOT NULL")
end
HTH
edit: I am not sure, if below will work, but you may try it:
def self.has_subscribers
Product.joins(:products_subscribers)
end
Since, joins use INNER JOIN, it should return only those records of products which have a relationship with subscribers in the joining table.
Edit 2: Just joining with joining table may not (in fact should not) work because joining table usually do not have a model.
The above is working for me, except I forgot to GROUP the results.
Product.joins("LEFT JOIN products_subscribers ON products_subscribers.product_id = products.id").where("products_subscribers.product_id IS NOT NULL").group("products.id")
If you are still getting Subscribers, then it is kinda odd. Please post your function and querying code.

Product.where( '(select count(1) from products_subscribers where product_id = products.id > 0)')

Related

How to Sort Record in Ruby on Rails based on Last Record Timestamp from References Table

I need to create a live chat app and now I have three models :
ChatRoom
ChatRoomMember
ChatRoomMessage
From this three models, I use includes and references to get the list of chat_rooms for current login user. Here is the code that I have wrote.
#chat_rooms = ChatRoom.includes(:members).references(:members)
#chat_rooms = #chat_rooms.includes(:messages).references(:messages)
#chat_rooms = #chat_rooms.where 'chat_room_members.user_id = ?', #current_user.id
#chat_rooms = #chat_rooms.order 'chat_room_messages.created_at DESC'
#chat_rooms = #chat_rooms.limit(limit).offset(offset)
However, the order didn't work as I expected. What I want is that the chat_rooms are sorted by created_at column from the last message in that room. How can I do this ?
Here is the database structure :
Use association to avoid where 'chat_room_members.user_id = ?', #current_user.id
Here is my suggestion, assuming User has associations looking like:
class User
has_many :chat_room_members
has_many :chat_rooms, through: :chat_room_members
end
# list only rooms with at least on message
#chat_rooms = #current_user.chat_rooms.joins(:messages).order('chat_room_messages.created_at DESC').limit(limit).offset(offset)
# list all rooms even if there is no message attached
#chat_rooms = #current_user.chat_rooms.distinct.left_joins(:messages).order('chat_room_messages.created_at DESC').limit(limit).offset(offset)
Try this:
ChatRoom.includes(:messages).order('chat_room_messages.created_at DESC')
Thanks for everyone has help me to solve this probel. I have my own answer. Here is it.
SELECT chat_rooms.*, (SELECT chat_room_messages.created_at FROM chat_room_messages WHERE chat_room_messages.chat_room_id = chat_rooms.id ORDER BY chat_room_messages.id DESC LIMIT 1) AS last_message_at FROM chat_rooms INNER JOIN chat_room_members ON chat_room_members.chat_room_id = chat_rooms.id WHERE chat_room_members.user_id = #{#current_user.id} ORDER BY last_message_at DESC
I use raw query for solve this problem. Hope this can help anyone who need it !

Ruby, query model with aggregate with model

In my rails application, at some point, I query my model simply. I want to query customers order information like how many orders were given by this customer within three months.
Just now, I query the model in that way:
#customer = Customer.all
customer.rb
class Customer < ApplicationRecord
audited
has_many :orders
end
And customer may have orders.
order.rb
class Order < ApplicationRecord
audited
belongs_to :customer
end
What I would like to do is to query customers model and to inject aggregate function result to every customer records.
EDİT
I tried to simulate every solution but couln't achieve.
I have the following query in mysql.
How do I need to code in ruby with activerecord to create that query ?
SELECT
(SELECT
COUNT(*)
FROM
orders o
WHERE
o.customer_id = c.id
AND startTime BETWEEN '2017.12.04' AND '2018.01.04') AS count_last_month,
(SELECT
COUNT(*)
FROM
orders o
WHERE
o.customer_id = c.id
AND startTime BETWEEN '2017.10.04' AND '2018.01.04') AS count_last_three_month,
c.*
FROM
customers c;
How can I achieve that?
Thanks.
Customer.
joins(:orders).
group('customers.id').
where('orders.created_at > DATE_SUB(NOW(), INTERVAL 3 MONTH)')
select('sum(orders.id), customers.*')
As my understanding of you question. I have this solution for you question. Please have a look and try it once. In below query, 'includes' used to solve N+1 problem.
Customer.includes(:orders).where('created_at BETWEEN ? AND ?', Time.now.beginning_of_day, Time.now.beginning_of_day-3.months).group_by{|c|c.orders.count}
If you are looking for particular customer's order count then you can try this one.
#customer.orders.where('created_at BETWEEN ? AND ?', Time.now.beginning_of_day, Time.now.beginning_of_day-3.months).group_by{|c|c.orders.count}

With Rails 5, how to left joins with selected associations and order by association count

here are my models:
class Article
has_many :votes
end
class Vote
belongs_to :article
belongs_to :user
end
Now I am trying to order the articles, by the count of votes in the past 24 hours. Any suggestions for how to do this?
I have tried this:
Article.left_joins(:votes).group("articles.id").order("count(votes.id) DESC")
However, this is ordering by all the votes, not the votes in last 24h. Any suggestions?
One more thing is, I still need to get the articles with no votes. So I am not sure how to use the where clause here...
You need to add the date when the vote was created for an article prior to the count of votes in your order.
Try this:
Article.left_joins(:votes)
.group("articles.id")
.order("DATE(votes.created_at) DESC, count(votes.id) DESC")
Then if you only want to get the articles that has been upvoted for the past 24hr, you can chain this to your query:
.where("votes.created_at >= ?", 1.day.ago)
Finally I got it work. It turns out left_joins is not necessary. My current solution is using select clause in the order() function:
Article.order("(select count(*) from votes where votes.article_id = articles.id and votes.created_at >= NOW() - '1 day'::INTERVAL ) desc")
Maybe not elegant, but works well.

Exclude object if one of the has_many related entities has the attribute with value x

I came across about the problem excluding data, if the attribute x of one of the associated data has the value 'a'.
Example:
class Order < ActiveRecord::Base
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :order
validate_presence_of :status
end
The query should return all Orders that don't have an Item with status = 'paid' (status != 'paid').
Because of the 1:n association an Order can have many Items. And one of the Itmes can have the status = 'paid'. These Orders must be excluded from the result of my query even if the order has other items with status different from 'paid'.
How would I solve this problem:
paid_items = Items.where(status: 'paid').pluck(:order_id)
orders_wo_paid = Order.where('id NOT IN (?)', paid_items)
Is there an ActiveRecord solution, that solves this problem in one query.
Or are there other ways to solve this question?
I 'm not looking for ruby solution such as:
Order.select do |order|
!order.items.pluck(:status).include?('paid')
end
thx for ideas and inspirations.
You can do:
Order.where('orders.id NOT IN (?)', Item.where(status: 'paid').select(:order_id))
If you're using Rails 4.x then:
Order.where.not(id: Item.where(status: 'paid').select(:order_id))
The query you are interested in is the following, but creating with activerecord will be hard/no very readable:
SELECT
orders.*
FROM
orders
LEFT JOIN
order_items ON orders.id = order_items.order_id
GROUP BY
order_items.order_id
HAVING
COUNT(DISTINCT order_items.id) = COUNT(DISTINCT order_items.status <> 'paid')
Sorry for the sql indentation, I have no idea which are the conventions for it.
A way (not the best one at all) to it with rails (unfortunately writing sql for the most important parts) would be the following:
Order.group(:order_id).joins("LEFT JOIN order_items ON orders.id = order_items.order_id")
.having("COUNT(DISTINCT order_items.id) = COUNT(DISTINCT order_items.status <> 'paid')")
Of course you can play with AREL to get rid of the hard coded sql, but in my opinion it will not be easier to read.
You can have an example of creating lefts joins in this gist: https://gist.github.com/mildmojo/3724189

Arel: order by association count

Here is what I'm trying to do
class Question
has_many :votes
end
class Vote
belongs_to :question
end
I want to find all questions ordered by the number of votes they have. I want to express this in Arel (in Rails 3) without using any counter caches.
Is there any way of doing this ?
Thanks.
Try next one:
Question.joins(:votes).select("questions.id, *other question coulmns*, count(votes.id) as vote_count").order("vote_count DESC").group("questions.id")
Try this:
Question.select("questions.*, a.vote_count AS vote_count").
joins("LEFT OUTER JOIN (
SELECT b.question_id, COUNT(b.id) AS vote_count
FROM votes b
GROUP BY b.question_id
) a ON a.question_id = questions.id")
Solution is DB agnostic. Make sure you add an index on the question_id column in the votes table( you should add the index even if you don't use this solution).

Resources