Lesson 2
Applying Data Filtering and Aggregation in a User Management System
Introduction

Welcome to today’s lesson on applying data filtering and aggregation in a real-world scenario through a user management system. We’ll start by building a foundational structure that can handle basic user operations, then gradually expand it by introducing more advanced functionalities to allow filtering and aggregating user data.

Starter Task Methods

In our starter task, we’ll implement a class to manage basic operations on a collection of user data, specifically handling the addition of new users, retrieving user profiles, and updating existing user profiles. Here are the methods we’ll begin with:

  • add_user(user_id, age, country, subscribed) — Adds a new user with specified attributes. Returns true if the user is added successfully; returns false if a user with the same user_id already exists.
  • get_user(user_id) — Retrieves the user’s profile as a hash if the user exists; returns nil if the user does not exist.
  • update_user(user_id, age = nil, country = nil, subscribed = nil) — Updates the user’s profile with provided parameters. Returns true if the user exists and was updated, and false otherwise.
Implementing Basic User Management

Let’s begin by creating a UserManager class that manages user data within a hash structure, @users, where each key is a user_id and each value is a hash representing the user’s profile.

Ruby
1class UserManager 2 def initialize 3 @users = {} 4 end 5end

The initialize method sets up an empty hash, @users, which will store all user profiles.

Adding and Retrieving Users

The add_user and get_user methods handle adding new users and retrieving existing user profiles.

Ruby
1def add_user(user_id, age, country, subscribed) 2 return false if @users.key?(user_id) 3 4 @users[user_id] = { age: age, country: country, subscribed: subscribed } 5 true 6end 7 8def get_user(user_id) 9 @users[user_id] 10end

In add_user, we check if a user with the specified user_id already exists. If not, we create a new entry in @users with the user’s details and return true. If the user already exists, it returns false. The get_user method retrieves a user’s profile from @users, returning the profile if found or nil if the user does not exist.

Updating User Profiles

The update_user method allows updating the details of an existing user.

Ruby
1def update_user(user_id, age = nil, country = nil, subscribed = nil) 2 return false unless @users.key?(user_id) 3 4 @users[user_id][:age] = age if age 5 @users[user_id][:country] = country if country 6 @users[user_id][:subscribed] = subscribed if subscribed 7 true 8end

In update_user, we first check if the specified user_id exists in @users. If it does, we update the user’s details based on the provided arguments. Each attribute is only updated if a value is passed in. This method returns true if the user is updated, and false if the user does not exist.

Adding Advanced Functions for Data Filtering and Aggregation

With our basic structure in place, let’s add two additional methods to handle more advanced operations, such as filtering user data based on criteria and aggregating user statistics.

  • filter_users(min_age = nil, max_age = nil, country = nil, subscribed = nil) — Filters and returns a list of user IDs that match the specified criteria. Any criterion can be omitted by setting it to nil, meaning that criterion will be ignored.
  • aggregate_stats — Returns statistics as a hash, including:
    • total_users: Total number of users
    • average_age: Average age of all users (rounded down to the nearest integer)
    • subscribed_ratio: Ratio of subscribed users to total users (as a float rounded to two decimals)
Filtering Users with filter_users

Here’s the filter_users method using Ruby’s select to efficiently filter users based on specified criteria.

Ruby
1def filter_users(min_age = nil, max_age = nil, country = nil, subscribed = nil) 2 @users.select do |user_id, profile| 3 (min_age.nil? || profile[:age] >= min_age) && 4 (max_age.nil? || profile[:age] <= max_age) && 5 (country.nil? || profile[:country] == country) && 6 (subscribed.nil? || profile[:subscribed] == subscribed) 7 end.keys 8end

This method filters @users by applying each criterion only if it’s not nil. Using select, we return a filtered hash containing only users who meet all specified conditions. Calling .keys on the result gives an array of user IDs that match the criteria.

Aggregating User Statistics with aggregate_stats

The aggregate_stats method calculates and returns aggregate statistics from the user data.

