diff --git a/app/controllers/debates_controller.rb b/app/controllers/debates_controller.rb index 30a683404..ded2d2aa0 100644 --- a/app/controllers/debates_controller.rb +++ b/app/controllers/debates_controller.rb @@ -12,7 +12,7 @@ class DebatesController < ApplicationController @debates = @search_terms.present? ? Debate.search(@search_terms) : Debate.all @debates = @debates.tagged_with(@tag_filter) if @tag_filter @debates = @debates.page(params[:page]).for_render.send("sort_by_#{@current_order}") - @tag_cloud = Debate.tag_counts.order(taggings_count: :desc, name: :asc).limit(20) + @tag_cloud = Debate.tag_counts.order(debates_count: :desc, name: :asc).limit(20) set_debate_votes(@debates) end diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index 1f1a66241..55d6fab0e 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -11,7 +11,7 @@ class ProposalsController < ApplicationController @proposals = @search_terms.present? ? Proposal.search(@search_terms) : Proposal.all @proposals = @proposals.tagged_with(@tag_filter) if @tag_filter @proposals = @proposals.page(params[:page]).for_render.send("sort_by_#{@current_order}") - @tag_cloud = Proposal.tag_counts.order(taggings_count: :desc, name: :asc).limit(20) + @tag_cloud = Proposal.tag_counts.order(proposals_count: :desc, name: :asc).limit(20) set_proposal_votes(@proposals) end diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb index b8476ac0a..59bc56bce 100644 --- a/app/helpers/tags_helper.rb +++ b/app/helpers/tags_helper.rb @@ -1,7 +1,7 @@ module TagsHelper - def taggable_path(taggable, tag_name) - case taggable + def taggable_path(taggable_type, tag_name) + case taggable_type when 'debate' debates_path(tag: tag_name) when 'proposal' @@ -11,4 +11,18 @@ module TagsHelper end end + def taggable_counter_field(taggable_type) + "#{taggable_type.underscore.pluralize}_count" + end + + def tag_cloud(tags, classes, counter_field = :taggings_count) + return [] if tags.empty? + + max_count = tags.sort_by(&counter_field).last.send(counter_field).to_f + + tags.each do |tag| + index = ((tag.send(counter_field) / max_count) * (classes.size - 1)) + yield tag, classes[index.nan? ? 0 : index.round] + end + end end diff --git a/app/models/debate.rb b/app/models/debate.rb index a7b84f0ce..4785ab04f 100644 --- a/app/models/debate.rb +++ b/app/models/debate.rb @@ -127,6 +127,14 @@ class Debate < ActiveRecord::Base cached_votes_up/flags_count.to_f < 5 end + def after_hide + self.tags.each{ |t| t.decrement_custom_counter_for('Debate') } + end + + def after_restore + self.tags.each{ |t| t.increment_custom_counter_for('Debate') } + end + def self.title_max_length @@title_max_length ||= self.columns.find { |c| c.name == 'title' }.limit || 80 end diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 24045e8f7..5d21be8d2 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -105,6 +105,14 @@ class Proposal < ActiveRecord::Base cached_votes_up) end + def after_hide + self.tags.each{ |t| t.decrement_custom_counter_for('Proposal') } + end + + def after_restore + self.tags.each{ |t| t.increment_custom_counter_for('Proposal') } + end + def self.title_max_length @@title_max_length ||= self.columns.find { |c| c.name == 'title' }.limit || 80 end diff --git a/app/views/debates/_debate.html.erb b/app/views/debates/_debate.html.erb index 936de966e..3be675bb9 100644 --- a/app/views/debates/_debate.html.erb +++ b/app/views/debates/_debate.html.erb @@ -42,7 +42,7 @@ <%= link_to debate.description, debate %>
- <%= render "shared/tags", debate: debate, limit: 5 %> + <%= render "shared/tags", taggable: debate, limit: 5 %> diff --git a/app/views/debates/show.html.erb b/app/views/debates/show.html.erb index 23134d424..c15b5b709 100644 --- a/app/views/debates/show.html.erb +++ b/app/views/debates/show.html.erb @@ -59,7 +59,7 @@ <%= safe_html_with_links @debate.description %> - <%= render 'shared/tags', debate: @debate %> + <%= render 'shared/tags', taggable: @debate %>
<%= render 'actions', debate: @debate %> diff --git a/app/views/proposals/_proposal.html.erb b/app/views/proposals/_proposal.html.erb index 865365493..6d8116b1c 100644 --- a/app/views/proposals/_proposal.html.erb +++ b/app/views/proposals/_proposal.html.erb @@ -42,7 +42,7 @@ <%= link_to proposal.summary, proposal %>
- <%= render "shared/tags", proposal: proposal, limit: 5 %> + <%= render "shared/tags", taggable: proposal, limit: 5 %> diff --git a/app/views/proposals/show.html.erb b/app/views/proposals/show.html.erb index 72c8d2918..68b599f66 100644 --- a/app/views/proposals/show.html.erb +++ b/app/views/proposals/show.html.erb @@ -78,7 +78,7 @@

