Merge pull request #2206 from consul/related-contents-score
Related contents score
This commit is contained in:
@@ -2424,11 +2424,28 @@ table {
|
|||||||
|
|
||||||
li {
|
li {
|
||||||
border-bottom: 1px solid $border;
|
border-bottom: 1px solid $border;
|
||||||
padding: $line-height / 4;
|
margin-bottom: 0 !important;
|
||||||
|
padding: $line-height / 2;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
border-top: 1px solid $border;
|
border-top: 1px solid $border;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include breakpoint(medium) {
|
||||||
|
|
||||||
|
.score-actions {
|
||||||
|
display: none;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f9f9f9;
|
||||||
|
|
||||||
|
.score-actions {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
@@ -2436,7 +2453,7 @@ table {
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
span:not(.icon-flag) {
|
.related-content-title {
|
||||||
color: #4f4f4f;
|
color: #4f4f4f;
|
||||||
font-size: rem-calc(12);
|
font-size: rem-calc(12);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -2447,6 +2464,47 @@ table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.relate-content-score {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
@include breakpoint(medium) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: $line-height;
|
||||||
|
padding-left: rem-calc(20);
|
||||||
|
position: relative;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&.score-positive:before,
|
||||||
|
&.score-negative:before {
|
||||||
|
font-family: 'icons';
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.score-positive {
|
||||||
|
color: $color-success;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
color: $color-success;
|
||||||
|
content: '\6c';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.score-negative {
|
||||||
|
color: $color-alert;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
color: $color-alert;
|
||||||
|
content: '\76';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 22. Images
|
// 22. Images
|
||||||
// -----------------
|
// -----------------
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
class RelatedContentsController < ApplicationController
|
class RelatedContentsController < ApplicationController
|
||||||
VALID_URL = /#{Setting['url']}\/.*\/.*/
|
|
||||||
|
|
||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
respond_to :html, :js
|
respond_to :html, :js
|
||||||
|
|
||||||
def create
|
def create
|
||||||
if relationable_object && related_object
|
if relationable_object && related_object
|
||||||
@relationable.relate_content(@related)
|
RelatedContent.create(parent_relationable: @relationable, child_relationable: @related, author: current_user)
|
||||||
|
|
||||||
flash[:success] = t('related_content.success')
|
flash[:success] = t('related_content.success')
|
||||||
else
|
else
|
||||||
@@ -17,28 +15,25 @@ class RelatedContentsController < ApplicationController
|
|||||||
redirect_to @relationable
|
redirect_to @relationable
|
||||||
end
|
end
|
||||||
|
|
||||||
def flag
|
def score_positive
|
||||||
@related = RelatedContent.find_by(id: params[:id])
|
score(:positive)
|
||||||
|
|
||||||
Flag.flag(current_user, @related)
|
|
||||||
Flag.flag(current_user, @related.opposite_related_content)
|
|
||||||
|
|
||||||
render template: 'relationable/_refresh_flag_actions'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def unflag
|
def score_negative
|
||||||
@related = RelatedContent.find_by(id: params[:id])
|
score(:negative)
|
||||||
|
|
||||||
Flag.unflag(current_user, @related)
|
|
||||||
Flag.unflag(current_user, @related.opposite_related_content)
|
|
||||||
|
|
||||||
render template: 'relationable/_refresh_flag_actions'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def score(action)
|
||||||
|
@related = RelatedContent.find_by(id: params[:id])
|
||||||
|
@related.send("score_#{action}", current_user)
|
||||||
|
|
||||||
|
render template: 'relationable/_refresh_score_actions'
|
||||||
|
end
|
||||||
|
|
||||||
def valid_url?
|
def valid_url?
|
||||||
params[:url].match(VALID_URL)
|
params[:url].start_with?(Setting['url'])
|
||||||
end
|
end
|
||||||
|
|
||||||
def relationable_object
|
def relationable_object
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ module FlagsHelper
|
|||||||
def own_flaggable?(flaggable)
|
def own_flaggable?(flaggable)
|
||||||
if flaggable.is_a? Comment
|
if flaggable.is_a? Comment
|
||||||
flaggable.user_id == current_user.id
|
flaggable.user_id == current_user.id
|
||||||
elsif flaggable.is_a? RelatedContent
|
|
||||||
false
|
|
||||||
else
|
else
|
||||||
flaggable.author_id == current_user.id
|
flaggable.author_id == current_user.id
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,19 +5,11 @@ module Relationable
|
|||||||
has_many :related_contents, as: :parent_relationable, dependent: :destroy
|
has_many :related_contents, as: :parent_relationable, dependent: :destroy
|
||||||
end
|
end
|
||||||
|
|
||||||
def relate_content(relationable)
|
def find_related_content(relationable)
|
||||||
RelatedContent.find_or_create_by(parent_relationable: self, child_relationable: relationable)
|
RelatedContent.where(parent_relationable: self, child_relationable: relationable).first
|
||||||
end
|
end
|
||||||
|
|
||||||
def relationed_contents
|
def relationed_contents
|
||||||
related_contents.not_hidden.map { |related_content| related_content.child_relationable }
|
related_contents.not_hidden.map { |related_content| related_content.child_relationable }
|
||||||
end
|
end
|
||||||
|
|
||||||
def report_related_content(relationable)
|
|
||||||
related_content = related_contents.find_by(child_relationable: relationable)
|
|
||||||
if related_content.present?
|
|
||||||
related_content.increment!(:flags_count)
|
|
||||||
related_content.opposite_related_content.increment!(:flags_count)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
class RelatedContent < ActiveRecord::Base
|
class RelatedContent < ActiveRecord::Base
|
||||||
include Flaggable
|
RELATED_CONTENT_SCORE_THRESHOLD = Setting['related_content_score_threshold'].to_f
|
||||||
|
|
||||||
RELATED_CONTENTS_REPORT_THRESHOLD = Setting['related_contents_report_threshold'].to_i
|
|
||||||
RELATIONABLE_MODELS = %w{proposals debates}.freeze
|
RELATIONABLE_MODELS = %w{proposals debates}.freeze
|
||||||
|
|
||||||
|
acts_as_paranoid column: :hidden_at
|
||||||
|
include ActsAsParanoidAliases
|
||||||
|
|
||||||
|
belongs_to :author, class_name: 'User', foreign_key: 'author_id'
|
||||||
belongs_to :parent_relationable, polymorphic: true, touch: true
|
belongs_to :parent_relationable, polymorphic: true, touch: true
|
||||||
belongs_to :child_relationable, polymorphic: true, touch: true
|
belongs_to :child_relationable, polymorphic: true, touch: true
|
||||||
has_one :opposite_related_content, class_name: 'RelatedContent', foreign_key: :related_content_id
|
has_one :opposite_related_content, class_name: 'RelatedContent', foreign_key: :related_content_id
|
||||||
|
has_many :related_content_scores
|
||||||
|
|
||||||
validates :parent_relationable_id, presence: true
|
validates :parent_relationable_id, presence: true
|
||||||
validates :parent_relationable_type, presence: true
|
validates :parent_relationable_type, presence: true
|
||||||
@@ -15,22 +18,46 @@ class RelatedContent < ActiveRecord::Base
|
|||||||
validates :parent_relationable_id, uniqueness: { scope: [:parent_relationable_type, :child_relationable_id, :child_relationable_type] }
|
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_create :create_opposite_related_content, unless: proc { opposite_related_content.present? }
|
||||||
after_destroy :destroy_opposite_related_content, if: proc { opposite_related_content.present? }
|
after_create :create_author_score
|
||||||
|
|
||||||
scope :not_hidden, -> { where('flags_count <= ?', RELATED_CONTENTS_REPORT_THRESHOLD) }
|
scope :not_hidden, -> { where(hidden_at: nil) }
|
||||||
|
|
||||||
def hidden_by_reports?
|
def score_positive(user)
|
||||||
flags_count > RELATED_CONTENTS_REPORT_THRESHOLD
|
score(RelatedContentScore::SCORES[:POSITIVE], user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def score_negative(user)
|
||||||
|
score(RelatedContentScore::SCORES[:NEGATIVE], user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def scored_by_user?(user)
|
||||||
|
related_content_scores.exists?(user: user)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def create_opposite_related_content
|
def create_opposite_related_content
|
||||||
related_content = RelatedContent.create!(opposite_related_content: self, parent_relationable: child_relationable, child_relationable: parent_relationable)
|
related_content = RelatedContent.create!(opposite_related_content: self, parent_relationable: child_relationable,
|
||||||
|
child_relationable: parent_relationable, author: author)
|
||||||
self.opposite_related_content = related_content
|
self.opposite_related_content = related_content
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy_opposite_related_content
|
def score(value, user)
|
||||||
opposite_related_content.destroy
|
score_with_opposite(value, user)
|
||||||
|
hide_with_opposite if (related_content_scores.sum(:value) / related_content_scores_count) < RELATED_CONTENT_SCORE_THRESHOLD
|
||||||
|
end
|
||||||
|
|
||||||
|
def hide_with_opposite
|
||||||
|
hide
|
||||||
|
opposite_related_content.hide
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_author_score
|
||||||
|
score_positive(author)
|
||||||
|
end
|
||||||
|
|
||||||
|
def score_with_opposite(value, user)
|
||||||
|
RelatedContentScore.create(user: user, related_content: self, value: value)
|
||||||
|
RelatedContentScore.create(user: user, related_content: opposite_related_content, value: value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
13
app/models/related_content_score.rb
Normal file
13
app/models/related_content_score.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
class RelatedContentScore < ActiveRecord::Base
|
||||||
|
SCORES = {
|
||||||
|
POSITIVE: 1,
|
||||||
|
NEGATIVE: -1
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
belongs_to :related_content, touch: true, counter_cache: :related_content_scores_count
|
||||||
|
belongs_to :user
|
||||||
|
|
||||||
|
validates :user, presence: true
|
||||||
|
validates :related_content, presence: true
|
||||||
|
validates :related_content_id, uniqueness: { scope: [:user_id] }
|
||||||
|
end
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<span class="flag-content">
|
|
||||||
<% if show_flag_action? related %>
|
|
||||||
<a id="flag-expand-related-<%= related.id %>" data-toggle="flag-drop-related-<%= related.id %>" title="<%= t('shared.flag') %>" class="float-right flag">
|
|
||||||
<span class="icon-flag flag-disable"></span>
|
|
||||||
</a>
|
|
||||||
<span class="dropdown-pane" id="flag-drop-related-<%= related.id %>" data-dropdown data-auto-focus="true">
|
|
||||||
<%= link_to t('shared.flag'), flag_related_content_path(related), method: :put, remote: true, id: "flag-related-#{ related.id }" %>
|
|
||||||
</span>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% if show_unflag_action? related %>
|
|
||||||
<a id="unflag-expand-related-<%= related.id %>" data-toggle="unflag-drop-related-<%= related.id %>" title="<%= t('shared.unflag') %>" class="float-right flag">
|
|
||||||
<span class="icon-flag flag-active"></span>
|
|
||||||
</a>
|
|
||||||
<span class="dropdown-pane" id="unflag-drop-related-<%= related.id %>" data-dropdown data-auto-focus="true">
|
|
||||||
<%= link_to t('shared.unflag'), unflag_related_content_path(related), method: :put, remote: true, id: "unflag-related-#{ related.id }" %>
|
|
||||||
</span>
|
|
||||||
<% end %>
|
|
||||||
</span>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
$("#<%= dom_id(@related) %>.js-flag-actions").html('<%= j render("relationable/flag_actions", related: @related) %>');
|
|
||||||
1
app/views/relationable/_refresh_score_actions.js.erb
Normal file
1
app/views/relationable/_refresh_score_actions.js.erb
Normal file
@@ -0,0 +1 @@
|
|||||||
|
$("#<%= dom_id(@related) %>.js-score-actions").html('');
|
||||||
@@ -4,11 +4,13 @@
|
|||||||
<h2 class="inline-block">
|
<h2 class="inline-block">
|
||||||
<%= t("related_content.title") %> <span>(<%= relationable.relationed_contents.count %>)</span>
|
<%= t("related_content.title") %> <span>(<%= relationable.relationed_contents.count %>)</span>
|
||||||
</h2>
|
</h2>
|
||||||
<a>
|
<% if current_user %>
|
||||||
<button type="button" data-toggle="related_content" class="add-related-content" id="add-related-content">
|
<a>
|
||||||
<%= t("related_content.add") %>
|
<button type="button" data-toggle="related_content" class="add-related-content" id="add-related-content">
|
||||||
</button>
|
<%= t("related_content.add") %>
|
||||||
</a>
|
</button>
|
||||||
|
</a>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= render 'relationable/form', relationable: relationable %>
|
<%= render 'relationable/form', relationable: relationable %>
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
<ul class="related-content-list" id="related-content-list">
|
<ul class="related-content-list" id="related-content-list">
|
||||||
<% @related_contents.each do |related| %>
|
<% @related_contents.each do |related| %>
|
||||||
<li>
|
<li id="related-content-<%= related.find_related_content(relationable).id %>">
|
||||||
<span id="<%= dom_id(related.relate_content(relationable)) %>" class="js-flag-actions">
|
<% related_content = related.find_related_content(relationable) %>
|
||||||
<%= render 'relationable/flag_actions', related: related.relate_content(relationable) %>
|
<% if current_user && related_content.author != current_user && !related_content.scored_by_user?(current_user)%>
|
||||||
</span>
|
<span id="<%= dom_id(related.find_related_content(relationable)) %>" class="js-score-actions score-actions">
|
||||||
|
<%= render 'relationable/score', related: related_content %>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<span><%= t("related_content.content_title.#{related.class.name.downcase}") %></span><br>
|
<span class="related-content-title"><%= t("related_content.content_title.#{related.class.name.downcase}") %></span><br>
|
||||||
<h3 class="inline-block">
|
<h3 class="inline-block">
|
||||||
<%= link_to related.title, eval("#{related.class.name.downcase}_path(related)") %>
|
<%= link_to related.title, eval("#{related.class.name.downcase}_path(related)") %>
|
||||||
</h3>
|
</h3>
|
||||||
|
|||||||
17
app/views/relationable/_score.html.erb
Normal file
17
app/views/relationable/_score.html.erb
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<small><%= t("related_content.is_related") %></small>
|
||||||
|
|
||||||
|
<span class="relate-content-score">
|
||||||
|
<%= link_to t("related_content.score_positive"),
|
||||||
|
score_positive_related_content_path(related),
|
||||||
|
method: :put,
|
||||||
|
remote: true,
|
||||||
|
id: "score-positive-related-#{ related.id }",
|
||||||
|
class: "score-positive" %>
|
||||||
|
|
||||||
|
<%= link_to t("related_content.score_negative"),
|
||||||
|
score_negative_related_content_path(related),
|
||||||
|
method: :put,
|
||||||
|
remote: true,
|
||||||
|
id: "score-negative-related-#{ related.id }",
|
||||||
|
class: "score-negative" %>
|
||||||
|
</span>
|
||||||
@@ -820,6 +820,9 @@ en:
|
|||||||
submit: "Add"
|
submit: "Add"
|
||||||
error: "Link not valid. Remember to start with %{url}."
|
error: "Link not valid. Remember to start with %{url}."
|
||||||
success: "You added a new related content"
|
success: "You added a new related content"
|
||||||
|
is_related: "¿Is it related content?"
|
||||||
|
score_positive: "Yes"
|
||||||
|
score_negative: "No"
|
||||||
content_title:
|
content_title:
|
||||||
proposal: "Proposal"
|
proposal: "Proposal"
|
||||||
debate: "Debate"
|
debate: "Debate"
|
||||||
|
|||||||
@@ -819,6 +819,9 @@ es:
|
|||||||
submit: "Añadir"
|
submit: "Añadir"
|
||||||
error: "Enlace no válido. Recuerda que debe empezar por %{url}."
|
error: "Enlace no válido. Recuerda que debe empezar por %{url}."
|
||||||
success: "Has añadido un nuevo contenido relacionado"
|
success: "Has añadido un nuevo contenido relacionado"
|
||||||
|
is_related: "¿Es contenido relacionado?"
|
||||||
|
score_positive: "Sí"
|
||||||
|
score_negative: "No"
|
||||||
content_title:
|
content_title:
|
||||||
proposal: "Propuesta"
|
proposal: "Propuesta"
|
||||||
debate: "Debate"
|
debate: "Debate"
|
||||||
|
|||||||
@@ -464,8 +464,8 @@ Rails.application.routes.draw do
|
|||||||
|
|
||||||
resources :related_contents, only: [:create] do
|
resources :related_contents, only: [:create] do
|
||||||
member do
|
member do
|
||||||
put :flag
|
put :score_positive
|
||||||
put :unflag
|
put :score_negative
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ section "Creating Settings" do
|
|||||||
Setting.create(key: 'map_latitude', value: 51.48)
|
Setting.create(key: 'map_latitude', value: 51.48)
|
||||||
Setting.create(key: 'map_longitude', value: 0.0)
|
Setting.create(key: 'map_longitude', value: 0.0)
|
||||||
Setting.create(key: 'map_zoom', value: 10)
|
Setting.create(key: 'map_zoom', value: 10)
|
||||||
Setting.create(key: 'related_contents_report_threshold', value: 2)
|
Setting.create(key: 'related_content_score_threshold', value: -0.3)
|
||||||
end
|
end
|
||||||
|
|
||||||
section "Creating Geozones" do
|
section "Creating Geozones" do
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class RemoveRelatedContentsFlagsCount < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
remove_column :related_contents, :flags_count
|
||||||
|
end
|
||||||
|
end
|
||||||
11
db/migrate/20171219184209_create_related_content_scores.rb
Normal file
11
db/migrate/20171219184209_create_related_content_scores.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
class CreateRelatedContentScores < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :related_content_scores do |t|
|
||||||
|
t.references :user, index: true, foreign_key: true
|
||||||
|
t.references :related_content, index: true, foreign_key: true
|
||||||
|
t.integer :value
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :related_content_scores, [:user_id, :related_content_id], name: "unique_user_related_content_scoring", unique: true, using: :btree
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
class AddHiddenAtToRelatedContents < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :related_contents, :hidden_at, :datetime
|
||||||
|
add_index :related_contents, :hidden_at
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class AddRelatedContentScoresCounterToRelatedContent < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :related_contents, :related_content_scores_count, :integer, default: 0
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class AddAuthorToRelatedContent < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :related_contents, :author_id, :integer
|
||||||
|
end
|
||||||
|
end
|
||||||
19
db/schema.rb
19
db/schema.rb
@@ -11,7 +11,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: 20171215152244) do
|
ActiveRecord::Schema.define(version: 20171220010000) 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 "plpgsql"
|
enable_extension "plpgsql"
|
||||||
@@ -854,6 +854,16 @@ ActiveRecord::Schema.define(version: 20171215152244) do
|
|||||||
add_index "proposals", ["title"], name: "index_proposals_on_title", using: :btree
|
add_index "proposals", ["title"], name: "index_proposals_on_title", using: :btree
|
||||||
add_index "proposals", ["tsv"], name: "index_proposals_on_tsv", using: :gin
|
add_index "proposals", ["tsv"], name: "index_proposals_on_tsv", using: :gin
|
||||||
|
|
||||||
|
create_table "related_content_scores", force: :cascade do |t|
|
||||||
|
t.integer "user_id"
|
||||||
|
t.integer "related_content_id"
|
||||||
|
t.integer "value"
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "related_content_scores", ["related_content_id"], name: "index_related_content_scores_on_related_content_id", using: :btree
|
||||||
|
add_index "related_content_scores", ["user_id", "related_content_id"], name: "unique_user_related_content_scoring", unique: true, using: :btree
|
||||||
|
add_index "related_content_scores", ["user_id"], name: "index_related_content_scores_on_user_id", using: :btree
|
||||||
|
|
||||||
create_table "related_contents", force: :cascade do |t|
|
create_table "related_contents", force: :cascade do |t|
|
||||||
t.integer "parent_relationable_id"
|
t.integer "parent_relationable_id"
|
||||||
t.string "parent_relationable_type"
|
t.string "parent_relationable_type"
|
||||||
@@ -862,10 +872,13 @@ ActiveRecord::Schema.define(version: 20171215152244) do
|
|||||||
t.integer "related_content_id"
|
t.integer "related_content_id"
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
t.datetime "updated_at"
|
t.datetime "updated_at"
|
||||||
t.integer "flags_count", default: 0
|
t.datetime "hidden_at"
|
||||||
|
t.integer "related_content_scores_count", default: 0
|
||||||
|
t.integer "author_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "related_contents", ["child_relationable_type", "child_relationable_id"], name: "index_related_contents_on_child_relationable", using: :btree
|
add_index "related_contents", ["child_relationable_type", "child_relationable_id"], name: "index_related_contents_on_child_relationable", using: :btree
|
||||||
|
add_index "related_contents", ["hidden_at"], name: "index_related_contents_on_hidden_at", 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_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", ["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
|
add_index "related_contents", ["related_content_id"], name: "opposite_related_content", using: :btree
|
||||||
@@ -1189,6 +1202,8 @@ ActiveRecord::Schema.define(version: 20171215152244) do
|
|||||||
add_foreign_key "poll_recounts", "poll_officer_assignments", column: "officer_assignment_id"
|
add_foreign_key "poll_recounts", "poll_officer_assignments", column: "officer_assignment_id"
|
||||||
add_foreign_key "poll_voters", "polls"
|
add_foreign_key "poll_voters", "polls"
|
||||||
add_foreign_key "proposals", "communities"
|
add_foreign_key "proposals", "communities"
|
||||||
|
add_foreign_key "related_content_scores", "related_contents"
|
||||||
|
add_foreign_key "related_content_scores", "users"
|
||||||
add_foreign_key "users", "geozones"
|
add_foreign_key "users", "geozones"
|
||||||
add_foreign_key "valuators", "users"
|
add_foreign_key "valuators", "users"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -120,4 +120,4 @@ Setting['map_longitude'] = 0.0
|
|||||||
Setting['map_zoom'] = 10
|
Setting['map_zoom'] = 10
|
||||||
|
|
||||||
# Related content
|
# Related content
|
||||||
Setting['related_contents_report_threshold'] = 5
|
Setting['related_content_score_threshold'] = -0.3
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ describe RelatedContent do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "should not allow repeated related contents" do
|
it "should not allow repeated related contents" do
|
||||||
related_content = create(:related_content, parent_relationable: parent_relationable, child_relationable: child_relationable)
|
related_content = create(:related_content, parent_relationable: parent_relationable, child_relationable: child_relationable, author: build(:user))
|
||||||
new_related_content = build(:related_content, parent_relationable: related_content.parent_relationable, child_relationable: related_content.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
|
expect(new_related_content).not_to be_valid
|
||||||
end
|
end
|
||||||
@@ -26,7 +26,7 @@ describe RelatedContent do
|
|||||||
describe 'create_opposite_related_content' do
|
describe 'create_opposite_related_content' do
|
||||||
let(:parent_relationable) { create(:proposal) }
|
let(:parent_relationable) { create(:proposal) }
|
||||||
let(:child_relationable) { create(:debate) }
|
let(:child_relationable) { create(:debate) }
|
||||||
let(:related_content) { build(:related_content, parent_relationable: parent_relationable, child_relationable: child_relationable) }
|
let(:related_content) { build(:related_content, parent_relationable: parent_relationable, child_relationable: child_relationable, author: build(:user)) }
|
||||||
|
|
||||||
it 'creates an opposite related_content' do
|
it 'creates an opposite related_content' do
|
||||||
expect { related_content.save }.to change { RelatedContent.count }.by(2)
|
expect { related_content.save }.to change { RelatedContent.count }.by(2)
|
||||||
@@ -38,32 +38,18 @@ describe RelatedContent do
|
|||||||
end
|
end
|
||||||
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 flags_count 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.flags_count).to eq(1)
|
|
||||||
expect(related_content.reload.opposite_related_content.flags_count).to eq(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#relationed_contents' do
|
describe '#relationed_contents' do
|
||||||
before do
|
before do
|
||||||
create(:related_content, parent_relationable: parent_relationable, child_relationable: create(:proposal), flags_count: 6)
|
related_content = create(:related_content, parent_relationable: parent_relationable, child_relationable: create(:proposal), author: build(:user))
|
||||||
create(:related_content, parent_relationable: parent_relationable, child_relationable: child_relationable)
|
create(:related_content, parent_relationable: parent_relationable, child_relationable: child_relationable, author: build(:user))
|
||||||
|
|
||||||
|
2.times do
|
||||||
|
related_content.send("score_positive", build(:user))
|
||||||
|
end
|
||||||
|
|
||||||
|
6.times do
|
||||||
|
related_content.send("score_negative", build(:user))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns not hidden by reports related contents' do
|
it 'returns not hidden by reports related contents' do
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ shared_examples "relationable" do |relationable_model_name|
|
|||||||
let(:user) { create(:user) }
|
let(:user) { create(:user) }
|
||||||
|
|
||||||
scenario 'related contents are listed' do
|
scenario 'related contents are listed' do
|
||||||
related_content = create(:related_content, parent_relationable: relationable, child_relationable: related1)
|
related_content = create(:related_content, parent_relationable: relationable, child_relationable: related1, author: build(:user))
|
||||||
|
|
||||||
visit eval("#{relationable.class.name.downcase}_path(relationable)")
|
visit eval("#{relationable.class.name.downcase}_path(relationable)")
|
||||||
within("#related-content-list") do
|
within("#related-content-list") do
|
||||||
@@ -25,6 +25,7 @@ shared_examples "relationable" do |relationable_model_name|
|
|||||||
end
|
end
|
||||||
|
|
||||||
scenario 'related contents can be added' do
|
scenario 'related contents can be added' do
|
||||||
|
login_as(user)
|
||||||
visit eval("#{relationable.class.name.downcase}_path(relationable)")
|
visit eval("#{relationable.class.name.downcase}_path(relationable)")
|
||||||
|
|
||||||
expect(page).to have_selector('#related_content', visible: false)
|
expect(page).to have_selector('#related_content', visible: false)
|
||||||
@@ -57,6 +58,7 @@ shared_examples "relationable" do |relationable_model_name|
|
|||||||
end
|
end
|
||||||
|
|
||||||
scenario 'if related content URL is invalid returns error' do
|
scenario 'if related content URL is invalid returns error' do
|
||||||
|
login_as(user)
|
||||||
visit eval("#{relationable.class.name.downcase}_path(relationable)")
|
visit eval("#{relationable.class.name.downcase}_path(relationable)")
|
||||||
|
|
||||||
click_on("Add related content")
|
click_on("Add related content")
|
||||||
@@ -69,37 +71,54 @@ shared_examples "relationable" do |relationable_model_name|
|
|||||||
expect(page).to have_content("Link not valid. Remember to start with #{Setting[:url]}.")
|
expect(page).to have_content("Link not valid. Remember to start with #{Setting[:url]}.")
|
||||||
end
|
end
|
||||||
|
|
||||||
scenario 'related content can be flagged', :js do
|
scenario 'related content can be scored positively', :js do
|
||||||
related_content = create(:related_content, parent_relationable: relationable, child_relationable: related1)
|
related_content = create(:related_content, parent_relationable: relationable, child_relationable: related1, author: build(:user))
|
||||||
|
|
||||||
login_as(user)
|
login_as(user)
|
||||||
visit eval("#{relationable.class.name.downcase}_path(relationable)")
|
visit eval("#{relationable.class.name.downcase}_path(relationable)")
|
||||||
|
|
||||||
within("#related-content-list") do
|
within("#related-content-list") do
|
||||||
expect(page).to have_css("#flag-expand-related-#{related_content.opposite_related_content.id}")
|
find("#related-content-#{related_content.opposite_related_content.id}").hover
|
||||||
find("#flag-expand-related-#{related_content.opposite_related_content.id}").click
|
find("#score-positive-related-#{related_content.opposite_related_content.id}").click
|
||||||
expect(page).to have_css("#flag-drop-related-#{related_content.opposite_related_content.id}", visible: true)
|
expect(page).to_not have_css("#score-positive-related-#{related_content.opposite_related_content.id}")
|
||||||
click_link("flag-related-#{related_content.opposite_related_content.id}")
|
|
||||||
|
|
||||||
expect(page).to have_css("#unflag-expand-related-#{related_content.opposite_related_content.id}")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(related_content.reload.flags_count).to eq(1)
|
expect(related_content.related_content_scores.find_by(user_id: user.id, related_content_id: related_content.id).value).to eq(1)
|
||||||
expect(related_content.opposite_related_content.flags_count).to eq(1)
|
expect(related_content.opposite_related_content.related_content_scores.find_by(user_id: user.id, related_content_id: related_content.opposite_related_content.id).value).to eq(1)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
scenario 'if related content has been flagged more than 5 times it will be hidden', :js do
|
scenario 'related content can be scored negatively', :js do
|
||||||
related_content = create(:related_content, parent_relationable: relationable, child_relationable: related1)
|
related_content = create(:related_content, parent_relationable: relationable, child_relationable: related1, author: build(:user))
|
||||||
|
|
||||||
related_content.flags_count = Setting['related_contents_report_threshold'].to_i + 1
|
|
||||||
related_content.opposite_related_content.flags_count = related_content.flags_count
|
|
||||||
|
|
||||||
related_content.save
|
|
||||||
related_content.opposite_related_content.save
|
|
||||||
|
|
||||||
login_as(user)
|
login_as(user)
|
||||||
visit eval("#{relationable.class.name.downcase}_path(relationable)")
|
visit eval("#{relationable.class.name.downcase}_path(relationable)")
|
||||||
|
|
||||||
|
within("#related-content-list") do
|
||||||
|
find("#related-content-#{related_content.opposite_related_content.id}").hover
|
||||||
|
find("#score-negative-related-#{related_content.opposite_related_content.id}").click
|
||||||
|
expect(page).to_not have_css("#score-negative-related-#{related_content.opposite_related_content.id}")
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(related_content.related_content_scores.find_by(user_id: user.id, related_content_id: related_content.id).value).to eq(-1)
|
||||||
|
expect(related_content.opposite_related_content.related_content_scores.find_by(user_id: user.id, related_content_id: related_content.opposite_related_content.id).value).to eq(-1)
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'if related content has negative score it will be hidden' do
|
||||||
|
related_content = create(:related_content, parent_relationable: relationable, child_relationable: related1, author: build(:user))
|
||||||
|
|
||||||
|
2.times do
|
||||||
|
related_content.send("score_positive", build(:user))
|
||||||
|
end
|
||||||
|
|
||||||
|
6.times do
|
||||||
|
related_content.send("score_negative", build(:user))
|
||||||
|
end
|
||||||
|
|
||||||
|
login_as(user)
|
||||||
|
|
||||||
|
visit eval("#{relationable.class.name.downcase}_path(relationable)")
|
||||||
|
|
||||||
expect(page).to_not have_css("#related-content-list")
|
expect(page).to_not have_css("#related-content-list")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user