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
31 changes: 14 additions & 17 deletions app/controllers/blogs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 12 additions & 0 deletions app/jobs/import_blogs_job.rb
Original file line number Diff line number Diff line change
@@ -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
56 changes: 20 additions & 36 deletions app/jobs/memory_leak_job.rb
Original file line number Diff line number Diff line change
@@ -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
end
3 changes: 3 additions & 0 deletions app/models/blog.rb
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions app/services/blog_import_service.rb
Original file line number Diff line number Diff line change
@@ -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
3 changes: 1 addition & 2 deletions app/views/blogs/_blog.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
<th scope="row"><%= blog.id %></th>
<td><%= blog.title %></td>
<td><%= blog.body %></td>
<td><%= blog.user.email %></td>

<% if blog.user == current_user %>
<td><a href="<%= edit_blog_path(blog)%>" class="card-link">Edit</a></td>
<% end %>
<td><a href="<%= blog_path(blog)%>" class="card-link">Show</a></td>
</tr>
</div>
</div>
13 changes: 4 additions & 9 deletions app/views/blogs/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
<p style="color: green"><%= notice %></p>

<p style="color: red"><%= alert %></p>

<div class="container" id="blogs">
<h1>Blogs</h1>
<h1>Your Blogs</h1>

<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">No#</th>
<th scope="col">Title</th>
<th scope="col">Body</th>
<th scope="col">User</th>
<th colspan="2" scope="col">Actions</th>
</tr>
</thead>
<tbody>
<% @blogs.each do |blog| %>
<%= render blog %>
<% end %>
<%= render @blogs %>
</tbody>
</table>
<%== pagy_bootstrap_nav(@pagy) %>

</div>


34 changes: 17 additions & 17 deletions app/views/employees/_edit_employee_modal.html.erb
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
<!-- Modal -->
<div class="modal fade" id="editEmployee" tabindex="-1" aria-labelledby="editEmployeeLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editEmployeeLabel">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
<%= render 'layouts/modal', modal_id: 'editEmployee', title: 'Edit Employee', form_action: employee_path(@employee) do %>
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<%= text_field_tag :name, @employee.dig("name"), class: 'form-control' %>
</div>
</div>
<div class="mb-3">
<label for="position" class="form-label">Position</label>
<%= text_field_tag :position, @employee.dig("position"), class: 'form-control' %>
</div>
<div class="mb-3">
<label for="salary" class="form-label">Salary</label>
<%= number_field_tag :salary, @employee.dig("salary"), class: 'form-control' %>
</div>
<div class="mb-3">
<label for="date_of_birth" class="form-label">Date of Birth</label>
<%= date_field_tag :date_of_birth, @employee.dig("date_of_birth"), class: 'form-control' %>
</div>
<% end %>
18 changes: 18 additions & 0 deletions app/views/employees/_employee_details.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<table class="table">
<tr>
<th>Name</th>
<td><%= @employee.dig("name") %></td>
</tr>
<tr>
<th>Position</th>
<td><%= @employee.dig("position") %></td>
</tr>
<tr>
<th>Salary</th>
<td><%= @employee.dig("salary") %></td>
</tr>
<tr>
<th>Date of Birth</th>
<td><%= @employee.dig("date_of_birth") %></td>
</tr>
</table>
9 changes: 9 additions & 0 deletions app/views/employees/_employee_row.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<tr>
<th scope="row"><%= employee.dig("id") %></th>
<td><%= employee.dig("name") %></td>
<td><%= employee.dig("salary") %></td>
<td><%= employee.dig("position") %></td>
<td><%= employee.dig("date_of_birth") %></td>
<td><%= link_to 'Edit', edit_employee_path(employee["id"]) %></td>
<td><%= link_to 'Show', employee_path(employee["id"]) %></td>
</tr>
8 changes: 8 additions & 0 deletions app/views/employees/_pagination.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<% if @employees.dig("meta", "total_pages") > 1 %>
<div class="pagination">
<%= link_to 'First Page', employees_path(page: 1) if @employees.dig("meta", "current_page") > 1 %> |
<%= link_to 'Previouse', employees_path(page: @employees.dig("meta","prev_page")) if @employees.dig("meta", "prev_page").present? %> |
<%= link_to 'Next', employees_path(page: @employees.dig("meta","next_page")) if @employees.dig("meta", "next_page").present? %> |
<%= link_to 'Last Page', employees_path(page: @employees.dig("meta","total_pages")) if @employees.dig("meta", "current_page") < @employees.dig("meta", "total_pages") %>
</div>
<% end %>
20 changes: 4 additions & 16 deletions app/views/employees/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,11 @@
</tr>
</thead>
<tbody>
<% @employees.dig("data").each do |e| %>
<tr>
<th scope="row"><%= e.dig("id") %></th>
<td><%= e.dig("name") %></td>
<td><%= e.dig("salary") %></td>
<td><%= e.dig("position") %></td>
<td><%= e.dig("date_of_birth") %></td>
<td><%= link_to 'Edit', edit_employee_path(e["id"]) %> </td>
<td><%= link_to 'Show', employee_path(e["id"]) %> </td>

