Skip to content
Draft
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 .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ruby 4.0.6
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ gem "thruster", require: false
gem "image_processing", "~> 2.0"
gem "ruby-vips", "~> 2.0"

gem "csv"

group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ GEM
cruise (0.2.0-arm64-darwin)
cruise (0.2.0-x86_64-linux-gnu)
cruise (0.2.0-x86_64-linux-musl)
csv (3.3.6)
date (3.5.1)
debug (1.11.1)
irb (~> 1.10)
Expand Down Expand Up @@ -434,6 +435,7 @@ DEPENDENCIES
brakeman
bundler-audit
capybara
csv
debug
faker (~> 3.8)
herb
Expand Down Expand Up @@ -496,6 +498,7 @@ CHECKSUMS
cruise (0.2.0-arm64-darwin) sha256=fb3e9b265868e077dd754b4a430ab14d8abb209281c61c906b74f326b797a10a
cruise (0.2.0-x86_64-linux-gnu) sha256=3d16f6a6a3409f2cae5dbdb3fb322389fa61a6a18d80a2df50fc4c807f1f0fd9
cruise (0.2.0-x86_64-linux-musl) sha256=2be5c5f2fb474ff5a4f42e3421dbb27fb439008a53697f7a6a3d2f3ec2de711c
csv (3.3.6) sha256=aba61e7e507a66f03d45cb1f3c4b6359861c3504038b422962875dce099e4456
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6
dotenv (3.2.0) sha256=e375b83121ea7ca4ce20f214740076129ab8514cd81378161f11c03853fe619d
Expand Down
42 changes: 42 additions & 0 deletions app/controllers/admin/csv_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@

require 'csv'

Check failure on line 2 in app/controllers/admin/csv_controller.rb

View workflow job for this annotation

GitHub Actions / lint

Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

class CSVHeadersError < StandardError; end
class StudentBulkImportError < StandardError; end
class ClassroomBulkImportError < StandardError; end

class Admin::CsvController < ApplicationController
skip_forgery_protection only: :import
CSV_HEADERS = ["Student First Name", "Student Last Name", "Grade Level", "Class Name", "Teacher", "Program", "Program Level"].freeze

Check failure on line 10 in app/controllers/admin/csv_controller.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.

Check failure on line 10 in app/controllers/admin/csv_controller.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.

def download
csv_data = CSV.generate do |csv|
csv << CSV_HEADERS
end

send_data csv_data,
filename: "students-#{Date.today}.csv",
type: "text/csv; charset=utf-8",
disposition: "attachment"
end

def import
csv_file = params[:file]
if csv_file.present?
csv = CSV.read(csv_file.path, headers: true)
#check if headers are equal to CSV_HEADERS and return with Headers must match CSV headers error if not

Check failure on line 27 in app/controllers/admin/csv_controller.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/LeadingCommentSpace: Missing space after `#`.
raise CSVHeadersError, "Headers must match CSV headers" unless CSV_HEADERS == csv.headers

#iterate through each row and validate that each row has the same number of columns as the headers

Check failure on line 30 in app/controllers/admin/csv_controller.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/LeadingCommentSpace: Missing space after `#`.
#if not, return with Row must have same number of columns as headers error

Check failure on line 31 in app/controllers/admin/csv_controller.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/LeadingCommentSpace: Missing space after `#`.
puts "Checking CSV Header validity"
csv.each do |row|
raise CSVHeadersError, "Row must have same number of columns as headers" unless CSV_HEADERS.length == row.length
raise StudentBulkImportError, "Student already exists" if Student.find_by(first_name: row["Student First Name"], last_name: row["Student Last Name"])
raise ClassroomBulkImportError, "Classroom: #{row["Class Name"]} already exists" if Classroom.find_by(name: row["Class Name"])
end
# pass file to importer
StudentCsvImporter.new(csv: csv, school_id: 1).import
end
end
end

Check failure on line 42 in app/controllers/admin/csv_controller.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/TrailingEmptyLines: Final newline missing.
106 changes: 106 additions & 0 deletions app/services/student_csv_importer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@


class StudentCsvImporter
class InvalidClassroomError < ActiveRecord::Rollback; end
class InvalidStudentError < ActiveRecord::Rollback; end
class InvalidTeacherError < ActiveRecord::Rollback; end

def initialize(csv:, school_id:)
@csv = csv
@school_id = school_id
@error_messages = { students: {}, classrooms: {}, teachers: {} }
@students = []
@classrooms = []
@teachers = []
end


def import
puts "Importing classrooms, teachers, and students"


@csv.each_with_index do |row, index|
next if row.blank?

classroom = Classroom.new(
school_id: @school_id,
name: row['Class Name'],

Check failure on line 27 in app/services/student_csv_importer.rb

View workflow job for this annotation

GitHub Actions / lint

Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
)

@error_messages[:classrooms][index] = classroom.errors.full_messages if classroom.invalid?
@classrooms << classroom


Check failure on line 33 in app/services/student_csv_importer.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/TrailingWhitespace: Trailing whitespace detected.
# Student must be associated with a Classroom before checking validity of Student records
student = Student.new(
first_name: row['Student First Name'],

Check failure on line 36 in app/services/student_csv_importer.rb

View workflow job for this annotation

GitHub Actions / lint

Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
last_name: row['Student Last Name'],
grade_level: row['Grade Level'],
school_id: @school_id,
)

student.classroom = classroom

@students << student

teacher = Teacher.new(
name: row['Teacher'],
school_id: @school_id,
)

@error_messages[:teachers][index] = teacher.errors.full_messages if teacher.invalid?
teacher.classrooms << classroom
@teachers << teacher
end

save_records


end

def save_records
ActiveRecord::Base.transaction do
if @error_messages[:classrooms].empty?
@classrooms.each(&:save!)
else
raise InvalidClassroomError.new(error_messages[:classrooms])
end

if @error_messages[:teachers].empty?
@teachers.each(&:save!)
else
raise InvalidTeacherError, @error_messages[:teachers]
end

@students.each_with_index { |student, index| @error_messages[:students][index] = student.errors.full_messages if student.invalid? }

puts "CSV Passed Validations... Creating School Records"

if @error_messages[:students].empty?
create_school_records
else
raise InvalidStudentError, error_messages[:students]
end

end
end

def create_school_records
#school_id should be passed in from the url params
#Iterate CSV rows
@csv.each do |row|
#Extract and apply teacher column when creating classroom
teacher = Teacher.find_by!(school_id: @school_id, name: row['Teacher'])

classroom = Classroom.find_by!(school_id: @school_id, teacher_id: teacher.id, name: row['Class Name'])
puts "Found Classroom: #{classroom.name}"

program = Program.create_or_find_by!(name: row['Program'])
classroom.classroom_programs.create_or_find_by!(program: program, level: row['Program Level'])
puts "Created Program: #{classroom.programs.first.name}"
#Extract and apply program level column when creating classroom
classroom.students.create_or_find_by!(first_name: row['Student First Name'], last_name: row['Student Last Name'], grade_level: row['Grade Level'], school_id: @school_id)
puts "Created Student: #{classroom.students.first.first_name}"
end
end
end
6 changes: 6 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
resources :links, shallow: true, except: %i[index show]
end
resources :classroom_modules, only: %i[update]

end

namespace :admin do
get :csv_template, to: "csv#download"
post :csv_import, to: "csv#import"
end
root to: "schools#index"

Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.