Skip to content

Commit

Permalink
Refactoring; Ensure queried tag is 8bit ASCII
Browse files Browse the repository at this point in the history
Fixes SQLITE3 failures
  • Loading branch information
bf4 committed Dec 10, 2013
1 parent 801e153 commit 83a3656
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 28 deletions.
32 changes: 26 additions & 6 deletions lib/acts_as_taggable_on/tag.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# coding: utf-8
module ActsAsTaggableOn
class Tag < ::ActiveRecord::Base
include ActsAsTaggableOn::Utils
Expand Down Expand Up @@ -31,18 +32,29 @@ def self.named(name)

def self.named_any(list)
if ActsAsTaggableOn.strict_case_match
where(list.map { |tag| sanitize_sql(["name = #{binary}?", tag.to_s.mb_chars]) }.join(" OR "))
clause = list.map { |tag|
sanitize_sql(["name = #{binary}?", as_8bit_ascii(tag)])
}.join(" OR ")
where(clause)
else
where(list.map { |tag| sanitize_sql(["lower(name) = ?", tag.to_s.mb_chars.downcase]) }.join(" OR "))
clause = list.map { |tag|
lowercase_ascii_tag = as_8bit_ascii(tag).downcase
sanitize_sql(["lower(name) = ?", lowercase_ascii_tag])
}.join(" OR ")
where(clause)
end
end

def self.named_like(name)
where(["name #{like_operator} ? ESCAPE '!'", "%#{escape_like(name)}%"])
clause = ["name #{like_operator} ? ESCAPE '!'", "%#{escape_like(name)}%"]
where(clause)
end

def self.named_like_any(list)
where(list.map { |tag| sanitize_sql(["name #{like_operator} ? ESCAPE '!'", "%#{escape_like(tag.to_s)}%"]) }.join(" OR "))
clause = list.map { |tag|
sanitize_sql(["name #{like_operator} ? ESCAPE '!'", "%#{escape_like(tag.to_s)}%"])
}.join(" OR ")
where(clause)
end

### CLASS METHODS:
Expand All @@ -56,7 +68,7 @@ def self.find_or_create_with_like_by_name(name)
end

def self.find_or_create_all_with_like_by_name(*list)
list = [list].flatten
list = Array(list).flatten

return [] if list.empty?

Expand Down Expand Up @@ -88,12 +100,20 @@ class << self
private

def comparable_name(str)
str.mb_chars.downcase.to_s
as_8bit_ascii(str).downcase
end

def binary
/mysql/ === ActiveRecord::Base.connection_config[:adapter] ? "BINARY " : nil
end

def as_8bit_ascii(string)
if defined?(Encoding)
string.to_s.force_encoding('BINARY')
else
string.to_s.mb_chars
end
end
end
end
end
7 changes: 4 additions & 3 deletions spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# coding: utf-8
require 'spec_helper'

describe "Acts As Taggable On" do
Expand All @@ -8,7 +9,7 @@
it "should provide a class method 'taggable?' that is false for untaggable models" do
UntaggableModel.should_not be_taggable
end

describe "Taggable Method Generation To Preserve Order" do
before(:each) do
clean_database!
Expand Down Expand Up @@ -46,15 +47,15 @@
it "should have all tag types" do
@taggable.tag_types.should == [:tags, :languages, :skills, :needs, :offerings]
end

it "should create a class attribute for preserve tag order" do
@taggable.class.should respond_to(:preserve_tag_order?)
end

it "should create an instance attribute for preserve tag order" do
@taggable.should respond_to(:preserve_tag_order?)
end

it "should respond 'false' to preserve_tag_order?" do
@taggable.class.preserve_tag_order?.should be_false
end
Expand Down
38 changes: 19 additions & 19 deletions spec/acts_as_taggable_on/acts_as_tagger_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
before(:each) do
clean_database!
end

describe "Tagger Method Generation" do
before(:each) do
@tagger = User.new
Expand All @@ -13,68 +13,68 @@
it "should add #is_tagger? query method to the class-side" do
User.should respond_to(:is_tagger?)
end

it "should return true from the class-side #is_tagger?" do
User.is_tagger?.should be_true
end

it "should return false from the base #is_tagger?" do
ActiveRecord::Base.is_tagger?.should be_false
end

it "should add #is_tagger? query method to the singleton" do
@tagger.should respond_to(:is_tagger?)
end

it "should add #tag method on the instance-side" do
@tagger.should respond_to(:tag)
end

it "should generate an association for #owned_taggings and #owned_tags" do
@tagger.should respond_to(:owned_taggings, :owned_tags)
end
end

describe "#tag" do
context 'when called with a non-existent tag context' do
before(:each) do
@tagger = User.new
@taggable = TaggableModel.new(:name=>"Richard Prior")
end

it "should by default not throw an exception " do
@taggable.tag_list_on(:foo).should be_empty
lambda {
@tagger.tag(@taggable, :with=>'this, and, that', :on=>:foo)
}.should_not raise_error
end

it 'should by default create the tag context on-the-fly' do
@taggable.tag_list_on(:here_ond_now).should be_empty
@tagger.tag(@taggable, :with=>'that', :on => :here_ond_now)
@taggable.tag_list_on(:here_ond_now).should_not include('that')
@taggable.all_tags_list_on(:here_ond_now).should include('that')
end

it "should show all the tag list when both public and owned tags exist" do
@taggable.tag_list = 'ruby, python'
@tagger.tag(@taggable, :with => 'java, lisp', :on => :tags)
@taggable.all_tags_on(:tags).map(&:name).sort.should == %w(ruby python java lisp).sort
end

it "should not add owned tags to the common list" do
@taggable.tag_list = 'ruby, python'
@tagger.tag(@taggable, :with => 'java, lisp', :on => :tags)
@taggable.tag_list.should == %w(ruby python)
@tagger.tag(@taggable, :with => '', :on => :tags)
@taggable.tag_list.should == %w(ruby python)
end

it "should throw an exception when the default is over-ridden" do
@taggable.tag_list_on(:foo_boo).should be_empty
lambda {
@tagger.tag(@taggable, :with=>'this, and, that', :on=>:foo_boo, :force=>false)
}.should raise_error
}.should raise_error
end

it "should not create the tag context on-the-fly when the default is over-ridden" do
Expand All @@ -83,28 +83,28 @@
@taggable.tag_list_on(:foo_boo).should be_empty
end
end

describe "when called by multiple tagger's" do
before(:each) do
@user_x = User.create(:name => "User X")
@user_y = User.create(:name => "User Y")
@taggable = TaggableModel.create(:name => 'acts_as_taggable_on', :tag_list => 'plugin')

@user_x.tag(@taggable, :with => 'ruby, rails', :on => :tags)
@user_y.tag(@taggable, :with => 'ruby, plugin', :on => :tags)

@user_y.tag(@taggable, :with => '', :on => :tags)
@user_y.tag(@taggable, :with => '', :on => :tags)
end
it "should delete owned tags" do

it "should delete owned tags" do
@user_y.owned_tags.should == []
end

it "should not delete other taggers tags" do
@user_x.owned_tags.should have(2).items
end

it "should not delete original tags" do
@taggable.all_tags_list_on(:tags).should include('plugin')
end
Expand Down

0 comments on commit 83a3656

Please sign in to comment.