Building efficient recommendation systems in Ruby on Rails requires a thoughtful approach to both algorithm design and implementation. I’ve spent years working with recommendation engines, and I’ll share the most effective techniques I’ve discovered.
Collaborative Filtering Implementation
Collaborative filtering forms the foundation of modern recommendation systems. In Rails, we can implement user-based collaborative filtering effectively:
class CollaborativeFilter
def initialize(user)
@user = user
@similarity_matrix = {}
end
def recommend
calculate_user_similarities
generate_recommendations
end
private
def calculate_user_similarities
User.find_each do |other_user|
next if other_user.id == @user.id
@similarity_matrix[other_user.id] = calculate_similarity(@user, other_user)
end
end
def calculate_similarity(user1, user2)
common_items = user1.ratings & user2.ratings
return 0 if common_items.empty?
pearson_correlation(user1.ratings, user2.ratings, common_items)
end
def generate_recommendations
similar_users = @similarity_matrix.sort_by { |_, v| -v }.take(10)
Item.find_by_sql([<<-SQL, @user.id, similar_users.map(&:first)])
SELECT items.*, AVG(ratings.score) as predicted_rating
FROM items
JOIN ratings ON ratings.item_id = items.id
WHERE ratings.user_id IN (?)
AND items.id NOT IN (
SELECT item_id FROM ratings WHERE user_id = ?
)
GROUP BY items.id
ORDER BY predicted_rating DESC
LIMIT 20
SQL
end
end
Content-Based Recommendations
Content-based filtering analyzes item attributes to make recommendations. Here’s my implementation using TF-IDF:
class ContentBasedRecommender
def initialize
@tfidf = TfIdfVectorizer.new
end
def process_items
Item.find_each do |item|
vector = @tfidf.transform(item.description)
item.update(feature_vector: vector)
end
end
def recommend_for_user(user)
user_profile = calculate_user_profile(user)
Item.find_each.sort_by do |item|
cosine_similarity(user_profile, item.feature_vector)
end.reverse.take(10)
end
private
def calculate_user_profile(user)
user.liked_items.map(&:feature_vector).reduce(&:+)
end
def cosine_similarity(v1, v2)
dot_product = v1.zip(v2).map { |x, y| x * y }.sum
magnitude = Math.sqrt(v1.map { |x| x**2 }.sum) * Math.sqrt(v2.map { |x| x**2 }.sum)
dot_product / magnitude
end
end
Performance Optimization
Caching plays a crucial role in recommendation system performance. I’ve developed this caching strategy:
class RecommendationCache
def self.fetch_recommendations(user)
Rails.cache.fetch("user_recommendations/#{user.id}", expires_in: 1.hour) do
recommendations = generate_fresh_recommendations(user)
update_recommendation_stats(user, recommendations)
recommendations
end
end
def self.generate_fresh_recommendations(user)
Recommendation.transaction do
collaborative_recommendations = CollaborativeFilter.new(user).recommend
content_recommendations = ContentBasedRecommender.new.recommend_for_user(user)
merge_recommendations(collaborative_recommendations, content_recommendations)
end
end
private
def self.merge_recommendations(collaborative, content)
weighted_merge(collaborative, content, collaborative_weight: 0.7, content_weight: 0.3)
end
end
User Behavior Tracking
Tracking user behavior provides valuable data for recommendations:
class UserBehaviorTracker
include Sidekiq::Worker
def perform(user_id, action, item_id)
user = User.find(user_id)
UserAction.create!(
user: user,
action_type: action,
item_id: item_id,
context: capture_context
)
update_user_preferences(user, action, item_id)
end
private
def capture_context
{
timestamp: Time.current,
session_duration: calculate_session_duration,
device_type: detect_device_type,
location: detect_location
}
end
def update_user_preferences(user, action, item_id)
preference = user.preferences.find_or_initialize_by(item_id: item_id)
preference.score = calculate_preference_score(action)
preference.save!
end
end
A/B Testing Framework
Testing different recommendation algorithms requires a robust A/B testing framework:
class RecommendationExperiment
def initialize(user)
@user = user
@experiment = create_experiment
end
def get_recommendations
variant = @experiment.variant
case variant
when 'control'
CollaborativeFilter.new(@user).recommend
when 'treatment_a'
ContentBasedRecommender.new.recommend_for_user(@user)
when 'treatment_b'
HybridRecommender.new(@user).recommend
end
end
private
def create_experiment
Experiment.create!(
name: 'recommendation_algorithm',
user: @user,
variants: ['control', 'treatment_a', 'treatment_b'],
weights: [0.33, 0.33, 0.34]
)
end
end
Machine Learning Integration
Integrating machine learning enhances recommendation accuracy:
class MLRecommender
def initialize
@model = load_trained_model
end
def predict_ratings(user, items)
features = extract_features(user, items)
predictions = @model.predict(features)
items.zip(predictions).sort_by { |_, rating| -rating }
end
private
def extract_features(user, items)
items.map do |item|
[
user.age,
user.gender_encoded,
item.category_encoded,
item.price,
user.average_rating,
item.average_rating,
interaction_count(user, item.category)
]
end
end
def load_trained_model
model_path = Rails.root.join('lib/models/recommendation_model.pkl')
PyCall.import_module('joblib').load(model_path)
end
end
Data Preprocessing
Proper data preprocessing ensures quality recommendations:
class DataPreprocessor
def preprocess_user_data
User.find_each do |user|
normalized_ratings = normalize_ratings(user.ratings)
user_features = extract_user_features(user)
user.update!(
normalized_ratings: normalized_ratings,
feature_vector: user_features
)
end
end
def preprocess_item_data
Item.find_each do |item|
item_features = extract_item_features(item)
popularity_score = calculate_popularity(item)
item.update!(
feature_vector: item_features,
popularity_score: popularity_score
)
end
end
private
def normalize_ratings(ratings)
mean = ratings.average(:score)
std = ratings.standard_deviation(:score)
ratings.map { |r| (r.score - mean) / std }
end
def calculate_popularity(item)
views = item.view_count
ratings = item.ratings.count
revenue = item.purchase_total
(views * 0.3) + (ratings * 0.4) + (revenue * 0.3)
end
end
This comprehensive approach to building recommendation systems in Rails provides a solid foundation for creating personalized user experiences. The combination of different techniques allows for flexible and accurate recommendations while maintaining good performance through proper caching and optimization strategies.