Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions day_2/tn_rails_concurrent/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ gem "parallel", "~> 1.26.3"
gem "sidekiq", "~> 7.3.6"

gem "activerecord-import", "~> 2.0"
gem "active_model_serializers"
9 changes: 9 additions & 0 deletions day_2/tn_rails_concurrent/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ GEM
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
active_model_serializers (0.10.15)
actionpack (>= 4.1)
activemodel (>= 4.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.1.5.1)
activesupport (= 7.1.5.1)
globalid (>= 0.3.6)
Expand Down Expand Up @@ -90,6 +95,8 @@ GEM
bootsnap (1.18.4)
msgpack (~> 1.2)
builder (3.3.0)
case_transform (0.2)
activesupport
concurrent-ruby (1.3.4)
connection_pool (2.4.1)
crass (1.0.6)
Expand All @@ -111,6 +118,7 @@ GEM
irb (1.14.2)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jsonapi-renderer (0.2.2)
logger (1.6.3)
loofah (2.23.1)
crass (~> 1.0.2)
Expand Down Expand Up @@ -233,6 +241,7 @@ PLATFORMS
x86_64-linux

DEPENDENCIES
active_model_serializers
activerecord-import (~> 2.0)
annotate
bootsnap
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class OrdersController < ApplicationController
def top_products_report
top_products = Order.all
render json: top_products
top_products = ProductsByOrdersCount.new.call
render json: top_products, each_serializer: TopProductsSerializer
end
end
2 changes: 1 addition & 1 deletion day_2/tn_rails_concurrent/app/models/order.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# updated_at :datetime not null
#
class Order < ApplicationRecord
belongs_to :product
belongs_to :product, counter_cache: true

scope :pending, -> { where(current_status: :pending) }
scope :processed, -> { where(current_status: :processed) }
Expand Down
43 changes: 43 additions & 0 deletions day_2/tn_rails_concurrent/app/queries/products_by_orders_count.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
class ProductsByOrdersCount
def initialize(count: 10)
@count = count
end

def call
sanitized_sql = ActiveRecord::Base.sanitize_sql_array([sql, count, count])
Product.find_by_sql(sanitized_sql)
end

private

attr_reader :count

def sql
@sql ||= <<-SQL.squish
WITH top_products AS (
SELECT *
FROM products
ORDER BY orders_count DESC
LIMIT ?
),
max_days AS (
SELECT product_id, DATE(created_at) AS order_date, SUM(quantity) AS daily_quantity
FROM orders
WHERE product_id IN (SELECT product_id FROM top_products)
GROUP BY product_id, DATE(created_at)
),
max_days_per_product AS (
SELECT product_id, order_date, daily_quantity,
RANK() OVER (PARTITION BY product_id ORDER BY daily_quantity DESC) as rank
FROM max_days
)

SELECT t.*, m.order_date as date
FROM max_days_per_product as m
JOIN top_products as t ON t.id = m.product_id
WHERE m.rank = 1
ORDER BY t.orders_count DESC
LIMIT ?;
SQL
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class TopProductsSerializer < ActiveModel::Serializer
attributes :product_id, :date, :total_quantity

def product_id = self.object.id
def total_quantity = self.object.orders_count
def date = self.object.date.strftime("%d-%m-%Y")
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddOrdersCountToProducts < ActiveRecord::Migration[7.1]
def change
add_column :products, :orders_count, :integer, default: 0, null: false
end
end
4 changes: 2 additions & 2 deletions day_2/tn_rails_concurrent/db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
Product.destroy_all
puts "Products destroyed"

Product.insert_all(Array.new(100) { { name: "Product #{rand(1..1000)}", stock: rand(1..50), price: rand(10..100) } })
Product.insert_all(Array.new(100_000) { { name: "Product #{rand(1..100_000_000)}", stock: rand(1..50_00), price: rand(10..100_000) } })
puts "Products created: #{Product.count}"

product_ids = Product.pluck(:id)
statuses = ["pending", "processed", "cancelled"]
dates = (1..30).map { |n| n.days.ago }
ActiveRecord::Base.record_timestamps = false
Order.insert_all(Array.new(20_000) {
Order.insert_all(Array.new(100_000_000) {
{
product_id: product_ids.sample, quantity: rand(1..10),
current_status: statuses.sample,
Expand Down
4 changes: 3 additions & 1 deletion day_2/tn_rails_concurrent/db/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ CREATE TABLE public.products (
stock integer,
price numeric,
created_at timestamp(6) without time zone NOT NULL,
updated_at timestamp(6) without time zone NOT NULL
updated_at timestamp(6) without time zone NOT NULL,
orders_count integer DEFAULT 0 NOT NULL
);


Expand Down Expand Up @@ -164,6 +165,7 @@ ALTER TABLE ONLY public.schema_migrations
SET search_path TO "$user", public;

INSERT INTO "schema_migrations" (version) VALUES
('20241227185054'),
('20241216120643'),
('20241216120641');