</tr>
<% @employees.dig("data").each do |employee| %>
<%= render 'employees/employee_row', employee: employee %>
<% end %>
</tbody>
</table>

<%= link_to 'First Page', employees_path(page: 1) %> |
<%= link_to 'Next Page', employees_path(page: @employees.dig("meta","next_page")) %> |
<%= link_to 'Previous Page', employees_path(page: @employees.dig("meta","prev_page")) %> |
<%= link_to 'Last Page', employees_path(page: @employees.dig("meta","total_pages")) %>
</div>
<%= render 'employees/pagination' %>
</div>
19 changes: 1 addition & 18 deletions app/views/employees/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,18 +1 @@
<table class="table">
<tr>
<th>Name</th>
<td><%= @employee.dig("name") %></td>
</tr>
<tr>
<th>Position</th>
<td><%= @employee.dig("position") %></td>
</tr>
<tr>
<th>Salary</th>
<td><%= @employee.dig("salary") %></td>
</tr>
<tr>
<th>Date of Birth</th>
<td><%= @employee.dig("date_of_birth") %></td>
</tr>
</table>
<%= render 'employees/employee_details' %>
3 changes: 3 additions & 0 deletions app/views/layouts/_import_modal.html.erb
Original file line number Diff line number Diff line change
@@ -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 %>
19 changes: 19 additions & 0 deletions app/views/layouts/_modal.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div class="modal fade" id="<%= modal_id %>" tabindex="-1" aria-labelledby="<%= modal_id %>Label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="<%= modal_id %>Label"><%= title %></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<%= form_tag(form_action, method: "post", multipart: true) do %>
<div class="modal-body">
<%= yield %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<%= submit_tag "Save", class: "btn btn-primary" %>
</div>
<% end %>
</div>
</div>
</div>
43 changes: 43 additions & 0 deletions app/views/layouts/_navbar.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="<%= root_path %>"><img width="136" src="https://staunch.co/images/logo.svg"></a>

<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<% if user_signed_in? %>
<li class="nav-item">
<a class="nav-link" aria-current="page" href="<%= new_blog_path %>">New Blog</a>
</li>

<li class="nav-item">
<a class="nav-link" data-bs-toggle="modal" data-bs-target="#BlogImportModal" aria-current="page" href="#">Import Blogs </a>
</li>

<li class="nav-item">
<a class="nav-link" aria-current="page" href="<%= employees_path %>">Employees</a>
</li>

<li class="nav-item">
<a class="nav-link" aria-current="page" href="<%= new_employee_path %>">New Employee</a>
</li>

<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<%= current_user.email %>
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li>
<%= link_to "Edit Profile", edit_user_registration_path, class: 'dropdown-item' %>
</li>
<li><hr class="dropdown-divider"></li>
<li><%= link_to "Logout", destroy_user_session_path, data: { turbo_method: :delete }, class: 'dropdown-item' %></li>
</ul>
</li>
<% end %>
</ul>
</div>
</div>
</nav>
Loading