Merge pull request #2160 from consul/relationables
Related Content model and Relatable concern
This commit is contained in:
@@ -17,6 +17,7 @@ class Budget
|
||||
acts_as_votable
|
||||
acts_as_paranoid column: :hidden_at
|
||||
include ActsAsParanoidAliases
|
||||
include Relationable
|
||||
|
||||
belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id'
|
||||
belongs_to :heading
|
||||
|
||||
23
app/models/concerns/relationable.rb
Normal file
23
app/models/concerns/relationable.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
module Relationable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :related_contents, as: :parent_relationable, dependent: :destroy
|
||||
end
|
||||
|
||||
def relate_content(relationable)
|
||||
RelatedContent.find_or_create_by(parent_relationable: self, child_relationable: relationable)
|
||||
end
|
||||
|
||||
def relationed_contents
|
||||
related_contents.not_hidden.map { |related_content| related_content.child_relationable }
|
||||
end
|
||||
|
||||
def report_related_content(relationable)
|
||||
related_content = related_contents.find_by(child_relationable: relationable)
|
||||
if related_content.present?
|
||||
related_content.increment!(:times_reported)
|
||||
related_content.opposite_related_content.increment!(:times_reported)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -9,6 +9,7 @@ class Debate < ActiveRecord::Base
|
||||
include Filterable
|
||||
include HasPublicAuthor
|
||||
include Graphqlable
|
||||
include Relationable
|
||||
|
||||
acts_as_votable
|
||||
acts_as_paranoid column: :hidden_at
|
||||
|
||||
@@ -17,6 +17,7 @@ class Proposal < ActiveRecord::Base
|
||||
max_file_size: 3.megabytes,
|
||||
accepted_content_types: [ "application/pdf" ]
|
||||
include EmbedVideosHelper
|
||||
include Relationable
|
||||
|
||||
acts_as_votable
|
||||
acts_as_paranoid column: :hidden_at
|
||||
|
||||
33
app/models/related_content.rb
Normal file
33
app/models/related_content.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
class RelatedContent < ActiveRecord::Base
|
||||
RELATED_CONTENTS_REPORT_THRESHOLD = Setting['related_contents_report_threshold'].to_i
|
||||
|
||||
belongs_to :parent_relationable, polymorphic: true
|
||||
belongs_to :child_relationable, polymorphic: true
|
||||
has_one :opposite_related_content, class_name: 'RelatedContent', foreign_key: :related_content_id
|
||||
|
||||
validates :parent_relationable_id, presence: true
|
||||
validates :parent_relationable_type, presence: true
|
||||
validates :child_relationable_id, presence: true
|
||||
validates :child_relationable_type, presence: true
|
||||
validates :parent_relationable_id, uniqueness: { scope: [:parent_relationable_type, :child_relationable_id, :child_relationable_type] }
|
||||
|
||||
after_create :create_opposite_related_content, unless: proc { opposite_related_content.present? }
|
||||
after_destroy :destroy_opposite_related_content, if: proc { opposite_related_content.present? }
|
||||
|
||||
scope :not_hidden, -> { where('times_reported <= ?', RELATED_CONTENTS_REPORT_THRESHOLD) }
|
||||
|
||||
def hidden_by_reports?
|
||||
times_reported > RELATED_CONTENTS_REPORT_THRESHOLD
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_opposite_related_content
|
||||
related_content = RelatedContent.create!(opposite_related_content: self, parent_relationable: child_relationable, child_relationable: parent_relationable)
|
||||
self.opposite_related_content = related_content
|
||||
end
|
||||
|
||||
def destroy_opposite_related_content
|
||||
opposite_related_content.destroy
|
||||
end
|
||||
end
|
||||
@@ -65,6 +65,7 @@ section "Creating Settings" do
|
||||
Setting.create(key: 'map_latitude', value: 51.48)
|
||||
Setting.create(key: 'map_longitude', value: 0.0)
|
||||
Setting.create(key: 'map_zoom', value: 10)
|
||||
Setting.create(key: 'related_contents_report_threshold', value: 2)
|
||||
end
|
||||
|
||||
section "Creating Geozones" do
|
||||
|
||||
12
db/migrate/20171127171925_create_related_content.rb
Normal file
12
db/migrate/20171127171925_create_related_content.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
class CreateRelatedContent < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :related_contents do |t|
|
||||
t.references :parent_relationable, polymorphic: true, index: { name: 'index_related_contents_on_parent_relationable' }
|
||||
t.references :child_relationable, polymorphic: true, index: { name: 'index_related_contents_on_child_relationable' }
|
||||
t.references :related_content, index: { name: 'opposite_related_content' }
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :related_contents, [:parent_relationable_id, :parent_relationable_type, :child_relationable_id, :child_relationable_type], name: "unique_parent_child_related_content", unique: true, using: :btree
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddTimeReportedToRelatedContent < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :related_contents, :times_reported, :integer, default: 0
|
||||
end
|
||||
end
|
||||
18
db/schema.rb
18
db/schema.rb
@@ -11,7 +11,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20171115164152) do
|
||||
ActiveRecord::Schema.define(version: 20171127230716) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@@ -851,6 +851,22 @@ ActiveRecord::Schema.define(version: 20171115164152) do
|
||||
add_index "proposals", ["title"], name: "index_proposals_on_title", using: :btree
|
||||
add_index "proposals", ["tsv"], name: "index_proposals_on_tsv", using: :gin
|
||||
|
||||
create_table "related_contents", force: :cascade do |t|
|
||||
t.integer "parent_relationable_id"
|
||||
t.string "parent_relationable_type"
|
||||
t.integer "child_relationable_id"
|
||||
t.string "child_relationable_type"
|
||||
t.integer "related_content_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "times_reported", default: 0
|
||||
end
|
||||
|
||||
add_index "related_contents", ["child_relationable_type", "child_relationable_id"], name: "index_related_contents_on_child_relationable", using: :btree
|
||||
add_index "related_contents", ["parent_relationable_id", "parent_relationable_type", "child_relationable_id", "child_relationable_type"], name: "unique_parent_child_related_content", unique: true, using: :btree
|
||||
add_index "related_contents", ["parent_relationable_type", "parent_relationable_id"], name: "index_related_contents_on_parent_relationable", using: :btree
|
||||
add_index "related_contents", ["related_content_id"], name: "opposite_related_content", using: :btree
|
||||
|
||||
create_table "settings", force: :cascade do |t|
|
||||
t.string "key"
|
||||
t.string "value"
|
||||
|
||||
@@ -116,3 +116,6 @@ Setting['proposal_improvement_path'] = nil
|
||||
Setting['map_latitude'] = 51.48
|
||||
Setting['map_longitude'] = 0.0
|
||||
Setting['map_zoom'] = 10
|
||||
|
||||
# Related content
|
||||
Setting['related_contents_report_threshold'] = 5
|
||||
|
||||
@@ -882,4 +882,7 @@ LOREM_IPSUM
|
||||
end
|
||||
end
|
||||
|
||||
factory :related_content do
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
76
spec/models/relation_spec.rb
Normal file
76
spec/models/relation_spec.rb
Normal file
@@ -0,0 +1,76 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe RelatedContent do
|
||||
|
||||
let(:parent_relationable) { create([:proposal, :debate, :budget_investment].sample) }
|
||||
let(:child_relationable) { create([:proposal, :debate, :budget_investment].sample) }
|
||||
|
||||
it "should allow relationables from various classes" do
|
||||
expect(build(:related_content, parent_relationable: parent_relationable, child_relationable: child_relationable)).to be_valid
|
||||
expect(build(:related_content, parent_relationable: parent_relationable, child_relationable: child_relationable)).to be_valid
|
||||
expect(build(:related_content, parent_relationable: parent_relationable, child_relationable: child_relationable)).to be_valid
|
||||
end
|
||||
|
||||
it "should not allow empty relationables" do
|
||||
expect(build(:related_content)).not_to be_valid
|
||||
expect(build(:related_content, parent_relationable: parent_relationable)).not_to be_valid
|
||||
expect(build(:related_content, child_relationable: child_relationable)).not_to be_valid
|
||||
end
|
||||
|
||||
it "should not allow repeated related contents" do
|
||||
related_content = create(:related_content, parent_relationable: parent_relationable, child_relationable: child_relationable)
|
||||
new_related_content = build(:related_content, parent_relationable: related_content.parent_relationable, child_relationable: related_content.child_relationable)
|
||||
expect(new_related_content).not_to be_valid
|
||||
end
|
||||
|
||||
describe 'create_opposite_related_content' do
|
||||
let(:parent_relationable) { create(:proposal) }
|
||||
let(:child_relationable) { create(:debate) }
|
||||
let(:related_content) { build(:related_content, parent_relationable: parent_relationable, child_relationable: child_relationable) }
|
||||
|
||||
it 'creates an opposite related_content' do
|
||||
expect { related_content.save }.to change { RelatedContent.count }.by(2)
|
||||
expect(related_content.opposite_related_content.child_relationable_id).to eq(parent_relationable.id)
|
||||
expect(related_content.opposite_related_content.child_relationable_type).to eq(parent_relationable.class.name)
|
||||
expect(related_content.opposite_related_content.parent_relationable_id).to eq(child_relationable.id)
|
||||
expect(related_content.opposite_related_content.parent_relationable_type).to eq(child_relationable.class.name)
|
||||
expect(related_content.opposite_related_content.opposite_related_content.id).to eq(related_content.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'relationable destroy' do
|
||||
let(:parent_relationable) { create(:proposal) }
|
||||
let(:child_relationable) { create(:debate) }
|
||||
|
||||
it 'destroys both related contents involved' do
|
||||
related_content = create(:related_content, parent_relationable: parent_relationable, child_relationable: child_relationable)
|
||||
expect { related_content.parent_relationable.destroy }.to change { RelatedContent.all.count }.by(-2)
|
||||
expect(child_relationable.related_contents).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Move this into a Relationable shared context
|
||||
describe '#report_related_content' do
|
||||
it 'increments both relation and opposite relation times_reported counters' do
|
||||
related_content = create(:related_content, parent_relationable: parent_relationable, child_relationable: child_relationable)
|
||||
parent_relationable.report_related_content(child_relationable)
|
||||
|
||||
expect(related_content.reload.times_reported).to eq(1)
|
||||
expect(related_content.reload.opposite_related_content.times_reported).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#relationed_contents' do
|
||||
before do
|
||||
create(:related_content, parent_relationable: parent_relationable, child_relationable: create(:proposal), times_reported: 6)
|
||||
create(:related_content, parent_relationable: parent_relationable, child_relationable: child_relationable)
|
||||
end
|
||||
|
||||
it 'returns not hidden by reports related contents' do
|
||||
expect(parent_relationable.relationed_contents.count).to eq(1)
|
||||
expect(parent_relationable.relationed_contents.first.class.name).to eq(child_relationable.class.name)
|
||||
expect(parent_relationable.relationed_contents.first.id).to eq(child_relationable.id)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user