app/models/user_import_file.rb
class UserImportFile < ApplicationRecord
include Statesman::Adapters::ActiveRecordQueries[
transition_class: UserImportFileTransition,
initial_state: UserImportFileStateMachine.initial_state
]
include ImportFile
default_scope {order('user_import_files.id DESC')}
scope :not_imported, -> { in_state(:pending) }
scope :stucked, -> { in_state(:pending).where('user_import_files.created_at < ?', 1.hour.ago) }
has_one_attached :attachment
belongs_to :user
belongs_to :default_user_group, class_name: 'UserGroup'
belongs_to :default_library, class_name: 'Library'
has_many :user_import_results, dependent: :destroy
has_many :user_import_file_transitions, autosave: false, dependent: :destroy
attr_accessor :mode
def state_machine
UserImportFileStateMachine.new(self, transition_class: UserImportFileTransition)
end
delegate :can_transition_to?, :transition_to!, :transition_to, :current_state,
to: :state_machine
# 利用者情報をTSVファイルを用いて作成します。
def import
transition_to!(:started)
num = { user_imported: 0, user_found: 0, failed: 0, error: 0 }
rows = open_import_file(create_import_temp_file(attachment))
row_num = 1
field = rows.first
if [field['username']].reject{ |f| f.to_s.strip == "" }.empty?
raise "username column is not found"
end
rows.each do |row|
row_num += 1
import_result = UserImportResult.create!(
user_import_file_id: id, body: row.fields.join("\t")
)
next if row['dummy'].to_s.strip.present?
username = row['username']
new_user = User.find_by(username: username)
if new_user
import_result.user = new_user
import_result.save
num[:user_found] += 1
else
new_user = User.new
new_user.role = Role.find_by(name: row['role'])
if new_user.role
unless user.has_role?(new_user.role.name)
num[:failed] += 1
next
end
else
new_user.role = Role.find(2) # User
end
new_user.username = username
new_user.assign_attributes(set_user_params(row))
if row['password'].to_s.strip.present?
new_user.password = row['password']
else
new_user.password = Devise.friendly_token[0..7]
end
profile = Profile.new
profile.assign_attributes(set_profile_params(row))
Profile.transaction do
if new_user.valid? && profile.valid?
new_user.profile = profile
import_result.user = new_user
import_result.save!
num[:user_imported] += 1
else
error_message = "line #{row_num}: "
error_message += new_user.errors.map(&:full_message).join(' ')
error_message += profile.errors.map(&:full_message).join(' ')
import_result.error_message = error_message
import_result.save
num[:error] += 1
end
end
end
end
Sunspot.commit
rows.close
error_messages = user_import_results.order(:id).pluck(:error_message).compact
unless error_messages.empty?
self.error_message = '' if error_message.nil?
self.error_message += "\n"
self.error_message += error_messages.join("\n")
end
save
if num[:error] >= 1
transition_to!(:failed)
else
transition_to!(:completed)
end
mailer = UserImportMailer.completed(self)
send_message(mailer)
num
rescue StandardError => e
transition_to!(:failed)
mailer = UserImportMailer.failed(self)
send_message(mailer)
raise e
end
# 利用者情報をTSVファイルを用いて更新します。
def modify
transition_to!(:started)
num = { user_updated: 0, user_not_found: 0, failed: 0 }
rows = open_import_file(create_import_temp_file(attachment))
row_num = 1
field = rows.first
if [field['username']].reject{|f| f.to_s.strip == ""}.empty?
raise "username column is not found"
end
rows.each do |row|
row_num += 1
next if row['dummy'].to_s.strip.present?
import_result = UserImportResult.create!(
user_import_file_id: id, body: row.fields.join("\t")
)
username = row['username']
new_user = User.find_by(username: username)
if new_user.try(:profile)
new_user.assign_attributes(set_user_params(row))
new_user.password = row['password'] if row['password'].to_s.strip.present?
new_user.profile.assign_attributes(set_profile_params(row))
Profile.transaction do
if new_user.save && new_user.profile.save
num[:user_updated] += 1
import_result.user = new_user
import_result.save!
else
num[:failed] += 1
end
end
else
num[:user_not_found] += 1
end
end
rows.close
Sunspot.commit
transition_to!(:completed)
mailer = UserImportMailer.completed(self)
send_message(mailer)
num
rescue StandardError => e
self.error_message = "line #{row_num}: #{e.message}"
save
transition_to!(:failed)
mailer = UserImportMailer.failed(self)
send_message(mailer)
raise e
end
# 利用者情報をTSVファイルを用いて削除します。
def remove
transition_to!(:started)
row_num = 1
rows = open_import_file(create_import_temp_file(attachment))
field = rows.first
if [field['username']].reject{ |f| f.to_s.strip == "" }.empty?
raise "username column is not found"
end
rows.each do |row|
row_num += 1
username = row['username'].to_s.strip
remove_user = User.find_by(username: username)
next unless remove_user.try(:deletable_by?, user)
UserImportFile.transaction do
remove_user.destroy
remove_user.profile.destroy
end
end
transition_to!(:completed)
mailer = UserImportMailer.completed(self)
send_message(mailer)
rescue StandardError => e
self.error_message = "line #{row_num}: #{e.message}"
save
transition_to!(:failed)
mailer = UserImportMailer.failed(self)
send_message(mailer)
raise e
end
private
# インポート作業用のファイルを読み込みます。
# @param [File] tempfile 作業用のファイル
def open_import_file(tempfile)
file = CSV.open(tempfile.path, 'r:utf-8', col_sep: "\t")
header_columns = %w(
username role email password user_group user_number expired_at
full_name full_name_transcription required_role locked
keyword_list note locale library dummy
)
if defined?(EnjuCirculation)
header_columns += %w(checkout_icalendar_token save_checkout_history)
end
if defined?(EnjuSearchLog)
header_columns += %w(save_search_history)
end
if defined?(EnjuBookmark)
header_columns += %w(share_bookmarks)
end
header = file.first
ignored_columns = header - header_columns
unless ignored_columns.empty?
self.error_message = I18n.t('import.following_column_were_ignored', column: ignored_columns.join(', '))
save!
end
rows = CSV.open(tempfile.path, 'r:utf-8', headers: header, col_sep: "\t")
UserImportResult.create!(user_import_file_id: id, body: header.join("\t"))
tempfile.close(true)
file.close
rows
end
# 未処理のインポート作業用のファイルを一括で処理します。
def self.import
UserImportFile.not_imported.each do |file|
file.import_start
end
rescue StandardError
Rails.logger.info "#{Time.zone.now} importing resources failed!"
end
private
# ユーザ情報のパラメータを設定します。
# @param [Hash] row 利用者情報のハッシュ
def set_user_params(row)
params = {}
params[:email] = row['email'] if row['email'].present?
if %w(t true).include?(row['locked'].to_s.downcase.strip)
params[:locked] = '1'
end
params
end
# 利用者情報のパラメータを設定します。
# @param [Hash] row 利用者情報のハッシュ
def set_profile_params(row)
params = {}
user_group = UserGroup.find_by(name: row['user_group']) || default_user_group
params[:user_group_id] = user_group.id if user_group
required_role = Role.find_by(name: row['required_role']) || Role.find_by(name: 'Librarian')
params[:required_role_id] = required_role.id if required_role
params[:user_number] = row['user_number'] if row['user_number']
params[:full_name] = row['full_name'] if row['full_name']
params[:full_name_transcription] = row['full_name_transcription'] if row['full_name_transcription']
if row['expired_at'].present?
params[:expired_at] = Time.zone.parse(row['expired_at']).end_of_day
end
if row['keyword_list'].present?
params[:keyword_list] = row['keyword_list'].split('//').join("\n")
end
params[:note] = row['note'] if row['note']
if I18n.available_locales.include?(row['locale'].to_s.to_sym)
params[:locale] = row['locale']
end
library = Library.find_by(name: row['library'].to_s.strip) || default_library || Library.web
params[:library_id] = library.id if library
if defined?(EnjuCirculation)
params[:checkout_icalendar_token] = row['checkout_icalendar_token'] if row['checkout_icalendar_token'].present?
params[:save_checkout_history] = row['save_checkout_history'] if row['save_checkout_history'].present?
end
if defined?(EnjuSearchLog)
params[:save_search_history] = row['save_search_history'] if row['save_search_history'].present?
end
if defined?(EnjuBookmark)
params[:share_bookmarks] = row['share_bookmarks'] if row['share_bookmarks'].present?
end
params
end
end
# == Schema Information
#
# Table name: user_import_files
#
# id :bigint not null, primary key
# user_id :bigint
# note :text
# executed_at :datetime
# user_import_fingerprint :string
# edit_mode :string
# error_message :text
# created_at :datetime not null
# updated_at :datetime not null
# user_encoding :string
# default_library_id :bigint
# default_user_group_id :bigint
#