1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88# frozen_string_literal: true
class SubscriptionService
class << self
def create_subscription(work_email:, first_name:, last_name:, category_guids:)
return if category_guids.blank?
ActiveRecord::Base.transaction do
customer = Customer.find_or_create_by!(work_email:) do |c|
c.first_name = first_name
c.last_name = last_name
c.guid = SecureRandom.uuid
end
subscriptions = prepare_subscription_data(customer, category_guids)
Subscription.insert_all!(subscriptions)
end
end
def get_subscriptions(category_guids: nil, pagination_id: nil, pagination_direction: 'forward', limit: 10)
data_size = limit + 1
subscriptions = fetch_subscriptions_from_db(
category_guids: category_guids,
limit: data_size,
pagination_id: pagination_id,
pagination_direction: pagination_direction
)
subscription_list = build_subscription_list(subscriptions, limit, pagination_direction)
{
subscriptions: subscription_list.as_json(except: :id),
previous_cursor: subscription_list.first&.id,
next_cursor: subscription_list.last&.id,
has_more: subscriptions.size > limit
}
end
private
def get_unsubscribed_category_ids(customer, category_guids)
subscribed_guids = customer.subscriptions.joins(:category).pluck('categories.guid')
unsubscribed_category_guids = category_guids - subscribed_guids
Category.where(guid: unsubscribed_category_guids).pluck(:id)
end
def prepare_subscription_data(customer, category_guids)
unsubscribed_category_ids = get_unsubscribed_category_ids(customer, category_guids)
# This is a very rare edge case but adding it just to be safe.
raise ActiveRecord::Rollback if unsubscribed_category_ids.empty?
unsubscribed_category_ids.map do |category_id|
{
customer_id: customer.id,
category_id: category_id,
guid: SecureRandom.uuid,
created_at: Time.current,
updated_at: Time.current
}
end
end
def fetch_subscriptions_from_db(category_guids: nil, limit:, pagination_id: nil, pagination_direction: 'forward')
subscriptions = Subscription.active
.joins(:customer, :category)
.select('subscriptions.id',
'customers.work_email',
'customers.first_name',
'customers.last_name',
'categories.name as category_name')
subscriptions = subscriptions.where(categories: { guid: category_guids }) if category_guids.present?
#using cursor-based pagination because it is more efficient and scales better with large data sets than offset-based pagination
subscriptions = subscriptions.where(pagination_direction == 'forward' ? 'subscriptions.id > ?' : 'subscriptions.id < ?', pagination_id) if pagination_id.present?
subscriptions = subscriptions.order(id: pagination_direction == 'forward' ? :asc : :desc).limit(limit)
# Reverse the order when pagination direction is backward to ensure the value of previous cursor is correct
pagination_direction == 'backward' ? subscriptions.reverse : subscriptions
end
def build_subscription_list(subscriptions, limit, pagination_direction)
return subscriptions.take(limit) if pagination_direction == 'forward'
return subscriptions.drop(1).take(limit) if subscriptions.size > limit
subscriptions.take(limit)
end
end
end