-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into 69-create-copy-paste-info-method
- Loading branch information
Showing
8 changed files
with
307 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
# frozen_string_literal: true | ||
|
||
class ChapterImporterService | ||
attr_reader :book, :chapter_number, :pastel, :prompt, :chapter | ||
|
||
def initialize(book, chapter_number) | ||
@book = book | ||
@chapter_number = chapter_number | ||
@pastel = Pastel.new | ||
@prompt = TTY::Prompt.new | ||
end | ||
|
||
def call | ||
ensure_chapter_exists! | ||
ActiveRecord::Base.transaction do | ||
each_csv_row do |row| | ||
process_row(row) | ||
end | ||
end | ||
end | ||
|
||
private | ||
|
||
def each_csv_row(&) | ||
blob = chapter.csv_rows | ||
blob.each(&) | ||
end | ||
|
||
def ensure_chapter_exists! | ||
@chapter = book.chapters.find_by(:number => chapter_number) | ||
raise "Chapter not found: #{chapter_number}" unless @chapter | ||
end | ||
|
||
def process_row(row) | ||
validate_and_update_chapter_title(row) | ||
process_pauri(row) | ||
process_tuk(row) | ||
create_tuk_translation(row) | ||
create_pauri_translation(row) | ||
end | ||
|
||
def validate_and_update_chapter_title(row) | ||
chapter_name = row['Chapter_Name'].try(:strip) | ||
return if chapter.title == chapter_name | ||
|
||
message = "The name in Book #{book.sequence}, Chapter #{chapter_number} is " + | ||
pastel.bold('presently') + | ||
" '#{chapter.title}'. The CSV says '#{chapter_name}'." | ||
prompt.say(message, :color => :yellow) | ||
answer = prompt.yes?("Do you want to continue and update this title to '#{chapter_name}'?") | ||
raise 'Aborted by user' unless answer | ||
|
||
chapter.update(:title => chapter_name) | ||
Rails.logger.debug pastel.green("✓ Chapter #{chapter_number}'s title updated to '#{chapter_name}'") | ||
end | ||
|
||
def process_pauri(row) | ||
pauri_number = row['Pauri_Number'].to_i | ||
@pauri = chapter.pauris.find_by(:number => pauri_number) | ||
|
||
raise "Pauri not found: #{pauri_number}" if @pauri.nil? | ||
end | ||
|
||
def process_tuk(row) | ||
tuk = row['Tuk'].try(:strip) | ||
tuk_number = row['Tuk_Number'].to_i | ||
@tuk = @pauri.tuks.find_by(:sequence => tuk_number) | ||
|
||
raise "Tuk #{tuk_number} not found: #{tuk}" if @tuk.nil? | ||
|
||
return if @tuk.original_content == tuk | ||
|
||
diff = Diffy::Diff.new(@tuk.original_content, tuk, :include_diff_info => true).to_s(:color) | ||
Rails.logger.debug diff | ||
|
||
choices = [ | ||
"Keep the NEW one from the CSV (update the original) :: #{tuk}", | ||
pastel.red("Keep the original :: #{@tuk.original_content}") | ||
] | ||
|
||
selected_choice = prompt.select('Choose an option:', choices) | ||
case selected_choice | ||
when choices[0] | ||
@tuk.update(:original_content => tuk) | ||
Rails.logger.debug pastel.green("✓ `Tuk` `original_content` updated to #{tuk} - For #{@pauri.number}.#{tuk_number}") | ||
when choices[1] | ||
Rails.logger.debug pastel.red("x Keeping the original `Tuk` `original_content` - For #{@pauri.number}.#{tuk_number}") | ||
end | ||
end | ||
|
||
def create_tuk_translation(row) | ||
tuk_translation_en = row['Tuk_Translation_EN'].try(:strip) || row['Translation_EN'].try(:strip) | ||
return if tuk_translation_en.blank? | ||
|
||
# If there's a Pauri translation and a Tuk translation, ask what to do | ||
if @pauri.translation.present? | ||
choices = [ | ||
'Continue with the `TukTranslation` and KEEP the `PauriTranslation`, too!', | ||
'Destroy the existing `PauriTranslation`, and continue with only the `TukTranslation`', | ||
pastel.red('Abort') | ||
] | ||
selected_choice = prompt.select('Choose an option:', choices) | ||
|
||
case selected_choice | ||
when choices[0] | ||
upsert_tuk_translation(tuk_translation_en, row['Assigned_Singh'].try(:strip)) | ||
when choices[1] | ||
upsert_tuk_translation(tuk_translation_en, row['Assigned_Singh'].try(:strip)) | ||
@pauri.translation.destroy | ||
when choices[2] | ||
raise 'Aborted by user' | ||
end | ||
else | ||
upsert_tuk_translation(tuk_translation_en, row['Assigned_Singh'].try(:strip)) | ||
end | ||
end | ||
|
||
def upsert_tuk_translation(translation, translator) | ||
@tuk_translation = @tuk.translation || TukTranslation.new(:tuk_id => @tuk.id) | ||
@tuk_translation.update(:en_translation => translation, :en_translator => translator) | ||
Rails.logger.debug pastel.green("✓ `TukTranslation` created or updated for Tuk #{translation} - Pauri # #{@pauri.number}") | ||
end | ||
|
||
def create_pauri_translation(row) | ||
pauri_translation_en = row['Pauri_Translation_EN'].try(:strip) | ||
return if pauri_translation_en.blank? | ||
|
||
pauri_translation = @pauri.translation || PauriTranslation.new(:pauri_id => @pauri.id) | ||
pauri_translation.update(:en_translation => pauri_translation_en, :en_translator => row['Assigned_Singh'].try(:strip)) | ||
Rails.logger.debug pastel.green("✓ `PauriTranslation` created or updated for Pauri # #{@pauri.number}") | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
# rubocop:disable RSpec/NestedGroups, RSpec/MultipleExpectations | ||
# frozen_string_literal: true | ||
|
||
require 'rails_helper' | ||
require 'csv' | ||
|
||
RSpec.describe Book do | ||
let(:book) { create(:book) } | ||
|
||
describe '#import_chapter' do | ||
context 'when the `chapter.number` is invalid' do | ||
it 'raises an error if the `chapter_number` does not exist in `book`' do | ||
expect { book.import_chapter(99) }.to raise_error(RuntimeError, /Chapter not found: 99/) | ||
end | ||
|
||
it 'raises an error if the CSV does not exist' do | ||
# Create the `Chapter` row only! Not the CSV. | ||
create(:chapter, :book => book, :number => 100) | ||
expect { book.import_chapter(100) }.to raise_error(RuntimeError, %r{CSV file lib/imports/1/100\.csv not found}) | ||
end | ||
end | ||
|
||
context 'when the `chapter_number` is valid' do | ||
csv_content = <<~CSV | ||
Chapter_Number,Chapter_Name,Chhand_Type ,Tuk,Pauri_Number,Tuk_Number,Pauri_Translation_EN,Translation_EN ,Footnotes,Custom_Footnotes,Extended_Ref ,Assigned_Singh,Status,Extended_Meaning | ||
CSV | ||
|
||
csv_rows = CSV.parse(csv_content, :headers => true) | ||
let(:chapter) { create(:chapter, :book => book) } | ||
|
||
it 'does not raise an error' do | ||
file_path = "lib/imports/#{book.sequence}/#{chapter.number}.csv" | ||
|
||
allow(File).to receive(:exist?).with(file_path).and_return(true) | ||
allow(CSV).to receive(:foreach).with(file_path, :headers => true).and_return(csv_rows) | ||
|
||
expect { book.import_chapter(chapter.number) }.not_to raise_error | ||
end | ||
end | ||
|
||
context 'when given valid input and CSV' do | ||
let(:chapter_number) { 99 } | ||
let(:chapter_title) { 'ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ' } | ||
let(:csv_content) do | ||
<<~CSV | ||
Chapter_Number,Chapter_Name,Chhand_Type ,Tuk,Pauri_Number,Tuk_Number,Pauri_Translation_EN,Translation_EN ,Footnotes,Custom_Footnotes,Extended_Ref ,Assigned_Singh,Status,Extended_Meaning | ||
99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,,,,,, | ||
99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,,,,,, | ||
CSV | ||
end | ||
|
||
let(:chapter) { create(:chapter, :number => chapter_number, :book => book, :title => chapter_title) } | ||
let(:chhand_type) { create(:chhand_type, :name => 'ਦੋਹਰਾ') } | ||
let(:chhand) { create(:chhand, :chhand_type => chhand_type, :chapter => chapter) } | ||
let(:pauri) { create(:pauri, :chapter => chapter, :chhand => chhand, :number => 1) } | ||
let(:prompt) { instance_double(TTY::Prompt) } | ||
|
||
before do | ||
# Initialize mocks for TTY::Prompt | ||
allow(TTY::Prompt).to receive(:new).and_return(prompt) | ||
allow(prompt).to receive(:say) | ||
|
||
# Associations for the chapter - This one reflects out mock `csv_content` | ||
create(:tuk, :pauri => pauri, :chapter => chapter, :original_content => 'ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ', :sequence => 1) | ||
create(:tuk, :pauri => pauri, :chapter => chapter, :original_content => 'ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ', :sequence => 2) | ||
|
||
# Mocking the CSV data | ||
allow(book.chapters).to receive(:find_by).with(:number => chapter_number).and_return(chapter) | ||
allow(chapter).to receive(:csv_rows).and_return(CSV.parse(csv_content, :headers => true)) | ||
end | ||
|
||
context 'when prompted to update `chapter.title`' do | ||
it 'does not prompt user if the `chapter.title` is unchanged' do | ||
allow(prompt).to receive(:yes?) | ||
book.import_chapter(chapter_number) | ||
expect(prompt).not_to have_received(:yes?) | ||
|
||
expect(chapter.reload.title).to eq(chapter_title) | ||
end | ||
|
||
it 'updates the `chapter.title` if user confirms' do | ||
# Change the `chapter.title` so it is different than the one in CSV | ||
chapter.update(:title => 'Different title') | ||
|
||
allow(prompt).to receive(:yes?).and_return(true) | ||
book.import_chapter(chapter_number) | ||
|
||
expect(prompt).to have_received(:yes?).with("Do you want to continue and update this title to '#{chapter_title}'?") | ||
expect(chapter.reload.title).to eq(chapter_title) | ||
end | ||
|
||
it 'aborts and does not update the chapter title if user declines' do | ||
# Change the `chapter.title` so it is different than the one in CSV | ||
chapter.update(:title => 'Something Else - Suraj Suraj Suraj') | ||
allow(prompt).to receive(:yes?).and_return(false) | ||
expect { book.import_chapter(chapter_number) }.to raise_error(RuntimeError, 'Aborted by user') | ||
end | ||
end | ||
|
||
context 'when `chapter` associations do not exist' do | ||
it 'raises an error when `pauri` is nil' do | ||
pauri.destroy | ||
expect { book.import_chapter(chapter_number) }.to raise_error(StandardError, /Pauri not found/) | ||
end | ||
|
||
it 'raises an error when `tuks` are nil' do | ||
Tuk.destroy_all | ||
expect { book.import_chapter(chapter_number) }.to raise_error(StandardError, /Tuk 1 not found/) | ||
end | ||
|
||
it 'raises an error when 2nd `tuk` is missing' do | ||
chapter.tuks.second.destroy | ||
expect { book.import_chapter(chapter_number) }.to raise_error(StandardError, /Tuk 2 not found/) | ||
end | ||
end | ||
|
||
# TODO: Write tests for Translations, Footnotes, etc. | ||
# TODO: and write tests for the TTY stuff | ||
end | ||
end | ||
end | ||
|
||
# rubocop:enable RSpec/NestedGroups, RSpec/MultipleExpectations |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters