Add SDG LocalTarget model

This commit is contained in:
Senén Rodero Rodríguez
2020-11-23 17:00:17 +01:00
parent 427dec8cbd
commit 2ad66409e2
10 changed files with 198 additions and 3 deletions

View File

@@ -4,7 +4,7 @@ module SDG::Relatable
included do included do
has_many :sdg_relations, as: :relatable, dependent: :destroy, class_name: "SDG::Relation" has_many :sdg_relations, as: :relatable, dependent: :destroy, class_name: "SDG::Relation"
%w[SDG::Goal SDG::Target].each do |sdg_type| %w[SDG::Goal SDG::Target SDG::LocalTarget].each do |sdg_type|
has_many sdg_type.constantize.table_name.to_sym, has_many sdg_type.constantize.table_name.to_sym,
through: :sdg_relations, through: :sdg_relations,
source: :related_sdg, source: :related_sdg,

View File

@@ -0,0 +1,37 @@
class SDG::LocalTarget < ApplicationRecord
include Comparable
include SDG::Related
delegate :goal, to: :target
translates :title, touch: true
translates :description, touch: true
include Globalizable
validates_translation :title, presence: true
validates_translation :description, presence: true
validates :code, presence: true, uniqueness: true,
format: ->(local_target) { /\A#{local_target.target&.code}\.\d+/ }
validates :target, presence: true
belongs_to :target
def <=>(local_target)
return unless local_target.class == self.class
[target, numeric_subcode] <=> [local_target.target, local_target.numeric_subcode]
end
protected
def numeric_subcode
subcode.to_i
end
private
def subcode
code.split(".").last
end
end

View File

@@ -6,6 +6,7 @@ class SDG::Target < ApplicationRecord
validates :goal, presence: true validates :goal, presence: true
belongs_to :goal belongs_to :goal
has_many :local_targets, dependent: :destroy
def title def title
I18n.t("sdg.goals.goal_#{goal.code}.targets.target_#{code_key}.title") I18n.t("sdg.goals.goal_#{goal.code}.targets.target_#{code_key}.title")

View File

@@ -571,6 +571,10 @@ en:
attributes: attributes:
locale: locale:
already_translated: Already translated resource already_translated: Already translated resource
sdg/local_target:
attributes:
code:
invalid: "must start with the same code as its target followed by a dot and end with a number"
messages: messages:
translations_too_short: Is mandatory to provide one translation at least translations_too_short: Is mandatory to provide one translation at least
record_invalid: "Validation failed: %{errors}" record_invalid: "Validation failed: %{errors}"

View File

@@ -573,6 +573,10 @@ es:
attributes: attributes:
locale: locale:
already_translated: Recurso ya traducido already_translated: Recurso ya traducido
sdg/local_target:
attributes:
code:
invalid: "debe empezar con el código de su meta seguido de un punto y terminar con un número"
messages: messages:
translations_too_short: El obligatorio proporcionar una traducción como mínimo translations_too_short: El obligatorio proporcionar una traducción como mínimo
record_invalid: "Error de validación: %{errors}" record_invalid: "Error de validación: %{errors}"

View File

@@ -0,0 +1,22 @@
class CreateSDGLocalTargets < ActiveRecord::Migration[5.2]
def change
create_table :sdg_local_targets do |t|
t.references :target
t.string :code
t.timestamps
t.index :code, unique: true
end
create_table :sdg_local_target_translations do |t|
t.bigint :sdg_local_target_id, null: false
t.string :locale, null: false
t.string :title
t.text :description
t.timestamps null: false
t.index :locale
t.index :sdg_local_target_id
end
end
end

View File

@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_11_17_200945) do ActiveRecord::Schema.define(version: 2020_11_23_124006) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm" enable_extension "pg_trgm"
@@ -1304,6 +1304,26 @@ ActiveRecord::Schema.define(version: 2020_11_17_200945) do
t.index ["code"], name: "index_sdg_goals_on_code", unique: true t.index ["code"], name: "index_sdg_goals_on_code", unique: true
end end
create_table "sdg_local_target_translations", force: :cascade do |t|
t.bigint "sdg_local_target_id", null: false
t.string "locale", null: false
t.string "title"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["locale"], name: "index_sdg_local_target_translations_on_locale"
t.index ["sdg_local_target_id"], name: "index_sdg_local_target_translations_on_sdg_local_target_id"
end
create_table "sdg_local_targets", force: :cascade do |t|
t.bigint "target_id"
t.string "code"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["code"], name: "index_sdg_local_targets_on_code", unique: true
t.index ["target_id"], name: "index_sdg_local_targets_on_target_id"
end
create_table "sdg_relations", force: :cascade do |t| create_table "sdg_relations", force: :cascade do |t|
t.string "related_sdg_type" t.string "related_sdg_type"
t.bigint "related_sdg_id" t.bigint "related_sdg_id"

View File