<%= @proposal.question %>

- <%= render 'shared/tags', proposal: @proposal %> + <%= render 'shared/tags', taggable: @proposal %>
<%= render 'actions', proposal: @proposal %> diff --git a/app/views/shared/_tag_cloud.html.erb b/app/views/shared/_tag_cloud.html.erb index 171b85b3c..cc377f34d 100644 --- a/app/views/shared/_tag_cloud.html.erb +++ b/app/views/shared/_tag_cloud.html.erb @@ -3,6 +3,9 @@

<%= t("shared.tags_cloud.tags") %>


<% tag_cloud @tag_cloud, %w[s m l] do |tag, css_class| %> - <%= link_to sanitize("#{tag.name} #{tag.taggings_count}"), taggable_path(taggable, tag.name), class: css_class %> + <%= link_to taggable_path(taggable, tag.name), class: css_class do %> + <%= tag.name %> + <%= tag.send(taggable_counter_field(taggable)) %> + <% end %> <% end %>
diff --git a/app/views/shared/_tags.html.erb b/app/views/shared/_tags.html.erb index 6c6d87096..17f158d8a 100644 --- a/app/views/shared/_tags.html.erb +++ b/app/views/shared/_tags.html.erb @@ -1,5 +1,4 @@ <%- limit ||= nil %> -<%- taggable = defined?(debate) ? debate : proposal %> <% if taggable.tags.any? %> diff --git a/config/initializers/acts_as_taggable_on.rb b/config/initializers/acts_as_taggable_on.rb index 7ad1bbb22..829b43725 100644 --- a/config/initializers/acts_as_taggable_on.rb +++ b/config/initializers/acts_as_taggable_on.rb @@ -1,8 +1,43 @@ -ActsAsTaggableOn::Tagging.class_eval do - after_destroy :touch_taggable +module ActsAsTaggableOn - def touch_taggable - taggable.touch if taggable.present? + Tagging.class_eval do + + after_create :increment_tag_custom_counter + after_destroy :touch_taggable, :decrement_tag_custom_counter + + def touch_taggable + taggable.touch if taggable.present? + end + + def increment_tag_custom_counter + tag.increment_custom_counter_for(taggable_type) + end + + def decrement_tag_custom_counter + tag.decrement_custom_counter_for(taggable_type) + end end -end \ No newline at end of file + Tag.class_eval do + + def increment_custom_counter_for(taggable_type) + Tag.increment_counter(custom_counter_field_name_for(taggable_type), id) + end + + def decrement_custom_counter_for(taggable_type) + Tag.decrement_counter(custom_counter_field_name_for(taggable_type), id) + end + + def recalculate_custom_counter_for(taggable_type) + visible_taggables = taggable_type.constantize.includes(:taggings).where('taggings.taggable_type' => taggable_type, 'taggings.tag_id' => id) + + update(custom_counter_field_name_for(taggable_type) => visible_taggables.count) + end + + private + def custom_counter_field_name_for(taggable_type) + "#{taggable_type.underscore.pluralize}_count" + end + end + +end diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index b9232f055..ec6731239 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -68,7 +68,7 @@ tags = Faker::Lorem.words(25) author = User.reorder("RANDOM()").first description = "

