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"