Add SDG LocalTarget model
This commit is contained in:
@@ -4,7 +4,7 @@ module SDG::Relatable
|
||||
included do
|
||||
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,
|
||||
through: :sdg_relations,
|
||||
source: :related_sdg,
|
||||
|
||||
37
app/models/sdg/local_target.rb
Normal file
37
app/models/sdg/local_target.rb
Normal 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
|
||||
@@ -6,6 +6,7 @@ class SDG::Target < ApplicationRecord
|
||||
validates :goal, presence: true
|
||||
|
||||
belongs_to :goal
|
||||
has_many :local_targets, dependent: :destroy
|
||||
|
||||
def title
|
||||
I18n.t("sdg.goals.goal_#{goal.code}.targets.target_#{code_key}.title")
|
||||
|
||||
@@ -571,6 +571,10 @@ en:
|
||||
attributes:
|
||||
locale:
|
||||
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:
|
||||
translations_too_short: Is mandatory to provide one translation at least
|
||||
record_invalid: "Validation failed: %{errors}"
|
||||
|
||||
@@ -573,6 +573,10 @@ es:
|
||||
attributes:
|
||||
locale:
|
||||
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:
|
||||
translations_too_short: El obligatorio proporcionar una traducción como mínimo
|
||||
record_invalid: "Error de validación: %{errors}"
|
||||
|
||||
22
db/migrate/20201123124006_create_sdg_local_targets.rb
Normal file
22
db/migrate/20201123124006_create_sdg_local_targets.rb
Normal 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
|
||||
22
db/schema.rb
22
db/schema.rb
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# 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
|
||||
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
|
||||
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|
|
||||
t.string "related_sdg_type"
|
||||
t.bigint "related_sdg_id"
|
||||
|
||||
@@ -6,4 +6,12 @@ FactoryBot.define do
|
||||
factory :sdg_target, class: "SDG::Target" do
|
||||
sequence(:code, 1) { |n| "#{n}.#{n}" }
|
||||
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
|
||||
|
||||
77
spec/models/sdg/local_target_spec.rb
Normal file
77
spec/models/sdg/local_target_spec.rb
Normal 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
|
||||
@@ -3,8 +3,10 @@ require "rails_helper"
|
||||
describe SDG::Relatable do
|
||||
let(:goal) { SDG::Goal[1] }
|
||||
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_target) { SDG::Target["2.3"] }
|
||||
let(:another_local_target) { create(:sdg_local_target, code: "2.3.1") }
|
||||
|
||||
let(:relatable) { create(:proposal) }
|
||||
|
||||
@@ -44,12 +46,32 @@ describe SDG::Relatable do
|
||||
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
|
||||
it "returns all related goals and targets" do
|
||||
relatable.sdg_goals = [goal, another_goal]
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user