diff --git a/db/migrate/20180323190027_add_translate_milestones.rb b/db/migrate/20180323190027_add_translate_milestones.rb index 6e27583a8e9..6767d8e8423 100644 --- a/db/migrate/20180323190027_add_translate_milestones.rb +++ b/db/migrate/20180323190027_add_translate_milestones.rb @@ -1,9 +1,12 @@ class AddTranslateMilestones < ActiveRecord::Migration def self.up - Budget::Investment::Milestone.create_translation_table!({ - title: :string, - description: :text - }) + Budget::Investment::Milestone.create_translation_table!( + { + title: :string, + description: :text + }, + { migrate_data: true } + ) end def self.down diff --git a/db/migrate/20180718115545_create_i18n_content_translations.rb b/db/migrate/20180718115545_create_i18n_content_translations.rb index e472f06222e..8e6fde21cf5 100644 --- a/db/migrate/20180718115545_create_i18n_content_translations.rb +++ b/db/migrate/20180718115545_create_i18n_content_translations.rb @@ -6,7 +6,10 @@ def change reversible do |dir| dir.up do - I18nContent.create_translation_table! :value => :text + I18nContent.create_translation_table!( + { value: :text }, + { migrate_data: true } + ) end dir.down do diff --git a/db/migrate/20180727140800_add_banner_translations.rb b/db/migrate/20180727140800_add_banner_translations.rb index 7678a34a105..4c4c9391edb 100644 --- a/db/migrate/20180727140800_add_banner_translations.rb +++ b/db/migrate/20180727140800_add_banner_translations.rb @@ -2,8 +2,11 @@ class AddBannerTranslations < ActiveRecord::Migration def self.up Banner.create_translation_table!( - title: :string, - description: :text + { + title: :string, + description: :text + }, + { migrate_data: true } ) end diff --git a/db/migrate/20180730120800_add_homepage_content_translations.rb b/db/migrate/20180730120800_add_homepage_content_translations.rb index 41596453622..b344f95c347 100644 --- a/db/migrate/20180730120800_add_homepage_content_translations.rb +++ b/db/migrate/20180730120800_add_homepage_content_translations.rb @@ -2,10 +2,13 @@ class AddHomepageContentTranslations < ActiveRecord::Migration def self.up Widget::Card.create_translation_table!( - label: :string, - title: :string, - description: :text, - link_text: :string + { + label: :string, + title: :string, + description: :text, + link_text: :string + }, + { migrate_data: true } ) end diff --git a/db/migrate/20180730213824_add_poll_translations.rb b/db/migrate/20180730213824_add_poll_translations.rb index 4a4fa72a4df..75495d3272c 100644 --- a/db/migrate/20180730213824_add_poll_translations.rb +++ b/db/migrate/20180730213824_add_poll_translations.rb @@ -2,9 +2,12 @@ class AddPollTranslations < ActiveRecord::Migration def self.up Poll.create_translation_table!( - name: :string, - summary: :text, - description: :text + { + name: :string, + summary: :text, + description: :text + }, + { migrate_data: true } ) end diff --git a/db/migrate/20180731150800_add_admin_notification_translations.rb b/db/migrate/20180731150800_add_admin_notification_translations.rb index 519751fd7b1..fb76a42d29a 100644 --- a/db/migrate/20180731150800_add_admin_notification_translations.rb +++ b/db/migrate/20180731150800_add_admin_notification_translations.rb @@ -2,8 +2,11 @@ class AddAdminNotificationTranslations < ActiveRecord::Migration def self.up AdminNotification.create_translation_table!( - title: :string, - body: :text + { + title: :string, + body: :text + }, + { migrate_data: true } ) end diff --git a/db/migrate/20180731173147_add_poll_question_translations.rb b/db/migrate/20180731173147_add_poll_question_translations.rb index a167ea00a29..f62fbc1613d 100644 --- a/db/migrate/20180731173147_add_poll_question_translations.rb +++ b/db/migrate/20180731173147_add_poll_question_translations.rb @@ -2,7 +2,8 @@ class AddPollQuestionTranslations < ActiveRecord::Migration def self.up Poll::Question.create_translation_table!( - title: :string + { title: :string }, + { migrate_data: true } ) end diff --git a/db/migrate/20180801114529_add_poll_question_answer_translations.rb b/db/migrate/20180801114529_add_poll_question_answer_translations.rb index 91643f4433a..3203d7a1f51 100644 --- a/db/migrate/20180801114529_add_poll_question_answer_translations.rb +++ b/db/migrate/20180801114529_add_poll_question_answer_translations.rb @@ -2,8 +2,11 @@ class AddPollQuestionAnswerTranslations < ActiveRecord::Migration def self.up Poll::Question::Answer.create_translation_table!( - title: :string, - description: :text + { + title: :string, + description: :text + }, + { migrate_data: true } ) end diff --git a/db/migrate/20180801140800_add_collaborative_legislation_translations.rb b/db/migrate/20180801140800_add_collaborative_legislation_translations.rb index 7c0fb9eb32a..569689a73ce 100644 --- a/db/migrate/20180801140800_add_collaborative_legislation_translations.rb +++ b/db/migrate/20180801140800_add_collaborative_legislation_translations.rb @@ -2,25 +2,33 @@ class AddCollaborativeLegislationTranslations < ActiveRecord::Migration def self.up Legislation::Process.create_translation_table!( - title: :string, - summary: :text, - description: :text, - additional_info: :text, + { + title: :string, + summary: :text, + description: :text, + additional_info: :text, + }, + { migrate_data: true } ) Legislation::Question.create_translation_table!( - title: :text + { title: :text }, + { migrate_data: true } ) Legislation::DraftVersion.create_translation_table!( - title: :string, - changelog: :text, - body: :text, - body_html: :text, - toc_html: :text + { + title: :string, + changelog: :text, + body: :text, + body_html: :text, + toc_html: :text + }, + { migrate_data: true } ) Legislation::QuestionOption.create_translation_table!( - value: :string + { value: :string }, + { migrate_data: true } ) end diff --git a/db/migrate/20180924071722_add_translate_pages.rb b/db/migrate/20180924071722_add_translate_pages.rb index 6dc939dec00..250f46dee7d 100644 --- a/db/migrate/20180924071722_add_translate_pages.rb +++ b/db/migrate/20180924071722_add_translate_pages.rb @@ -1,10 +1,14 @@ class AddTranslatePages < ActiveRecord::Migration def self.up - SiteCustomization::Page.create_translation_table!({ - title: :string, - subtitle: :string, - content: :text - }) + SiteCustomization::Page.create_translation_table!( + { + title: :string, + subtitle: :string, + content: :text + }, + { migrate_data: true } + ) + change_column :site_customization_pages, :title, :string, :null => true end diff --git a/lib/tasks/globalize.rake b/lib/tasks/globalize.rake new file mode 100644 index 00000000000..51a23042e02 --- /dev/null +++ b/lib/tasks/globalize.rake @@ -0,0 +1,89 @@ +namespace :globalize do + def translatable_classes + [ + AdminNotification, + Banner, + Budget::Investment::Milestone, + I18nContent, + Legislation::DraftVersion, + Legislation::Process, + Legislation::Question, + Legislation::QuestionOption, + Poll, + Poll::Question, + Poll::Question::Answer, + SiteCustomization::Page, + Widget::Card + ] + end + + def migrate_data + @errored = false + + translatable_classes.each do |model_class| + logger.info "Migrating #{model_class} data" + + fields = model_class.translated_attribute_names + + model_class.find_each do |record| + fields.each do |field| + locale = if model_class == SiteCustomization::Page && record.locale.present? + record.locale + else + I18n.locale + end + + translated_field = record.localized_attr_name_for(field, locale) + + if record.send(translated_field).blank? + record.send(:"#{translated_field}=", record.untranslated_attributes[field.to_s]) + end + end + + begin + record.save! + rescue ActiveRecord::RecordInvalid + logger.warn "Failed to save #{model_class} with id #{record.id}" + @errored = true + end + end + end + end + + def logger + @logger ||= Logger.new(STDOUT).tap do |logger| + logger.formatter = proc { |severity, _datetime, _progname, msg| "#{severity} #{msg}\n" } + end + end + + def errored? + @errored + end + + desc "Simulates migrating existing data to translation tables" + task simulate_migrate_data: :environment do + logger.info "Starting migrate data simulation" + + ActiveRecord::Base.transaction do + migrate_data + raise ActiveRecord::Rollback + end + + if errored? + logger.warn "Some database records will not be migrated" + else + logger.info "Migrate data simulation ended successfully" + end + end + + desc "Migrates existing data to translation tables" + task migrate_data: :environment do + logger.info "Starting data migration" + migrate_data + logger.info "Finished data migration" + + if errored? + logger.warn "Some database records couldn't be migrated; please check the log messages" + end + end +end diff --git a/spec/lib/tasks/globalize_spec.rb b/spec/lib/tasks/globalize_spec.rb new file mode 100644 index 00000000000..b685bb95e1d --- /dev/null +++ b/spec/lib/tasks/globalize_spec.rb @@ -0,0 +1,168 @@ +require "rails_helper" +require "rake" + +describe "Globalize tasks" do + + describe "#migrate_data" do + + before do + Rake.application.rake_require "tasks/globalize" + Rake::Task.define_task(:environment) + end + + let :run_rake_task do + Rake::Task["globalize:migrate_data"].reenable + Rake.application.invoke_task "globalize:migrate_data" + end + + context "Original data with no translated data" do + let(:poll) do + create(:poll).tap do |poll| + poll.translations.delete_all + poll.update_column(:name, "Original") + poll.reload + end + end + + it "copies the original data" do + expect(poll.send(:"name_#{I18n.locale}")).to be nil + expect(poll.name).to eq("Original") + + run_rake_task + poll.reload + + expect(poll.name).to eq("Original") + expect(poll.send(:"name_#{I18n.locale}")).to eq("Original") + end + end + + context "Original data with blank translated data" do + let(:banner) do + create(:banner).tap do |banner| + banner.update_column(:title, "Original") + banner.translations.first.update_column(:title, "") + end + end + + it "copies the original data" do + expect(banner.title).to eq("") + + run_rake_task + banner.reload + + expect(banner.title).to eq("Original") + expect(banner.send(:"title_#{I18n.locale}")).to eq("Original") + end + end + + context "Original data with translated data" do + let(:notification) do + create(:admin_notification, title: "Translated").tap do |notification| + notification.update_column(:title, "Original") + end + end + + it "maintains the translated data" do + expect(notification.title).to eq("Translated") + + run_rake_task + notification.reload + + expect(notification.title).to eq("Translated") + expect(notification.send(:"title_#{I18n.locale}")).to eq("Translated") + end + end + + context "Custom page with a different locale and no translations" do + let(:page) do + create(:site_customization_page, locale: :fr).tap do |page| + page.translations.delete_all + page.update_column(:title, "en Français") + page.reload + end + end + + it "copies the original data to both the page's locale" do + expect(page.title).to eq("en Français") + expect(page.title_fr).to be nil + expect(page.send(:"title_#{I18n.locale}")).to be nil + + run_rake_task + page.reload + + expect(page.title).to eq("en Français") + expect(page.title_fr).to eq("en Français") + expect(page.send(:"title_#{I18n.locale}")).to be nil + end + end + + context "Custom page with a different locale and existing translations" do + let(:page) do + create(:site_customization_page, title: "In English", locale: :fr).tap do |page| + page.update_column(:title, "en Français") + end + end + + it "copies the original data to the page's locale" do + expect(page.title_fr).to be nil + expect(page.title).to eq("In English") + + run_rake_task + page.reload + + expect(page.title).to eq("In English") + expect(page.title_fr).to eq("en Français") + expect(page.send(:"title_#{I18n.locale}")).to eq("In English") + end + end + + context "Invalid data" do + let!(:valid_process) do + create(:legislation_process).tap do |process| + process.translations.delete_all + process.update_column(:title, "Title") + process.reload + end + end + + let!(:invalid_process) do + create(:legislation_process).tap do |process| + process.translations.delete_all + process.update_column(:title, "") + process.reload + end + end + + it "ignores invalid data and migrates valid data" do + expect(valid_process).to be_valid + expect(invalid_process).not_to be_valid + + run_rake_task + + expect(valid_process.translations.count).to eq 1 + expect(valid_process.reload.title).to eq "Title" + + expect(invalid_process.translations.count).to eq 0 + expect(invalid_process.reload.title).to eq "" + end + end + + context "locale with non-underscored name" do + before { I18n.locale = :"pt-BR" } + + let!(:milestone) do + create(:budget_investment_milestone).tap do |milestone| + milestone.translations.delete_all + milestone.update_column(:title, "Português") + milestone.reload + end + end + + it "runs the migration successfully" do + run_rake_task + + expect(milestone.reload.title).to eq "Português" + end + end + end +end