diff --git a/app/controllers/admin/budget_groups_controller.rb b/app/controllers/admin/budget_groups_controller.rb index b6def19ed..aa87e2373 100644 --- a/app/controllers/admin/budget_groups_controller.rb +++ b/app/controllers/admin/budget_groups_controller.rb @@ -1,4 +1,5 @@ class Admin::BudgetGroupsController < Admin::BaseController + include Translatable include FeatureFlags feature_flag :budgets @@ -57,7 +58,8 @@ class Admin::BudgetGroupsController < Admin::BaseController end def budget_group_params - params.require(:budget_group).permit(:name, :max_votable_headings) + valid_attributes = [:max_votable_headings] + params.require(:budget_group).permit(*valid_attributes, translation_params(Budget::Group)) end end diff --git a/app/helpers/budget_headings_helper.rb b/app/helpers/budget_headings_helper.rb index 22eabfe11..cc28ed60e 100644 --- a/app/helpers/budget_headings_helper.rb +++ b/app/helpers/budget_headings_helper.rb @@ -3,7 +3,7 @@ module BudgetHeadingsHelper def budget_heading_select_options(budget) budget.headings.order_by_group_name.map do |heading| [heading.name_scoped_by_group, heading.id] - end + end.uniq end def heading_link(assigned_heading = nil, budget = nil) diff --git a/app/models/budget/group.rb b/app/models/budget/group.rb index bfbfd4741..37d6e658b 100644 --- a/app/models/budget/group.rb +++ b/app/models/budget/group.rb @@ -2,14 +2,21 @@ class Budget class Group < ActiveRecord::Base include Sluggable + translates :name, touch: true + include Globalizable + belongs_to :budget has_many :headings, dependent: :destroy + before_validation :assign_model_to_translations + + validates_translation :name, presence: true validates :budget_id, presence: true - validates :name, presence: true, uniqueness: { scope: :budget } validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/ + scope :sort_by_name, -> { includes(:translations).order(:name) } + def single_heading_group? headings.count == 1 end diff --git a/app/models/budget/group/translation.rb b/app/models/budget/group/translation.rb new file mode 100644 index 000000000..36489eb10 --- /dev/null +++ b/app/models/budget/group/translation.rb @@ -0,0 +1,13 @@ +class Budget::Group::Translation < Globalize::ActiveRecord::Translation + delegate :budget, to: :globalized_model + + validate :name_uniqueness_by_budget + + def name_uniqueness_by_budget + if budget.groups.joins(:translations) + .where(name: name) + .where.not("budget_group_translations.budget_group_id": budget_group_id).any? + errors.add(:name, I18n.t("errors.messages.taken")) + end + end +end diff --git a/app/models/budget/heading.rb b/app/models/budget/heading.rb index 50c5d3992..e63ca2542 100644 --- a/app/models/budget/heading.rb +++ b/app/models/budget/heading.rb @@ -21,7 +21,9 @@ class Budget delegate :budget, :budget_id, to: :group, allow_nil: true - scope :order_by_group_name, -> { includes(:group).order('budget_groups.name', 'budget_headings.name') } + scope :order_by_group_name, -> do + joins(group: :translations).order("budget_group_translations.name DESC", "budget_headings.name") + end scope :allow_custom_content, -> { where(allow_custom_content: true).order(:name) } def name_scoped_by_group diff --git a/app/views/admin/budget_groups/_form.html.erb b/app/views/admin/budget_groups/_form.html.erb index 5da9493d8..cbf53ae30 100644 --- a/app/views/admin/budget_groups/_form.html.erb +++ b/app/views/admin/budget_groups/_form.html.erb @@ -1,10 +1,16 @@ -
- <%= form_for [:admin, @budget, @group], url: path do |f| %> +<%= render "admin/shared/globalize_locales", resource: @group %> - <%= f.text_field :name, - label: t("admin.budget_groups.form.name"), - maxlength: 50, - placeholder: t("admin.budget_groups.form.name") %> +
+ <%= translatable_form_for [:admin, @budget, @group], url: path do |f| %> + + <%= render 'shared/errors', resource: @group %> + + <%= f.translatable_fields do |translations_form| %> + <%= translations_form.text_field :name, + label: t("admin.budget_groups.form.name"), + maxlength: 50, + placeholder: t("admin.budget_groups.form.name") %> + <% end %> <% if @group.persisted? %> <%= f.select :max_votable_headings, diff --git a/app/views/budgets/ballot/_ballot.html.erb b/app/views/budgets/ballot/_ballot.html.erb index 51f2750e9..57082c4d9 100644 --- a/app/views/budgets/ballot/_ballot.html.erb +++ b/app/views/budgets/ballot/_ballot.html.erb @@ -19,7 +19,7 @@
- <% ballot_groups = @ballot.groups.order(name: :asc) %> + <% ballot_groups = @ballot.groups.sort_by_name %> <% ballot_groups.each do |group| %>
@@ -52,7 +52,7 @@
<% end %> - <% no_balloted_groups = @budget.groups.order(name: :asc) - ballot_groups %> + <% no_balloted_groups = @budget.groups.sort_by_name - ballot_groups %> <% no_balloted_groups.each do |group| %>
diff --git a/config/initializers/routes_hierarchy.rb b/config/initializers/routes_hierarchy.rb index d2a95e395..349483767 100644 --- a/config/initializers/routes_hierarchy.rb +++ b/config/initializers/routes_hierarchy.rb @@ -5,7 +5,7 @@ module ActionDispatch::Routing::UrlFor def resource_hierarchy_for(resource) case resource.class.name - when "Budget::Investment", "Budget::Phase" + when "Budget::Investment", "Budget::Phase", "Budget::Group" [resource.budget, resource] when "Milestone" [*resource_hierarchy_for(resource.milestoneable), resource] diff --git a/config/locales/en/general.yml b/config/locales/en/general.yml index a53e0ed8d..17ba3fbfa 100644 --- a/config/locales/en/general.yml +++ b/config/locales/en/general.yml @@ -181,6 +181,7 @@ en: proposal_notification: "Notification" spending_proposal: Spending proposal budget/investment: Investment + budget/group: Budget Group budget/heading: Budget Heading poll/shift: Shift poll/question/answer: Answer diff --git a/config/locales/es/general.yml b/config/locales/es/general.yml index 3acbb6a23..144716687 100644 --- a/config/locales/es/general.yml +++ b/config/locales/es/general.yml @@ -181,6 +181,7 @@ es: proposal_notification: "la notificación" spending_proposal: la propuesta de gasto budget/investment: el proyecto de gasto + budget/group: el grupo de partidas presupuestarias budget/heading: la partida presupuestaria poll/shift: el turno poll/question/answer: la respuesta diff --git a/db/dev_seeds/budgets.rb b/db/dev_seeds/budgets.rb index 72fb523cc..2e346d154 100644 --- a/db/dev_seeds/budgets.rb +++ b/db/dev_seeds/budgets.rb @@ -52,14 +52,22 @@ section "Creating Budgets" do end Budget.all.each do |budget| - city_group = budget.groups.create!(name: I18n.t('seeds.budgets.groups.all_city')) + city_group_params = { + name_en: I18n.t("seeds.budgets.groups.all_city", locale: :en), + name_es: I18n.t("seeds.budgets.groups.all_city", locale: :es) + } + city_group = budget.groups.create!(city_group_params) city_group.headings.create!(name: I18n.t('seeds.budgets.groups.all_city'), price: 1000000, population: 1000000, latitude: '40.416775', longitude: '-3.703790') - districts_group = budget.groups.create!(name: I18n.t('seeds.budgets.groups.districts')) + districts_group_params = { + name_en: I18n.t("seeds.budgets.groups.districts", locale: :en), + name_es: I18n.t("seeds.budgets.groups.districts", locale: :es) + } + districts_group = budget.groups.create!(districts_group_params) districts_group.headings.create!(name: I18n.t('seeds.geozones.north_district'), price: rand(5..10) * 100000, population: 350000, diff --git a/db/migrate/20190120155819_add_budget_group_translations.rb b/db/migrate/20190120155819_add_budget_group_translations.rb new file mode 100644 index 000000000..7be1575d3 --- /dev/null +++ b/db/migrate/20190120155819_add_budget_group_translations.rb @@ -0,0 +1,16 @@ +class AddBudgetGroupTranslations < ActiveRecord::Migration + + def self.up + Budget::Group.create_translation_table!( + { + name: :string + }, + { migrate_data: true } + ) + end + + def self.down + Budget::Group.drop_translation_table! + end + +end diff --git a/db/schema.rb b/db/schema.rb index 02e3db15b..3ed86662a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -138,6 +138,17 @@ ActiveRecord::Schema.define(version: 20190205131722) do add_index "budget_content_blocks", ["heading_id"], name: "index_budget_content_blocks_on_heading_id", using: :btree + create_table "budget_group_translations", force: :cascade do |t| + t.integer "budget_group_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_group_translations", ["budget_group_id"], name: "index_budget_group_translations_on_budget_group_id", using: :btree + add_index "budget_group_translations", ["locale"], name: "index_budget_group_translations_on_locale", using: :btree + create_table "budget_groups", force: :cascade do |t| t.integer "budget_id" t.string "name", limit: 50 diff --git a/spec/features/admin/budget_groups_spec.rb b/spec/features/admin/budget_groups_spec.rb index 869ef55e9..a1bb01bcf 100644 --- a/spec/features/admin/budget_groups_spec.rb +++ b/spec/features/admin/budget_groups_spec.rb @@ -9,6 +9,11 @@ feature "Admin budget groups" do login_as(admin.user) end + it_behaves_like "translatable", + "budget_group", + "edit_admin_budget_group_path", + %w[name] + context "Feature flag" do background do @@ -140,6 +145,30 @@ feature "Admin budget groups" do expect(page).to have_field "Maximum number of headings in which a user can vote", with: "2" end + scenario "Changing name for current locale will update the slug if budget is in draft phase", :js do + group = create(:budget_group, budget: budget) + old_slug = group.slug + + visit edit_admin_budget_group_path(budget, group) + + select "Español", from: "translation_locale" + fill_in "Group name", with: "Spanish name" + click_button "Save group" + + expect(page).to have_content "Group updated successfully" + expect(group.reload.slug).to eq old_slug + + visit edit_admin_budget_group_path(budget, group) + + click_link "English" + fill_in "Group name", with: "New English Name" + click_button "Save group" + + expect(page).to have_content "Group updated successfully" + expect(group.reload.slug).not_to eq old_slug + expect(group.slug).to eq "new-english-name" + end + end context "Update" do @@ -173,7 +202,7 @@ feature "Admin budget groups" do expect(page).not_to have_content "Group updated successfully" expect(page).to have_css("label.error", text: "Group name") - expect(page).to have_content "has already been taken" + expect(page).to have_css("small.error", text: "has already been taken") end end diff --git a/spec/models/budget/group_spec.rb b/spec/models/budget/group_spec.rb index 27392e81f..2ab137ba5 100644 --- a/spec/models/budget/group_spec.rb +++ b/spec/models/budget/group_spec.rb @@ -1,23 +1,35 @@ -require 'rails_helper' +require "rails_helper" describe Budget::Group do - - let(:budget) { create(:budget) } - it_behaves_like "sluggable", updatable_slug_trait: :drafting_budget - describe "name" do - before do - create(:budget_group, budget: budget, name: 'object name') + describe "Validations" do + + let(:budget) { create(:budget) } + let(:group) { create(:budget_group, budget: budget) } + + describe "name" do + before do + group.update(name: "object name") + end + + it "can be repeatead in other budget's groups" do + expect(build(:budget_group, budget: create(:budget), name: "object name")).to be_valid + end + + it "may be repeated for the same group and a different locale" do + group.update(name_fr: "object name") + + expect(group.translations.last).to be_valid + end + + it "must not be repeated for a different group in any locale" do + group.update(name_en: "English", name_es: "Español") + + expect(build(:budget_group, budget: budget, name_en: "English")).not_to be_valid + expect(build(:budget_group, budget: budget, name_en: "Español")).not_to be_valid + end end - it "can be repeatead in other budget's groups" do - expect(build(:budget_group, budget: create(:budget), name: 'object name')).to be_valid - end - - it "must be unique among all budget's groups" do - expect(build(:budget_group, budget: budget, name: 'object name')).not_to be_valid - end end - end diff --git a/spec/shared/features/translatable.rb b/spec/shared/features/translatable.rb index b76467a9c..09235ac69 100644 --- a/spec/shared/features/translatable.rb +++ b/spec/shared/features/translatable.rb @@ -345,6 +345,8 @@ def update_button_text "Update Custom page" when "Widget::Card" "Save card" + when "Budget::Group" + "Save group" else "Save changes" end