Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use group rather than distinct or .uniq when using tagged_with(any:true). Fixes PostgreSQL errors #496

Merged
merged 1 commit into from
May 1, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ As such, a _Feature_ would map to either major or minor. A _bug fix_ to a patch.
* Features
* Fixes
* [@jonseaberg #499 fix for race condition when multiple processes try to add the same tag](https://github.com/mbleigh/acts-as-taggable-on/pull/499)
* [@leklund #496 Fix for distinct and postgresql json columns errors](https://github.com/mbleigh/acts-as-taggable-on/pull/496)
* Performance
* Misc

Expand Down
5 changes: 2 additions & 3 deletions lib/acts_as_taggable_on/acts_as_taggable_on/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ def tagged_with(tags, options = {})
end

joins << tagging_join
group = "#{table_name}.#{primary_key}"
else
tags = ActsAsTaggableOn::Tag.named_any(tag_list)

Expand Down Expand Up @@ -179,7 +180,7 @@ def tagged_with(tags, options = {})
end
end

group = [] # Rails interprets this as a no-op in the group() call below
group ||= [] # Rails interprets this as a no-op in the group() call below
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}"
Expand Down Expand Up @@ -208,8 +209,6 @@ def tagged_with(tags, options = {})
having(having).
order(order_by.join(', ')).
readonly(false)

((context and tag_types.one?) && options.delete(:any)) ? request : request.uniq
end

def is_taggable?
Expand Down
60 changes: 60 additions & 0 deletions spec/acts_as_taggable_on/taggable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@
expect(TaggableModel.tagged_with('ruby').group(:created_at).count.count).to eq(1)
end

it "shouldn't generate a query with DISTINCT by default" do
@taggable.skill_list = "ruby, rails, css"
@taggable.save

TaggableModel.tagged_with('ruby').to_sql.should_not match /DISTINCT/
end

it 'should be able to find by tag with context' do
@taggable.skill_list = 'ruby, rails, css'
@taggable.tag_list = 'bob, charlie'
Expand All @@ -236,6 +243,27 @@
expect(TaggableModel.tagged_with('ruby').to_a).to eq(TaggableModel.tagged_with('Ruby').to_a)
end

it "should be able to find by tags with other joins in the query" do
@taggable.skill_list = "ruby, rails, css"
@taggable.tag_list = "bob, charlie"
@taggable.save

TaggableModel.group(:created_at).tagged_with(['bob','css'], :any => true).first.should == @taggable

bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby, java")

# Test for explicit distinct in select
bob.untaggable_models.create!
frank.untaggable_models.create!
charlie.untaggable_models.create!

TaggableModel.select("distinct(taggable_models.id), taggable_models.*").joins(:untaggable_models).tagged_with(['css','java'], :any => true).to_a.sort.should == [bob,charlie].sort

TaggableModel.select("distinct(taggable_models.id), taggable_models.*").joins(:untaggable_models).tagged_with(['rails','ruby'], :any => false).to_a.sort.should == [bob,frank].sort
end

unless ActsAsTaggableOn::Tag.using_sqlite?
it 'should not care about case for unicode names' do
ActsAsTaggableOn.strict_case_match = false
Expand Down Expand Up @@ -754,3 +782,35 @@
end
end
end


if ActsAsTaggableOn::Tag.using_postgresql?
describe "Taggable model with json columns" do
before(:each) do
clean_database!
@taggable = TaggableModelWithJson.new(:name => "Bob Jones")
@taggables = [@taggable, TaggableModelWithJson.new(:name => "John Doe")]
end

it "should be able to find by tag with context" do
@taggable.skill_list = "ruby, rails, css"
@taggable.tag_list = "bob, charlie"
@taggable.save

TaggableModelWithJson.tagged_with("ruby").first.should == @taggable
TaggableModelWithJson.tagged_with("ruby, css").first.should == @taggable
TaggableModelWithJson.tagged_with("bob", :on => :skills).first.should_not == @taggable
TaggableModelWithJson.tagged_with("bob", :on => :tags).first.should == @taggable
end

it "should be able to find tagged with any tag" do
bob = TaggableModelWithJson.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
frank = TaggableModelWithJson.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
steve = TaggableModelWithJson.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')

TaggableModelWithJson.tagged_with(["ruby", "java"], :order => 'taggable_model_with_jsons.name', :any => true).to_a.should == [bob, frank, steve]
TaggableModelWithJson.tagged_with(["c++", "fitter"], :order => 'taggable_model_with_jsons.name', :any => true).to_a.should == [bob, steve]
TaggableModelWithJson.tagged_with(["depressed", "css"], :order => 'taggable_model_with_jsons.name', :any => true).to_a.should == [bob, frank]
end
end
end
5 changes: 5 additions & 0 deletions spec/internal/app/models/models.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,8 @@ class OrderedTaggableModel < ActiveRecord::Base
acts_as_ordered_taggable
acts_as_ordered_taggable_on :colours
end

class TaggableModelWithJson < ActiveRecord::Base
acts_as_taggable
acts_as_taggable_on :skills
end
11 changes: 11 additions & 0 deletions spec/internal/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,15 @@
t.column :name, :string
t.column :type, :string
end

create_table :taggable_model_with_jsons, :force => true do |t|
t.column :name, :string
t.column :type, :string
if self.connection.adapter_name == 'PostgreSQL' && self.connection.execute("SHOW SERVER_VERSION").first["server_version"].to_f >= 9.2
type = :json
else
type = :string
end
t.column :opts, type
end
end
82 changes: 82 additions & 0 deletions spec/schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
ActiveRecord::Schema.define :version => 0 do
create_table :tags, :force => true do |t|
t.string :name
t.integer :taggings_count, :default => 0
end
add_index "tags", ["name"], name: "index_tags_on_name", unique: true

create_table :taggings, :force => true do |t|
t.references :tag

# You should make sure that the column created is
# long enough to store the required class names.
t.references :taggable, :polymorphic => true
t.references :tagger, :polymorphic => true

# Limit is created to prevent MySQL error on index
# length for MyISAM table type: http://bit.ly/vgW2Ql
t.string :context, :limit => 128

t.datetime :created_at
end
add_index "taggings",
["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"],
unique: true, name: "taggings_idx"

# above copied from
# generators/acts_as_taggable_on/migration/migration_generator

create_table :taggable_models, :force => true do |t|
t.column :name, :string
t.column :type, :string
end

create_table :non_standard_id_taggable_models, :primary_key => "an_id", :force => true do |t|
t.column :name, :string
t.column :type, :string
end

create_table :untaggable_models, :force => true do |t|
t.column :taggable_model_id, :integer
t.column :name, :string
end

create_table :cached_models, :force => true do |t|
t.column :name, :string
t.column :type, :string
t.column :cached_tag_list, :string
end

create_table :other_cached_models, :force => true do |t|
t.column :name, :string
t.column :type, :string
t.column :cached_language_list, :string
t.column :cached_status_list, :string
t.column :cached_glass_list, :string
end

create_table :users, :force => true do |t|
t.column :name, :string
end

create_table :other_taggable_models, :force => true do |t|
t.column :name, :string
t.column :type, :string
end

create_table :ordered_taggable_models, :force => true do |t|
t.column :name, :string
t.column :type, :string
end

create_table :taggable_model_with_jsons, :force => true do |t|
t.column :name, :string
t.column :type, :string
if self.connection.adapter_name == 'PostgreSQL' && self.connection.execute("SHOW SERVER_VERSION").first["server_version"].to_f >= 9.2
type = :json
else
type = :string
end
t.column :opts, type
end
end
2 changes: 1 addition & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def logger_off
def clean_database!
models = [ActsAsTaggableOn::Tag, ActsAsTaggableOn::Tagging, TaggableModel, OtherTaggableModel,
InheritingTaggableModel, AlteredInheritingTaggableModel, User, UntaggableModel,
OrderedTaggableModel]
OrderedTaggableModel, TaggableModelWithJson]
models.each do |model|
#Sqlite don't support truncate
if ActsAsTaggableOn::Utils.using_sqlite?
Expand Down