gaku_imex 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +31 -0
  5. data/Gemfile +21 -0
  6. data/LICENSE +674 -0
  7. data/README.md +29 -0
  8. data/Rakefile +27 -0
  9. data/app/controllers/gaku/admin/import_files_controller.rb +74 -0
  10. data/app/controllers/gaku/students/reports_controller.rb +19 -0
  11. data/app/models/gaku/import_file.rb +23 -0
  12. data/app/models/gaku/student_injector.rb +32 -0
  13. data/app/overrides/import_menu.rb +6 -0
  14. data/app/overrides/reports.rb +6 -0
  15. data/app/reports/student_record_personal_info.en.tlf +1 -0
  16. data/app/reports/student_record_personal_info.ja.tlf +1 -0
  17. data/app/views/gaku/admin/import_files/_check_modal.html.slim +10 -0
  18. data/app/views/gaku/admin/import_files/_created_students.html.slim +8 -0
  19. data/app/views/gaku/admin/import_files/_duplicated_students.html.slim +8 -0
  20. data/app/views/gaku/admin/import_files/_form.html.slim +3 -0
  21. data/app/views/gaku/admin/import_files/_form_fields.html.slim +4 -0
  22. data/app/views/gaku/admin/import_files/_import_file.html.slim +2 -0
  23. data/app/views/gaku/admin/import_files/_import_files.html.slim +24 -0
  24. data/app/views/gaku/admin/import_files/_not_saved_student.html.slim +3 -0
  25. data/app/views/gaku/admin/import_files/_not_saved_students.html.slim +8 -0
  26. data/app/views/gaku/admin/import_files/_student.html.slim +4 -0
  27. data/app/views/gaku/admin/import_files/_table_fields.html.slim +8 -0
  28. data/app/views/gaku/admin/import_files/_tabs.html.slim +20 -0
  29. data/app/views/gaku/admin/import_files/check.js.erb +2 -0
  30. data/app/views/gaku/admin/import_files/create.js.erb +7 -0
  31. data/app/views/gaku/admin/import_files/destroy.js.erb +2 -0
  32. data/app/views/gaku/admin/import_files/import.js.erb +1 -0
  33. data/app/views/gaku/admin/import_files/index.js.erb +1 -0
  34. data/app/views/gaku/admin/import_files/new.js.erb +3 -0
  35. data/app/views/gaku/shared/menu/_imex_menu.html.erb +9 -0
  36. data/app/views/gaku/students/reports/_report_link.html.slim +7 -0
  37. data/app/views/gaku/students/reports/index.en.pdf.thinreports +15 -0
  38. data/app/views/gaku/students/reports/index.ja.pdf.thinreports +15 -0
  39. data/app/workers/gaku/importers/students_worker.rb +14 -0
  40. data/config/initializers/sidekiq.rb +6 -0
  41. data/config/routes.rb +17 -0
  42. data/db/migrate/20131011100726_create_gaku_import_files.rb +14 -0
  43. data/gaku_imex.gemspec +32 -0
  44. data/lib/gaku/imex.rb +13 -0
  45. data/lib/gaku/imex/engine.rb +21 -0
  46. data/lib/gaku/importers/students/csv.rb +58 -0
  47. data/lib/gaku/importers/students/student_identity.rb +15 -0
  48. data/lib/gaku_imex.rb +2 -0
  49. data/lib/generators/gaku_imex/install/install_generator.rb +77 -0
  50. data/lib/generators/gaku_imex/install/templates/Procfile +2 -0
  51. data/lib/generators/gaku_imex/install/templates/config/sidekiq.yml +8 -0
  52. data/lib/generators/gaku_imex/install/templates/log/sidekiq.log +0 -0
  53. data/spec/features/import_file_spec.rb +115 -0
  54. data/spec/lib/gaku/importers/students/csv_spec.rb +84 -0
  55. data/spec/models/import_file_spec.rb +32 -0
  56. data/spec/models/student_spec.rb +17 -0
  57. data/spec/spec_helper.rb +14 -0
  58. data/spec/support/factories.rb +12 -0
  59. data/spec/support/foreign_system_students.csv +3 -0
  60. data/spec/support/format_error_students.csv +3 -0
  61. data/spec/support/new_students.csv +2 -0
  62. data/spec/support/students.csv +1006 -0
  63. data/spec/workers/students_worker_spec.rb +9 -0
  64. metadata +216 -0
