Let’s implement a Rails model method that finds all users with more than N orders in the last M days, using efficient ActiveRecord querying, scopes, and avoiding N+1 issues.
✅ Problem Breakdown
Goal:
Return users who have placed more than
Norders in the lastMdays.
🧩 Assumptions
- You have a
Usermodel and anOrdermodel. - Association:
User has_many :orders.
# app/models/user.rb
class User < ApplicationRecord
has_many :orders
end
# app/models/order.rb
class Order < ApplicationRecord
belongs_to :user
end
🧠 Step-by-Step Plan
We want to:
- Join users with their orders.
- Filter orders within last
Mdays. - Group by user ID.
- Count orders per user.
- Filter groups with count > N.
- Avoid loading all orders into memory.
💎 Final Implementation
Scope in User model:
# app/models/user.rb
class User < ApplicationRecord
has_many :orders
scope :with_min_orders_in_last_days, ->(min_orders:, days:) {
select("users.*, count(orders.id) as order_count")
.joins(:orders)
.where("orders.created_at >= ?", days.days.ago)
.group("users.id")
.having("count(orders.id) >= ?", min_orders)
.order("order_count desc")
}
def self.min_orders_in_last_days(min_orders, days)
find_by_sql([
"SELECT users.*, count(orders.id) AS order_count
FROM users
JOIN orders ON users.id = orders.user_id
WHERE orders.created_at >= ?
GROUP BY users.id
HAVING count(orders.id) >= ?
ORDER BY order_count desc", days.days.ago, min_orders
])
end
end
📥 Example Usage in Rails Console
# Find users with more than 3 orders in last 7 days
User.with_orders_in_last_days(min_order_count: 3, days: 7)
🧪 Avoiding N+1 Problem
This query only loads users — not individual orders — so there’s no N+1 issue. If you need to show order details too, you can preload like this:
User
.with_orders_in_last_days(min_order_count: 3, days: 7)
.includes(:orders)
🧾 Bonus: Add a class method for more customization
def self.active_buyers(min_order_count:, days:)
with_orders_in_last_days(min_order_count: min_order_count, days: days)
end
📌 Conclusion
By leveraging ActiveRecord scopes, grouping, and having, we wrote a performant query that avoids the N+1 problem and stays readable.