@@ -6,4 +6,12 @@ FactoryBot.define do
factory :sdg_target, class: "SDG::Target" do factory :sdg_target, class: "SDG::Target" do
sequence(:code, 1) { |n| "#{n}.#{n}" } sequence(:code, 1) { |n| "#{n}.#{n}" }
end end
factory :sdg_local_target, class: "SDG::LocalTarget" do
code { "1.1.1" }
sequence(:title) { |n| "Local Target #{n} title" }
sequence(:description) { |n| "Help for Local Target #{n}" }
target { SDG::Target[code.rpartition(".").first] }
end
end end

View File

@@ -0,0 +1,77 @@
require "rails_helper"
describe SDG::LocalTarget do
describe "Concerns" do
it_behaves_like "globalizable", :sdg_local_target
end
it "is valid" do
expect(build(:sdg_local_target)).to be_valid
end
it "is not valid without a title" do
expect(build(:sdg_local_target, title: nil)).not_to be_valid
end
it "is not valid without a description" do
expect(build(:sdg_local_target, description: nil)).not_to be_valid
end
it "is not valid without a code" do
expect(build(:sdg_local_target, code: nil, target: SDG::Target[1.1])).not_to be_valid
end
it "is not valid when code does not include associated target code" do
local_target = build(:sdg_local_target, code: "1.6.1", target: SDG::Target[1.1])
expect(local_target).not_to be_valid
expect(local_target.errors.full_messages).to include "Code must start with the same code as its target followed by a dot and end with a number"
end
it "is not valid when local target code part is not a number" do
local_target = build(:sdg_local_target, code: "1.1.A", target: SDG::Target[1.1])
expect(local_target).not_to be_valid
expect(local_target.errors.full_messages).to include "Code must start with the same code as its target followed by a dot and end with a number"
end
it "is not valid if code is not unique" do
create(:sdg_local_target, code: "1.1.1")
local_target = build(:sdg_local_target, code: "1.1.1")
expect(local_target).not_to be_valid
expect(local_target.errors.full_messages).to include "Code has already been taken"
end
it "is not valid without a target" do
expect(build(:sdg_local_target, target: nil)).not_to be_valid
end
describe "#goal" do
it "returns the target goal" do
local_target = create(:sdg_local_target, code: "1.1.1")
expect(local_target.goal).to eq(SDG::Goal[1])
end
end
describe "#<=>" do
let(:local_target,) { create(:sdg_local_target, code: "10.B.10") }
it "compares using the target first" do
lesser_local_target = create(:sdg_local_target, code: "10.A.1")
greater_local_target = create(:sdg_local_target, code: "11.1.1")
expect(local_target).to be > lesser_local_target
expect(local_target).to be < greater_local_target
end
it "compares using the local target code when the target is the same" do
lesser_local_target = create(:sdg_local_target, code: "10.B.9")
greater_local_target = create(:sdg_local_target, code: "10.B.11")
expect(local_target).to be > lesser_local_target
expect(local_target).to be < greater_local_target
end
end
end

View File

@@ -3,8 +3,10 @@ require "rails_helper"
describe SDG::Relatable do describe SDG::Relatable do
let(:goal) { SDG::Goal[1] } let(:goal) { SDG::Goal[1] }
let(:target) { SDG::Target["1.2"] } let(:target) { SDG::Target["1.2"] }
let(:local_target) { create(:sdg_local_target, code: "1.2.1") }
let(:another_goal) { SDG::Goal[2] } let(:another_goal) { SDG::Goal[2] }
let(:another_target) { SDG::Target["2.3"] } let(:another_target) { SDG::Target["2.3"] }
let(:another_local_target) { create(:sdg_local_target, code: "2.3.1") }
let(:relatable) { create(:proposal) } let(:relatable) { create(:proposal) }
@@ -44,12 +46,32 @@ describe SDG::Relatable do
end end
end end
describe "#sdg_local_targets" do
it "can assign local targets to a model" do
relatable.sdg_local_targets = [local_target, another_local_target]
expect(SDG::Relation.count).to be 2
expect(SDG::Relation.first.relatable).to eq relatable
expect(SDG::Relation.last.relatable).to eq relatable
expect(SDG::Relation.first.related_sdg).to eq local_target
expect(SDG::Relation.last.related_sdg).to eq another_local_target
end
it "can obtain the list of local targets" do
relatable.sdg_local_targets = [local_target, another_local_target]
expect(relatable.reload.sdg_local_targets).to match_array [local_target, another_local_target]
end
end
describe "#related_sdgs" do describe "#related_sdgs" do
it "returns all related goals and targets" do it "returns all related goals and targets" do
relatable.sdg_goals = [goal, another_goal] relatable.sdg_goals = [goal, another_goal]
relatable.sdg_targets = [target, another_target] relatable.sdg_targets = [target, another_target]
relatable.sdg_local_targets = [local_target, another_local_target]
expect(relatable.reload.related_sdgs).to match_array [goal, another_goal, target, another_target] related_sdgs = [goal, another_goal, target, another_target, local_target, another_local_target]
expect(relatable.reload.related_sdgs).to match_array related_sdgs
end end
end end
end end