Make budget headings translatable
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
class Admin::BudgetHeadingsController < Admin::BaseController
|
class Admin::BudgetHeadingsController < Admin::BaseController
|
||||||
|
include Translatable
|
||||||
include FeatureFlags
|
include FeatureFlags
|
||||||
feature_flag :budgets
|
feature_flag :budgets
|
||||||
|
|
||||||
@@ -62,8 +63,8 @@ class Admin::BudgetHeadingsController < Admin::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def budget_heading_params
|
def budget_heading_params
|
||||||
params.require(:budget_heading).permit(:name, :price, :population, :allow_custom_content,
|
valid_attributes = [:price, :population, :allow_custom_content, :latitude, :longitude]
|
||||||
:latitude, :longitude)
|
params.require(:budget_heading).permit(*valid_attributes, translation_params(Budget::Heading))
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -75,8 +75,8 @@ class Valuation::BudgetInvestmentsController < Valuation::BaseController
|
|||||||
def heading_filters
|
def heading_filters
|
||||||
investments = @budget.investments.by_valuator(current_user.valuator.try(:id))
|
investments = @budget.investments.by_valuator(current_user.valuator.try(:id))
|
||||||
.visible_to_valuators.distinct
|
.visible_to_valuators.distinct
|
||||||
|
investment_headings = Budget::Heading.joins(:translations)
|
||||||
investment_headings = Budget::Heading.where(id: investments.pluck(:heading_id).uniq)
|
.where(id: investments.pluck(:heading_id).uniq)
|
||||||
.order(name: :asc)
|
.order(name: :asc)
|
||||||
|
|
||||||
all_headings_filter = [
|
all_headings_filter = [
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
module BudgetHeadingsHelper
|
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_name.map do |heading|
|
||||||
[heading.name_scoped_by_group, heading.id]
|
[heading.name_scoped_by_group, heading.id]
|
||||||
end.uniq
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def heading_link(assigned_heading = nil, budget = nil)
|
def heading_link(assigned_heading = nil, budget = nil)
|
||||||
|
|||||||
@@ -4,13 +4,18 @@ class Budget
|
|||||||
|
|
||||||
include Sluggable
|
include Sluggable
|
||||||
|
|
||||||
|
translates :name, touch: true
|
||||||
|
include Globalizable
|
||||||
|
|
||||||
belongs_to :group
|
belongs_to :group
|
||||||
|
|
||||||
has_many :investments
|
has_many :investments
|
||||||
has_many :content_blocks
|
has_many :content_blocks
|
||||||
|
|
||||||
|
before_validation :assign_model_to_translations
|
||||||
|
|
||||||
|
validates_translation :name, presence: true
|
||||||
validates :group_id, presence: true
|
validates :group_id, presence: true
|
||||||
validates :name, presence: true, uniqueness: { if: :name_exists_in_budget_headings }
|
|
||||||
validates :price, presence: true
|
validates :price, presence: true
|
||||||
validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/
|
validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/
|
||||||
validates :population, numericality: { greater_than: 0 }, allow_nil: true
|
validates :population, numericality: { greater_than: 0 }, allow_nil: true
|
||||||
@@ -21,19 +26,17 @@ 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, -> do
|
scope :i18n, -> { includes(:translations) }
|
||||||
joins(group: :translations).order("budget_group_translations.name DESC", "budget_headings.name")
|
scope :with_group, -> { joins(group: :translations).where("budget_group_translations.locale = ?", I18n.locale) }
|
||||||
end
|
scope :order_by_group_name, -> { i18n.with_group.order("budget_group_translations.name DESC") }
|
||||||
scope :allow_custom_content, -> { where(allow_custom_content: true).order(:name) }
|
scope :order_by_heading_name, -> { i18n.with_group.order("budget_heading_translations.name") }
|
||||||
|
scope :order_by_name, -> { i18n.with_group.order_by_group_name.order_by_heading_name }
|
||||||
|
scope :allow_custom_content, -> { i18n.where(allow_custom_content: true).order(:name) }
|
||||||
|
|
||||||
def name_scoped_by_group
|
def name_scoped_by_group
|
||||||
group.single_heading_group? ? name : "#{group.name}: #{name}"
|
group.single_heading_group? ? name : "#{group.name}: #{name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def name_exists_in_budget_headings
|
|
||||||
group.budget.headings.where(name: name).where.not(id: id).any?
|
|
||||||
end
|
|
||||||
|
|
||||||
def can_be_deleted?
|
def can_be_deleted?
|
||||||
investments.empty?
|
investments.empty?
|
||||||
end
|
end
|
||||||
|
|||||||
14
app/models/budget/heading/translation.rb
Normal file
14
app/models/budget/heading/translation.rb
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
class Budget::Heading::Translation < Globalize::ActiveRecord::Translation
|
||||||
|
delegate :budget, to: :globalized_model
|
||||||
|
|
||||||
|
validate :name_uniqueness_by_budget
|
||||||
|
|
||||||
|
def name_uniqueness_by_budget
|
||||||
|
if budget.headings
|
||||||
|
.joins(:translations)
|
||||||
|
.where(name: name)
|
||||||
|
.where.not("budget_heading_translations.budget_heading_id": budget_heading_id).any?
|
||||||
|
errors.add(:name, I18n.t("errors.messages.taken"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
|
<%= render "admin/shared/globalize_locales", resource: @heading %>
|
||||||
|
|
||||||
<div class="small-12 medium-6">
|
<div class="small-12 medium-6">
|
||||||
|
|
||||||
<%= form_for [:admin, @budget, @group, @heading], url: path do |f| %>
|
<%= translatable_form_for [:admin, @budget, @group, @heading], url: path do |f| %>
|
||||||
|
|
||||||
<%= f.text_field :name,
|
<%= render 'shared/errors', resource: @heading %>
|
||||||
label: t("admin.budget_headings.form.name"),
|
|
||||||
maxlength: 50,
|
<%= f.translatable_fields do |translations_form| %>
|
||||||
placeholder: t("admin.budget_headings.form.name") %>
|
<%= translations_form.text_field :name,
|
||||||
|
label: t("admin.budget_headings.form.name"),
|
||||||
|
maxlength: 50,
|
||||||
|
placeholder: t("admin.budget_headings.form.name") %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<%= f.text_field :price,
|
<%= f.text_field :price,
|
||||||
label: t("admin.budget_headings.form.amount"),
|
label: t("admin.budget_headings.form.amount"),
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ module ActionDispatch::Routing::UrlFor
|
|||||||
case resource.class.name
|
case resource.class.name
|
||||||
when "Budget::Investment", "Budget::Phase", "Budget::Group"
|
when "Budget::Investment", "Budget::Phase", "Budget::Group"
|
||||||
[resource.budget, resource]
|
[resource.budget, resource]
|
||||||
|
when "Budget::Heading"
|
||||||
|
[resource.group.budget, resource.group, resource]
|
||||||
when "Milestone"
|
when "Milestone"
|
||||||
[*resource_hierarchy_for(resource.milestoneable), resource]
|
[*resource_hierarchy_for(resource.milestoneable), resource]
|
||||||
when "ProgressBar"
|
when "ProgressBar"
|
||||||
|
|||||||
@@ -57,37 +57,62 @@ section "Creating Budgets" do
|
|||||||
name_es: I18n.t("seeds.budgets.groups.all_city", locale: :es)
|
name_es: I18n.t("seeds.budgets.groups.all_city", locale: :es)
|
||||||
}
|
}
|
||||||
city_group = budget.groups.create!(city_group_params)
|
city_group = budget.groups.create!(city_group_params)
|
||||||
city_group.headings.create!(name: I18n.t('seeds.budgets.groups.all_city'),
|
|
||||||
price: 1000000,
|
city_heading_params = {
|
||||||
population: 1000000,
|
name_en: I18n.t("seeds.budgets.groups.all_city", locale: :en),
|
||||||
latitude: '40.416775',
|
name_es: I18n.t("seeds.budgets.groups.all_city", locale: :es),
|
||||||
longitude: '-3.703790')
|
price: 1000000,
|
||||||
|
population: 1000000,
|
||||||
|
latitude: "40.416775",
|
||||||
|
longitude: "-3.703790"
|
||||||
|
}
|
||||||
|
city_group.headings.create!(city_heading_params)
|
||||||
|
|
||||||
districts_group_params = {
|
districts_group_params = {
|
||||||
name_en: I18n.t("seeds.budgets.groups.districts", locale: :en),
|
name_en: I18n.t("seeds.budgets.groups.districts", locale: :en),
|
||||||
name_es: I18n.t("seeds.budgets.groups.districts", locale: :es)
|
name_es: I18n.t("seeds.budgets.groups.districts", locale: :es)
|
||||||
}
|
}
|
||||||
districts_group = budget.groups.create!(districts_group_params)
|
districts_group = budget.groups.create!(districts_group_params)
|
||||||
districts_group.headings.create!(name: I18n.t('seeds.geozones.north_district'),
|
|
||||||
price: rand(5..10) * 100000,
|
north_heading_params = {
|
||||||
population: 350000,
|
name_en: I18n.t("seeds.geozones.north_district", locale: :en),
|
||||||
latitude: '40.416775',
|
name_es: I18n.t("seeds.geozones.north_district", locale: :es),
|
||||||
longitude: '-3.703790')
|
price: rand(5..10) * 100000,
|
||||||
districts_group.headings.create!(name: I18n.t('seeds.geozones.west_district'),
|
population: 350000,
|
||||||
price: rand(5..10) * 100000,
|
latitude: "40.416775",
|
||||||
population: 300000,
|
longitude: "-3.703790"
|
||||||
latitude: '40.416775',
|
}
|
||||||
longitude: '-3.703790')
|
districts_group.headings.create!(north_heading_params)
|
||||||
districts_group.headings.create!(name: I18n.t('seeds.geozones.east_district'),
|
|
||||||
price: rand(5..10) * 100000,
|
west_heading_params = {
|
||||||
population: 200000,
|
name_en: I18n.t("seeds.geozones.west_district", locale: :en),
|
||||||
latitude: '40.416775',
|
name_es: I18n.t("seeds.geozones.west_district", locale: :es),
|
||||||
longitude: '-3.703790')
|
price: rand(5..10) * 100000,
|
||||||
districts_group.headings.create!(name: I18n.t('seeds.geozones.central_district'),
|
population: 300000,
|
||||||
price: rand(5..10) * 100000,
|
latitude: "40.416775",
|
||||||
population: 150000,
|
longitude: "-3.703790"
|
||||||
latitude: '40.416775',
|
}
|
||||||
longitude: '-3.703790')
|
districts_group.headings.create!(west_heading_params)
|
||||||
|
|
||||||
|
east_heading_params = {
|
||||||
|
name_en: I18n.t("seeds.geozones.east_district", locale: :en),
|
||||||
|
name_es: I18n.t("seeds.geozones.east_district", locale: :es),
|
||||||
|
price: rand(5..10) * 100000,
|
||||||
|
population: 200000,
|
||||||
|
latitude: "40.416775",
|
||||||
|
longitude: "-3.703790"
|
||||||
|
}
|
||||||
|
districts_group.headings.create!(east_heading_params)
|
||||||
|
|
||||||
|
central_heading_params = {
|
||||||
|
name_en: I18n.t("seeds.geozones.central_district", locale: :en),
|
||||||
|
name_es: I18n.t("seeds.geozones.central_district", locale: :es),
|
||||||
|
price: rand(5..10) * 100000,
|
||||||
|
population: 150000,
|
||||||
|
latitude: "40.416775",
|
||||||
|
longitude: "-3.703790"
|
||||||
|
}
|
||||||
|
districts_group.headings.create!(central_heading_params)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
16
db/migrate/20190121171237_add_budget_heading_translations.rb
Normal file
16
db/migrate/20190121171237_add_budget_heading_translations.rb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
class AddBudgetHeadingTranslations < ActiveRecord::Migration
|
||||||
|
|
||||||
|
def self.up
|
||||||
|
Budget::Heading.create_translation_table!(
|
||||||
|
{
|
||||||
|
name: :string
|
||||||
|
},
|
||||||
|
{ migrate_data: true }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
Budget::Heading.drop_translation_table!
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
11
db/schema.rb
11
db/schema.rb
@@ -158,6 +158,17 @@ ActiveRecord::Schema.define(version: 20190205131722) do
|
|||||||
|
|
||||||
add_index "budget_groups", ["budget_id"], name: "index_budget_groups_on_budget_id", using: :btree
|
add_index "budget_groups", ["budget_id"], name: "index_budget_groups_on_budget_id", using: :btree
|
||||||
|
|
||||||
|
create_table "budget_heading_translations", force: :cascade do |t|
|
||||||
|
t.integer "budget_heading_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_heading_translations", ["budget_heading_id"], name: "index_budget_heading_translations_on_budget_heading_id", using: :btree
|
||||||
|
add_index "budget_heading_translations", ["locale"], name: "index_budget_heading_translations_on_locale", using: :btree
|
||||||
|
|
||||||
create_table "budget_headings", force: :cascade do |t|
|
create_table "budget_headings", force: :cascade do |t|
|
||||||
t.integer "group_id"
|
t.integer "group_id"
|
||||||
t.string "name", limit: 50
|
t.string "name", limit: 50
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ feature "Admin budget headings" do
|
|||||||
login_as(admin.user)
|
login_as(admin.user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like "translatable",
|
||||||
|
"budget_heading",
|
||||||
|
"edit_admin_budget_group_heading_path",
|
||||||
|
%w[name]
|
||||||
|
|
||||||
context "Feature flag" do
|
context "Feature flag" do
|
||||||
|
|
||||||
background do
|
background do
|
||||||
@@ -151,6 +156,30 @@ feature "Admin budget headings" do
|
|||||||
expect(find_field("Allow content block")).not_to be_checked
|
expect(find_field("Allow content block")).not_to be_checked
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scenario "Changing name for current locale will update the slug if budget is in draft phase", :js do
|
||||||
|
heading = create(:budget_heading, group: group)
|
||||||
|
old_slug = heading.slug
|
||||||
|
|
||||||
|
visit edit_admin_budget_group_heading_path(budget, group, heading)
|
||||||
|
|
||||||
|
select "Español", from: "translation_locale"
|
||||||
|
fill_in "Heading name", with: "Spanish name"
|
||||||
|
click_button "Save heading"
|
||||||
|
|
||||||
|
expect(page).to have_content "Heading updated successfully"
|
||||||
|
expect(heading.reload.slug).to eq old_slug
|
||||||
|
|
||||||
|
visit edit_admin_budget_group_heading_path(budget, group, heading)
|
||||||
|
|
||||||
|
click_link "English"
|
||||||
|
fill_in "Heading name", with: "New English Name"
|
||||||
|
click_button "Save heading"
|
||||||
|
|
||||||
|
expect(page).to have_content "Heading updated successfully"
|
||||||
|
expect(heading.reload.slug).not_to eq old_slug
|
||||||
|
expect(heading.slug).to eq "new-english-name"
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "Update" do
|
context "Update" do
|
||||||
@@ -203,7 +232,7 @@ feature "Admin budget headings" do
|
|||||||
|
|
||||||
expect(page).not_to have_content "Heading updated successfully"
|
expect(page).not_to have_content "Heading updated successfully"
|
||||||
expect(page).to have_css("label.error", text: "Heading name")
|
expect(page).to have_css("label.error", text: "Heading 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
|
||||||
|
|||||||
@@ -948,9 +948,9 @@ feature 'Budget Investments' do
|
|||||||
|
|
||||||
select_options = find('#budget_investment_heading_id').all('option').collect(&:text)
|
select_options = find('#budget_investment_heading_id').all('option').collect(&:text)
|
||||||
expect(select_options.first).to eq('')
|
expect(select_options.first).to eq('')
|
||||||
expect(select_options.second).to eq('Health: More health professionals')
|
expect(select_options.second).to eq("Toda la ciudad")
|
||||||
expect(select_options.third).to eq('Health: More hospitals')
|
expect(select_options.third).to eq("Health: More health professionals")
|
||||||
expect(select_options.fourth).to eq('Toda la ciudad')
|
expect(select_options.fourth).to eq("Health: More hospitals")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -14,20 +14,41 @@ describe Budget::Heading do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "name" do
|
describe "name" do
|
||||||
|
|
||||||
|
let(:heading) { create(:budget_heading, group: group) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
create(:budget_heading, group: group, name: 'object name')
|
heading.update(name_en: "object name")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can be repeatead in other budget's groups" do
|
it "can be repeatead in other budgets" do
|
||||||
expect(build(:budget_heading, group: create(:budget_group), name: 'object name')).to be_valid
|
new_budget = create(:budget)
|
||||||
|
new_group = create(:budget_group, budget: new_budget)
|
||||||
|
|
||||||
|
expect(build(:budget_heading, group: new_group, name_en: "object name")).to be_valid
|
||||||
end
|
end
|
||||||
|
|
||||||
it "must be unique among all budget's groups" do
|
it "must be unique among all budget's groups" do
|
||||||
expect(build(:budget_heading, group: create(:budget_group, budget: budget), name: 'object name')).not_to be_valid
|
new_group = create(:budget_group, budget: budget)
|
||||||
|
|
||||||
|
expect(build(:budget_heading, group: new_group, name_en: "object name")).not_to be_valid
|
||||||
end
|
end
|
||||||
|
|
||||||
it "must be unique among all it's group" do
|
it "must be unique among all it's group" do
|
||||||
expect(build(:budget_heading, group: group, name: 'object name')).not_to be_valid
|
expect(build(:budget_heading, group: group, name_en: "object name")).not_to be_valid
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can be repeated for the same heading and a different locale" do
|
||||||
|
heading.update(name_fr: "object name")
|
||||||
|
|
||||||
|
expect(heading.translations.last).to be_valid
|
||||||
|
end
|
||||||
|
|
||||||
|
it "must not be repeated for a different heading in any locale" do
|
||||||
|
heading.update(name_en: "English", name_es: "Español")
|
||||||
|
|
||||||
|
expect(build(:budget_heading, group: group, name_en: "English")).not_to be_valid
|
||||||
|
expect(build(:budget_heading, group: group, name_en: "Español")).not_to be_valid
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -259,4 +280,45 @@ describe Budget::Heading do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "scope order_by_group_name" do
|
||||||
|
it "only sort headings using the group name (DESC) in the current locale" do
|
||||||
|
last_group = create(:budget_group, name_en: "CCC", name_es: "BBB")
|
||||||
|
first_group = create(:budget_group, name_en: "DDD", name_es: "AAA")
|
||||||
|
|
||||||
|
last_heading = create(:budget_heading, group: last_group, name: "Name")
|
||||||
|
first_heading = create(:budget_heading, group: first_group, name: "Name")
|
||||||
|
|
||||||
|
expect(Budget::Heading.order_by_group_name.count).to be 2
|
||||||
|
expect(Budget::Heading.order_by_group_name.first).to eq first_heading
|
||||||
|
expect(Budget::Heading.order_by_group_name.last).to eq last_heading
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "scope order_by_name" do
|
||||||
|
it "returns headings sorted by DESC group name first and then ASC heading name" do
|
||||||
|
last_group = create(:budget_group, name: "Group A")
|
||||||
|
first_group = create(:budget_group, name: "Group B")
|
||||||
|
|
||||||
|
heading4 = create(:budget_heading, group: last_group, name: "Name B")
|
||||||
|
heading3 = create(:budget_heading, group: last_group, name: "Name A")
|
||||||
|
heading2 = create(:budget_heading, group: first_group, name: "Name D")
|
||||||
|
heading1 = create(:budget_heading, group: first_group, name: "Name C")
|
||||||
|
|
||||||
|
sorted_headings = [heading1, heading2, heading3, heading4]
|
||||||
|
expect(Budget::Heading.order_by_name.to_a).to eq sorted_headings
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "scope allow_custom_content" do
|
||||||
|
it "returns headings with allow_custom_content order by name" do
|
||||||
|
excluded_heading = create(:budget_heading, name: "Name A")
|
||||||
|
last_heading = create(:budget_heading, allow_custom_content: true, name: "Name C")
|
||||||
|
first_heading = create(:budget_heading, allow_custom_content: true, name: "Name B")
|
||||||
|
|
||||||
|
expect(Budget::Heading.allow_custom_content.count).to be 2
|
||||||
|
expect(Budget::Heading.allow_custom_content.first).to eq first_heading
|
||||||
|
expect(Budget::Heading.allow_custom_content.last).to eq last_heading
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -347,6 +347,8 @@ def update_button_text
|
|||||||
"Save card"
|
"Save card"
|
||||||
when "Budget::Group"
|
when "Budget::Group"
|
||||||
"Save group"
|
"Save group"
|
||||||
|
when "Budget::Heading"
|
||||||
|
"Save heading"
|
||||||
else
|
else
|
||||||
"Save changes"
|
"Save changes"
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user