@@ -0,0 +1,6 @@
1
+ require 'sidekiq'
2
+ #Sidekiq.configure_server do |config|
3
+ # config.redis = { url: 'redis://redistogo:23997367276d9d8e473756154c3da248@spadefish.redistogo.com:9679/'}
4
+ #end
5
+
6
+ ENV['REDISTOGO_URL'] = 'redis://redistogo:23997367276d9d8e473756154c3da248@spadefish.redistogo.com:9679/'
@@ -0,0 +1,17 @@
1
+ #Gaku::Core::Engine.routes.prepend do
2
+ Gaku::Core::Engine.routes.draw do
3
+
4
+ namespace :admin do
5
+ resources :import_files do
6
+ member do
7
+ get :import
8
+ get :check
9
+ end
10
+ end
11
+ end
12
+
13
+ resources :students do
14
+ resources :reports, only: :index, controller: 'students/reports'
15
+ end
16
+
17
+ end
@@ -0,0 +1,14 @@
1
+ class CreateGakuImportFiles < ActiveRecord::Migration
2
+ def change
3
+
4
+ create_table :gaku_import_files do |t|
5
+ t.string 'context'
6
+ t.string 'importer_type'
7
+ t.string 'data_file_file_name'
8
+ t.string 'data_file_content_type'
9
+ t.integer 'data_file_file_size'
10
+ t.datetime 'data_file_updated_at'
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: UTF-8
2
+
3
+ Gem::Specification.new do |s|
4
+ s.platform = Gem::Platform::RUBY
5
+ s.name = 'gaku_imex'
6
+ s.version = '0.2.2'
7
+ s.summary = 'Importers and Exporters for GAKU Engine'
8
+ s.description = "Importers and Exporters for GAKU Engine"
9
+ s.required_ruby_version = '>= 2.1.3'
10
+
11
+ s.authors = ['Rei Kagetsuki', 'Vassil Kalkov']
12
+ s.email = 'info@genshin.org'
13
+ s.homepage = 'http://github.com/GAKUEngine/gaku_imex'
14
+
15
+ s.files = `git ls-files`.split($RS)
16
+ s.test_files = s.files.grep(/^spec\//)
17
+ s.require_path = 'lib'
18
+
19
+ s.requirements << 'postgres'
20
+ s.requirements << 'redis'
21
+
22
+ s.add_dependency 'gaku_core', '~> 0.2.2'
23
+ s.add_dependency 'gaku_testing', '~> 0.2.2'
24
+ s.add_dependency 'gaku_admin', '~> 0.2.2'
25
+
26
+ s.add_dependency 'sidekiq'
27
+ s.add_dependency 'sinatra'
28
+
29
+ s.add_dependency 'smarter_csv'
30
+ s.add_dependency 'thinreports-rails'
31
+
32
+ end
@@ -0,0 +1,13 @@
1
+ require 'gaku/core'
2
+ require 'gaku/admin'
3
+ require 'sidekiq/web'
4
+ require 'smarter_csv'
5
+
6
+ module Gaku
7
+
8
+ module Imex
9
+ end
10
+
11
+ end
12
+
13
+ require 'gaku/imex/engine'
@@ -0,0 +1,21 @@
1
+ module Gaku
2
+ module Imex
3
+ class Engine < Rails::Engine
4
+ engine_name 'gaku_imex'
5
+
6
+ config.autoload_paths += %W(#{config.root}/lib)
7
+
8
+ def self.activate
9
+ Dir.glob(File.join(File.dirname(__FILE__), '../../../app/**/*_injector*.rb')) do |c|
10
+ Rails.configuration.cache_classes ? require(c) : load(c)
11
+ end
12
+ end
13
+
14
+ config.to_prepare &method(:activate).to_proc
15
+
16
+ config.after_initialize do
17
+ Rails.application.routes_reloader.reload!
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,58 @@
1
+ module Gaku::Importers::Students
2
+ class Csv
3
+ include Gaku::Importers::Students::StudentIdentity
4
+
5
+ attr_reader :csv, :import_file_id
6
+
7
+ def initialize(import_file_id, path)
8
+ @import_file_id = import_file_id
9
+ @csv = SmarterCSV.process(path)
10
+ end
11
+
12
+ def import
13
+ cleanup_student_states
14
+
15
+ enrollment_status_code = Gaku::EnrollmentStatus.where(code: 'enrolled', active: true, immutable: true)
16
+ .first_or_create!.try(:code)
17
+
18
+ ActiveRecord::Base.transaction do
19
+ @csv.each do |row|
20
+
21
+ next if student_record(row)
22
+
23
+ student = Gaku::Student.new(filter_row(row))
24
+ student.enrollment_status_code = enrollment_status_code
25
+
26
+ if student.save
27
+ $redis.rpush "import_file:#{@import_file_id}:created", student.id
28
+ else
29
+ $redis.rpush "import_file:#{@import_file_id}:not_saved", not_saved_students(student)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def student_record(row)
38
+ Gaku::Student.where(foreign_id_code: row[:foreign_id_code].to_s).first.try(:tap) do |student|
39
+ $redis.rpush "import_file:#{@import_file_id}:duplicated", student.id
40
+ end
41
+ end
42
+
43
+ def filter_row(row)
44
+ row.select { |k, v| Gaku::Student.csv_column_fields.include?(k.to_s) }
45
+ end
46
+
47
+ def not_saved_students(student)
48
+ "#{student.foreign_id_code}, #{student.name}, #{student.surname}"
49
+ end
50
+
51
+ def cleanup_student_states
52
+ %w( created duplicated not_saved ).each do |state|
53
+ $redis.del "import_file:#{@import_file_id}:#{state}"
54
+ end
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,15 @@
1
+ module Gaku::Importers::Students::StudentIdentity
2
+ def normalize_id_num(id_number)
3
+ if id_number.to_i == 0 # ID is alphanumeric
4
+ return id_number.to_s
5
+ else # ID number is a number, defaulted to float from sheet data
6
+ return id_number.to_i.to_s
7
+ end
8
+ end
9
+
10
+ def find_student_by_student_ids(serial_id, foreign_id_code = nil)
11
+ student = Gaku::Student.where(serial_id: normalize_id_num(student_id_number)).first
12
+ return student unless student.nil?
13
+ Gaku::Student.where(foreign_id_code: normalize_id_num(foreign_id_code)).first
14
+ end
15
+ end
@@ -0,0 +1,2 @@
1
+ require 'gaku/imex'
2
+ require 'thinreports-rails'
@@ -0,0 +1,77 @@
1
+ require 'rails/generators'
2
+ require 'highline/import'
3
+ require 'bundler'
4
+ require 'bundler/cli'
5
+
6
+ module GakuImex
7
+ class InstallGenerator < Rails::Generators::Base
8
+
9
+ class_option :auto_accept, type: :boolean
10
+ class_option :lib_name, type: :string, default: 'gaku_imex'
11
+ class_option :env, type: :string, default: 'development'
12
+
13
+ def self.source_paths
14
+ paths = superclass.source_paths
15
+ paths << File.expand_path('../templates', "../../#{__FILE__}")
16
+ paths << File.expand_path('../templates', "../#{__FILE__}")
17
+ paths << File.expand_path('../templates', __FILE__)
18
+ paths.flatten
19
+ end
20
+
21
+ def prepare_options
22
+ @env = options[:env]
23
+ end
24
+
25
+ def clear_logs
26
+ remove_file 'log/sidekiq.log'
27
+ add_file 'log/sidekiq.log'
28
+ end
29
+
30
+ def add_files
31
+ template 'config/sidekiq.yml', 'config/sidekiq.yml'
32
+ template 'Procfile', 'Procfile'
33
+ end
34
+
35
+ def add_migrations
36
+ run 'rake railties:install:migrations FROM=gaku_imex'
37
+ end
38
+
39
+ def run_migrations
40
+ run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask 'Would you like to run the migrations now? [Y/n]')
41
+ if run_migrations
42
+ run 'rake db:migrate'
43
+ else
44
+ puts "Skiping rake db:migrate, don't forget to run it!"
45
+ end
46
+ end
47
+
48
+ def notify_about_routes
49
+ insert_into_file File.join('config', 'routes.rb'), after: "Application.routes.draw do\n" do
50
+ %Q{
51
+ # This line mounts GakuImex routes
52
+
53
+ require 'sidekiq/web'
54
+ mount Sidekiq::Web => '/sidekiq'
55
+
56
+ }
57
+ end
58
+
59
+ unless options[:quiet]
60
+ puts '*' * 50
61
+ puts "We added the following line to your application's config/routes.rb file:"
62
+ puts ' '
63
+ puts " mount Sidekiq::Web => '/sidekiq'"
64
+ end
65
+ end
66
+
67
+ def complete
68
+ unless options[:quiet]
69
+ puts '*' * 50
70
+ puts "GakuImex has been installed successfully. You're all ready to go!"
71
+ puts ' '
72
+ puts 'Enjoy!'
73
+ end
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,2 @@
1
+ web: bundle exec rails s -p $PORT
2
+ worker: bundle exec sidekiq -C config/sidekiq.yml
@@ -0,0 +1,8 @@
1
+ # Sample configuration file for Sidekiq.
2
+ # Options here can still be overridden by cmd line args.
3
+ # sidekiq -C config.yml
4
+ ---
5
+ :verbose: false
6
+ :pidfile: ./tmp/pids/sidekiq.pid
7
+ :logfile: ./log/sidekiq.log
8
+ :concurrency: 25
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Admin ImportFile' do
4
+
5
+ before { as :admin }
6
+ before(:all) { set_resource 'admin-import-file' }
7
+
8
+ let(:import_file) { create(:import_file) }
9
+
10
+ let(:student) { create(:student) }
11
+
12
+ context 'new', js: true do
13
+ before do
14
+ visit gaku.admin_root_path
15
+ click_link 'Importer'
16
+ click new_link
17
+ end
18
+
19
+ it 'creates and shows' do
20
+ expect do
21
+ absolute_path = Rails.root + '../support/new_students.csv'
22
+ attach_file 'import_file_data_file', absolute_path
23
+ select 'students', from: 'import_file_importer_type'
24
+ click submit
25
+ flash_created?
26
+ end.to change(Gaku::ImportFile, :count).by 1
27
+
28
+ has_content? 'students'
29
+ has_content? 'Import files list(1)'
30
+ end
31
+
32
+ it { has_validations? }
33
+ end
34
+
35
+ context 'importing', js: true do
36
+ before do
37
+ end
38
+
39
+ it 'create student' do
40
+ student
41
+ import_file
42
+ $redis.rpush "import_file:#{import_file.id}:created", student.id
43
+ visit gaku.admin_root_path
44
+ click_link 'Importer'
45
+ click new_link
46
+
47
+ click '.check-link'
48
+ click '#created-students-tab-link'
49
+ has_content? student.name
50
+ has_content? student.surname
51
+ expect(page.all('#created-students-index tbody tr').count).to eq 1
52
+ end
53
+
54
+ it 'duplicate student' do
55
+ student
56
+ import_file
57
+ $redis.rpush "import_file:#{import_file.id}:duplicated", student.id
58
+ visit gaku.admin_root_path
59
+ click_link 'Importer'
60
+ click new_link
61
+
62
+ click '.check-link'
63
+ click '#duplicated-students-tab-link'
64
+ has_content? student.name
65
+ has_content? student.surname
66
+ expect(page.all('#duplicated-students-index tbody tr').count).to eq 1
67
+ end
68
+
69
+ it 'not saved student' do
70
+ import_file
71
+ $redis.rpush "import_file:#{import_file.id}:not_saved", "10052014, John, Doe"
72
+ visit gaku.admin_root_path
73
+ click_link 'Importer'
74
+ click new_link
75
+
76
+ click '.check-link'
77
+ click '#not-saved-students-tab-link'
78
+ has_content? '10052014'
79
+ has_content? 'John'
80
+ has_content? 'Doe'
81
+ expect(page.all('#not-saved-students-index tbody tr').count).to eq 1
82
+ end
83
+
84
+
85
+ end
86
+
87
+
88
+ context 'existing', js: true do
89
+
90
+ before do
91
+ import_file
92
+ visit gaku.admin_root_path
93
+ click_link 'Importer'
94
+ end
95
+
96
+ it 'import' do
97
+ click '.import-link'
98
+ expect(page).to have_content('Importing for students started!')
99
+
100
+ end
101
+
102
+ it 'deletes' do
103
+ has_content? import_file.data_file_file_name
104
+ count? 'Import files list(1)'
105
+
106
+ expect do
107
+ ensure_delete_is_working
108
+ flash_destroyed?
109
+ end.to change(Gaku::ImportFile, :count).by(-1)
110
+
111
+ has_no_content? import_file.data_file_file_name
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ describe Gaku::Importers::Students::Csv do
4
+
5
+ let!(:import_file) { create(:import_file) }
6
+
7
+ describe 'initialize' do
8
+ it 'initializes' do
9
+ expect(described_class.new(import_file.id, 'spec/support/new_students.csv').csv)
10
+ .to eq [{:national_registration_code=>999,
11
+ :name=>"零紀",
12
+ :middle_name=>"Flux",
13
+ :surname=>"影月",
14
+ :name_reading=>"レイキ",
15
+ :middle_name_reading=>"フラックス",
16
+ :surname_reading=>"カゲツキ",
17
+ :gender=>1,
18
+ :birth_date=>"2006-04-19",
19
+ :enrollment_status_code=>"enrolled"
20
+ }]
21
+ end
22
+ end
23
+
24
+ describe '#import' do
25
+ subject { described_class.new(import_file.id, 'spec/support/new_students.csv').import }
26
+
27
+ it 'creates new student' do
28
+ expect do
29
+ subject
30
+ end.to change(Gaku::Student, :count).by(1)
31
+ end
32
+
33
+ it 'saves all attributes' do
34
+ subject
35
+
36
+ created_student = Gaku::Student.last
37
+ expect(created_student.name).to eq '零紀'
38
+ expect(created_student.surname).to eq '影月'
39
+ expect(created_student.name_reading).to eq 'レイキ'
40
+ expect(created_student.surname_reading).to eq 'カゲツキ'
41
+ expect(created_student.gender).to eq true
42
+ expect(created_student.birth_date.to_s).to eq '2006-04-19'
43
+ end
44
+
45
+ it 'sets enrollment status' do
46
+ subject
47
+
48
+ created_student = Gaku::Student.last
49
+ expect(created_student.enrollment_status.code).to eq 'enrolled'
50
+ end
51
+
52
+ it 'returns an array of created students' do
53
+ students = subject
54
+ created_student = Gaku::Student.last
55
+ expect($redis.lrange "import_file:#{import_file.id}:created", 0, -1).to eq [created_student.id.to_s]
56
+ expect($redis.llen "import_file:#{import_file.id}:created").to eq 1
57
+ expect($redis.llen "import_file:#{import_file.id}:not_saved").to eq 0
58
+ end
59
+
60
+ xit 'ignores records with ID set to prevent tainting the DB' do
61
+ students = described_class.new('spec/support/students.csv').import
62
+ expect(students[:created].count).to eq 0
63
+ end
64
+
65
+ it 'imports students from a foreign system' do
66
+ described_class.new(import_file.id, 'spec/support/foreign_system_students.csv').import
67
+ expect($redis.llen "import_file:#{import_file.id}:created").to eq 2
68
+ end
69
+
70
+ it 'checks foreign_id_code and does not overwrite or re-create existing records' do
71
+ create(:student, foreign_id_code: 55567)
72
+ create(:student, foreign_id_code: 55568)
73
+
74
+ described_class.new(import_file.id, 'spec/support/foreign_system_students.csv').import
75
+ expect($redis.llen "import_file:#{import_file.id}:created").to eq 0
76
+ expect($redis.llen "import_file:#{import_file.id}:duplicated").to eq 2
77
+ end
78
+
79
+ it 'returns an array of students with errors' do
80
+ described_class.new(import_file.id, 'spec/support/format_error_students.csv').import
81
+ expect($redis.llen "import_file:#{import_file.id}:not_saved").to eq 2
82
+ end
83
+ end
84
+ end