@@ -13,6 +13,7 @@ class Budget
|
||||
include Imageable
|
||||
include Mappable
|
||||
include Documentable
|
||||
include SDG::Relatable
|
||||
|
||||
acts_as_taggable_on :valuation_tags
|
||||
acts_as_votable
|
||||
|
||||
18
app/models/concerns/sdg/relatable.rb
Normal file
18
app/models/concerns/sdg/relatable.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
module SDG::Relatable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :sdg_relations, as: :relatable, dependent: :destroy, class_name: "SDG::Relation"
|
||||
|
||||
%w[SDG::Goal SDG::Target].each do |sdg_type|
|
||||
has_many sdg_type.constantize.table_name.to_sym,
|
||||
through: :sdg_relations,
|
||||
source: :related_sdg,
|
||||
source_type: sdg_type
|
||||
end
|
||||
end
|
||||
|
||||
def related_sdgs
|
||||
sdg_relations.map(&:related_sdg)
|
||||
end
|
||||
end
|
||||
26
app/models/concerns/sdg/related.rb
Normal file
26
app/models/concerns/sdg/related.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
module SDG::Related
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
RELATABLE_TYPES = %w[
|
||||
Budget::Investment
|
||||
Debate
|
||||
Legislation::Process
|
||||
Poll
|
||||
Proposal
|
||||
].freeze
|
||||
|
||||
included do
|
||||
has_many :relations, as: :related_sdg, dependent: :destroy
|
||||
|
||||
RELATABLE_TYPES.each do |relatable_type|
|
||||
has_many relatable_type.constantize.table_name.to_sym,
|
||||
through: :relations,
|
||||
source: :relatable,
|
||||
source_type: relatable_type
|
||||
end
|
||||
end
|
||||
|
||||
def relatables
|
||||
relations.map(&:relatable)
|
||||
end
|
||||
end
|
||||
@@ -14,6 +14,7 @@ class Debate < ApplicationRecord
|
||||
include Relationable
|
||||
include Notifiable
|
||||
include Randomizable
|
||||
include SDG::Relatable
|
||||
|
||||
acts_as_votable
|
||||
acts_as_paranoid column: :hidden_at
|
||||
|
||||
@@ -4,6 +4,7 @@ class Legislation::Process < ApplicationRecord
|
||||
include Milestoneable
|
||||
include Imageable
|
||||
include Documentable
|
||||
include SDG::Relatable
|
||||
|
||||
acts_as_paranoid column: :hidden_at
|
||||
acts_as_taggable_on :customs
|
||||
|
||||
@@ -8,6 +8,7 @@ class Poll < ApplicationRecord
|
||||
include Sluggable
|
||||
include StatsVersionable
|
||||
include Reportable
|
||||
include SDG::Relatable
|
||||
|
||||
translates :name, touch: true
|
||||
translates :summary, touch: true
|
||||
|
||||
@@ -19,6 +19,7 @@ class Proposal < ApplicationRecord
|
||||
include Relationable
|
||||
include Milestoneable
|
||||
include Randomizable
|
||||
include SDG::Relatable
|
||||
|
||||
acts_as_votable
|
||||
acts_as_paranoid column: :hidden_at
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
class SDG::Goal < ApplicationRecord
|
||||
include SDG::Related
|
||||
|
||||
validates :code, presence: true, uniqueness: true, inclusion: { in: 1..17 }
|
||||
|
||||
has_many :targets, dependent: :destroy
|
||||
|
||||
6
app/models/sdg/relation.rb
Normal file
6
app/models/sdg/relation.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
class SDG::Relation < ApplicationRecord
|
||||
validates :related_sdg_id, uniqueness: { scope: [:related_sdg_type, :relatable_id, :relatable_type] }
|
||||
|
||||
belongs_to :relatable, polymorphic: true, optional: false
|
||||
belongs_to :related_sdg, polymorphic: true, optional: false
|
||||
end
|
||||
@@ -1,5 +1,6 @@
|
||||
class SDG::Target < ApplicationRecord
|
||||
include Comparable
|
||||
include SDG::Related
|
||||
|
||||
validates :code, presence: true, uniqueness: true
|
||||
validates :goal, presence: true
|
||||
|
||||
12
db/migrate/20201117200945_create_sdg_relations.rb
Normal file
12
db/migrate/20201117200945_create_sdg_relations.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
class CreateSDGRelations < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :sdg_relations do |t|
|
||||
t.references :related_sdg, polymorphic: true
|
||||
t.references :relatable, polymorphic: true
|
||||
|
||||
t.index [:related_sdg_id, :related_sdg_type, :relatable_id, :relatable_type], name: "sdg_relations_unique", unique: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
14
db/schema.rb
14
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_12_155047) do
|
||||
ActiveRecord::Schema.define(version: 2020_11_17_200945) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_trgm"
|
||||
@@ -1304,6 +1304,18 @@ ActiveRecord::Schema.define(version: 2020_11_12_155047) do
|
||||
t.index ["code"], name: "index_sdg_goals_on_code", unique: true
|
||||
end
|
||||
|
||||
create_table "sdg_relations", force: :cascade do |t|
|
||||
t.string "related_sdg_type"
|
||||
t.bigint "related_sdg_id"
|
||||
t.string "relatable_type"
|
||||
t.bigint "relatable_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["relatable_type", "relatable_id"], name: "index_sdg_relations_on_relatable_type_and_relatable_id"
|
||||
t.index ["related_sdg_id", "related_sdg_type", "relatable_id", "relatable_type"], name: "sdg_relations_unique", unique: true
|
||||
t.index ["related_sdg_type", "related_sdg_id"], name: "index_sdg_relations_on_related_sdg_type_and_related_sdg_id"
|
||||
end
|
||||
|
||||
create_table "sdg_targets", force: :cascade do |t|
|
||||
t.bigint "goal_id"
|
||||
t.string "code", null: false
|
||||
|
||||
55
spec/models/sdg/relatable_spec.rb
Normal file
55
spec/models/sdg/relatable_spec.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
require "rails_helper"
|
||||
|
||||
describe SDG::Relatable do
|
||||
let(:goal) { SDG::Goal[1] }
|
||||
let(:target) { SDG::Target["1.2"] }
|
||||
let(:another_goal) { SDG::Goal[2] }
|
||||
let(:another_target) { SDG::Target["2.3"] }
|
||||
|
||||
let(:relatable) { create(:proposal) }
|
||||
|
||||
describe "#sdg_goals" do
|
||||
it "can assign goals to a model" do
|
||||
relatable.sdg_goals = [goal, another_goal]
|
||||
|
||||
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 goal
|
||||
expect(SDG::Relation.last.related_sdg).to eq another_goal
|
||||
end
|
||||
|
||||
it "can obtain the list of goals" do
|
||||
relatable.sdg_goals = [goal, another_goal]
|
||||
|
||||
expect(relatable.reload.sdg_goals).to match_array [goal, another_goal]
|
||||
end
|
||||
end
|
||||
|
||||
describe "#sdg_targets" do
|
||||
it "can assign targets to a model" do
|
||||
relatable.sdg_targets = [target, another_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 target
|
||||
expect(SDG::Relation.last.related_sdg).to eq another_target
|
||||
end
|
||||
|
||||
it "can obtain the list of targets" do
|
||||
relatable.sdg_targets = [target, another_target]
|
||||
|
||||
expect(relatable.reload.sdg_targets).to match_array [target, another_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]
|
||||
|
||||
expect(relatable.reload.related_sdgs).to match_array [goal, another_goal, target, another_target]
|
||||
end
|
||||
end
|
||||
end
|
||||
36
spec/models/sdg/related_spec.rb
Normal file
36
spec/models/sdg/related_spec.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
require "rails_helper"
|
||||
|
||||
describe SDG::Related do
|
||||
let(:proposal) { create(:proposal) }
|
||||
let(:another_proposal) { create(:proposal) }
|
||||
let(:related_sdg) { SDG::Goal[1] }
|
||||
|
||||
describe "#proposals" do
|
||||
it "can assign proposals to a model" do
|
||||
related_sdg.proposals = [proposal, another_proposal]
|
||||
|
||||
expect(SDG::Relation.count).to be 2
|
||||
expect(SDG::Relation.first.related_sdg).to eq related_sdg
|
||||
expect(SDG::Relation.last.related_sdg).to eq related_sdg
|
||||
expect(SDG::Relation.first.relatable).to eq proposal
|
||||
expect(SDG::Relation.last.relatable).to eq another_proposal
|
||||
end
|
||||
|
||||
it "can obtain the list of proposals" do
|
||||
related_sdg.proposals = [proposal, another_proposal]
|
||||
|
||||
expect(related_sdg.reload.proposals).to match_array [proposal, another_proposal]
|
||||
end
|
||||
end
|
||||
|
||||
describe "#relatables" do
|
||||
let(:investment) { create(:budget_investment) }
|
||||
|
||||
it "returns all related content" do
|
||||
related_sdg.proposals = [proposal, another_proposal]
|
||||
related_sdg.budget_investments = [investment]
|
||||
|
||||
expect(related_sdg.reload.relatables).to match_array [proposal, another_proposal, investment]
|
||||
end
|
||||
end
|
||||
end
|
||||
33
spec/models/sdg/relation_spec.rb
Normal file
33
spec/models/sdg/relation_spec.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
require "rails_helper"
|
||||
|
||||
describe SDG::Relation do
|
||||
describe "Validations" do
|
||||
it "is valid with a related SDG and a relatable model" do
|
||||
relation = SDG::Relation.new(related_sdg: SDG::Goal[1], relatable: create(:proposal))
|
||||
|
||||
expect(relation).to be_valid
|
||||
end
|
||||
|
||||
it "is not valid without a related SDG" do
|
||||
relation = SDG::Relation.new(relatable: create(:proposal))
|
||||
|
||||
expect(relation).not_to be_valid
|
||||
end
|
||||
|
||||
it "is not valid without a relatable model" do
|
||||
relation = SDG::Relation.new(related_sdg: SDG::Goal[1])
|
||||
|
||||
expect(relation).not_to be_valid
|
||||
end
|
||||
|
||||
it "is not valid when a relation already exists" do
|
||||
proposal = create(:proposal)
|
||||
goal = SDG::Goal[1]
|
||||
|
||||
SDG::Relation.create!(related_sdg: goal, relatable: proposal)
|
||||
relation = SDG::Relation.new(related_sdg: goal, relatable: proposal)
|
||||
|
||||
expect(relation).not_to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user