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.
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; returnsfalse
if a user with the sameuser_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, andfalse
otherwise.
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.
Ruby1class UserManager 2 def initialize 3 @users = {} 4 end 5end
The initialize
method sets up an empty hash, @users
, which will store all user profiles.
The add_user
and get_user
methods handle adding new users and retrieving existing user profiles.
Ruby1def 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.
The update_user
method allows updating the details of an existing user.
Ruby1def 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.
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 usersaverage_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)
Here’s the filter_users
method using Ruby’s select
to efficiently filter users based on specified criteria.
Ruby1def 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.
The aggregate_stats
method calculates and returns aggregate statistics from the user data.
Ruby1def 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.
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.
Ruby1class 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.
Ruby1um = 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.
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!