From f0818531b71f9781c5702741aac6aac6b28d67c3 Mon Sep 17 00:00:00 2001 From: James Burke Date: Fri, 24 Jan 2014 12:18:38 +0000 Subject: [PATCH] Add option to order by number of matching tags. - Adds :order_by_matching_tag_count to tagged_with method --- CHANGELOG.md | 1 + .../acts_as_taggable_on/core.rb | 17 +++++++++++++---- spec/acts_as_taggable_on/taggable_spec.rb | 12 ++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67af73c67..fa5225fd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ As such, a _Feature_ would map to either major or minor. A _bug fix_ to a patch. * Breaking Changes * Features + * [@jamesburke-examtime #467 Add :order_by_matching_tag_count option](https://github.com/mbleigh/acts-as-taggable-on/pull/469) * Fixes * [@rafael #406 Dirty attributes not correctly derived](https://github.com/mbleigh/acts-as-taggable-on/pull/406) * [@bzbnhang #440 Did not respect strict_case_match](https://github.com/mbleigh/acts-as-taggable-on/pull/440) diff --git a/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb b/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb index 879270800..27b355ec3 100644 --- a/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +++ b/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb @@ -71,6 +71,7 @@ def grouped_column_names_for(object) # @param [Hash] options A hash of options to alter you query: # * :exclude - if set to true, return objects that are *NOT* tagged with the specified tags # * :any - if set to true, return objects that are tagged with *ANY* of the specified tags + # * :order_by_matching_tag_count - if set to true and used with :any, sort by objects matching the most tags, descending # * :match_all - if set to true, return objects that are *ONLY* tagged with the specified tags # * :owned_by - return objects that are *ONLY* owned by the owner # @@ -78,6 +79,7 @@ def grouped_column_names_for(object) # User.tagged_with("awesome", "cool") # Users that are tagged with awesome and cool # User.tagged_with("awesome", "cool", :exclude => true) # Users that are not tagged with awesome or cool # User.tagged_with("awesome", "cool", :any => true) # Users that are tagged with awesome or cool + # User.tagged_with("awesome", "cool", :any => true, :order_by_matching_tag_count => true) # Sort by users who match the most tags, descending # User.tagged_with("awesome", "cool", :match_all => true) # Users that are tagged with just awesome and cool # User.tagged_with("awesome", "cool", :owned_by => foo ) # Users that are tagged with just awesome and cool by 'foo' def tagged_with(tags, options = {}) @@ -90,6 +92,7 @@ def tagged_with(tags, options = {}) conditions = [] having = [] select_clause = [] + order_by = [] context = options.delete(:on) owned_by = options.delete(:owned_by) @@ -177,25 +180,31 @@ def tagged_with(tags, options = {}) end end - taggings_alias, _ = adjust_taggings_alias("#{alias_base_name}_taggings_group"), "#{alias_base_name}_tags_group" + if options.delete(:order_by_matching_tag_count) + select_clause = "#{table_name}.*, COUNT(#{taggings_alias}.tag_id) AS #{taggings_alias}_count" + group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}" + group = group_columns + order_by << "#{taggings_alias}_count DESC" - if options.delete(:match_all) + elsif options.delete(:match_all) + taggings_alias, _ = adjust_taggings_alias("#{alias_base_name}_taggings_group"), "#{alias_base_name}_tags_group" joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" + " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" + " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}" - group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}" group = group_columns having = "COUNT(#{taggings_alias}.taggable_id) = #{tags.size}" end + order_by << options[:order] if options[:order].present? + select(select_clause). joins(joins.join(" ")). where(conditions.join(" AND ")). group(group). having(having). - order(options[:order]). + order(order_by.join(", ")). readonly(false) end diff --git a/spec/acts_as_taggable_on/taggable_spec.rb b/spec/acts_as_taggable_on/taggable_spec.rb index 851a31819..bbf675839 100644 --- a/spec/acts_as_taggable_on/taggable_spec.rb +++ b/spec/acts_as_taggable_on/taggable_spec.rb @@ -369,6 +369,18 @@ TaggableModel.tagged_with(["depressed", "css"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank] end + it "should be able to order by number of matching tags when matching any" do + bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css") + frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css") + steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby') + + TaggableModel.tagged_with(["ruby", "java"], :any => true, :order_by_matching_tag_count => true, :order => 'taggable_models.name').to_a.should == [steve, bob, frank] + TaggableModel.tagged_with(["c++", "fitter"], :any => true, :order_by_matching_tag_count => true, :order => 'taggable_models.name').to_a.should == [steve, bob] + TaggableModel.tagged_with(["depressed", "css"], :any => true, :order_by_matching_tag_count => true, :order => 'taggable_models.name').to_a.should == [frank, bob] + TaggableModel.tagged_with(["fitter", "happier", "more productive", "c++", "java", "ruby"], :any => true, :order_by_matching_tag_count => true, :order => 'taggable_models.name').to_a.should == [steve, bob, frank] + TaggableModel.tagged_with(["c++", "java", "ruby", "fitter"], :any => true, :order_by_matching_tag_count => true, :order => 'taggable_models.name').to_a.should == [steve, bob, frank] + end + context "wild: true" do it "should use params as wildcards" do bob = TaggableModel.create(:name => "Bob", :tag_list => "bob, tricia")