Software Architect Guide: Rails Model method/scope to find users with m orders in last n days

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 N orders in the last M days.

🧩 Assumptions

  • You have a User model and an Order model.
  • 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:

  1. Join users with their orders.
  2. Filter orders within last M days.
  3. Group by user ID.
  4. Count orders per user.
  5. Filter groups with count > N.
  6. 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.