From 47c8d82a6bc813665481397f997eb787df229b8d Mon Sep 17 00:00:00 2001 From: Talha Nasir Date: Sun, 16 Feb 2025 21:32:27 +0500 Subject: [PATCH 1/3] Task:1 Optimized Blogs Batch import process --- app/controllers/blogs_controller.rb | 31 +++++++++++++---------------- app/jobs/import_blogs_job.rb | 12 +++++++++++ app/services/blog_import_service.rb | 24 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 app/jobs/import_blogs_job.rb create mode 100644 app/services/blog_import_service.rb diff --git a/app/controllers/blogs_controller.rb b/app/controllers/blogs_controller.rb index 9e0888a..08ab498 100644 --- a/app/controllers/blogs_controller.rb +++ b/app/controllers/blogs_controller.rb @@ -62,26 +62,23 @@ def destroy end def import - file = params[:attachment] - data = CSV.parse(file.to_io, headers: true, encoding: 'utf8') - # Start code to handle CSV data - ActiveRecord::Base.transaction do - data.each do |row| - current_user.blogs.create!(row.to_h) - end + if params[:attachment].present? + ImportBlogsJob.perform_later(params[:attachment].read, current_user.id) + + redirect_to blogs_path, notice: 'File uploaded successfully. Processing in the background.' + else + redirect_to blogs_path, alert: 'No file uploaded.' end - # End code to handle CSV data - redirect_to blogs_path end private - # Use callbacks to share common setup or constraints between actions. - def set_blog - @blog = current_user.blogs.find(params[:id]) - end + # Use callbacks to share common setup or constraints between actions. + def set_blog + @blog = current_user.blogs.find(params[:id]) + end - # Only allow a list of trusted parameters through. - def blog_params - params.require(:blog).permit(:title, :body, :user_id) - end + # Only allow a list of trusted parameters through. + def blog_params + params.require(:blog).permit(:title, :body, :user_id) + end end diff --git a/app/jobs/import_blogs_job.rb b/app/jobs/import_blogs_job.rb new file mode 100644 index 0000000..479077c --- /dev/null +++ b/app/jobs/import_blogs_job.rb @@ -0,0 +1,12 @@ +require 'csv' + +# This job processes CSV files for importing blogs in the background. +class ImportBlogsJob < ApplicationJob + queue_as :default + + def perform(file_data, user_id) + BlogImportService.new(file_data, user_id).import + rescue StandardError => e + Rails.logger.error("CSV Import failed: #{e.message}") + end +end diff --git a/app/services/blog_import_service.rb b/app/services/blog_import_service.rb new file mode 100644 index 0000000..351b8c3 --- /dev/null +++ b/app/services/blog_import_service.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# This class handles the import of blog records from a CSV file. +class BlogImportService + BATCH_SIZE = 5000 + + def initialize(file_data, user_id) + @file_data = file_data + @user_id = user_id + end + + def import + user = User.find(@user_id) + rows = CSV.parse(@file_data, headers: true) + rows.each_slice(BATCH_SIZE) do |batch| + batch = batch.map { |row| { title: row['title'], body: row['body'] } } + begin + user.blogs.insert_all(batch) + rescue StandardError => e + Rails.logger.error("Error inserting batch: #{e.message}") + end + end + end +end From 86a1cce140e9ba9770fc2c01ba7cbc2303064d65 Mon Sep 17 00:00:00 2001 From: Talha Nasir Date: Sun, 16 Feb 2025 22:22:14 +0500 Subject: [PATCH 2/3] Task:2 Optimize MemoryLeakJob for Efficient Batch Processing --- app/jobs/memory_leak_job.rb | 56 +++++++++++++------------------------ app/models/blog.rb | 3 ++ 2 files changed, 23 insertions(+), 36 deletions(-) diff --git a/app/jobs/memory_leak_job.rb b/app/jobs/memory_leak_job.rb index b55b51f..88ce914 100644 --- a/app/jobs/memory_leak_job.rb +++ b/app/jobs/memory_leak_job.rb @@ -1,47 +1,31 @@ +# frozen_string_literal: true + +# Processes blog records in batches and delegates the processing to ProcessBlogBatchJob class MemoryLeakJob < ApplicationJob + BATCH_SIZE = 5000 queue_as :default - # The purpose of this job to take each blog record and send it to an api and save that api response. - def perform - blogs = Blog.all - - blogs.each do |blog| - validate_and_process(blog) + Blog.valid_blogs.in_batches(of: BATCH_SIZE) do |batch| + batch_ids = batch.pluck(:id) + batch_ids.each_slice(1000) do |slice| + ApiResponse.insert_all(build_api_responses(slice)) + end end + + invalid_blogs_count = Blog.invalid_blogs.count + Rails.logger.info("#{invalid_blogs_count} invalid blogs") end private - def validate_and_process(blog) - # Perform some validations - if blog_valid?(blog) - # Make an API request - blog_to_api(blog) - else - Rails.logger.info "Invalid blog: #{blog.id}" + def build_api_responses(blog_ids) + blog_ids.map do |blog_id| + { + blog_id: blog_id, + api_response_id: "#{SecureRandom.hex}-#{blog_id}", + api_status: ApiResponse.api_statuses.keys.sample + } end - - # Memory leak: storing blog in an array, which grows indefinitely - @processed_blogs ||= [] - @processed_blogs << blog - - # This prevents the blog object from being garbage collected - end - - def blog_valid?(blog) - blog.title.present? && blog.body.present? - end - - def blog_to_api(blog) - # Mock API call - can be replaced with real HTTP call - sleep(0.1) # Simulate some network latency - temp_id = 'blog-id' - # Save API Response - api_response_id = temp_id.gsub("id","#{SecureRandom.hex}-#{blog.id}") - blog.api_responses.create!( - api_response_id: api_response_id, - api_status: ApiResponse.api_statuses.keys.sample - ) end -end \ No newline at end of file +end diff --git a/app/models/blog.rb b/app/models/blog.rb index 70d1a02..c39ee5b 100644 --- a/app/models/blog.rb +++ b/app/models/blog.rb @@ -1,4 +1,7 @@ class Blog < ApplicationRecord belongs_to :user has_many :api_responses + + scope :valid_blogs, -> { where("title != '' AND body != ''") } + scope :invalid_blogs, -> { where("title = '' OR body = ''") } end From 3bb8a4ed07b1a3513476fa09fb7ae11e1c7df9e7 Mon Sep 17 00:00:00 2001 From: Talha Nasir Date: Sun, 16 Feb 2025 22:56:05 +0500 Subject: [PATCH 3/3] Refactor UI and Clean Up Unused Code - Updated Employee and Blog page UI for better user experience - Refactored using partials for improved code maintainability - Removed unused code to enhance performance and readability --- app/views/blogs/_blog.html.erb | 3 +- app/views/blogs/index.html.erb | 13 +-- .../employees/_edit_employee_modal.html.erb | 34 ++++---- .../employees/_employee_details.html.erb | 18 ++++ app/views/employees/_employee_row.html.erb | 9 ++ app/views/employees/_pagination.html.erb | 8 ++ app/views/employees/index.html.erb | 20 +---- app/views/employees/show.html.erb | 19 +---- app/views/layouts/_import_modal.html.erb | 3 + app/views/layouts/_modal.html.erb | 19 +++++ app/views/layouts/_navbar.html.erb | 43 ++++++++++ app/views/layouts/application.html.erb | 84 ++----------------- 12 files changed, 132 insertions(+), 141 deletions(-) create mode 100644 app/views/employees/_employee_details.html.erb create mode 100644 app/views/employees/_employee_row.html.erb create mode 100644 app/views/employees/_pagination.html.erb create mode 100644 app/views/layouts/_import_modal.html.erb create mode 100644 app/views/layouts/_modal.html.erb create mode 100644 app/views/layouts/_navbar.html.erb diff --git a/app/views/blogs/_blog.html.erb b/app/views/blogs/_blog.html.erb index 9b083d0..0ab6cb7 100644 --- a/app/views/blogs/_blog.html.erb +++ b/app/views/blogs/_blog.html.erb @@ -3,11 +3,10 @@ <%= blog.id %> <%= blog.title %> <%= blog.body %> - <%= blog.user.email %> <% if blog.user == current_user %> Edit <% end %> Show - \ No newline at end of file + diff --git a/app/views/blogs/index.html.erb b/app/views/blogs/index.html.erb index 117981c..8129b8f 100644 --- a/app/views/blogs/index.html.erb +++ b/app/views/blogs/index.html.erb @@ -1,27 +1,22 @@

