Skip to content

Commit f59bc8a

Browse files
committed
feat: create User and Import models with business logic
- User model with role management (admin, manager, user) - Email validation and full name presentation methods - Import model with status tracking and file attachment - Dashboard broadcaster concern for real-time updates - Application record base class and model associations - Application mailer for email functionality
1 parent ddcc6d1 commit f59bc8a

File tree

6 files changed

+165
-0
lines changed

6 files changed

+165
-0
lines changed

app/mailers/application_mailer.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class ApplicationMailer < ActionMailer::Base
2+
default from: "[email protected]"
3+
layout "mailer"
4+
end

app/models/application_record.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class ApplicationRecord < ActiveRecord::Base
2+
primary_abstract_class
3+
end

app/models/concerns/.keep

Whitespace-only changes.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module DashboardBroadcaster
2+
extend ActiveSupport::Concern
3+
4+
included do
5+
after_commit :broadcast_dashboard_update, on: [ :create, :update, :destroy ]
6+
end
7+
8+
private
9+
10+
def broadcast_dashboard_update
11+
# Broadcast updated statistics to admin dashboard
12+
ActionCable.server.broadcast(
13+
"dashboard_updates",
14+
{
15+
type: "stats_update",
16+
stats: {
17+
total_users: User.total_count,
18+
admin_users: User.admin_count,
19+
regular_users: User.user_count
20+
},
21+
timestamp: Time.current.iso8601
22+
}
23+
)
24+
end
25+
end

app/models/import.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
class Import < ApplicationRecord
2+
belongs_to :user
3+
has_one_attached :file
4+
5+
# Status enum
6+
STATUSES = %w[pending processing completed failed].freeze
7+
8+
validates :file_name, presence: true
9+
validates :status, inclusion: { in: STATUSES }
10+
validates :progress, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }
11+
12+
scope :recent, -> { order(created_at: :desc) }
13+
scope :by_status, ->(status) { where(status: status) if status.present? }
14+
15+
# Status helpers
16+
def pending?
17+
status == 'pending'
18+
end
19+
20+
def processing?
21+
status == 'processing'
22+
end
23+
24+
def completed?
25+
status == 'completed'
26+
end
27+
28+
def failed?
29+
status == 'failed'
30+
end
31+
32+
# Progress calculation
33+
def calculate_progress
34+
return 0 if total_rows.zero?
35+
((processed_rows.to_f / total_rows) * 100).round(2)
36+
end
37+
38+
def update_progress!
39+
self.progress = calculate_progress
40+
save!
41+
end
42+
43+
# Error handling
44+
def add_error(error_message)
45+
self.error_details = error_details.to_s + "\n#{Time.current}: #{error_message}"
46+
save!
47+
end
48+
49+
def success_rate
50+
return 0 if processed_rows.zero?
51+
((successful_rows.to_f / processed_rows) * 100).round(2)
52+
end
53+
54+
# Display helpers
55+
def display_status
56+
status.humanize
57+
end
58+
59+
def estimated_time_remaining
60+
return nil unless processing? && processed_rows > 0
61+
62+
elapsed_time = Time.current - updated_at
63+
avg_time_per_row = elapsed_time / processed_rows
64+
remaining_rows = total_rows - processed_rows
65+
66+
(remaining_rows * avg_time_per_row).seconds
67+
end
68+
end

app/models/user.rb

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
class User < ApplicationRecord
2+
include DashboardBroadcaster
3+
4+
# Include default devise modules. Others available are:
5+
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
6+
devise :database_authenticatable, :registerable,
7+
:recoverable, :rememberable, :validatable, :trackable
8+
9+
# Active Storage for avatar image
10+
has_one_attached :avatar_image
11+
12+
# Validations
13+
validates :full_name, presence: true, length: { minimum: 2, maximum: 100 }
14+
validates :role, presence: true, inclusion: { in: %w[user admin] }
15+
validates :avatar_url, format: { with: URI::DEFAULT_PARSER.make_regexp([ "http", "https" ]) }, allow_blank: true
16+
17+
# Enums
18+
enum :role, { user: "user", admin: "admin" }
19+
20+
# Scopes
21+
scope :admins, -> { where(role: "admin") }
22+
scope :regular_users, -> { where(role: "user") }
23+
24+
# Methods
25+
def admin?
26+
role == "admin"
27+
end
28+
29+
def display_name
30+
full_name.presence || email
31+
end
32+
33+
def initials
34+
return "??" if full_name.blank?
35+
36+
full_name.split(" ")
37+
.map { |word| word[0]&.upcase }
38+
.compact
39+
.first(2)
40+
.join
41+
end
42+
43+
def avatar
44+
if avatar_image.attached?
45+
avatar_image
46+
elsif avatar_url.present?
47+
avatar_url
48+
else
49+
nil
50+
end
51+
end
52+
53+
# Class methods
54+
def self.total_count
55+
count
56+
end
57+
58+
def self.admin_count
59+
admins.count
60+
end
61+
62+
def self.user_count
63+
regular_users.count
64+
end
65+
end

0 commit comments

Comments
 (0)