Make budget headings translatable

This commit is contained in:
Julian Herrero
2019-01-22 18:18:24 +01:00
parent 1c35ec99c1
commit 922600252c
14 changed files with 225 additions and 54 deletions

View File

@@ -1,4 +1,5 @@
class Admin::BudgetHeadingsController < Admin::BaseController
include Translatable
include FeatureFlags
feature_flag :budgets
@@ -62,8 +63,8 @@ class Admin::BudgetHeadingsController < Admin::BaseController
end
def budget_heading_params
params.require(:budget_heading).permit(:name, :price, :population, :allow_custom_content,
:latitude, :longitude)
valid_attributes = [:price, :population, :allow_custom_content, :latitude, :longitude]
params.require(:budget_heading).permit(*valid_attributes, translation_params(Budget::Heading))
end
end

View File

@@ -75,8 +75,8 @@ class Valuation::BudgetInvestmentsController < Valuation::BaseController
def heading_filters
investments = @budget.investments.by_valuator(current_user.valuator.try(:id))
.visible_to_valuators.distinct
investment_headings = Budget::Heading.where(id: investments.pluck(:heading_id).uniq)
investment_headings = Budget::Heading.joins(:translations)
.where(id: investments.pluck(:heading_id).uniq)
.order(name: :asc)
all_headings_filter = [

View File

@@ -1,9 +1,9 @@
module BudgetHeadingsHelper
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]
end.uniq
end
end
def heading_link(assigned_heading = nil, budget = nil)

View File

@@ -4,13 +4,18 @@ class Budget
include Sluggable
translates :name, touch: true
include Globalizable
belongs_to :group
has_many :investments
has_many :content_blocks
before_validation :assign_model_to_translations
validates_translation :name, presence: true
validates :group_id, presence: true
validates :name, presence: true, uniqueness: { if: :name_exists_in_budget_headings }
validates :price, presence: true
validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/
validates :population, numericality: { greater_than: 0 }, allow_nil: true
@@ -21,19 +26,17 @@ class Budget
delegate :budget, :budget_id, to: :group, allow_nil: true
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 :i18n, -> { includes(:translations) }
scope :with_group, -> { joins(group: :translations).where("budget_group_translations.locale = ?", I18n.locale) }
scope :order_by_group_name, -> { i18n.with_group.order("budget_group_translations.name DESC") }
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
group.single_heading_group? ? name : "#{group.name}: #{name}"
end
def name_exists_in_budget_headings
group.budget.headings.where(name: name).where.not(id: id).any?
end
def can_be_deleted?
investments.empty?
end

View 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

View File

@@ -1,11 +1,17 @@
<%= render "admin/shared/globalize_locales", resource: @heading %>
<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 %>
<%= f.translatable_fields do |translations_form| %>
<%= 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,
label: t("admin.budget_headings.form.amount"),

View File

@@ -7,6 +7,8 @@ module ActionDispatch::Routing::UrlFor
case resource.class.name
when "Budget::Investment", "Budget::Phase", "Budget::Group"
[resource.budget, resource]
when "Budget::Heading"
[resource.group.budget, resource.group, resource]
when "Milestone"
[*resource_hierarchy_for(resource.milestoneable), resource]
when "ProgressBar"

View File

@@ -57,37 +57,62 @@ section "Creating Budgets" do
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_heading_params = {
name_en: I18n.t("seeds.budgets.groups.all_city", locale: :en),
name_es: I18n.t("seeds.budgets.groups.all_city", locale: :es),
price: 1000000,
population: 1000000,
latitude: '40.416775',
longitude: '-3.703790')
latitude: "40.416775",
longitude: "-3.703790"
}
city_group.headings.create!(city_heading_params)
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'),
north_heading_params = {
name_en: I18n.t("seeds.geozones.north_district", locale: :en),
name_es: I18n.t("seeds.geozones.north_district", locale: :es),
price: rand(5..10) * 100000,
population: 350000,
latitude: '40.416775',
longitude: '-3.703790')
districts_group.headings.create!(name: I18n.t('seeds.geozones.west_district'),
latitude: "40.416775",
longitude: "-3.703790"
}
districts_group.headings.create!(north_heading_params)
west_heading_params = {
name_en: I18n.t("seeds.geozones.west_district", locale: :en),
name_es: I18n.t("seeds.geozones.west_district", locale: :es),
price: rand(5..10) * 100000,
population: 300000,
latitude: '40.416775',
longitude: '-3.703790')
districts_group.headings.create!(name: I18n.t('seeds.geozones.east_district'),
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!(name: I18n.t('seeds.geozones.central_district'),
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')
latitude: "40.416775",
longitude: "-3.703790"
}
districts_group.headings.create!(central_heading_params)
end
end

View 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

View File

@@ -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
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|
t.integer "group_id"
t.string "name", limit: 50

View File

@@ -10,6 +10,11 @@ feature "Admin budget headings" do
login_as(admin.user)
end
it_behaves_like "translatable",
"budget_heading",
"edit_admin_budget_group_heading_path",
%w[name]
context "Feature flag" do
background do
@@ -151,6 +156,30 @@ feature "Admin budget headings" do
expect(find_field("Allow content block")).not_to be_checked
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
context "Update" do
@@ -203,7 +232,7 @@ feature "Admin budget headings" do
expect(page).not_to have_content "Heading updated successfully"
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

View File

@@ -948,9 +948,9 @@ feature 'Budget Investments' do
select_options = find('#budget_investment_heading_id').all('option').collect(&:text)
expect(select_options.first).to eq('')
expect(select_options.second).to eq('Health: More health professionals')
expect(select_options.third).to eq('Health: More hospitals')
expect(select_options.fourth).to eq('Toda la ciudad')
expect(select_options.second).to eq("Toda la ciudad")
expect(select_options.third).to eq("Health: More health professionals")
expect(select_options.fourth).to eq("Health: More hospitals")
end
end

View File

@@ -14,20 +14,41 @@ describe Budget::Heading do
end
describe "name" do
let(:heading) { create(:budget_heading, group: group) }
before do
create(:budget_heading, group: group, name: 'object name')
heading.update(name_en: "object name")
end
it "can be repeatead in other budget's groups" do
expect(build(:budget_heading, group: create(:budget_group), name: 'object name')).to be_valid
it "can be repeatead in other budgets" do
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
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
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
@@ -259,4 +280,45 @@ describe Budget::Heading do
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

View File

@@ -347,6 +347,8 @@ def update_button_text
"Save card"
when "Budget::Group"
"Save group"
when "Budget::Heading"
"Save heading"
else
"Save changes"
end