Make budget groups translatable
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
class Admin::BudgetGroupsController < Admin::BaseController
|
class Admin::BudgetGroupsController < Admin::BaseController
|
||||||
|
include Translatable
|
||||||
include FeatureFlags
|
include FeatureFlags
|
||||||
feature_flag :budgets
|
feature_flag :budgets
|
||||||
|
|
||||||
@@ -57,7 +58,8 @@ class Admin::BudgetGroupsController < Admin::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def budget_group_params
|
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
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module BudgetHeadingsHelper
|
|||||||
def budget_heading_select_options(budget)
|
def budget_heading_select_options(budget)
|
||||||
budget.headings.order_by_group_name.map do |heading|
|
budget.headings.order_by_group_name.map do |heading|
|
||||||
[heading.name_scoped_by_group, heading.id]
|
[heading.name_scoped_by_group, heading.id]
|
||||||
end
|
end.uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
def heading_link(assigned_heading = nil, budget = nil)
|
def heading_link(assigned_heading = nil, budget = nil)
|
||||||
|
|||||||
@@ -2,14 +2,21 @@ class Budget
|
|||||||
class Group < ActiveRecord::Base
|
class Group < ActiveRecord::Base
|
||||||
include Sluggable
|
include Sluggable
|
||||||
|
|
||||||
|
translates :name, touch: true
|
||||||
|
include Globalizable
|
||||||
|
|
||||||
belongs_to :budget
|
belongs_to :budget
|
||||||
|
|
||||||
has_many :headings, dependent: :destroy
|
has_many :headings, dependent: :destroy
|
||||||
|
|
||||||
|
before_validation :assign_model_to_translations
|
||||||
|
|
||||||
|
validates_translation :name, presence: true
|
||||||
validates :budget_id, presence: true
|
validates :budget_id, presence: true
|
||||||
validates :name, presence: true, uniqueness: { scope: :budget }
|
|
||||||
validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/
|
validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/
|
||||||
|
|
||||||
|
scope :sort_by_name, -> { includes(:translations).order(:name) }
|
||||||
|
|
||||||
def single_heading_group?
|
def single_heading_group?
|
||||||
headings.count == 1
|
headings.count == 1
|
||||||
end
|
end
|
||||||
|
|||||||
13
app/models/budget/group/translation.rb
Normal file
13
app/models/budget/group/translation.rb
Normal file
@@ -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
|
||||||
@@ -21,7 +21,9 @@ class Budget
|
|||||||
|
|
||||||
delegate :budget, :budget_id, to: :group, allow_nil: true
|
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) }
|
scope :allow_custom_content, -> { where(allow_custom_content: true).order(:name) }
|
||||||
|
|
||||||
def name_scoped_by_group
|
def name_scoped_by_group
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
<div class="small-12 medium-6">
|
<%= render "admin/shared/globalize_locales", resource: @group %>
|
||||||
<%= form_for [:admin, @budget, @group], url: path do |f| %>
|
|
||||||
|
|
||||||
<%= f.text_field :name,
|
<div class="small-12 medium-6">
|
||||||
|
<%= 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"),
|
label: t("admin.budget_groups.form.name"),
|
||||||
maxlength: 50,
|
maxlength: 50,
|
||||||
placeholder: t("admin.budget_groups.form.name") %>
|
placeholder: t("admin.budget_groups.form.name") %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% if @group.persisted? %>
|
<% if @group.persisted? %>
|
||||||
<%= f.select :max_votable_headings,
|
<%= f.select :max_votable_headings,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row ballot">
|
<div class="row ballot">
|
||||||
<% ballot_groups = @ballot.groups.order(name: :asc) %>
|
<% ballot_groups = @ballot.groups.sort_by_name %>
|
||||||
<% ballot_groups.each do |group| %>
|
<% ballot_groups.each do |group| %>
|
||||||
<div id="<%= dom_id(group) %>" class="small-12 medium-6 column end">
|
<div id="<%= dom_id(group) %>" class="small-12 medium-6 column end">
|
||||||
<div class="margin-top ballot-content">
|
<div class="margin-top ballot-content">
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% 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| %>
|
<% no_balloted_groups.each do |group| %>
|
||||||
<div id="<%= dom_id(group) %>" class="small-12 medium-6 column end">
|
<div id="<%= dom_id(group) %>" class="small-12 medium-6 column end">
|
||||||
<div class="margin-top ballot-content">
|
<div class="margin-top ballot-content">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
module ActionDispatch::Routing::UrlFor
|
module ActionDispatch::Routing::UrlFor
|
||||||
def resource_hierarchy_for(resource)
|
def resource_hierarchy_for(resource)
|
||||||
case resource.class.name
|
case resource.class.name
|
||||||
when "Budget::Investment", "Budget::Phase"
|
when "Budget::Investment", "Budget::Phase", "Budget::Group"
|
||||||
[resource.budget, resource]
|
[resource.budget, resource]
|
||||||
when "Milestone"
|
when "Milestone"
|
||||||
[*resource_hierarchy_for(resource.milestoneable), resource]
|
[*resource_hierarchy_for(resource.milestoneable), resource]
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ en:
|
|||||||
proposal_notification: "Notification"
|
proposal_notification: "Notification"
|
||||||
spending_proposal: Spending proposal
|
spending_proposal: Spending proposal
|
||||||
budget/investment: Investment
|
budget/investment: Investment
|
||||||
|
budget/group: Budget Group
|
||||||
budget/heading: Budget Heading
|
budget/heading: Budget Heading
|
||||||
poll/shift: Shift
|
poll/shift: Shift
|
||||||
poll/question/answer: Answer
|
poll/question/answer: Answer
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ es:
|
|||||||
proposal_notification: "la notificación"
|
proposal_notification: "la notificación"
|
||||||
spending_proposal: la propuesta de gasto
|
spending_proposal: la propuesta de gasto
|
||||||
budget/investment: el proyecto de gasto
|
budget/investment: el proyecto de gasto
|
||||||
|
budget/group: el grupo de partidas presupuestarias
|
||||||
budget/heading: la partida presupuestaria
|
budget/heading: la partida presupuestaria
|
||||||
poll/shift: el turno
|
poll/shift: el turno
|
||||||
poll/question/answer: la respuesta
|
poll/question/answer: la respuesta
|
||||||
|
|||||||
@@ -52,14 +52,22 @@ section "Creating Budgets" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
Budget.all.each do |budget|
|
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'),
|
city_group.headings.create!(name: I18n.t('seeds.budgets.groups.all_city'),
|
||||||
price: 1000000,
|
price: 1000000,
|
||||||
population: 1000000,
|
population: 1000000,
|
||||||
latitude: '40.416775',
|
latitude: '40.416775',
|
||||||
longitude: '-3.703790')
|
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'),
|
districts_group.headings.create!(name: I18n.t('seeds.geozones.north_district'),
|
||||||
price: rand(5..10) * 100000,
|
price: rand(5..10) * 100000,
|
||||||
population: 350000,
|
population: 350000,
|
||||||
|
|||||||
16
db/migrate/20190120155819_add_budget_group_translations.rb
Normal file
16
db/migrate/20190120155819_add_budget_group_translations.rb
Normal file
@@ -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
|
||||||
11
db/schema.rb
11
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
|
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|
|
create_table "budget_groups", force: :cascade do |t|
|
||||||
t.integer "budget_id"
|
t.integer "budget_id"
|
||||||
t.string "name", limit: 50
|
t.string "name", limit: 50
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ feature "Admin budget groups" do
|
|||||||
login_as(admin.user)
|
login_as(admin.user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like "translatable",
|
||||||
|
"budget_group",
|
||||||
|
"edit_admin_budget_group_path",
|
||||||
|
%w[name]
|
||||||
|
|
||||||
context "Feature flag" do
|
context "Feature flag" do
|
||||||
|
|
||||||
background 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"
|
expect(page).to have_field "Maximum number of headings in which a user can vote", with: "2"
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
context "Update" do
|
context "Update" do
|
||||||
@@ -173,7 +202,7 @@ feature "Admin budget groups" do
|
|||||||
|
|
||||||
expect(page).not_to have_content "Group updated successfully"
|
expect(page).not_to have_content "Group updated successfully"
|
||||||
expect(page).to have_css("label.error", text: "Group name")
|
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
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,23 +1,35 @@
|
|||||||
require 'rails_helper'
|
require "rails_helper"
|
||||||
|
|
||||||
describe Budget::Group do
|
describe Budget::Group do
|
||||||
|
it_behaves_like "sluggable", updatable_slug_trait: :drafting_budget
|
||||||
|
|
||||||
|
describe "Validations" do
|
||||||
|
|
||||||
let(:budget) { create(:budget) }
|
let(:budget) { create(:budget) }
|
||||||
|
let(:group) { create(:budget_group, budget: budget) }
|
||||||
it_behaves_like "sluggable", updatable_slug_trait: :drafting_budget
|
|
||||||
|
|
||||||
describe "name" do
|
describe "name" do
|
||||||
before do
|
before do
|
||||||
create(:budget_group, budget: budget, name: 'object name')
|
group.update(name: "object name")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can be repeatead in other budget's groups" do
|
it "can be repeatead in other budget's groups" do
|
||||||
expect(build(:budget_group, budget: create(:budget), name: 'object name')).to be_valid
|
expect(build(:budget_group, budget: create(:budget), name: "object name")).to be_valid
|
||||||
end
|
end
|
||||||
|
|
||||||
it "must be unique among all budget's groups" do
|
it "may be repeated for the same group and a different locale" do
|
||||||
expect(build(:budget_group, budget: budget, name: 'object name')).not_to be_valid
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -345,6 +345,8 @@ def update_button_text
|
|||||||
"Update Custom page"
|
"Update Custom page"
|
||||||
when "Widget::Card"
|
when "Widget::Card"
|
||||||
"Save card"
|
"Save card"
|
||||||
|
when "Budget::Group"
|
||||||
|
"Save group"
|
||||||
else
|
else
|
||||||
"Save changes"
|
"Save changes"
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user