<%= notice %>

- +

<%= alert %>

-

Blogs

+

Your Blogs

- + - - <% @blogs.each do |blog| %> - <%= render blog %> - <% end %> + <%= render @blogs %>
#No# Title BodyUser Actions
<%== pagy_bootstrap_nav(@pagy) %>
- - diff --git a/app/views/employees/_edit_employee_modal.html.erb b/app/views/employees/_edit_employee_modal.html.erb index afea3a3..5cd1d18 100644 --- a/app/views/employees/_edit_employee_modal.html.erb +++ b/app/views/employees/_edit_employee_modal.html.erb @@ -1,18 +1,18 @@ - - \ No newline at end of file + <%= render 'employees/pagination' %> + diff --git a/app/views/employees/show.html.erb b/app/views/employees/show.html.erb index 6cea18b..3d4bfcc 100644 --- a/app/views/employees/show.html.erb +++ b/app/views/employees/show.html.erb @@ -1,18 +1 @@ - - - - - - - - - - - - - - - - - -
Name<%= @employee.dig("name") %>
Position<%= @employee.dig("position") %>
Salary<%= @employee.dig("salary") %>
Date of Birth<%= @employee.dig("date_of_birth") %>
\ No newline at end of file +<%= render 'employees/employee_details' %> diff --git a/app/views/layouts/_import_modal.html.erb b/app/views/layouts/_import_modal.html.erb new file mode 100644 index 0000000..78edac2 --- /dev/null +++ b/app/views/layouts/_import_modal.html.erb @@ -0,0 +1,3 @@ +<%= render 'layouts/modal', modal_id: 'BlogImportModal', title: 'Import Blogs', form_action: '/blogs/import' do %> + <%= file_field_tag 'attachment', required: true %> +<% end %> diff --git a/app/views/layouts/_modal.html.erb b/app/views/layouts/_modal.html.erb new file mode 100644 index 0000000..0492cbd --- /dev/null +++ b/app/views/layouts/_modal.html.erb @@ -0,0 +1,19 @@ + diff --git a/app/views/layouts/_navbar.html.erb b/app/views/layouts/_navbar.html.erb new file mode 100644 index 0000000..03a33f9 --- /dev/null +++ b/app/views/layouts/_navbar.html.erb @@ -0,0 +1,43 @@ + diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 3f53b54..7375b32 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -11,86 +11,12 @@ - - - - - -