diff --git a/app/controllers/admin/budgets_controller.rb b/app/controllers/admin/budgets_controller.rb index dab8dcad2..132a757ba 100644 --- a/app/controllers/admin/budgets_controller.rb +++ b/app/controllers/admin/budgets_controller.rb @@ -1,4 +1,5 @@ class Admin::BudgetsController < Admin::BaseController + include Translatable include FeatureFlags feature_flag :budgets @@ -56,8 +57,8 @@ class Admin::BudgetsController < Admin::BaseController def budget_params descriptions = Budget::Phase::PHASE_KINDS.map{|p| "description_#{p}"}.map(&:to_sym) - valid_attributes = [:name, :phase, :currency_symbol] + descriptions - params.require(:budget).permit(*valid_attributes) + valid_attributes = [:phase, :currency_symbol] + descriptions + params.require(:budget).permit(*valid_attributes, translation_params(Budget)) end end diff --git a/app/models/budget.rb b/app/models/budget.rb index 93e6a095f..53bd02e82 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -3,9 +3,14 @@ class Budget < ActiveRecord::Base include Measurable include Sluggable + translates :name, touch: true + include Globalizable + CURRENCY_SYMBOLS = %w(€ $ £ ¥).freeze - validates :name, presence: true, uniqueness: true + before_validation :assign_model_to_translations + + validates_translation :name, presence: true validates :phase, inclusion: { in: Budget::Phase::PHASE_KINDS } validates :currency_symbol, presence: true validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/ diff --git a/app/models/budget/translation.rb b/app/models/budget/translation.rb new file mode 100644 index 000000000..1f3d93ea1 --- /dev/null +++ b/app/models/budget/translation.rb @@ -0,0 +1,11 @@ +class Budget::Translation < Globalize::ActiveRecord::Translation + validate :name_uniqueness_by_budget + + def name_uniqueness_by_budget + if Budget.joins(:translations) + .where(name: name) + .where.not("budget_translations.budget_id": budget_id).any? + errors.add(:name, I18n.t("errors.messages.taken")) + end + end +end diff --git a/app/models/concerns/globalizable.rb b/app/models/concerns/globalizable.rb index 386047f8b..e8772f19c 100644 --- a/app/models/concerns/globalizable.rb +++ b/app/models/concerns/globalizable.rb @@ -8,6 +8,10 @@ module Globalizable def locales_not_marked_for_destruction translations.reject(&:_destroy).map(&:locale) end + + def assign_model_to_translations + translations.each { |translation| translation.globalized_model = self } + end end class_methods do diff --git a/app/views/admin/budgets/_form.html.erb b/app/views/admin/budgets/_form.html.erb index 2755d51e0..aeaf0db37 100644 --- a/app/views/admin/budgets/_form.html.erb +++ b/app/views/admin/budgets/_form.html.erb @@ -1,7 +1,16 @@ -<%= form_for [:admin, @budget] do |f| %> +<%= render "admin/shared/globalize_locales", resource: @budget %> + +<%= translatable_form_for [:admin, @budget] do |f| %> + + <%= render 'shared/errors', resource: @budget %>
- <%= f.text_field :name, maxlength: Budget.title_max_length %> + <%= f.translatable_fields do |translations_form| %> + <%= translations_form.text_field :name, + label: t("activerecord.attributes.budget.name"), + maxlength: Budget.title_max_length, + placeholder: t("activerecord.attributes.budget.name") %> + <% end %>
diff --git a/db/dev_seeds/budgets.rb b/db/dev_seeds/budgets.rb index 5764ca40e..7bbc8cc10 100644 --- a/db/dev_seeds/budgets.rb +++ b/db/dev_seeds/budgets.rb @@ -26,14 +26,17 @@ end section "Creating Budgets" do Budget.create( - name: "#{I18n.t('seeds.budgets.budget')} #{Date.current.year - 1}", - currency_symbol: I18n.t('seeds.budgets.currency'), - phase: 'finished' + name_en: "#{I18n.t("seeds.budgets.budget", locale: :en)} #{Date.current.year - 1}", + name_es: "#{I18n.t("seeds.budgets.budget", locale: :es)} #{Date.current.year - 1}", + currency_symbol: I18n.t("seeds.budgets.currency"), + phase: "finished" ) + Budget.create( - name: "#{I18n.t('seeds.budgets.budget')} #{Date.current.year}", - currency_symbol: I18n.t('seeds.budgets.currency'), - phase: 'accepting' + name_en: "#{I18n.t("seeds.budgets.budget", locale: :en)} #{Date.current.year}", + name_es: "#{I18n.t("seeds.budgets.budget", locale: :es)} #{Date.current.year}", + currency_symbol: I18n.t("seeds.budgets.currency"), + phase: "accepting" ) Budget.all.each do |budget| diff --git a/db/migrate/20190118135741_add_budget_translations.rb b/db/migrate/20190118135741_add_budget_translations.rb new file mode 100644 index 000000000..4ba7a94a9 --- /dev/null +++ b/db/migrate/20190118135741_add_budget_translations.rb @@ -0,0 +1,16 @@ +class AddBudgetTranslations < ActiveRecord::Migration + + def self.up + Budget.create_translation_table!( + { + name: :string + }, + { migrate_data: true } + ) + end + + def self.down + Budget.drop_translation_table! + end + +end diff --git a/db/schema.rb b/db/schema.rb index 5a1e4cc20..e49f135de 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -266,6 +266,17 @@ ActiveRecord::Schema.define(version: 20190205131722) do t.datetime "updated_at", null: false end + create_table "budget_translations", force: :cascade do |t| + t.integer "budget_id", null: false + t.string "locale", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "name" + end + + add_index "budget_translations", ["budget_id"], name: "index_budget_translations_on_budget_id", using: :btree + add_index "budget_translations", ["locale"], name: "index_budget_translations_on_locale", using: :btree + create_table "budget_valuator_assignments", force: :cascade do |t| t.integer "valuator_id" t.integer "investment_id" diff --git a/spec/features/admin/budgets_spec.rb b/spec/features/admin/budgets_spec.rb index ba603038e..fb441844a 100644 --- a/spec/features/admin/budgets_spec.rb +++ b/spec/features/admin/budgets_spec.rb @@ -7,6 +7,11 @@ feature 'Admin budgets' do login_as(admin.user) end + it_behaves_like "translatable", + "budget", + "edit_admin_budget_path", + %w[name] + context 'Feature flag' do background do @@ -95,7 +100,7 @@ feature 'Admin budgets' do visit admin_budgets_path click_link 'Create new budget' - fill_in 'budget_name', with: 'M30 - Summer campaign' + fill_in "Name", with: "M30 - Summer campaign" select 'Accepting projects', from: 'budget[phase]' click_button 'Create Budget' @@ -112,6 +117,18 @@ feature 'Admin budgets' do expect(page).to have_css("label.error", text: "Name") end + scenario "Name should be unique" do + create(:budget, name: "Existing Name") + + visit new_admin_budget_path + fill_in "Name", with: "Existing Name" + click_button "Create Budget" + + expect(page).not_to have_content "New participatory budget created successfully!" + expect(page).to have_css("label.error", text: "Name") + expect(page).to have_css("small.error", text: "has already been taken") + end + end context 'Destroy' do @@ -174,6 +191,31 @@ feature 'Admin budgets' do end end end + + scenario "Changing name for current locale will update the slug if budget is in draft phase", :js do + budget.update(phase: "drafting") + old_slug = budget.slug + + visit edit_admin_budget_path(budget) + + select "Español", from: "translation_locale" + fill_in "Name", with: "Spanish name" + click_button "Update Budget" + + expect(page).to have_content "Participatory budget updated successfully" + expect(budget.reload.slug).to eq old_slug + + visit edit_admin_budget_path(budget) + + click_link "English" + fill_in "Name", with: "New English Name" + click_button "Update Budget" + + expect(page).to have_content "Participatory budget updated successfully" + expect(budget.reload.slug).not_to eq old_slug + expect(budget.slug).to eq "new-english-name" + end + end context 'Update' do @@ -186,7 +228,7 @@ feature 'Admin budgets' do visit admin_budgets_path click_link 'Edit budget' - fill_in 'budget_name', with: 'More trees on the streets' + fill_in "Name", with: "More trees on the streets" click_button 'Update Budget' expect(page).to have_content('More trees on the streets') diff --git a/spec/models/budget_spec.rb b/spec/models/budget_spec.rb index e5fe404ce..3faec8d21 100644 --- a/spec/models/budget_spec.rb +++ b/spec/models/budget_spec.rb @@ -8,11 +8,20 @@ describe Budget do describe "name" do before do - create(:budget, name: 'object name') + budget.update(name_en: "object name") end - it "is validated for uniqueness" do - expect(build(:budget, name: 'object name')).not_to be_valid + it "must not be repeated for a different budget and same locale" do + expect(build(:budget, name_en: "object name")).not_to be_valid + end + + it "must not be repeated for a different budget and a different locale" do + expect(build(:budget, name_fr: "object name")).not_to be_valid + end + + it "may be repeated for the same budget and a different locale" do + budget.update(name_fr: "object name") + expect(budget.translations.last).to be_valid end end diff --git a/spec/shared/features/translatable.rb b/spec/shared/features/translatable.rb index 7ce96e54e..09c25a8fb 100644 --- a/spec/shared/features/translatable.rb +++ b/spec/shared/features/translatable.rb @@ -329,6 +329,8 @@ def update_button_text "Update notification" when "Poll" "Update poll" + when "Budget" + "Update Budget" when "Poll::Question", "Poll::Question::Answer" "Save" when "SiteCustomization::Page"