From 33cf2ffaa77a20d93ac827732463b74236d46355 Mon Sep 17 00:00:00 2001 From: Dilraj Somel Date: Sat, 27 May 2023 16:37:46 -0400 Subject: [PATCH 01/15] Write and test the `@book.import_chapter(num)` method --- app/models/book.rb | 119 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/app/models/book.rb b/app/models/book.rb index ab8be42..58ae784 100644 --- a/app/models/book.rb +++ b/app/models/book.rb @@ -18,4 +18,123 @@ def last_pauri def last_tuk return last_pauri.tuks.last end + + ## + # Create (or update) the a specific chapter via CSV + # HOW IT WORKS: + # 1. Add your CSV file to `lib/imports/#{book.sequence}/#{chapter_number}.csv` + # 2. Pass in a `chapter_number: Integer` e.g. `3` + # 3. Call `@book.import_chapter(3)`. + # EXAMPLE: + # @book = Book.find_by(:sequence => 1) + # @book.import_chapter(3) + # This will search for a CSV file at `lib/imports/1/3.csv` (or raise error) + # The CSV file should have the following columns: + # - Chapter_Number: Integer + # - Chapter_Name: String + # - Chhand_Type: String + # - Tuk: String + # - Pauri_Number: Integer + # - Pauri_Translation_EN: String | NULL + # - Tuk_Translation_EN: String | NULL + # - Footnotes: String | NULL + # - Extended_Ref: String | NULL + # - Assigned_Singh: String | NULL + # - Extended_Meaning: String | NULL + ## + def import_chapter(chapter_number) + file_path = "lib/imports/#{sequence}/#{chapter_number}.csv" + raise "CSV file #{file_path} not found" unless File.exist?(file_path) + + prompt = TTY::Prompt.new + pastel = Pastel.new + + ActiveRecord::Base.transaction do + CSV.parse(file_path, :headers => true).each do |row| + puts "row is: #{row}" + chapter_number = row['Chapter_Number'].to_i + chapter_name = row['Chapter_Name'].try(:strip) + chhand_type = row['Chhand_Type'].try(:strip) + tuk = row['Tuk'].try(:strip) + pauri_number = row['Pauri_Number'].to_i + pauri_translation_en = row['Pauri_Translation_EN'].try(:strip) + tuk_translation_en = row['Tuk_Translation_EN'].try(:strip) + footnotes = row['Footnotes'].try(:strip) # Bhai Vir Singh footnotes + extended_ref = row['Extended_Ref'].try(:strip) + translator = row['Assigned_Singh'].try(:strip) + extended_meaning = row['Extended_Meaning'].try(:strip) + + @chapter = self.chapters.find_by(:number => chapter_number) + raise "Chapter not found: #{chapter_number}" if @chapter.nil? + + puts ' HEAD 30000' + if @chapter.title != chapter_name + message = "The name in Book #{sequence}, Chapter #{chapter_number} is " + + pastel.bold('presently') + + " '#{@chapter.title}'. The CSV says '#{chapter_name}'." + puts 'ER' + prompt.say(message, :color => :yellow) + answer = prompt.yes?("Do you want to continue and update this title to '#{chapter_name}'?") + puts 'DLFJSLDJF' + 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 + + @pauri = @chapter.pauris.find_by(:number => pauri_number) + raise "Pauri not found: #{pauri_number}" if @pauri.nil? + + @tuk = @pauri.tuks.find_by('original_content LIKE ?', tuk) + raise "Tuk not found: #{tuk}" if @tuk.nil? + + # CREATE `TukTranslation` + if tuk_translation_en.present? + if @pauri.translation.present? + destroy_tuk_option = @tuk.translation.nil? ? 'Do not create `TukTranslation`' : 'Destroy existing `TukTranslation`' + choices = [ + 'Continue with the `TukTranslation` and KEEP the `PauriTranslation`, too!', + 'Destroy the existing `PauriTranslation`, and continue with only the `TukTranslation`', + destroy_tuk_option += 'Keep the `PauriTranslation`', + pastel.red('Abort') + ] + selected_choice = prompt.select('Choose an option:', choices) + + case selected_choice + when choices[0] + Rails.logger.debug pastel.green('✓ Continuing with both `TukTranslation` and `PauriTranslation`') + @tuk_translation = @tuk.translation || TukTranslation.new(:tuk_id => @tuk.id) + @tuk_translation.update(:en_translation => tuk_translation_en, :en_translator => translator) + Rails.logger.debug pastel.green("✓ `TukTranslation` created or updated for Tuk #{tuk} - Pauri # #{pauri_number}") + when choices[1] + # Destroy `PauriTranslation` and continue with only `TukTranslation` + Rails.logger.debug pastel.yellow('⚠️ Deleting the existing `PauriTranslation`') + @tuk_translation = @tuk.translation || TukTranslation.new(:tuk_id => @tuk.id) + @tuk_translation.update(:en_translation => tuk_translation_en, :en_translator => translator) + Rails.logger.debug pastel.green("✓ `TukTranslation` created or updated for Tuk #{tuk} - Pauri # #{pauri_number}") + + pauri.translation.destroy + when choices[2] + # Destroy/ `TukTranslation` and continue with only `PauriTranslation` + if @tuk.translation.present? + Rails.logger.debug pastel.yellow('⚠️ Deleting the existing `TukTranslation`') + @tuk.translation.destroy + end + when choices[3] + raise 'Aborted by user' + end + else + @tuk_translation = @tuk.translation || TukTranslation.new(:tuk_id => @tuk.id) + @tuk_translation.update(:en_translation => tuk_translation_en, :en_translator => translator) + Rails.logger.debug pastel.green("✓ `TukTranslation` created or updated for Tuk #{tuk} - Pauri # #{pauri_number}") + end + end + + # CREATAE `PauriTranslation` + next unless pauri_translation_en.present? + pauri_translation = @pauri.translation || PauriTranslation.new(:pauri_id => @pauri.id) + pauri_translation.update(:en_translation => pauri_translation_en, :en_translator => translator) + Rails.logger.debug pastel.green("✓ `PauriTranslation` created or updated for Pauri # #{pauri_number}") + end + end + end end From 6a7687edae55e03423c6905fcd5f63708b32a8b2 Mon Sep 17 00:00:00 2001 From: Dilraj Somel Date: Sat, 27 May 2023 16:38:03 -0400 Subject: [PATCH 02/15] Start writing specs --- spec/models/book_spec.rb | 125 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 spec/models/book_spec.rb diff --git a/spec/models/book_spec.rb b/spec/models/book_spec.rb new file mode 100644 index 0000000..d5b98d2 --- /dev/null +++ b/spec/models/book_spec.rb @@ -0,0 +1,125 @@ +require 'rails_helper' + +RSpec.describe Book, :type => :model do + before do + # Instantiate a book and chapter object for testing + @book = create(:book) + @chapter = create(:chapter, :book => @book) + @chhand_type = create(:chhand_type) + end + + describe '#import_chapter' do + context 'when looking up file (`lib/imports/{book.sequence}/{chapter.number}.csv`)' do + it 'raises an error when the CSV does not exist' do + expect { @book.import_chapter(99) }.to raise_error(RuntimeError, %r{CSV file lib/imports/1/99.csv not found}) + end + + it 'does NOT raise an error when the CSV is found' do + file_name = "lib/imports/#{@book.sequence}/#{@chapter.number}.csv" + 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 + + allow(File).to receive(:exist?).with(file_name).and_return(true) + allow(CSV).to receive(:parse).with(file_name, :headers => true).and_return(CSV.parse(csv_content, :headers => true)) + + expect { @book.import_chapter(99) }.not_to raise_error + end + end + + context 'when the chapter number specified in the CSV is NOT found' do + it 'raises an error' do + file_name = "lib/imports/#{@book.sequence}/#{@chapter.number}.csv" + 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 + 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, + 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, + CSV + + allow(File).to receive(:exist?).with(file_name).and_return(true) + allow(CSV).to receive(:parse).with(file_name, :headers => true).and_return(CSV.parse(csv_content, :headers => true)) + expect { @book.import_chapter(99) }.to raise_error(RuntimeError, /Chapter not found: 99/) + end + end + + context 'when the chapter number specified in the CSV is found' do + it 'raises an error' do + @chapter99 = create(:chapter, :book => @book, :number => 99) + file_name = "lib/imports/#{@book.sequence}/#{@chapter99.number}.csv" + 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 + 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, + 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, + CSV + + allow(File).to receive(:exist?).with(file_name).and_return(true) + allow(CSV).to receive(:parse).with(file_name, :headers => true).and_return(CSV.parse(csv_content, :headers => true)) + + expect { @book.import_chapter(99) }.not_to raise_error(RuntimeError, /Chapter not found/) + @chapter99.destroy + end + end + + context 'when user says YES to prompt to update the chapter name' do + it 'updates the title to the one from the CSV' do + @chapter99 = create(:chapter, :number => 99, :title => 'ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ', :book => @book) + @chhand = create(:chhand, :chhand_type => @chhand_type, :chapter => @chapter99) + @pauri = create(:pauri, :chapter => @chapter99, :chhand => @chhand, :number => 1) + @tuk = create(:tuk, :pauri => @pauri, :chapter => @chapter99, :original_content => 'ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ', :sequence => 1) + @tuk = create(:tuk, :pauri => @pauri, :chapter => @chapter99, :original_content => 'ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ', :sequence => 2) + + file_name = "lib/imports/#{@book.sequence}/#{@chapter99.number}.csv" + 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 + 99,ਇਸ਼੍ਟ ਦੇਵ: ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ । ਮੰਗਲ,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, + 99,ਇਸ਼੍ਟ ਦੇਵ: ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ । ਮੰਗਲ,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, + CSV + + allow(File).to receive(:exist?).with(file_name).and_return(true) + allow(CSV).to receive(:parse).with(file_name, :headers => true).and_return(CSV.parse(csv_content, :headers => true)) + + prompt = instance_double(TTY::Prompt) + allow(TTY::Prompt).to receive(:new).and_return(prompt) + allow(prompt).to receive(:say) + allow(prompt).to receive(:yes?).and_return(true) + + # allow(prompt).to receive(:say).with("The name in Book 1, Chapter 99 is presently 'Chapter'. The CSV says 'ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ'.", :color => :yellow) + # expect(prompt).to receive(:yes?).with("Do you want to continue and update this title to 'ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ'?").and_return(true) + # expect(prompt).to receive(:say).with(/The name in Book 1, Chapter 99 is presently 'Chapter'. The CSV says 'ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ'", color: :yellow) + + @book.import_chapter(99) + expect(@chapter99.reload.title).to eq('ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ । ਮੰਗਲ,ਦੋਹਰਾ') + end + + # context 'when the last row chapter number does not match' do + # it 'aborts all changes' do + # # Stub your CSV read method to return data with last row chapter_number 95 + # expect { @book.import_chapter(3) }.to raise_error(StandardError, /Aborted due to chapter number mismatch/) + # end + # end + + # context 'when chapter title in CSV does not match with database and user confirms update' do + # it 'updates the chapter title' do + # # Mock the user input to return true + # allow(STDIN).to receive(:gets).and_return("yes\n") + # @book.import_chapter(1) + # expect(@chapter.reload.title).to eq('chapter_name') # Replace "chapter_name" with actual value + # end + # end + + # context 'when chapter title in CSV does not match with database and user denies update' do + # it 'aborts the operation' do + # # Mock the user input to return false + # allow(STDIN).to receive(:gets).and_return("no\n") + # expect { @book.import_chapter(1) }.to raise_error(StandardError, /Aborted by user/) + # end + # end + + # context 'when the pauri does not exist in the database' do + # it 'raises an error' do + # # Stub your CSV read method to return data with pauri 23 + # expect { @book.import_chapter(1) }.to raise_error(StandardError, /Pauri does not exist in database/) + # end + # end + end +end From 825856f9b2b77c2258500d53dcf079a60ee195b6 Mon Sep 17 00:00:00 2001 From: Dilraj Somel Date: Sat, 27 May 2023 16:56:04 -0400 Subject: [PATCH 03/15] Fix most of the tests --- spec/models/book_spec.rb | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/spec/models/book_spec.rb b/spec/models/book_spec.rb index d5b98d2..4ad52be 100644 --- a/spec/models/book_spec.rb +++ b/spec/models/book_spec.rb @@ -1,4 +1,5 @@ require 'rails_helper' +require 'csv' RSpec.describe Book, :type => :model do before do @@ -23,13 +24,13 @@ allow(File).to receive(:exist?).with(file_name).and_return(true) allow(CSV).to receive(:parse).with(file_name, :headers => true).and_return(CSV.parse(csv_content, :headers => true)) - expect { @book.import_chapter(99) }.not_to raise_error + expect { @book.import_chapter(@chapter.number) }.not_to raise_error end end context 'when the chapter number specified in the CSV is NOT found' do it 'raises an error' do - file_name = "lib/imports/#{@book.sequence}/#{@chapter.number}.csv" + file_name = "lib/imports/#{@book.sequence}/99.csv" 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 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, @@ -55,6 +56,11 @@ allow(File).to receive(:exist?).with(file_name).and_return(true) allow(CSV).to receive(:parse).with(file_name, :headers => true).and_return(CSV.parse(csv_content, :headers => true)) + prompt = instance_double(TTY::Prompt) + allow(TTY::Prompt).to receive(:new).and_return(prompt) + allow(prompt).to receive(:say) + allow(prompt).to receive(:yes?).and_return(true) + expect { @book.import_chapter(99) }.not_to raise_error(RuntimeError, /Chapter not found/) @chapter99.destroy end @@ -83,12 +89,11 @@ allow(prompt).to receive(:say) allow(prompt).to receive(:yes?).and_return(true) - # allow(prompt).to receive(:say).with("The name in Book 1, Chapter 99 is presently 'Chapter'. The CSV says 'ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ'.", :color => :yellow) - # expect(prompt).to receive(:yes?).with("Do you want to continue and update this title to 'ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ'?").and_return(true) - # expect(prompt).to receive(:say).with(/The name in Book 1, Chapter 99 is presently 'Chapter'. The CSV says 'ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ'", color: :yellow) @book.import_chapter(99) - expect(@chapter99.reload.title).to eq('ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ । ਮੰਗਲ,ਦੋਹਰਾ') + expect(prompt).to have_received(:yes?).with("Do you want to continue and update this title to 'ਇਸ਼੍ਟ ਦੇਵ: ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ । ਮੰਗਲ'?") + expect(@chapter99.reload.title).to eq('ਇਸ਼੍ਟ ਦੇਵ: ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ । ਮੰਗਲ') + end end # context 'when the last row chapter number does not match' do From 319ff0c470f71e5067d248ba57fd5f382f2a828d Mon Sep 17 00:00:00 2001 From: Dilraj Somel Date: Sat, 27 May 2023 17:01:53 -0400 Subject: [PATCH 04/15] Remove puts --- app/models/book.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/models/book.rb b/app/models/book.rb index 58ae784..75ac712 100644 --- a/app/models/book.rb +++ b/app/models/book.rb @@ -51,7 +51,6 @@ def import_chapter(chapter_number) ActiveRecord::Base.transaction do CSV.parse(file_path, :headers => true).each do |row| - puts "row is: #{row}" chapter_number = row['Chapter_Number'].to_i chapter_name = row['Chapter_Name'].try(:strip) chhand_type = row['Chhand_Type'].try(:strip) @@ -67,15 +66,12 @@ def import_chapter(chapter_number) @chapter = self.chapters.find_by(:number => chapter_number) raise "Chapter not found: #{chapter_number}" if @chapter.nil? - puts ' HEAD 30000' if @chapter.title != chapter_name message = "The name in Book #{sequence}, Chapter #{chapter_number} is " + pastel.bold('presently') + " '#{@chapter.title}'. The CSV says '#{chapter_name}'." - puts 'ER' prompt.say(message, :color => :yellow) answer = prompt.yes?("Do you want to continue and update this title to '#{chapter_name}'?") - puts 'DLFJSLDJF' 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}'") @@ -129,7 +125,7 @@ def import_chapter(chapter_number) end end - # CREATAE `PauriTranslation` + # CREATE `PauriTranslation` next unless pauri_translation_en.present? pauri_translation = @pauri.translation || PauriTranslation.new(:pauri_id => @pauri.id) pauri_translation.update(:en_translation => pauri_translation_en, :en_translator => translator) From 81edc6d832e6052fe1fed447327a54aad46ed4a9 Mon Sep 17 00:00:00 2001 From: Dilraj Somel Date: Sat, 27 May 2023 17:02:47 -0400 Subject: [PATCH 05/15] Spec for aborting when updating name --- spec/models/book_spec.rb | 49 ++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/spec/models/book_spec.rb b/spec/models/book_spec.rb index 4ad52be..9d4d2d9 100644 --- a/spec/models/book_spec.rb +++ b/spec/models/book_spec.rb @@ -68,7 +68,7 @@ context 'when user says YES to prompt to update the chapter name' do it 'updates the title to the one from the CSV' do - @chapter99 = create(:chapter, :number => 99, :title => 'ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ', :book => @book) + @chapter99 = create(:chapter, :number => 99, :title => 'TitleInDB', :book => @book) @chhand = create(:chhand, :chhand_type => @chhand_type, :chapter => @chapter99) @pauri = create(:pauri, :chapter => @chapter99, :chhand => @chhand, :number => 1) @tuk = create(:tuk, :pauri => @pauri, :chapter => @chapter99, :original_content => 'ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ', :sequence => 1) @@ -77,8 +77,8 @@ file_name = "lib/imports/#{@book.sequence}/#{@chapter99.number}.csv" 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 - 99,ਇਸ਼੍ਟ ਦੇਵ: ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ । ਮੰਗਲ,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, - 99,ਇਸ਼੍ਟ ਦੇਵ: ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ । ਮੰਗਲ,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, + 99,UpdatedTitleInCSV,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, + 99,UpdatedTitleInCSV,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, CSV allow(File).to receive(:exist?).with(file_name).and_return(true) @@ -91,26 +91,37 @@ @book.import_chapter(99) - expect(prompt).to have_received(:yes?).with("Do you want to continue and update this title to 'ਇਸ਼੍ਟ ਦੇਵ: ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ । ਮੰਗਲ'?") - expect(@chapter99.reload.title).to eq('ਇਸ਼੍ਟ ਦੇਵ: ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ । ਮੰਗਲ') + expect(prompt).to have_received(:yes?).with("Do you want to continue and update this title to 'UpdatedTitleInCSV'?") + expect(@chapter99.reload.title).to eq('UpdatedTitleInCSV') end end - # context 'when the last row chapter number does not match' do - # it 'aborts all changes' do - # # Stub your CSV read method to return data with last row chapter_number 95 - # expect { @book.import_chapter(3) }.to raise_error(StandardError, /Aborted due to chapter number mismatch/) - # end - # end + context 'when user says NO to prompt to update the chapter name' do + it 'aborts and does NOT udpate the chapter.title' do + @chapter99 = create(:chapter, :number => 99, :title => 'TitleInDB', :book => @book) - # context 'when chapter title in CSV does not match with database and user confirms update' do - # it 'updates the chapter title' do - # # Mock the user input to return true - # allow(STDIN).to receive(:gets).and_return("yes\n") - # @book.import_chapter(1) - # expect(@chapter.reload.title).to eq('chapter_name') # Replace "chapter_name" with actual value - # end - # end + file_name = "lib/imports/#{@book.sequence}/#{@chapter99.number}.csv" + 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 + 99,UpdatedTitleInCSV,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, + 99,UpdatedTitleInCSV,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, + CSV + + allow(File).to receive(:exist?).with(file_name).and_return(true) + allow(CSV).to receive(:parse).with(file_name, :headers => true).and_return(CSV.parse(csv_content, :headers => true)) + + prompt = instance_double(TTY::Prompt) + allow(TTY::Prompt).to receive(:new).and_return(prompt) + allow(prompt).to receive(:say) + allow(prompt).to receive(:yes?).and_return(false) + + + expect { @book.import_chapter(99) }.to raise_error(RuntimeError, 'Aborted by user') + + expect(prompt).to have_received(:yes?).with("Do you want to continue and update this title to 'UpdatedTitleInCSV'?") + expect(@chapter99.reload.title).to eq('TitleInDB') + end + end # context 'when chapter title in CSV does not match with database and user denies update' do # it 'aborts the operation' do From a4f7af6fc5d5531f9a8731157da7f982f0e7c7ec Mon Sep 17 00:00:00 2001 From: Dilraj Somel Date: Sat, 3 Jun 2023 06:48:49 -0400 Subject: [PATCH 06/15] Write better tests and refactor. --- app/models/book.rb | 19 ++++--- app/models/chapter.rb | 11 ++++ spec/models/book_spec.rb | 103 ++++++++++++++++++++++++------------ spec/models/chapter_spec.rb | 2 +- 4 files changed, 91 insertions(+), 44 deletions(-) diff --git a/app/models/book.rb b/app/models/book.rb index 75ac712..106edc6 100644 --- a/app/models/book.rb +++ b/app/models/book.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require 'csv' class Book < ApplicationRecord has_many :chapters, :dependent => :destroy @@ -43,14 +44,19 @@ def last_tuk # - Extended_Meaning: String | NULL ## def import_chapter(chapter_number) - file_path = "lib/imports/#{sequence}/#{chapter_number}.csv" - raise "CSV file #{file_path} not found" unless File.exist?(file_path) - prompt = TTY::Prompt.new pastel = Pastel.new + Rails.logger.debug 'HELLO' + @chapter = self.chapters.find_by(:number => chapter_number) + raise "Chapter not found: #{chapter_number}" if @chapter.nil? + + blob = @chapter.csv_rows + + # put "blob ::::: #{blob}" + ActiveRecord::Base.transaction do - CSV.parse(file_path, :headers => true).each do |row| + blob.each do |row| chapter_number = row['Chapter_Number'].to_i chapter_name = row['Chapter_Name'].try(:strip) chhand_type = row['Chhand_Type'].try(:strip) @@ -63,9 +69,6 @@ def import_chapter(chapter_number) translator = row['Assigned_Singh'].try(:strip) extended_meaning = row['Extended_Meaning'].try(:strip) - @chapter = self.chapters.find_by(:number => chapter_number) - raise "Chapter not found: #{chapter_number}" if @chapter.nil? - if @chapter.title != chapter_name message = "The name in Book #{sequence}, Chapter #{chapter_number} is " + pastel.bold('presently') + @@ -126,7 +129,7 @@ def import_chapter(chapter_number) end # CREATE `PauriTranslation` - next unless pauri_translation_en.present? + next 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 => translator) Rails.logger.debug pastel.green("✓ `PauriTranslation` created or updated for Pauri # #{pauri_number}") diff --git a/app/models/chapter.rb b/app/models/chapter.rb index 382912c..004927b 100644 --- a/app/models/chapter.rb +++ b/app/models/chapter.rb @@ -4,4 +4,15 @@ class Chapter < ApplicationRecord belongs_to :book has_many :chhands, :dependent => :destroy has_many :pauris, :dependent => :destroy + + def csv_rows + file_path = "lib/imports/#{self.book.sequence}/#{self.number}.csv" + raise "CSV file #{file_path} not found" unless File.exist?(file_path) + + rows = [] + CSV.foreach(file_path, :headers => true) do |row| + rows << row + end + return rows + end end diff --git a/spec/models/book_spec.rb b/spec/models/book_spec.rb index 9d4d2d9..14ca3ef 100644 --- a/spec/models/book_spec.rb +++ b/spec/models/book_spec.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + require 'rails_helper' require 'csv' -RSpec.describe Book, :type => :model do +RSpec.describe Book do before do # Instantiate a book and chapter object for testing @book = create(:book) @@ -10,19 +12,28 @@ end describe '#import_chapter' do + + ## + # These unit tests focus on method logic using mocks for File and CSV operations. While effective for verifying internal logic, + # they may lead to false positives due to lack of real file system interaction. Consider supplementing with integration tests + # for comprehensive validation. + ## context 'when looking up file (`lib/imports/{book.sequence}/{chapter.number}.csv`)' do it 'raises an error when the CSV does not exist' do - expect { @book.import_chapter(99) }.to raise_error(RuntimeError, %r{CSV file lib/imports/1/99.csv not found}) + expect { @book.import_chapter(@chapter.number) }.to raise_error(RuntimeError, "CSV file lib/imports/1/#{@chapter.number}.csv not found") end it 'does NOT raise an error when the CSV is found' do - file_name = "lib/imports/#{@book.sequence}/#{@chapter.number}.csv" + file_path = "lib/imports/#{@book.sequence}/#{@chapter.number}.csv" + 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 - allow(File).to receive(:exist?).with(file_name).and_return(true) - allow(CSV).to receive(:parse).with(file_name, :headers => true).and_return(CSV.parse(csv_content, :headers => true)) + csv_rows = CSV.parse(csv_content, :headers => true) + + 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 @@ -30,15 +41,12 @@ context 'when the chapter number specified in the CSV is NOT found' do it 'raises an error' do - file_name = "lib/imports/#{@book.sequence}/99.csv" 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 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, CSV - allow(File).to receive(:exist?).with(file_name).and_return(true) - allow(CSV).to receive(:parse).with(file_name, :headers => true).and_return(CSV.parse(csv_content, :headers => true)) expect { @book.import_chapter(99) }.to raise_error(RuntimeError, /Chapter not found: 99/) end end @@ -46,15 +54,14 @@ context 'when the chapter number specified in the CSV is found' do it 'raises an error' do @chapter99 = create(:chapter, :book => @book, :number => 99) - file_name = "lib/imports/#{@book.sequence}/#{@chapter99.number}.csv" 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 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, CSV - allow(File).to receive(:exist?).with(file_name).and_return(true) - allow(CSV).to receive(:parse).with(file_name, :headers => true).and_return(CSV.parse(csv_content, :headers => true)) + allow(@book.chapters).to receive(:find_by).with(:number => 99).and_return(@chapter99) + allow(@chapter99).to receive(:csv_rows).and_return(CSV.parse(csv_content, :headers => true)) prompt = instance_double(TTY::Prompt) allow(TTY::Prompt).to receive(:new).and_return(prompt) @@ -74,22 +81,20 @@ @tuk = create(:tuk, :pauri => @pauri, :chapter => @chapter99, :original_content => 'ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ', :sequence => 1) @tuk = create(:tuk, :pauri => @pauri, :chapter => @chapter99, :original_content => 'ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ', :sequence => 2) - file_name = "lib/imports/#{@book.sequence}/#{@chapter99.number}.csv" 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 99,UpdatedTitleInCSV,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, 99,UpdatedTitleInCSV,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, CSV - allow(File).to receive(:exist?).with(file_name).and_return(true) - allow(CSV).to receive(:parse).with(file_name, :headers => true).and_return(CSV.parse(csv_content, :headers => true)) + allow(@book.chapters).to receive(:find_by).with(:number => 99).and_return(@chapter99) + allow(@chapter99).to receive(:csv_rows).and_return(CSV.parse(csv_content, :headers => true)) prompt = instance_double(TTY::Prompt) allow(TTY::Prompt).to receive(:new).and_return(prompt) allow(prompt).to receive(:say) allow(prompt).to receive(:yes?).and_return(true) - @book.import_chapter(99) expect(prompt).to have_received(:yes?).with("Do you want to continue and update this title to 'UpdatedTitleInCSV'?") expect(@chapter99.reload.title).to eq('UpdatedTitleInCSV') @@ -100,42 +105,70 @@ it 'aborts and does NOT udpate the chapter.title' do @chapter99 = create(:chapter, :number => 99, :title => 'TitleInDB', :book => @book) - file_name = "lib/imports/#{@book.sequence}/#{@chapter99.number}.csv" 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 99,UpdatedTitleInCSV,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, 99,UpdatedTitleInCSV,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, CSV - allow(File).to receive(:exist?).with(file_name).and_return(true) - allow(CSV).to receive(:parse).with(file_name, :headers => true).and_return(CSV.parse(csv_content, :headers => true)) + allow(@book.chapters).to receive(:find_by).with(:number => 99).and_return(@chapter99) + allow(@chapter99).to receive(:csv_rows).and_return(CSV.parse(csv_content, :headers => true)) prompt = instance_double(TTY::Prompt) allow(TTY::Prompt).to receive(:new).and_return(prompt) allow(prompt).to receive(:say) allow(prompt).to receive(:yes?).and_return(false) - expect { @book.import_chapter(99) }.to raise_error(RuntimeError, 'Aborted by user') + end + end - expect(prompt).to have_received(:yes?).with("Do you want to continue and update this title to 'UpdatedTitleInCSV'?") - expect(@chapter99.reload.title).to eq('TitleInDB') + context 'when the pauri does not exist in the database' do + it 'raises an error' do + @chapter99 = create(:chapter, :number => 99, :book => @book) + + 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 + 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, + 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, + CSV + + allow(@book.chapters).to receive(:find_by).with(:number => 99).and_return(@chapter99) + allow(@chapter99).to receive(:csv_rows).and_return(CSV.parse(csv_content, :headers => true)) + + prompt = instance_double(TTY::Prompt) + allow(TTY::Prompt).to receive(:new).and_return(prompt) + allow(prompt).to receive(:say) + allow(prompt).to receive(:yes?).and_return(true) + + expect { @book.import_chapter(99) }.to raise_error(StandardError, /Pauri not found/) end end - # context 'when chapter title in CSV does not match with database and user denies update' do - # it 'aborts the operation' do - # # Mock the user input to return false - # allow(STDIN).to receive(:gets).and_return("no\n") - # expect { @book.import_chapter(1) }.to raise_error(StandardError, /Aborted by user/) - # end - # end - - # context 'when the pauri does not exist in the database' do - # it 'raises an error' do - # # Stub your CSV read method to return data with pauri 23 - # expect { @book.import_chapter(1) }.to raise_error(StandardError, /Pauri does not exist in database/) - # end - # end + context 'when the tuk does not exist in the database' do + it 'raises an error when the tuk content is too different' do + @chapter99 = create(:chapter, :number => 99, :book => @book) + @chhand = create(:chhand, :chhand_type => @chhand_type, :chapter => @chapter99) + @pauri = create(:pauri, :chapter => @chapter99, :chhand => @chhand, :number => 1) + @tuk = create(:tuk, :pauri => @pauri, :chapter => @chapter99, :original_content => 'ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ', :sequence => 1) + @tuk = create(:tuk, :pauri => @pauri, :chapter => @chapter99, :original_content => 'ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ', :sequence => 2) + + 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 + 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, something else",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, + 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, changed up a bit",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, + CSV + + allow(@book.chapters).to receive(:find_by).with(:number => 99).and_return(@chapter99) + allow(@chapter99).to receive(:csv_rows).and_return(CSV.parse(csv_content, :headers => true)) + + prompt = instance_double(TTY::Prompt) + allow(TTY::Prompt).to receive(:new).and_return(prompt) + allow(prompt).to receive(:say) + allow(prompt).to receive(:yes?).and_return(true) + + expect { @book.import_chapter(99) }.to raise_error(StandardError, /Tuk not found/) + end + end end end diff --git a/spec/models/chapter_spec.rb b/spec/models/chapter_spec.rb index d2f91ae..4e55905 100644 --- a/spec/models/chapter_spec.rb +++ b/spec/models/chapter_spec.rb @@ -27,7 +27,7 @@ it 'cannot save when `book_id` does not belong to a real book' do expect do - Chapter.create!({ :book_id => 1, :number => 2, :title => 'Mangal' }) + Chapter.create!({ :book_id => 99, :number => 2, :title => 'Mangal' }) end.to raise_error(ActiveRecord::RecordInvalid) end end From a01c46bc2cadb3864df3112b55145cb76f2c7e04 Mon Sep 17 00:00:00 2001 From: Dilraj Somel Date: Sat, 3 Jun 2023 07:51:33 -0400 Subject: [PATCH 07/15] HEAVY REFACTOR --- app/models/book.rb | 4 +- app/models/chapter.rb | 1 + spec/models/book_spec.rb | 209 +++++++++++++++------------------------ 3 files changed, 79 insertions(+), 135 deletions(-) diff --git a/app/models/book.rb b/app/models/book.rb index 106edc6..c592175 100644 --- a/app/models/book.rb +++ b/app/models/book.rb @@ -47,14 +47,11 @@ def import_chapter(chapter_number) prompt = TTY::Prompt.new pastel = Pastel.new - Rails.logger.debug 'HELLO' @chapter = self.chapters.find_by(:number => chapter_number) raise "Chapter not found: #{chapter_number}" if @chapter.nil? blob = @chapter.csv_rows - # put "blob ::::: #{blob}" - ActiveRecord::Base.transaction do blob.each do |row| chapter_number = row['Chapter_Number'].to_i @@ -81,6 +78,7 @@ def import_chapter(chapter_number) end @pauri = @chapter.pauris.find_by(:number => pauri_number) + raise "Pauri not found: #{pauri_number}" if @pauri.nil? @tuk = @pauri.tuks.find_by('original_content LIKE ?', tuk) diff --git a/app/models/chapter.rb b/app/models/chapter.rb index 004927b..32a5cc7 100644 --- a/app/models/chapter.rb +++ b/app/models/chapter.rb @@ -4,6 +4,7 @@ class Chapter < ApplicationRecord belongs_to :book has_many :chhands, :dependent => :destroy has_many :pauris, :dependent => :destroy + has_many :tuks, :through => :pauris def csv_rows file_path = "lib/imports/#{self.book.sequence}/#{self.number}.csv" diff --git a/spec/models/book_spec.rb b/spec/models/book_spec.rb index 14ca3ef..c4d27f4 100644 --- a/spec/models/book_spec.rb +++ b/spec/models/book_spec.rb @@ -4,171 +4,116 @@ require 'csv' RSpec.describe Book do - before do - # Instantiate a book and chapter object for testing - @book = create(:book) - @chapter = create(:chapter, :book => @book) - @chhand_type = create(:chhand_type) - end + 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 - ## - # These unit tests focus on method logic using mocks for File and CSV operations. While effective for verifying internal logic, - # they may lead to false positives due to lack of real file system interaction. Consider supplementing with integration tests - # for comprehensive validation. - ## - context 'when looking up file (`lib/imports/{book.sequence}/{chapter.number}.csv`)' do - it 'raises an error when the CSV does not exist' do - expect { @book.import_chapter(@chapter.number) }.to raise_error(RuntimeError, "CSV file lib/imports/1/#{@chapter.number}.csv not found") + 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, 'CSV file lib/imports/1/100.csv not found') end + end - it 'does NOT raise an error when the CSV is found' do - file_path = "lib/imports/#{@book.sequence}/#{@chapter.number}.csv" + 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_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) - csv_rows = CSV.parse(csv_content, :headers => true) + it 'does not raise an error' do + let(:chapter) { create(:chapter, :book => book) } + 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 + expect { book.import_chapter(chapter.number) }.not_to raise_error end end - context 'when the chapter number specified in the CSV is NOT found' do - it 'raises an error' do - csv_content = <<~CSV + 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,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, + 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,,,,,, + 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,,,,,, CSV - - expect { @book.import_chapter(99) }.to raise_error(RuntimeError, /Chapter not found: 99/) end - end - - context 'when the chapter number specified in the CSV is found' do - it 'raises an error' do - @chapter99 = create(:chapter, :book => @book, :number => 99) - 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 - 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, - 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, - CSV - - allow(@book.chapters).to receive(:find_by).with(:number => 99).and_return(@chapter99) - allow(@chapter99).to receive(:csv_rows).and_return(CSV.parse(csv_content, :headers => true)) + before do + # Initialize mocks for TTY::Prompt prompt = instance_double(TTY::Prompt) allow(TTY::Prompt).to receive(:new).and_return(prompt) allow(prompt).to receive(:say) - allow(prompt).to receive(:yes?).and_return(true) - expect { @book.import_chapter(99) }.not_to raise_error(RuntimeError, /Chapter not found/) - @chapter99.destroy - end - end + # Associations for the chapter - This one reflects out mock `csv_content` + let(:chapter) { create(:chapter, :number => chapter_number, :book => book, :title => chapter_title) } + let(:chhand) { create(:chhand, :chhand_type => '', :chapter => chapter) } + let(:pauri) { create(:pauri, :chapter => chapter, :chhand => chhand, :number => 1) } + create(:tuk, :pauri => pauri, :chapter => chapter, :original_content => 'ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ', :sequence => 1) + create(:tuk, :pauri => pauri, :chapter => chapter, :original_content => 'ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ', :sequence => 2) - context 'when user says YES to prompt to update the chapter name' do - it 'updates the title to the one from the CSV' do - @chapter99 = create(:chapter, :number => 99, :title => 'TitleInDB', :book => @book) - @chhand = create(:chhand, :chhand_type => @chhand_type, :chapter => @chapter99) - @pauri = create(:pauri, :chapter => @chapter99, :chhand => @chhand, :number => 1) - @tuk = create(:tuk, :pauri => @pauri, :chapter => @chapter99, :original_content => 'ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ', :sequence => 1) - @tuk = create(:tuk, :pauri => @pauri, :chapter => @chapter99, :original_content => 'ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ', :sequence => 2) - - 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 - 99,UpdatedTitleInCSV,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, - 99,UpdatedTitleInCSV,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, - CSV - - allow(@book.chapters).to receive(:find_by).with(:number => 99).and_return(@chapter99) - allow(@chapter99).to receive(:csv_rows).and_return(CSV.parse(csv_content, :headers => true)) - - prompt = instance_double(TTY::Prompt) - allow(TTY::Prompt).to receive(:new).and_return(prompt) - allow(prompt).to receive(:say) - allow(prompt).to receive(:yes?).and_return(true) - - @book.import_chapter(99) - expect(prompt).to have_received(:yes?).with("Do you want to continue and update this title to 'UpdatedTitleInCSV'?") - expect(@chapter99.reload.title).to eq('UpdatedTitleInCSV') + # 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 - end - - context 'when user says NO to prompt to update the chapter name' do - it 'aborts and does NOT udpate the chapter.title' do - @chapter99 = create(:chapter, :number => 99, :title => 'TitleInDB', :book => @book) - - 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 - 99,UpdatedTitleInCSV,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, - 99,UpdatedTitleInCSV,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, - CSV - allow(@book.chapters).to receive(:find_by).with(:number => 99).and_return(@chapter99) - allow(@chapter99).to receive(:csv_rows).and_return(CSV.parse(csv_content, :headers => true)) + 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?) - prompt = instance_double(TTY::Prompt) - allow(TTY::Prompt).to receive(:new).and_return(prompt) - allow(prompt).to receive(:say) - allow(prompt).to receive(:yes?).and_return(false) + expect(chapter.reload.title).to eq(chapter_title) + end - expect { @book.import_chapter(99) }.to raise_error(RuntimeError, 'Aborted by user') - end - 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') - context 'when the pauri does not exist in the database' do - it 'raises an error' do - @chapter99 = create(:chapter, :number => 99, :book => @book) + allow(prompt).to receive(:yes?).and_return(true) + book.import_chapter(chapter_number) - 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 - 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, - 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, - CSV - - allow(@book.chapters).to receive(:find_by).with(:number => 99).and_return(@chapter99) - allow(@chapter99).to receive(:csv_rows).and_return(CSV.parse(csv_content, :headers => true)) + 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 - prompt = instance_double(TTY::Prompt) - allow(TTY::Prompt).to receive(:new).and_return(prompt) - allow(prompt).to receive(:say) - allow(prompt).to receive(:yes?).and_return(true) - - expect { @book.import_chapter(99) }.to raise_error(StandardError, /Pauri not found/) + 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 - end - - context 'when the tuk does not exist in the database' do - it 'raises an error when the tuk content is too different' do - @chapter99 = create(:chapter, :number => 99, :book => @book) - @chhand = create(:chhand, :chhand_type => @chhand_type, :chapter => @chapter99) - @pauri = create(:pauri, :chapter => @chapter99, :chhand => @chhand, :number => 1) - @tuk = create(:tuk, :pauri => @pauri, :chapter => @chapter99, :original_content => 'ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ', :sequence => 1) - @tuk = create(:tuk, :pauri => @pauri, :chapter => @chapter99, :original_content => 'ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ', :sequence => 2) - - 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 - 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, something else",1,1,,,"ਅਲਿਪਤ = ਅਸੰਗ। ਜੋ ਲਿਪਾਯਮਾਨ ਨਾ ਹੋਵੇ, ਨਿਰਲੇਪ। ਪ੍ਰਬੀਨ = ਚਤੁਰ ਪੁਰਸ਼, ਲਾਇਕ।",,,,, - 99,ਇਸ਼੍ਟ ਦੇਵ-ਸ਼੍ਰੀ ਅਕਾਲ ਪੁਰਖ-ਮੰਗਲ,ਦੋਹਰਾ,"ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, changed up a bit",1,2,,,"ਬੀਨਤਿ = ਵਿੱਤ੍ਰੇਕ ਕਰਦੇ ਹਨ। ਇਹ ਨਹੀਂ, ਇਹ ਹੈ, ਇਉਂ ਵਿਚਾਰ ਦੁਆਰਾ ਉਸਦੇ ਸਰੂਪ ਲੱਛਣਾਂ ਨੂੰ ਛਾਂਟ ਲੈਂਦੇ ਹਨ, ਭਾਵ ਜਾਣ ਲੈਂਦੇ ਹਨ। ਮਰਮ = ਭੇਤ।",,,,, - CSV - allow(@book.chapters).to receive(:find_by).with(:number => 99).and_return(@chapter99) - allow(@chapter99).to receive(:csv_rows).and_return(CSV.parse(csv_content, :headers => true)) - - prompt = instance_double(TTY::Prompt) - allow(TTY::Prompt).to receive(:new).and_return(prompt) - allow(prompt).to receive(:say) - allow(prompt).to receive(:yes?).and_return(true) - - expect { @book.import_chapter(99) }.to raise_error(StandardError, /Tuk not found/) + 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 not found/) + end + + it 'raises an error when `tuk` content does not match' do + tuk = chapter.tuks.first + tuk.update(:content => 'Changed', :original_content => 'Totally Different') + Tuk.destroy_all + expect { book.import_chapter(chapter_number) }.to raise_error(StandardError, /Tuk not found/) + end end + + # TODO: Write tests for Translations, Footnotes, etc. end end end From dbdcf44e8485d8f98c2e960b81e0cdd4c200f9c3 Mon Sep 17 00:00:00 2001 From: Dilraj Somel Date: Sat, 3 Jun 2023 07:58:49 -0400 Subject: [PATCH 08/15] Linting and rubocop ignoring stuff --- app/models/book.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/models/book.rb b/app/models/book.rb index c592175..8fa2e65 100644 --- a/app/models/book.rb +++ b/app/models/book.rb @@ -43,7 +43,7 @@ def last_tuk # - Assigned_Singh: String | NULL # - Extended_Meaning: String | NULL ## - def import_chapter(chapter_number) + def import_chapter(chapter_number) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity prompt = TTY::Prompt.new pastel = Pastel.new @@ -52,19 +52,19 @@ def import_chapter(chapter_number) blob = @chapter.csv_rows - ActiveRecord::Base.transaction do - blob.each do |row| + ActiveRecord::Base.transaction do # rubocop:disable Metrics/BlockLength + blob.each do |row| # rubocop:disable Metrics/BlockLength chapter_number = row['Chapter_Number'].to_i chapter_name = row['Chapter_Name'].try(:strip) - chhand_type = row['Chhand_Type'].try(:strip) + # chhand_type = row['Chhand_Type'].try(:strip) # UNUSED tuk = row['Tuk'].try(:strip) pauri_number = row['Pauri_Number'].to_i pauri_translation_en = row['Pauri_Translation_EN'].try(:strip) tuk_translation_en = row['Tuk_Translation_EN'].try(:strip) - footnotes = row['Footnotes'].try(:strip) # Bhai Vir Singh footnotes - extended_ref = row['Extended_Ref'].try(:strip) + # footnotes = row['Footnotes'].try(:strip) # # UNUSED + # extended_ref = row['Extended_Ref'].try(:strip) # UNUSED translator = row['Assigned_Singh'].try(:strip) - extended_meaning = row['Extended_Meaning'].try(:strip) + # extended_meaning = row['Extended_Meaning'].try(:strip) # UNUSED if @chapter.title != chapter_name message = "The name in Book #{sequence}, Chapter #{chapter_number} is " + @@ -87,11 +87,11 @@ def import_chapter(chapter_number) # CREATE `TukTranslation` if tuk_translation_en.present? if @pauri.translation.present? - destroy_tuk_option = @tuk.translation.nil? ? 'Do not create `TukTranslation`' : 'Destroy existing `TukTranslation`' + # destroy_tuk_option = @tuk.translation.nil? ? 'Do not create `TukTranslation`' : 'Destroy existing `TukTranslation`' choices = [ 'Continue with the `TukTranslation` and KEEP the `PauriTranslation`, too!', 'Destroy the existing `PauriTranslation`, and continue with only the `TukTranslation`', - destroy_tuk_option += 'Keep the `PauriTranslation`', + # destroy_tuk_option += 'Keep the `PauriTranslation`', pastel.red('Abort') ] selected_choice = prompt.select('Choose an option:', choices) From 0e01c9477fb23327bab067da7d3e3b12c13b9049 Mon Sep 17 00:00:00 2001 From: Dilraj Somel Date: Sat, 3 Jun 2023 08:03:00 -0400 Subject: [PATCH 09/15] MORE RUBOCOP --- app/models/book.rb | 1 + spec/models/book_spec.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/app/models/book.rb b/app/models/book.rb index 8fa2e65..aca1a51 100644 --- a/app/models/book.rb +++ b/app/models/book.rb @@ -85,6 +85,7 @@ def import_chapter(chapter_number) # rubocop:disable Metrics/CyclomaticComplexit raise "Tuk not found: #{tuk}" if @tuk.nil? # CREATE `TukTranslation` + # rubocop:disable Metrics/BlockNesting if tuk_translation_en.present? if @pauri.translation.present? # destroy_tuk_option = @tuk.translation.nil? ? 'Do not create `TukTranslation`' : 'Destroy existing `TukTranslation`' diff --git a/spec/models/book_spec.rb b/spec/models/book_spec.rb index c4d27f4..db46293 100644 --- a/spec/models/book_spec.rb +++ b/spec/models/book_spec.rb @@ -1,3 +1,4 @@ +# rubocop:disable RSpec/NestedGroups, RSpec/MultipleExpectations # frozen_string_literal: true require 'rails_helper' @@ -117,3 +118,5 @@ end end end + +# rubocop:enable RSpec/NestedGroups, RSpec/MultipleExpectations From d1f415af714731697f8f80aee2bb266a8c5ef768 Mon Sep 17 00:00:00 2001 From: Dilraj Date: Sat, 3 Jun 2023 19:07:18 -0400 Subject: [PATCH 10/15] Add fallback to 'Translation_EN' if 'Tuk_Translation_EN' is blank in Book model --- app/models/book.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/book.rb b/app/models/book.rb index aca1a51..7f9c193 100644 --- a/app/models/book.rb +++ b/app/models/book.rb @@ -60,7 +60,7 @@ def import_chapter(chapter_number) # rubocop:disable Metrics/CyclomaticComplexit tuk = row['Tuk'].try(:strip) pauri_number = row['Pauri_Number'].to_i pauri_translation_en = row['Pauri_Translation_EN'].try(:strip) - tuk_translation_en = row['Tuk_Translation_EN'].try(:strip) + tuk_translation_en = row['Tuk_Translation_EN'].try(:strip) || row['Translation_EN'].try(:strip) # footnotes = row['Footnotes'].try(:strip) # # UNUSED # extended_ref = row['Extended_Ref'].try(:strip) # UNUSED translator = row['Assigned_Singh'].try(:strip) From 49a7e8a1f813d06aa21d25e75a2cb0401e87aac1 Mon Sep 17 00:00:00 2001 From: Dilraj Somel Date: Sat, 10 Jun 2023 12:16:45 -0400 Subject: [PATCH 11/15] Added a way to handle differentiating Tuk content --- Gemfile | 5 ++++- Gemfile.lock | 2 ++ app/models/book.rb | 26 +++++++++++++++++++++++++- app/models/chapter.rb | 6 +++++- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index da1f7aa..5ca36ab 100644 --- a/Gemfile +++ b/Gemfile @@ -68,6 +68,10 @@ group :development do gem 'rubocop-capybara' gem 'rubocop-rails' gem 'rubocop-rspec', :require => false + + # CLI Stuff + gem 'diffy' + gem 'tty-prompt' end group :test do @@ -78,4 +82,3 @@ group :test do end gem 'contentful' -gem 'tty-prompt' diff --git a/Gemfile.lock b/Gemfile.lock index 4844e14..09edeac 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -92,6 +92,7 @@ GEM irb (>= 1.5.0) reline (>= 0.3.1) diff-lcs (1.5.0) + diffy (3.4.2) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) @@ -310,6 +311,7 @@ DEPENDENCIES capybara contentful debug + diffy dotenv-rails factory_bot_rails importmap-rails diff --git a/app/models/book.rb b/app/models/book.rb index 7f9c193..f5084ea 100644 --- a/app/models/book.rb +++ b/app/models/book.rb @@ -36,6 +36,7 @@ def last_tuk # - Chhand_Type: String # - Tuk: String # - Pauri_Number: Integer + # - Tuk_Number: Integer # - Pauri_Translation_EN: String | NULL # - Tuk_Translation_EN: String | NULL # - Footnotes: String | NULL @@ -59,6 +60,7 @@ def import_chapter(chapter_number) # rubocop:disable Metrics/CyclomaticComplexit # chhand_type = row['Chhand_Type'].try(:strip) # UNUSED tuk = row['Tuk'].try(:strip) pauri_number = row['Pauri_Number'].to_i + tuk_number = row['Tuk_Number'].to_i pauri_translation_en = row['Pauri_Translation_EN'].try(:strip) tuk_translation_en = row['Tuk_Translation_EN'].try(:strip) || row['Translation_EN'].try(:strip) # footnotes = row['Footnotes'].try(:strip) # # UNUSED @@ -81,8 +83,30 @@ def import_chapter(chapter_number) # rubocop:disable Metrics/CyclomaticComplexit raise "Pauri not found: #{pauri_number}" if @pauri.nil? - @tuk = @pauri.tuks.find_by('original_content LIKE ?', tuk) + @tuk = @pauri.tuks.find_by(:sequence => tuk_number) raise "Tuk not found: #{tuk}" if @tuk.nil? + if @tuk.original_content != tuk + # Show the difference between the two strings + diff = Diffy::Diff.new(@tuk.original_content, tuk, :include_diff_info => true).to_s(:color) + puts diff + + # Prompt the user to decide what to do + 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] + # Do nothing, keeping the original content + Rails.logger.debug pastel.red("x Keeping the original `Tuk` `original_content` - For #{pauri_number}.#{tuk_number}") + end + end # CREATE `TukTranslation` # rubocop:disable Metrics/BlockNesting diff --git a/app/models/chapter.rb b/app/models/chapter.rb index 32a5cc7..74bd1f1 100644 --- a/app/models/chapter.rb +++ b/app/models/chapter.rb @@ -8,7 +8,11 @@ class Chapter < ApplicationRecord def csv_rows file_path = "lib/imports/#{self.book.sequence}/#{self.number}.csv" - raise "CSV file #{file_path} not found" unless File.exist?(file_path) + + unless File.exist?(file_path) + puts "CSV file #{file_path} not found. " + Pastel.new.red.on_bright_white.bold("Are you sure you added it to #{file_path}?") + raise "CSV file #{file_path} not found. " + end rows = [] CSV.foreach(file_path, :headers => true) do |row| From 5b7aec1dfa9eabeee35c2cc94eef3c218288f1f0 Mon Sep 17 00:00:00 2001 From: Dilraj Somel Date: Sun, 11 Jun 2023 13:13:28 -0400 Subject: [PATCH 12/15] Minor puts message improvement! --- app/models/book.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/book.rb b/app/models/book.rb index 1da57ac..dad4042 100644 --- a/app/models/book.rb +++ b/app/models/book.rb @@ -87,8 +87,9 @@ def import_chapter(chapter_number) # rubocop:disable Metrics/CyclomaticComplexit raise "Pauri not found: #{pauri_number}" if @pauri.nil? + # `Tuk` @tuk = @pauri.tuks.find_by(:sequence => tuk_number) - raise "Tuk not found: #{tuk}" if @tuk.nil? + raise "Tuk #{tuk_number} not found: #{tuk}" if @tuk.nil? if @tuk.original_content != tuk # Show the difference between the two strings diff = Diffy::Diff.new(@tuk.original_content, tuk, :include_diff_info => true).to_s(:color) From 14d1b2cd631436819e48edcf565c1de860034c74 Mon Sep 17 00:00:00 2001 From: Dilraj Date: Fri, 27 Oct 2023 02:40:51 -0400 Subject: [PATCH 13/15] #57 book_spec.rb passes --- Gemfile | 8 ++++---- spec/models/book_spec.rb | 25 +++++++++++++------------ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Gemfile b/Gemfile index 7d45914..b08febb 100644 --- a/Gemfile +++ b/Gemfile @@ -57,6 +57,10 @@ group :development, :test do gem 'factory_bot_rails' gem 'dotenv-rails' + + # CLI Stuff + gem 'diffy' + gem 'tty-prompt' end group :development do @@ -68,10 +72,6 @@ group :development do gem 'rubocop-capybara' gem 'rubocop-rails' gem 'rubocop-rspec', :require => false - - # CLI Stuff - gem 'diffy' - gem 'tty-prompt' end group :test do diff --git a/spec/models/book_spec.rb b/spec/models/book_spec.rb index db46293..bb93be6 100644 --- a/spec/models/book_spec.rb +++ b/spec/models/book_spec.rb @@ -16,7 +16,7 @@ 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, 'CSV file lib/imports/1/100.csv not found') + expect { book.import_chapter(100) }.to raise_error(RuntimeError, %r{CSV file lib/imports/1/100\.csv not found}) end end @@ -26,9 +26,9 @@ CSV csv_rows = CSV.parse(csv_content, :headers => true) + let(:chapter) { create(:chapter, :book => book) } it 'does not raise an error' do - let(:chapter) { create(:chapter, :book => book) } file_path = "lib/imports/#{book.sequence}/#{chapter.number}.csv" allow(File).to receive(:exist?).with(file_path).and_return(true) @@ -49,16 +49,18 @@ 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 - prompt = instance_double(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` - let(:chapter) { create(:chapter, :number => chapter_number, :book => book, :title => chapter_title) } - let(:chhand) { create(:chhand, :chhand_type => '', :chapter => chapter) } - let(:pauri) { create(:pauri, :chapter => chapter, :chhand => chhand, :number => 1) } create(:tuk, :pauri => pauri, :chapter => chapter, :original_content => 'ਤੀਨੋ ਕਾਲ ਅਲਿਪਤ ਰਹਿ, ਖੋਜੈਂ ਜਾਂਹਿ ਪ੍ਰਬੀਨ', :sequence => 1) create(:tuk, :pauri => pauri, :chapter => chapter, :original_content => 'ਬੀਨਤਿ ਸਚਿਦਾਨੰਦ ਤ੍ਰੈ, ਜਾਨਹਿਂ ਮਰਮ ਰਤੀ ਨ', :sequence => 2) @@ -103,18 +105,17 @@ it 'raises an error when `tuks` are nil' do Tuk.destroy_all - expect { book.import_chapter(chapter_number) }.to raise_error(StandardError, /Tuk not found/) + expect { book.import_chapter(chapter_number) }.to raise_error(StandardError, /Tuk 1 not found/) end - it 'raises an error when `tuk` content does not match' do - tuk = chapter.tuks.first - tuk.update(:content => 'Changed', :original_content => 'Totally Different') - Tuk.destroy_all - expect { book.import_chapter(chapter_number) }.to raise_error(StandardError, /Tuk not found/) + 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 From 64ca1bac7792320d8314d3fea7b9e8ab8cf0e430 Mon Sep 17 00:00:00 2001 From: Dilraj Date: Fri, 27 Oct 2023 02:47:31 -0400 Subject: [PATCH 14/15] #57 move stuff into a Service object --- app/models/book.rb | 117 +------------------- app/services/chapter_importer_service.rb | 132 +++++++++++++++++++++++ 2 files changed, 134 insertions(+), 115 deletions(-) create mode 100644 app/services/chapter_importer_service.rb diff --git a/app/models/book.rb b/app/models/book.rb index dad4042..77e137e 100644 --- a/app/models/book.rb +++ b/app/models/book.rb @@ -48,120 +48,7 @@ def number_of_chapters_released # - Assigned_Singh: String | NULL # - Extended_Meaning: String | NULL ## - def import_chapter(chapter_number) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity - prompt = TTY::Prompt.new - pastel = Pastel.new - - @chapter = self.chapters.find_by(:number => chapter_number) - raise "Chapter not found: #{chapter_number}" if @chapter.nil? - - blob = @chapter.csv_rows - - ActiveRecord::Base.transaction do # rubocop:disable Metrics/BlockLength - blob.each do |row| # rubocop:disable Metrics/BlockLength - chapter_number = row['Chapter_Number'].to_i - chapter_name = row['Chapter_Name'].try(:strip) - # chhand_type = row['Chhand_Type'].try(:strip) # UNUSED - tuk = row['Tuk'].try(:strip) - pauri_number = row['Pauri_Number'].to_i - tuk_number = row['Tuk_Number'].to_i - pauri_translation_en = row['Pauri_Translation_EN'].try(:strip) - tuk_translation_en = row['Tuk_Translation_EN'].try(:strip) || row['Translation_EN'].try(:strip) - # footnotes = row['Footnotes'].try(:strip) # # UNUSED - # extended_ref = row['Extended_Ref'].try(:strip) # UNUSED - translator = row['Assigned_Singh'].try(:strip) - # extended_meaning = row['Extended_Meaning'].try(:strip) # UNUSED - - if @chapter.title != chapter_name - message = "The name in 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 - - @pauri = @chapter.pauris.find_by(:number => pauri_number) - - raise "Pauri not found: #{pauri_number}" if @pauri.nil? - - # `Tuk` - @tuk = @pauri.tuks.find_by(:sequence => tuk_number) - raise "Tuk #{tuk_number} not found: #{tuk}" if @tuk.nil? - if @tuk.original_content != tuk - # Show the difference between the two strings - diff = Diffy::Diff.new(@tuk.original_content, tuk, :include_diff_info => true).to_s(:color) - puts diff - - # Prompt the user to decide what to do - 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] - # Do nothing, keeping the original content - Rails.logger.debug pastel.red("x Keeping the original `Tuk` `original_content` - For #{pauri_number}.#{tuk_number}") - end - end - - # CREATE `TukTranslation` - # rubocop:disable Metrics/BlockNesting - if tuk_translation_en.present? - if @pauri.translation.present? - # destroy_tuk_option = @tuk.translation.nil? ? 'Do not create `TukTranslation`' : 'Destroy existing `TukTranslation`' - choices = [ - 'Continue with the `TukTranslation` and KEEP the `PauriTranslation`, too!', - 'Destroy the existing `PauriTranslation`, and continue with only the `TukTranslation`', - # destroy_tuk_option += 'Keep the `PauriTranslation`', - pastel.red('Abort') - ] - selected_choice = prompt.select('Choose an option:', choices) - - case selected_choice - when choices[0] - Rails.logger.debug pastel.green('✓ Continuing with both `TukTranslation` and `PauriTranslation`') - @tuk_translation = @tuk.translation || TukTranslation.new(:tuk_id => @tuk.id) - @tuk_translation.update(:en_translation => tuk_translation_en, :en_translator => translator) - Rails.logger.debug pastel.green("✓ `TukTranslation` created or updated for Tuk #{tuk} - Pauri # #{pauri_number}") - when choices[1] - # Destroy `PauriTranslation` and continue with only `TukTranslation` - Rails.logger.debug pastel.yellow('⚠️ Deleting the existing `PauriTranslation`') - @tuk_translation = @tuk.translation || TukTranslation.new(:tuk_id => @tuk.id) - @tuk_translation.update(:en_translation => tuk_translation_en, :en_translator => translator) - Rails.logger.debug pastel.green("✓ `TukTranslation` created or updated for Tuk #{tuk} - Pauri # #{pauri_number}") - - pauri.translation.destroy - when choices[2] - # Destroy/ `TukTranslation` and continue with only `PauriTranslation` - if @tuk.translation.present? - Rails.logger.debug pastel.yellow('⚠️ Deleting the existing `TukTranslation`') - @tuk.translation.destroy - end - when choices[3] - raise 'Aborted by user' - end - else - @tuk_translation = @tuk.translation || TukTranslation.new(:tuk_id => @tuk.id) - @tuk_translation.update(:en_translation => tuk_translation_en, :en_translator => translator) - Rails.logger.debug pastel.green("✓ `TukTranslation` created or updated for Tuk #{tuk} - Pauri # #{pauri_number}") - end - end - - # CREATE `PauriTranslation` - next 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 => translator) - Rails.logger.debug pastel.green("✓ `PauriTranslation` created or updated for Pauri # #{pauri_number}") - end - end + def import_chapter(chapter_number) + ChapterImporterService.new(self, chapter_number).call end end diff --git a/app/services/chapter_importer_service.rb b/app/services/chapter_importer_service.rb new file mode 100644 index 0000000..7fae66e --- /dev/null +++ b/app/services/chapter_importer_service.rb @@ -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 From 44c505024328375b29db0dc4631936dbbaf0b286 Mon Sep 17 00:00:00 2001 From: Dilraj Date: Fri, 27 Oct 2023 02:54:37 -0400 Subject: [PATCH 15/15] Linting --- app/models/chapter.rb | 2 +- config/application.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/chapter.rb b/app/models/chapter.rb index df34832..b2b0c6d 100644 --- a/app/models/chapter.rb +++ b/app/models/chapter.rb @@ -22,7 +22,7 @@ def csv_rows file_path = "lib/imports/#{self.book.sequence}/#{self.number}.csv" unless File.exist?(file_path) - puts "CSV file #{file_path} not found. " + Pastel.new.red.on_bright_white.bold("Are you sure you added it to #{file_path}?") + Rails.logger.debug "CSV file #{file_path} not found. " + Pastel.new.red.on_bright_white.bold("Are you sure you added it to #{file_path}?") raise "CSV file #{file_path} not found. " end diff --git a/config/application.rb b/config/application.rb index 0bd5a52..c9c34b2 100644 --- a/config/application.rb +++ b/config/application.rb @@ -27,7 +27,7 @@ class Application < Rails::Application config.middleware.insert_before 0, Rack::Cors do allow do - origins 'https://spg.dev', /\A.*\.netlify\.app\z/ + origins 'https://spg.dev', /\A.*\.netlify\.app\z/, 'localhost:3000' resource '*', headers: :any,