#{Faker::Lorem.paragraphs.join('

')}

" debate = Debate.create!(author: author, - title: Faker::Lorem.sentence(3), + title: Faker::Lorem.sentence(3).truncate(60), created_at: rand((Time.now - 1.week) .. Time.now), description: description, tag_list: tags.sample(3).join(','), @@ -84,7 +84,7 @@ tags = Faker::Lorem.words(25) author = User.reorder("RANDOM()").first description = "

#{Faker::Lorem.paragraphs.join('

')}

" proposal = Proposal.create!(author: author, - title: Faker::Lorem.sentence(3), + title: Faker::Lorem.sentence(3).truncate(60), question: Faker::Lorem.sentence(3), summary: Faker::Lorem.sentence(3), responsible_name: Faker::Name.name, diff --git a/db/migrate/20150917102718_add_extra_counters_to_tags.rb b/db/migrate/20150917102718_add_extra_counters_to_tags.rb new file mode 100644 index 000000000..974784772 --- /dev/null +++ b/db/migrate/20150917102718_add_extra_counters_to_tags.rb @@ -0,0 +1,8 @@ +class AddExtraCountersToTags < ActiveRecord::Migration + def change + add_column :tags, :debates_count, :integer, default: 0 + add_column :tags, :proposals_count, :integer, default: 0 + add_index :tags, :debates_count + add_index :tags, :proposals_count + end +end diff --git a/db/schema.rb b/db/schema.rb index 395b773f6..741f82940 100644 --- a/db/schema.rb +++ b/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: 20150914191003) do +ActiveRecord::Schema.define(version: 20150917102718) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -253,12 +253,16 @@ ActiveRecord::Schema.define(version: 20150914191003) do add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "tags", force: :cascade do |t| - t.string "name", limit: 40 - t.integer "taggings_count", default: 0 - t.boolean "featured", default: false + t.string "name", limit: 40 + t.integer "taggings_count", default: 0 + t.boolean "featured", default: false + t.integer "debates_count", default: 0 + t.integer "proposals_count", default: 0 end + add_index "tags", ["debates_count"], name: "index_tags_on_debates_count", using: :btree add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree + add_index "tags", ["proposals_count"], name: "index_tags_on_proposals_count", using: :btree create_table "users", force: :cascade do |t| t.string "email", default: "", null: false diff --git a/lib/acts_as_paranoid_aliases.rb b/lib/acts_as_paranoid_aliases.rb index 6fd875ef6..7f2cfe4bd 100644 --- a/lib/acts_as_paranoid_aliases.rb +++ b/lib/acts_as_paranoid_aliases.rb @@ -4,6 +4,7 @@ module ActsAsParanoidAliases base.extend(ClassMethods) def hide + return false if hidden? update_attribute(:hidden_at, Time.now) after_hide end @@ -24,8 +25,13 @@ module ActsAsParanoidAliases end def restore(opts={}) + return false unless hidden? super(opts) update_attribute(:confirmed_hide_at, nil) + after_restore + end + + def after_restore end end @@ -48,12 +54,12 @@ module ActsAsParanoidAliases def hide_all(ids) return if ids.blank? - where(id: ids).update_all(hidden_at: Time.now) + where(id: ids).each(&:hide) end def restore_all(ids) return if ids.blank? - only_hidden.where(id: ids).update_all(hidden_at: nil) + only_hidden.where(id: ids).each(&:restore) end end end diff --git a/lib/tasks/tags.rake b/lib/tasks/tags.rake new file mode 100644 index 000000000..c4af963cc --- /dev/null +++ b/lib/tasks/tags.rake @@ -0,0 +1,11 @@ +namespace :tags do + desc "Recalculates the debate and proposals counters" + task custom_count: :environment do + ActsAsTaggableOn::Tag.find_in_batches do |tasks| + tasks.each do |task| + task.recalculate_custom_counter_for('Debate') + task.recalculate_custom_counter_for('Proposal') + end + end + end +end diff --git a/spec/lib/acts_as_taggable_on_spec.rb b/spec/lib/acts_as_taggable_on_spec.rb new file mode 100644 index 000000000..2362e8745 --- /dev/null +++ b/spec/lib/acts_as_taggable_on_spec.rb @@ -0,0 +1,72 @@ +require 'rails_helper' + +describe 'ActsAsTaggableOn' do + + describe 'Tagging' do + describe "when tagging debates or proposals" do + let(:proposal) { create(:proposal) } + let(:debate) { create(:debate) } + + it "increases and decreases the tag's custom counters" do + tag = ActsAsTaggableOn::Tag.create(name: "foo") + + expect(tag.debates_count).to eq(0) + expect(tag.proposals_count).to eq(0) + + proposal.tag_list.add("foo") + proposal.save + tag.reload + + expect(tag.debates_count).to eq(0) + expect(tag.proposals_count).to eq(1) + + debate.tag_list.add("foo") + debate.save + tag.reload + + expect(tag.debates_count).to eq(1) + expect(tag.proposals_count).to eq(1) + + proposal.tag_list.remove("foo") + proposal.save + tag.reload + + expect(tag.debates_count).to eq(1) + expect(tag.proposals_count).to eq(0) + + debate.tag_list.remove("foo") + debate.save + tag.reload + + expect(tag.debates_count).to eq(0) + expect(tag.proposals_count).to eq(0) + end + end + end + + describe 'Tag' do + describe "#recalculate_custom_counter_for" do + it "updates the counters of proposals and debates, taking into account hidden ones" do + tag = ActsAsTaggableOn::Tag.create(name: "foo") + + create(:proposal, tag_list: "foo") + create(:proposal, :hidden, tag_list: "foo") + + create(:debate, tag_list: "foo") + create(:debate, :hidden, tag_list: "foo") + + tag.update(debates_count: 0, proposals_count: 0) + + tag.recalculate_custom_counter_for('Debate') + expect(tag.debates_count).to eq(1) + + tag.recalculate_custom_counter_for('Proposal') + expect(tag.proposals_count).to eq(1) + end + end + end + + + + +end diff --git a/spec/models/debate_spec.rb b/spec/models/debate_spec.rb index 907bcef5b..1ab7b8a06 100644 --- a/spec/models/debate_spec.rb +++ b/spec/models/debate_spec.rb @@ -350,6 +350,20 @@ describe Debate do end end + describe "custom tag counters when hiding/restoring" do + it "decreases the tag counter when hiden, and increases it when restored" do + debate = create(:debate, tag_list: "foo") + tag = ActsAsTaggableOn::Tag.where(name: 'foo').first + expect(tag.debates_count).to eq(1) + + debate.hide + expect(tag.reload.debates_count).to eq(0) + + debate.restore + expect(tag.reload.debates_count).to eq(1) + end + end + describe "conflictive debates" do it "should return true when it has more than 1 flag for 5 positive votes" do diff --git a/spec/models/proposal_spec.rb b/spec/models/proposal_spec.rb index 2a5d1f9e5..20a461f15 100644 --- a/spec/models/proposal_spec.rb +++ b/spec/models/proposal_spec.rb @@ -193,6 +193,20 @@ describe Proposal do end end + describe "custom tag counters when hiding/restoring" do + it "decreases the tag counter when hiden, and increases it when restored" do + proposal = create(:proposal, tag_list: "foo") + tag = ActsAsTaggableOn::Tag.where(name: 'foo').first + expect(tag.proposals_count).to eq(1) + + proposal.hide + expect(tag.reload.proposals_count).to eq(0) + + proposal.restore + expect(tag.reload.proposals_count).to eq(1) + end + end + describe "#confidence_score" do it "takes into account votes" do