Ruby
1def aggregate_stats 2 total_users = @users.size 3 return { total_users: 0, average_age: 0, subscribed_ratio: 0.0 } if total_users.zero? 4 5 total_age = @users.values.sum { |profile| profile[:age] } 6 subscribed_users = @users.values.count { |profile| profile[:subscribed] } 7 8 average_age = total_age / total_users 9 subscribed_ratio = (subscribed_users.to_f / total_users).round(2) 10 11 { total_users: total_users, average_age: average_age, subscribed_ratio: subscribed_ratio } 12end

In aggregate_stats, we calculate total_users by finding the size of @users. If there are no users, we return a hash with zeroed statistics. Otherwise, we calculate total_age by summing the ages of all users, and subscribed_users by counting users with subscribed set to true. We compute average_age by dividing total_age by total_users, and subscribed_ratio by dividing subscribed_users by total_users, rounding to two decimal places.

Final UserManager Class with Example Usage

Below is the complete UserManager class, incorporating all the methods we’ve discussed. This final implementation allows you to add, retrieve, update, filter, and aggregate user data in a flexible and efficient way.

Ruby
1class UserManager 2 def initialize 3 @users = {} 4 end 5 6 def add_user(user_id, age, country, subscribed) 7 return false if @users.key?(user_id) 8 9 @users[user_id] = { age: age, country: country, subscribed: subscribed } 10 true 11 end 12 13 def get_user(user_id) 14 @users[user_id] 15 end 16 17 def update_user(user_id, age = nil, country = nil, subscribed = nil) 18 return false unless @users.key?(user_id) 19 20 @users[user_id][:age] = age if age 21 @users[user_id][:country] = country if country 22 @users[user_id][:subscribed] = subscribed if subscribed 23 true 24 end 25 26 def filter_users(min_age = nil, max_age = nil, country = nil, subscribed = nil) 27 @users.select do |user_id, profile| 28 (min_age.nil? || profile[:age] >= min_age) && 29 (max_age.nil? || profile[:age] <= max_age) && 30 (country.nil? || profile[:country] == country) && 31 (subscribed.nil? || profile[:subscribed] == subscribed) 32 end.keys 33 end 34 35 def aggregate_stats 36 total_users = @users.size 37 return { total_users: 0, average_age: 0, subscribed_ratio: 0.0 } if total_users.zero? 38 39 total_age = @users.values.sum { |profile| profile[:age] } 40 subscribed_users = @users.values.count { |profile| profile[:subscribed] } 41 42 average_age = total_age / total_users 43 subscribed_ratio = (subscribed_users.to_f / total_users).round(2) 44 45 { total_users: total_users, average_age: average_age, subscribed_ratio: subscribed_ratio } 46 end 47end

This complete UserManager class now provides all the functionality required for managing a collection of user profiles with robust filtering and aggregation capabilities.

To see this class in action, let’s look at a few example usage scenarios.

Ruby
1um = UserManager.new 2um.add_user('u1', 25, 'USA', true) 3um.add_user('u2', 30, 'Canada', false) 4um.add_user('u3', 22, 'USA', true) 5 6puts um.filter_users(20, 30, 'USA', true).inspect # ["u1", "u3"] 7puts um.filter_users(nil, 28).inspect # ["u1", "u3"] 8puts um.filter_users(nil, nil, 'Canada', false).inspect # ["u2"] 9 10puts um.aggregate_stats.inspect # {:total_users=>3, :average_age=>25, :subscribed_ratio=>0.67}

In these examples, we first add a few users to our UserManager instance and then demonstrate filtering them based on various criteria. We also use aggregate_stats to view statistical data on the users.

This example usage helps illustrate how the UserManager class can be practically applied, providing a strong foundation for managing user data with added flexibility for filtering and aggregation.

Lesson Summary

Great job! Today, you learned how to manage user data by implementing essential functionalities such as adding, retrieving, and updating profiles. Building on this foundation, you added advanced filtering and aggregation features, making the code versatile and effective for real-world applications. Practicing these techniques on similar challenges will further solidify your understanding of data filtering and aggregation. Keep up the great work, and see you in the next lesson!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.