Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
221: Building Custom Admin Dashboards with Charts
When you first start building an admin dashboard, the temptation is to treat it like any other page in your Rails app. You want a chart showing daily revenue for the last 30 days, so you think: "I'll just grab all the orders from the last month and let the frontend handle the rest." It feels intuitive, and for a few dozen records in development, it works perfectly. But this is where most developers stumble into a performance wall that doesn't show up until the app is actually in production.
The "Fetch Everything and Filter" Trap
The naive approach usually looks something like this: you fetch a collection of ActiveRecord objects, map them into a Ruby array, and pass that massive blob of JSON to a library like Chart.js or Highcharts. I've seen this a dozen times. The code looks clean because it's "pure Ruby," but under the hood, it's a disaster.
# The naive way
def dashboard
@orders = Order.where('created_at > ?', 30.days.ago)
@chart_data = @orders.map { |o| { date: o.created_at.to_date, amount: o.total } }
# This sends thousands of objects to the view/JS
end
The problem here is that you're loading thousands of full ActiveRecord objects into memory just to extract two fields: a date and a number. If you have 10,000 orders a month, you're allocating 10,000 Ruby objects, calculating their attributes, and then sending a massive JSON array over the wire. Your server's RAM will spike, and your browser will likely stutter while trying to parse a 5MB JSON response just to draw a few lines on a graph. It's a classic case of over-fetching.
Letting the Database Do the Heavy Lifting
The better way is to remember that SQL is incredibly good at math—much better than Ruby is at iterating over arrays. Instead of bringing the data to the logic, we bring the logic to the data. We want the database to return only the final coordinates for the chart, not the raw records used to calculate them.
By using GROUP BY and SUM, we can collapse thousands of rows into just 30 rows (one for each day). I prefer wrapping this logic in a Query Object to keep the controller lean and the logic reusable.
# The professional way: A dedicated query object
class DailyRevenueQuery
def initialize(start_date = 30.days.ago)
@start_date = start_date
end
def call
Order.where('created_at >= ?', @start_date)
.group("DATE(created_at)")
.sum(:total)
# Returns a Hash: { "2023-10-01" => 450.00, "2023-10-02" => 320.50, ... }
end
end
Now, your controller is just calling DailyRevenueQuery.new.call. Instead of transferring 10,000 records, you're transferring 30. The memory footprint drops from megabytes to kilobytes, and the response time becomes nearly instantaneous regardless of how many orders you have.
When the Trade-off Hits the Road
Now, you might argue that the naive way is more "flexible." If you suddenly need to filter those orders by a specific category or user attribute on the fly, having the objects in memory feels easier. But that's a trap. As your dataset grows, "flexibility" becomes "unusable."
The real trade-off is between the convenience of ActiveRecord's object-oriented nature and the raw power of SQL. When building dashboards, always lean toward the database. If the query gets too complex for a simple .group().sum(), don't be afraid to write a raw SQL fragment or use a View in your database. Your goal for a dashboard isn't to manipulate objects; it's to aggregate data. The moment you find yourself calling .each or .map on a collection that could be summarized in SQL, you're doing it the hard way.
📋 Practical Task
Build a "User Growth" Aggregation Query
You are tasked with adding a "New User Signups" chart to the admin dashboard. Currently, the app is fetching all users and counting them in the view, which is slowing down the page load.
Create a query object named UserGrowthQuery that returns a hash where the keys are dates (for the last 30 days) and the values are the count of users who signed up on that date. Your solution must use .group() and .count() to ensure the aggregation happens in the database, not in Ruby memory.
Requirements:
- The query must filter for users created within the last 30 days.
- The result must be a Hash compatible with a chart library (e.g.,
{"2023-11-01" => 5, "2023-11-02" => 12}). - Avoid loading full ActiveRecord objects into a Ruby array.
There are no comments for now.