acts-as-taggable-on 2.0.6 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ *.log
2
+ *.sqlite3
3
+ /pkg/*
4
+ .bundle
5
+ .rvmrc
6
+ Gemfile.lock
7
+ spec/database.yml
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --backtrace
@@ -0,0 +1,10 @@
1
+ script: "cp spec/database.yml.sample spec/database.yml && bundle install && bundle exec rake"
2
+ rvm:
3
+ - 1.8.7
4
+ - ree
5
+ - 1.9.2
6
+ - rbx
7
+ env:
8
+ - DB=sqlite3
9
+ - DB=mysql
10
+ - DB=postgresql
data/Gemfile CHANGED
@@ -1,10 +1,3 @@
1
- source :gemcutter
1
+ source 'http://rubygems.org'
2
+ gemspec
2
3
 
3
- # Rails 3.0
4
- gem 'rails', '3.0.0.beta3'
5
- gem 'rspec', '2.0.0.beta.8'
6
- gem 'sqlite3-ruby', :require => 'sqlite3'
7
- gem 'mysql'
8
- gem 'pg'
9
- gem 'jeweler'
10
- gem 'rcov'
@@ -0,0 +1,5 @@
1
+ guard 'rspec' do
2
+ watch(%r{^spec/.+_spec\.rb})
3
+ watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
@@ -96,7 +96,7 @@ This way you can mix and match to filter down your results, and it also improves
96
96
  compatibility with the will_paginate gem:
97
97
 
98
98
  class User < ActiveRecord::Base
99
- acts_as_taggable_on :tags
99
+ acts_as_taggable_on :tags, :skills
100
100
  named_scope :by_join_date, :order => "created_at DESC"
101
101
  end
102
102
 
@@ -108,7 +108,10 @@ compatibility with the will_paginate gem:
108
108
 
109
109
  # Find a user with any of the tags:
110
110
  User.tagged_with(["awesome", "cool"], :any => true)
111
-
111
+
112
+ # Find a user with any of tags based on context:
113
+ User.tagged_with(['awesome, cool'], :on => :tags, :any => true).tagged_with(['smart', 'shy'], :on => :skills, :any => true)
114
+
112
115
  === Relationships
113
116
 
114
117
  You can find objects of the same type based on similar tags on certain contexts.
data/Rakefile CHANGED
@@ -1,59 +1,13 @@
1
- begin
2
- # Rspec 1.3.0
3
- require 'spec/rake/spectask'
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.setup :default, :development
4
4
 
5
- desc 'Default: run specs'
6
- task :default => :spec
7
- Spec::Rake::SpecTask.new do |t|
8
- t.spec_files = FileList["spec/**/*_spec.rb"]
9
- end
5
+ desc 'Default: run specs'
6
+ task :default => :spec
10
7
 
11
- Spec::Rake::SpecTask.new('rcov') do |t|
12
- t.spec_files = FileList["spec/**/*_spec.rb"]
13
- t.rcov = true
14
- t.rcov_opts = ['--exclude', 'spec']
15
- end
16
-
17
- rescue LoadError
18
- # Rspec 2.0
19
- require 'rspec/core/rake_task'
20
-
21
- desc 'Default: run specs'
22
- task :default => :spec
23
- Rspec::Core::RakeTask.new do |t|
24
- t.pattern = "spec/**/*_spec.rb"
25
- end
26
-
27
- Rspec::Core::RakeTask.new('rcov') do |t|
28
- t.pattern = "spec/**/*_spec.rb"
29
- t.rcov = true
30
- t.rcov_opts = ['--exclude', 'spec']
31
- end
32
-
33
- rescue LoadError
34
- puts "Rspec not available. Install it with: gem install rspec"
35
- end
36
-
37
- namespace 'rails2.3' do
38
- task :spec do
39
- gemfile = File.join(File.dirname(__FILE__), 'lib', 'acts_as_taggable_on', 'compatibility', 'Gemfile')
40
- ENV['BUNDLE_GEMFILE'] = gemfile
41
- Rake::Task['spec'].invoke
42
- end
8
+ require 'rspec/core/rake_task'
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.pattern = "spec/**/*_spec.rb"
43
11
  end
44
12
 
45
- begin
46
- require 'jeweler'
47
- Jeweler::Tasks.new do |gemspec|
48
- gemspec.name = "acts-as-taggable-on"
49
- gemspec.summary = "ActsAsTaggableOn is a tagging plugin for Rails that provides multiple tagging contexts on a single model."
50
- gemspec.description = "With ActsAsTaggableOn, you could tag a single model on several contexts, such as skills, interests, and awards. It also provides other advanced functionality."
51
- gemspec.email = "michael@intridea.com"
52
- gemspec.homepage = "http://github.com/mbleigh/acts-as-taggable-on"
53
- gemspec.authors = ["Michael Bleigh"]
54
- gemspec.files = FileList["[A-Z]*", "{generators,lib,spec,rails}/**/*"] - FileList["**/*.log"]
55
- end
56
- Jeweler::GemcutterTasks.new
57
- rescue LoadError
58
- puts "Jeweler not available. Install it with: gem install jeweler"
59
- end
13
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,27 @@
1
+ $:.push File.dirname(__FILE__) + '/lib'
2
+ require 'acts-as-taggable-on/version'
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = %q{acts-as-taggable-on}
6
+ gem.authors = ["Michael Bleigh"]
7
+ gem.date = %q{2010-05-19}
8
+ gem.description = %q{With ActsAsTaggableOn, you can tag a single model on several contexts, such as skills, interests, and awards. It also provides other advanced functionality.}
9
+ gem.summary = "Advanced tagging for Rails."
10
+ gem.email = %q{michael@intridea.com}
11
+ gem.homepage = ''
12
+
13
+ gem.add_runtime_dependency 'rails'
14
+ gem.add_development_dependency 'rspec', '~> 2.5'
15
+ gem.add_development_dependency 'sqlite3'
16
+ gem.add_development_dependency 'mysql2', '< 0.3'
17
+ gem.add_development_dependency 'pg'
18
+ gem.add_development_dependency 'guard'
19
+ gem.add_development_dependency 'guard-rspec'
20
+
21
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ gem.files = `git ls-files`.split("\n")
23
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ gem.name = "acts-as-taggable-on"
25
+ gem.require_paths = ['lib']
26
+ gem.version = ActsAsTaggableOn::VERSION
27
+ end
@@ -1,9 +1,13 @@
1
1
  require "active_record"
2
+ require "active_record/version"
2
3
  require "action_view"
4
+ RAILS_3 = ::ActiveRecord::VERSION::MAJOR >= 3
3
5
 
4
6
  $LOAD_PATH.unshift(File.dirname(__FILE__))
5
7
 
6
- require "acts_as_taggable_on/compatibility/active_record_backports" if ActiveRecord::VERSION::MAJOR < 3
8
+ require "acts_as_taggable_on/compatibility/active_record_backports" unless RAILS_3
9
+
10
+ require "acts_as_taggable_on/utils"
7
11
 
8
12
  require "acts_as_taggable_on/acts_as_taggable_on"
9
13
  require "acts_as_taggable_on/acts_as_taggable_on/core"
@@ -12,6 +16,7 @@ require "acts_as_taggable_on/acts_as_taggable_on/cache"
12
16
  require "acts_as_taggable_on/acts_as_taggable_on/ownership"
13
17
  require "acts_as_taggable_on/acts_as_taggable_on/related"
14
18
 
19
+ #require "acts_as_taggable_on/utils"
15
20
  require "acts_as_taggable_on/acts_as_tagger"
16
21
  require "acts_as_taggable_on/tag"
17
22
  require "acts_as_taggable_on/tag_list"
@@ -20,6 +25,7 @@ require "acts_as_taggable_on/tagging"
20
25
 
21
26
  $LOAD_PATH.shift
22
27
 
28
+
23
29
  if defined?(ActiveRecord::Base)
24
30
  ActiveRecord::Base.extend ActsAsTaggableOn::Taggable
25
31
  ActiveRecord::Base.send :include, ActsAsTaggableOn::Tagger
@@ -0,0 +1,4 @@
1
+ module ActsAsTaggableOn
2
+ VERSION = '2.1.0'
3
+ end
4
+
@@ -28,10 +28,19 @@ module ActsAsTaggableOn
28
28
  tag_types = tag_types.to_a.flatten.compact.map(&:to_sym)
29
29
 
30
30
  if taggable?
31
- write_inheritable_attribute(:tag_types, (self.tag_types + tag_types).uniq)
31
+ if RAILS_3
32
+ self.tag_types = (self.tag_types + tag_types).uniq
33
+ else
34
+ write_inheritable_attribute(:tag_types, (self.tag_types + tag_types).uniq)
35
+ end
32
36
  else
33
- write_inheritable_attribute(:tag_types, tag_types)
34
- class_inheritable_reader(:tag_types)
37
+ if RAILS_3
38
+ class_attribute :tag_types
39
+ self.tag_types = tag_types
40
+ else
41
+ write_inheritable_attribute(:tag_types, tag_types)
42
+ class_inheritable_reader(:tag_types)
43
+ end
35
44
 
36
45
  class_eval do
37
46
  has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "ActsAsTaggableOn::Tagging"
@@ -40,7 +49,8 @@ module ActsAsTaggableOn
40
49
  def self.taggable?
41
50
  true
42
51
  end
43
-
52
+
53
+ include ActsAsTaggableOn::Utils
44
54
  include ActsAsTaggableOn::Taggable::Core
45
55
  include ActsAsTaggableOn::Taggable::Collection
46
56
  include ActsAsTaggableOn::Taggable::Cache
@@ -11,11 +11,11 @@ module ActsAsTaggableOn::Taggable
11
11
  before_save :save_cached_tag_list
12
12
  end
13
13
 
14
- base.intialize_acts_as_taggable_on_cache
14
+ base.initialize_acts_as_taggable_on_cache
15
15
  end
16
16
 
17
17
  module ClassMethods
18
- def intialize_acts_as_taggable_on_cache
18
+ def initialize_acts_as_taggable_on_cache
19
19
  tag_types.map(&:to_s).each do |tag_type|
20
20
  class_eval %(
21
21
  def self.caching_#{tag_type.singularize}_list?
@@ -27,7 +27,7 @@ module ActsAsTaggableOn::Taggable
27
27
 
28
28
  def acts_as_taggable_on(*args)
29
29
  super(*args)
30
- intialize_acts_as_taggable_on_cache
30
+ initialize_acts_as_taggable_on_cache
31
31
  end
32
32
 
33
33
  def caching_tag_list_on?(context)
@@ -61,65 +61,72 @@ module ActsAsTaggableOn::Taggable
61
61
 
62
62
  ## Generate conditions:
63
63
  options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
64
-
64
+
65
65
  start_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
66
66
  end_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
67
-
67
+
68
68
  taggable_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = ?", base_class.name])
69
- taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?", options.delete(:id)]) if options[:id]
70
-
71
- conditions = [
69
+ taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?", options.delete(:id)]) if options[:id]
70
+ taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", options.delete(:on).to_s]) if options[:on]
71
+
72
+ tagging_conditions = [
72
73
  taggable_conditions,
73
- options[:conditions],
74
74
  scope[:conditions],
75
75
  start_at_conditions,
76
76
  end_at_conditions
77
77
  ].compact.reverse
78
78
 
79
+ tag_conditions = [
80
+ options[:conditions]
81
+ ].compact.reverse
82
+
79
83
  ## Generate joins:
80
- tagging_join = "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} ON #{ActsAsTaggableOn::Tag.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.tag_id"
81
- tagging_join << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", options.delete(:on).to_s]) if options[:on]
82
-
83
84
  taggable_join = "INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id"
84
85
  taggable_join << " AND #{table_name}.#{inheritance_column} = '#{name}'" unless descends_from_active_record? # Current model is STI descendant, so add type checking to the join condition
85
86
 
86
- joins = [
87
- tagging_join,
87
+ tagging_joins = [
88
88
  taggable_join,
89
89
  scope[:joins]
90
90
  ].compact
91
91
 
92
- joins = joins.reverse if ActiveRecord::VERSION::MAJOR < 3
92
+ tag_joins = [
93
+ ].compact
93
94
 
95
+ [tagging_joins, tag_joins].each(&:reverse!) if ActiveRecord::VERSION::MAJOR < 3
94
96
 
95
97
  ## Generate scope:
96
- scope = ActsAsTaggableOn::Tag.scoped(:select => "#{ActsAsTaggableOn::Tag.table_name}.*, COUNT(*) AS count").order(options[:order]).limit(options[:limit])
97
-
98
+ tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id, COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) AS tags_count")
99
+ tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*, #{ActsAsTaggableOn::Tagging.table_name}.tags_count AS count").order(options[:order]).limit(options[:limit])
100
+
98
101
  # Joins and conditions
99
- joins.each { |join| scope = scope.joins(join) }
100
- conditions.each { |condition| scope = scope.where(condition) }
101
-
102
+ tagging_joins.each { |join| tagging_scope = tagging_scope.joins(join) }
103
+ tagging_conditions.each { |condition| tagging_scope = tagging_scope.where(condition) }
104
+
105
+ tag_joins.each { |join| tag_scope = tag_scope.joins(join) }
106
+ tag_conditions.each { |condition| tag_scope = tag_scope.where(condition) }
107
+
102
108
  # GROUP BY and HAVING clauses:
103
- at_least = sanitize_sql(['COUNT(*) >= ?', options.delete(:at_least)]) if options[:at_least]
104
- at_most = sanitize_sql(['COUNT(*) <= ?', options.delete(:at_most)]) if options[:at_most]
105
- having = [at_least, at_most].compact.join(' AND ')
109
+ at_least = sanitize_sql(['tags_count >= ?', options.delete(:at_least)]) if options[:at_least]
110
+ at_most = sanitize_sql(['tags_count <= ?', options.delete(:at_most)]) if options[:at_most]
111
+ having = ["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) > 0", at_least, at_most].compact.join(' AND ')
112
+
113
+ group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id"
106
114
 
107
115
  if ActiveRecord::VERSION::MAJOR >= 3
108
116
  # Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
109
117
  scoped_select = "#{table_name}.#{primary_key}"
110
- scope = scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{select(scoped_select).to_sql})")
111
-
112
- # We have having() in RoR 3.0 so use it:
113
- having = having.blank? ? "COUNT(*) > 0" : "COUNT(*) > 0 AND #{having}"
114
- scope = scope.group(grouped_column_names_for(ActsAsTaggableOn::Tag)).having(having)
118
+ tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{select(scoped_select).to_sql})").
119
+ group(group_columns).
120
+ having(having)
115
121
  else
116
122
  # Having is not available in 2.3.x:
117
- group_by = "#{grouped_column_names_for(ActsAsTaggableOn::Tag)} HAVING COUNT(*) > 0"
123
+ group_by = "#{group_columns} HAVING COUNT(*) > 0"
118
124
  group_by << " AND #{having}" unless having.blank?
119
- scope = scope.group(group_by)
125
+ tagging_scope = tagging_scope.group(group_by)
120
126
  end
121
127
 
122
- scope
128
+ tag_scope = tag_scope.joins("JOIN (#{tagging_scope.to_sql}) AS taggings ON taggings.tag_id = tags.id")
129
+ tag_scope
123
130
  end
124
131
  end
125
132
 
@@ -1,5 +1,5 @@
1
1
  module ActsAsTaggableOn::Taggable
2
- module Core
2
+ module Core
3
3
  def self.included(base)
4
4
  base.send :include, ActsAsTaggableOn::Taggable::Core::InstanceMethods
5
5
  base.extend ActsAsTaggableOn::Taggable::Core::ClassMethods
@@ -21,7 +21,7 @@ module ActsAsTaggableOn::Taggable
21
21
 
22
22
  class_eval do
23
23
  has_many context_taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "ActsAsTaggableOn::Tagging",
24
- :conditions => ["#{ActsAsTaggableOn::Tagging.table_name}.tagger_id IS NULL AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_type]
24
+ :conditions => ["#{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_type]
25
25
  has_many context_tags, :through => context_taggings, :source => :tag, :class_name => "ActsAsTaggableOn::Tag"
26
26
  end
27
27
 
@@ -67,31 +67,51 @@ module ActsAsTaggableOn::Taggable
67
67
  # User.tagged_with("awesome", "cool", :match_all => true) # Users that are tagged with just awesome and cool
68
68
  def tagged_with(tags, options = {})
69
69
  tag_list = ActsAsTaggableOn::TagList.from(tags)
70
+ empty_result = scoped(:conditions => "1 = 0")
70
71
 
71
- return {} if tag_list.empty?
72
+ return empty_result if tag_list.empty?
72
73
 
73
74
  joins = []
74
75
  conditions = []
75
76
 
76
77
  context = options.delete(:on)
78
+ alias_base_name = undecorated_table_name.gsub('.','_')
77
79
 
78
80
  if options.delete(:exclude)
79
- tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
81
+ tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{like_operator} ?", t]) }.join(" OR ")
80
82
  conditions << "#{table_name}.#{primary_key} NOT IN (SELECT #{ActsAsTaggableOn::Tagging.table_name}.taggable_id FROM #{ActsAsTaggableOn::Tagging.table_name} JOIN #{ActsAsTaggableOn::Tag.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"
81
83
 
82
84
  elsif options.delete(:any)
83
- tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
84
- conditions << "#{table_name}.#{primary_key} IN (SELECT #{ActsAsTaggableOn::Tagging.table_name}.taggable_id FROM #{ActsAsTaggableOn::Tagging.table_name} JOIN #{ActsAsTaggableOn::Tag.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"
85
+ # get tags, drop out if nothing returned (we need at least one)
86
+ tags = ActsAsTaggableOn::Tag.named_any(tag_list)
87
+ return scoped(:conditions => "1 = 0") unless tags.length > 0
88
+
89
+ # setup taggings alias so we can chain, ex: items_locations_taggings_awesome_cool_123
90
+ # avoid ambiguous column name
91
+ taggings_context = context ? "_#{context}" : ''
92
+
93
+ #TODO: fix alias to be smaller
94
+ taggings_alias = "#{alias_base_name}#{taggings_context}_taggings_#{tags.map(&:safe_name).join('_')}_#{rand(1024)}"
95
+
96
+ tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
97
+ " ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
98
+ " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}"
99
+ tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
100
+
101
+ # don't need to sanitize sql, map all ids and join with OR logic
102
+ conditions << tags.map { |t| "#{taggings_alias}.tag_id = #{t.id}" }.join(" OR ")
103
+ select_clause = "DISTINCT #{table_name}.*" unless context and tag_types.one?
104
+
105
+ joins << tagging_join
85
106
 
86
107
  else
87
108
  tags = ActsAsTaggableOn::Tag.named_any(tag_list)
88
- return scoped(:conditions => "1 = 0") unless tags.length == tag_list.length
109
+ return empty_result unless tags.length == tag_list.length
89
110
 
90
111
  tags.each do |tag|
91
- safe_tag = tag.name.gsub(/[^a-zA-Z0-9]/, '')
92
- prefix = "#{safe_tag}_#{rand(1024)}"
112
+ prefix = "#{tag.safe_name}_#{rand(1024)}"
93
113
 
94
- taggings_alias = "#{table_name}_taggings_#{prefix}"
114
+ taggings_alias = "#{alias_base_name}_taggings_#{prefix}"
95
115
 
96
116
  tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
97
117
  " ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
@@ -103,18 +123,20 @@ module ActsAsTaggableOn::Taggable
103
123
  end
104
124
  end
105
125
 
106
- taggings_alias, tags_alias = "#{table_name}_taggings_group", "#{table_name}_tags_group"
126
+ taggings_alias, tags_alias = "#{alias_base_name}_taggings_group", "#{alias_base_name}_tags_group"
107
127
 
108
128
  if options.delete(:match_all)
109
129
  joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
110
130
  " ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
111
131
  " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}"
112
132
 
113
- group = "#{grouped_column_names_for(self)} HAVING COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
114
- end
115
133
 
134
+ group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
135
+ group = "#{group_columns} HAVING COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
136
+ end
116
137
 
117
- scoped(:joins => joins.join(" "),
138
+ scoped(:select => select_clause,
139
+ :joins => joins.join(" "),
118
140
  :group => group,
119
141
  :conditions => conditions.join(" AND "),
120
142
  :order => options[:order],
@@ -176,8 +198,17 @@ module ActsAsTaggableOn::Taggable
176
198
  tag_table_name = ActsAsTaggableOn::Tag.table_name
177
199
  tagging_table_name = ActsAsTaggableOn::Tagging.table_name
178
200
 
179
- opts = ["#{tagging_table_name}.context = ?", context.to_s]
180
- base_tags.where(opts).order("max(#{tagging_table_name}.created_at)").group("#{tag_table_name}.id, #{tag_table_name}.name").all
201
+ opts = ["#{tagging_table_name}.context = ?", context.to_s]
202
+ scope = base_tags.where(opts)
203
+
204
+ if ActsAsTaggableOn::Tag.using_postgresql?
205
+ group_columns = grouped_column_names_for(ActsAsTaggableOn::Tag)
206
+ scope = scope.order("max(#{tagging_table_name}.created_at)").group(group_columns)
207
+ else
208
+ scope = scope.group("#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}")
209
+ end
210
+
211
+ scope.all
181
212
  end
182
213
 
183
214
  ##
@@ -238,4 +269,4 @@ module ActsAsTaggableOn::Taggable
238
269
  end
239
270
  end
240
271
  end
241
- end
272
+ end
@@ -30,9 +30,13 @@ module ActsAsTaggableOn::Taggable
30
30
 
31
31
  module InstanceMethods
32
32
  def owner_tags_on(owner, context)
33
- base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND
34
- #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = ? AND
35
- #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = ?), context.to_s, owner.id, owner.class.to_s]).all
33
+ if owner.nil?
34
+ base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ?), context.to_s]).all
35
+ else
36
+ base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND
37
+ #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = ? AND
38
+ #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = ?), context.to_s, owner.id, owner.class.to_s]).all
39
+ end
36
40
  end
37
41
 
38
42
  def cached_owned_tag_list_on(context)
@@ -18,7 +18,11 @@ module ActsAsTaggableOn::Taggable
18
18
  def find_related_#{tag_type}_for(klass, options = {})
19
19
  related_tags_for('#{tag_type}', klass, options)
20
20
  end
21
-
21
+ )
22
+ end
23
+
24
+ unless tag_types.empty?
25
+ class_eval %(
22
26
  def find_matching_contexts(search_context, result_context, options = {})
23
27
  matching_contexts_for(search_context.to_s, result_context.to_s, self.class, options)
24
28
  end
@@ -42,10 +46,12 @@ module ActsAsTaggableOn::Taggable
42
46
 
43
47
  exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
44
48
 
49
+ group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(klass) : "#{klass.table_name}.#{klass.primary_key}"
50
+
45
51
  klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.id) AS count",
46
52
  :from => "#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}",
47
53
  :conditions => ["#{exclude_self} #{klass.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, result_context],
48
- :group => grouped_column_names_for(klass),
54
+ :group => group_columns,
49
55
  :order => "count DESC" }.update(options))
50
56
  end
51
57
 
@@ -54,10 +60,12 @@ module ActsAsTaggableOn::Taggable
54
60
 
55
61
  exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
56
62
 
63
+ group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(klass) : "#{klass.table_name}.#{klass.primary_key}"
64
+
57
65
  klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.id) AS count",
58
66
  :from => "#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}",
59
67
  :conditions => ["#{exclude_self} #{klass.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?)", tags_to_find],
60
- :group => grouped_column_names_for(klass),
68
+ :group => group_columns,
61
69
  :order => "count DESC" }.update(options))
62
70
  end
63
71
  end
@@ -4,5 +4,5 @@ source :gemcutter
4
4
  gem 'rails', '2.3.5'
5
5
  gem 'rspec', '1.3.0', :require => 'spec'
6
6
  gem 'sqlite3-ruby', '1.2.5', :require => 'sqlite3'
7
- gem 'mysql'
7
+ gem 'mysql2', '~> 0.2.7'
8
8
  gem 'pg'
@@ -9,7 +9,11 @@ module ActsAsTaggableOn
9
9
  named_scope :order, lambda { |order| { :order => order } }
10
10
  named_scope :select, lambda { |select| { :select => select } }
11
11
  named_scope :limit, lambda { |limit| { :limit => limit } }
12
- named_scope :readonly, lambda { |readonly| { :readonly => readonly } }
12
+ named_scope :readonly, lambda { |readonly| { :readonly => readonly } }
13
+
14
+ def self.to_sql
15
+ construct_finder_sql({})
16
+ end
13
17
  end
14
18
  end
15
19
  end
@@ -1,7 +1,8 @@
1
1
  module ActsAsTaggableOn
2
2
  class Tag < ::ActiveRecord::Base
3
3
  include ActsAsTaggableOn::ActiveRecord::Backports if ::ActiveRecord::VERSION::MAJOR < 3
4
-
4
+ include ActsAsTaggableOn::Utils
5
+
5
6
  attr_accessible :name
6
7
 
7
8
  ### ASSOCIATIONS:
@@ -14,21 +15,21 @@ module ActsAsTaggableOn
14
15
  validates_uniqueness_of :name
15
16
 
16
17
  ### SCOPES:
17
-
18
+
18
19
  def self.named(name)
19
- where(["name #{like_operator} ?", name])
20
+ where(["name #{like_operator} ?", escape_like(name)])
20
21
  end
21
22
 
22
23
  def self.named_any(list)
23
- where(list.map { |tag| sanitize_sql(["name #{like_operator} ?", tag.to_s]) }.join(" OR "))
24
+ where(list.map { |tag| sanitize_sql(["name #{like_operator} ?", escape_like(tag.to_s)]) }.join(" OR "))
24
25
  end
25
26
 
26
27
  def self.named_like(name)
27
- where(["name #{like_operator} ?", "%#{name}%"])
28
+ where(["name #{like_operator} ?", "%#{escape_like(name)}%"])
28
29
  end
29
30
 
30
31
  def self.named_like_any(list)
31
- where(list.map { |tag| sanitize_sql(["name #{like_operator} ?", "%#{tag.to_s}%"]) }.join(" OR "))
32
+ where(list.map { |tag| sanitize_sql(["name #{like_operator} ?", "%#{escape_like(tag.to_s)}%"]) }.join(" OR "))
32
33
  end
33
34
 
34
35
  ### CLASS METHODS:
@@ -43,7 +44,10 @@ module ActsAsTaggableOn
43
44
  return [] if list.empty?
44
45
 
45
46
  existing_tags = Tag.named_any(list).all
46
- new_tag_names = list.reject { |name| existing_tags.any? { |tag| tag.name.mb_chars.downcase == name.mb_chars.downcase } }
47
+ new_tag_names = list.reject do |name|
48
+ name = comparable_name(name)
49
+ existing_tags.any? { |tag| comparable_name(tag.name) == name }
50
+ end
47
51
  created_tags = new_tag_names.map { |name| Tag.create(:name => name) }
48
52
 
49
53
  existing_tags + created_tags
@@ -62,13 +66,16 @@ module ActsAsTaggableOn
62
66
  def count
63
67
  read_attribute(:count).to_i
64
68
  end
65
-
69
+
70
+ def safe_name
71
+ name.gsub(/[^a-zA-Z0-9]/, '')
72
+ end
73
+
66
74
  class << self
67
- private
68
- def like_operator
69
- connection.adapter_name == 'PostgreSQL' ? 'ILIKE' : 'LIKE'
75
+ private
76
+ def comparable_name(str)
77
+ RUBY_VERSION >= "1.9" ? str.downcase : str.mb_chars.downcase
70
78
  end
71
79
  end
72
-
73
80
  end
74
81
  end
@@ -0,0 +1,31 @@
1
+ module ActsAsTaggableOn
2
+ module Utils
3
+ def self.included(base)
4
+
5
+ base.send :include, ActsAsTaggableOn::Utils::OverallMethods
6
+ base.extend ActsAsTaggableOn::Utils::OverallMethods
7
+ end
8
+
9
+ module OverallMethods
10
+ def using_postgresql?
11
+ ::ActiveRecord::Base.connection && ::ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
12
+ end
13
+
14
+ def using_sqlite?
15
+ ::ActiveRecord::Base.connection && ::ActiveRecord::Base.connection.adapter_name == 'SQLite'
16
+ end
17
+
18
+ private
19
+ def like_operator
20
+ using_postgresql? ? 'ILIKE' : 'LIKE'
21
+ end
22
+
23
+ # escape _ and % characters in strings, since these are wildcards in SQL.
24
+ def escape_like(str)
25
+ return str if using_sqlite? # skip escaping for SQLite
26
+ str.to_s.gsub("_", "\\\_").gsub("%", "\\\%")
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -19,7 +19,7 @@ module ActsAsTaggableOn
19
19
  end
20
20
 
21
21
  def self.next_migration_number(path)
22
- Time.now.utc.strftime("%Y%m%d%H%M%S")
22
+ ActiveRecord::Generators::Base.next_migration_number(path)
23
23
  end
24
24
 
25
25
  def create_migration_file
@@ -12,7 +12,7 @@ describe "Acts As Taggable On" do
12
12
  describe "Taggable Method Generation" do
13
13
  before(:each) do
14
14
  clean_database!
15
- TaggableModel.write_inheritable_attribute(:tag_types, [])
15
+ RAILS_3 ? TaggableModel.tag_types = [] : TaggableModel.write_inheritable_attribute(:tag_types, [])
16
16
  TaggableModel.acts_as_taggable_on(:tags, :languages, :skills, :needs, :offerings)
17
17
  @taggable = TaggableModel.new(:name => "Bob Jones")
18
18
  end
@@ -212,24 +212,36 @@ describe "Acts As Taggable On" do
212
212
  describe 'Caching' do
213
213
  before(:each) do
214
214
  @taggable = CachedModel.new(:name => "Bob Jones")
215
+ @another_taggable = OtherCachedModel.new(:name => "John Smith")
215
216
  end
216
217
 
217
218
  it "should add saving of tag lists and cached tag lists to the instance" do
218
219
  @taggable.should respond_to(:save_cached_tag_list)
220
+ @another_taggable.should respond_to(:save_cached_tag_list)
221
+
219
222
  @taggable.should respond_to(:save_tags)
220
223
  end
221
224
 
225
+ it "should add cached tag lists to the instance if cached column is not present" do
226
+ TaggableModel.new(:name => "Art Kram").should_not respond_to(:save_cached_tag_list)
227
+ end
228
+
222
229
  it "should generate a cached column checker for each tag type" do
223
230
  CachedModel.should respond_to(:caching_tag_list?)
231
+ OtherCachedModel.should respond_to(:caching_language_list?)
224
232
  end
225
233
 
226
234
  it 'should not have cached tags' do
227
235
  @taggable.cached_tag_list.should be_blank
236
+ @another_taggable.cached_language_list.should be_blank
228
237
  end
229
238
 
230
239
  it 'should cache tags' do
231
240
  @taggable.update_attributes(:tag_list => 'awesome, epic')
232
241
  @taggable.cached_tag_list.should == 'awesome, epic'
242
+
243
+ @another_taggable.update_attributes(:language_list => 'ruby, .net')
244
+ @another_taggable.cached_language_list.should == 'ruby, .net'
233
245
  end
234
246
 
235
247
  it 'should keep the cache' do
@@ -265,4 +277,13 @@ describe "Acts As Taggable On" do
265
277
  end
266
278
  end
267
279
 
280
+ describe "taggings" do
281
+ before(:each) do
282
+ @taggable = TaggableModel.new(:name => "Art Kram")
283
+ end
284
+
285
+ it 'should return [] taggings' do
286
+ @taggable.taggings.should == []
287
+ end
288
+ end
268
289
  end
@@ -112,4 +112,24 @@ describe ActsAsTaggableOn::Tag do
112
112
  @another_tag = ActsAsTaggableOn::Tag.create!(:name => "coolip")
113
113
  ActsAsTaggableOn::Tag.named_like('cool').should include(@tag, @another_tag)
114
114
  end
115
+
116
+ describe "escape wildcard symbols in like requests" do
117
+ before(:each) do
118
+ @tag.name = "cool"
119
+ @tag.save
120
+ @another_tag = ActsAsTaggableOn::Tag.create!(:name => "coo%")
121
+ @another_tag2 = ActsAsTaggableOn::Tag.create!(:name => "coolish")
122
+ end
123
+
124
+ it "return escaped result when '%' char present in tag" do
125
+ if @tag.using_sqlite?
126
+ ActsAsTaggableOn::Tag.named_like('coo%').should include(@tag)
127
+ ActsAsTaggableOn::Tag.named_like('coo%').should include(@another_tag)
128
+ else
129
+ ActsAsTaggableOn::Tag.named_like('coo%').should_not include(@tag)
130
+ ActsAsTaggableOn::Tag.named_like('coo%').should include(@another_tag)
131
+ end
132
+ end
133
+
134
+ end
115
135
  end
@@ -4,6 +4,7 @@ describe "Taggable" do
4
4
  before(:each) do
5
5
  clean_database!
6
6
  @taggable = TaggableModel.new(:name => "Bob Jones")
7
+ @taggables = [@taggable, TaggableModel.new(:name => "John Doe")]
7
8
  end
8
9
 
9
10
  it "should have tag types" do
@@ -68,6 +69,22 @@ describe "Taggable" do
68
69
  @taggable.should have(2).skills
69
70
  end
70
71
 
72
+ it "should be able to select taggables by subset of tags using ActiveRelation methods" do
73
+ @taggables[0].tag_list = "bob"
74
+ @taggables[1].tag_list = "charlie"
75
+ @taggables[0].skill_list = "ruby"
76
+ @taggables[1].skill_list = "css"
77
+ @taggables.each{|taggable| taggable.save}
78
+
79
+ @found_taggables_by_tag = TaggableModel.joins(:tags).where(:tags => {:name => ["bob"]})
80
+ @found_taggables_by_skill = TaggableModel.joins(:skills).where(:tags => {:name => ["ruby"]})
81
+
82
+ @found_taggables_by_tag.should include @taggables[0]
83
+ @found_taggables_by_tag.should_not include @taggables[1]
84
+ @found_taggables_by_skill.should include @taggables[0]
85
+ @found_taggables_by_skill.should_not include @taggables[1]
86
+ end
87
+
71
88
  it "should be able to find by tag" do
72
89
  @taggable.skill_list = "ruby, rails, css"
73
90
  @taggable.save
@@ -111,6 +128,15 @@ describe "Taggable" do
111
128
  TaggableModel.all_tag_counts(:order => 'tags.id').first.count.should == 3 # ruby
112
129
  end
113
130
 
131
+ it "should be able to use named scopes to chain tag finds by any tags by context" do
132
+ bob = TaggableModel.create(:name => "Bob", :need_list => "rails", :offering_list => "c++")
133
+ frank = TaggableModel.create(:name => "Frank", :need_list => "css", :offering_list => "css")
134
+ steve = TaggableModel.create(:name => 'Steve', :need_list => "c++", :offering_list => "java")
135
+
136
+ # Let's only find those who need rails or css and are offering c++ or java
137
+ TaggableModel.tagged_with(['rails, css'], :on => :needs, :any => true).tagged_with(['c++', 'java'], :on => :offerings, :any => true).to_a.should == [bob]
138
+ end
139
+
114
140
  if ActiveRecord::VERSION::MAJOR >= 3
115
141
  it "should not return read-only records" do
116
142
  TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
@@ -234,6 +260,12 @@ describe "Taggable" do
234
260
 
235
261
  TaggableModel.tagged_with("lazy", :exclude => true).to_a.should == [frank, steve]
236
262
  end
263
+
264
+ it "should return an empty scope for empty tags" do
265
+ TaggableModel.tagged_with('').should == []
266
+ TaggableModel.tagged_with(' ').should == []
267
+ TaggableModel.tagged_with(nil).should == []
268
+ end
237
269
 
238
270
  it "should not create duplicate taggings" do
239
271
  bob = TaggableModel.create(:name => "Bob")
@@ -0,0 +1,22 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe ActsAsTaggableOn::Utils do
4
+ describe "like_operator" do
5
+ before(:each) do
6
+ clean_database!
7
+ TaggableModel.write_inheritable_attribute(:tag_types, [])
8
+ TaggableModel.acts_as_taggable_on(:tags, :languages, :skills, :needs, :offerings)
9
+ @taggable = TaggableModel.new(:name => "Bob Jones")
10
+ end
11
+
12
+ it "should return 'ILIKE' when the adapter is PostgreSQL" do
13
+ TaggableModel.connection.stub(:adapter_name).and_return("PostgreSQL")
14
+ TaggableModel.send(:like_operator).should == "ILIKE"
15
+ end
16
+
17
+ it "should return 'LIKE' when the adapter is not PostgreSQL" do
18
+ TaggableModel.connection.stub(:adapter_name).and_return("MySQL")
19
+ TaggableModel.send(:like_operator).should == "LIKE"
20
+ end
21
+ end
22
+ end
@@ -3,15 +3,17 @@ sqlite3:
3
3
  database: acts_as_taggable_on.sqlite3
4
4
 
5
5
  mysql:
6
- adapter: mysql
6
+ adapter: mysql2
7
7
  hostname: localhost
8
8
  username: root
9
9
  password:
10
10
  database: acts_as_taggable_on
11
-
11
+ charset: utf8
12
+
12
13
  postgresql:
13
14
  adapter: postgresql
14
15
  hostname: localhost
15
16
  username: postgres
16
17
  password:
17
18
  database: acts_as_taggable_on
19
+ encoding: utf8
@@ -10,6 +10,10 @@ class CachedModel < ActiveRecord::Base
10
10
  acts_as_taggable
11
11
  end
12
12
 
13
+ class OtherCachedModel < ActiveRecord::Base
14
+ acts_as_taggable_on :languages
15
+ end
16
+
13
17
  class OtherTaggableModel < ActiveRecord::Base
14
18
  acts_as_taggable_on :tags, :languages
15
19
  acts_as_taggable_on :needs, :offerings
@@ -32,6 +32,12 @@ ActiveRecord::Schema.define :version => 0 do
32
32
  t.column :cached_tag_list, :string
33
33
  end
34
34
 
35
+ create_table :other_cached_models, :force => true do |t|
36
+ t.column :name, :string
37
+ t.column :type, :string
38
+ t.column :cached_language_list, :string
39
+ end
40
+
35
41
  create_table :taggable_users, :force => true do |t|
36
42
  t.column :name, :string
37
43
  end
@@ -1,4 +1,5 @@
1
1
  $LOAD_PATH << "." unless $LOAD_PATH.include?(".")
2
+ require 'logger'
2
3
 
3
4
  begin
4
5
  require "rubygems"
@@ -13,7 +14,7 @@ begin
13
14
  Bundler.setup
14
15
  rescue Bundler::GemNotFound
15
16
  raise RuntimeError, "Bundler couldn't find some gems." +
16
- "Did you run `bundle install`?"
17
+ "Did you run \`bundlee install\`?"
17
18
  end
18
19
 
19
20
  Bundler.require
@@ -29,14 +30,33 @@ unless [].respond_to?(:freq)
29
30
  end
30
31
  end
31
32
 
32
- ENV['DB'] ||= 'sqlite3'
33
-
33
+ db_name = ENV['DB'] || 'sqlite3'
34
34
  database_yml = File.expand_path('../database.yml', __FILE__)
35
+
35
36
  if File.exists?(database_yml)
36
- active_record_configuration = YAML.load_file(database_yml)[ENV['DB']]
37
+ active_record_configuration = YAML.load_file(database_yml)
37
38
 
38
- ActiveRecord::Base.establish_connection(active_record_configuration)
39
+ ActiveRecord::Base.configurations = active_record_configuration
40
+ config = ActiveRecord::Base.configurations[db_name]
41
+
42
+ begin
43
+ ActiveRecord::Base.establish_connection(db_name)
44
+ ActiveRecord::Base.connection
45
+ rescue
46
+ case db_name
47
+ when /mysql/
48
+ ActiveRecord::Base.establish_connection(config.merge('database' => nil))
49
+ ActiveRecord::Base.connection.create_database(config['database'], {:charset => 'utf8', :collation => 'utf8_unicode_ci'})
50
+ when 'postgresql'
51
+ ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
52
+ ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => 'utf8'))
53
+ end
54
+
55
+ ActiveRecord::Base.establish_connection(config)
56
+ end
57
+
39
58
  ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
59
+ ActiveRecord::Base.default_timezone = :utc
40
60
 
41
61
  ActiveRecord::Base.silence do
42
62
  ActiveRecord::Migration.verbose = false
@@ -57,4 +77,4 @@ def clean_database!
57
77
  end
58
78
  end
59
79
 
60
- clean_database!
80
+ clean_database!
@@ -0,0 +1 @@
1
+ # Uninstall hook code here
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts-as-taggable-on
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 11
5
+ prerelease:
5
6
  segments:
6
7
  - 2
8
+ - 1
7
9
  - 0
8
- - 6
9
- version: 2.0.6
10
+ version: 2.1.0
10
11
  platform: ruby
11
12
  authors:
12
13
  - Michael Bleigh
@@ -14,28 +15,133 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-05-19 00:00:00 +02:00
18
+ date: 2010-05-19 00:00:00 +03:00
18
19
  default_executable:
19
- dependencies: []
20
-
21
- description: With ActsAsTaggableOn, you could tag a single model on several contexts, such as skills, interests, and awards. It also provides other advanced functionality.
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ hash: 3
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ prerelease: false
32
+ name: rails
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ hash: 9
42
+ segments:
43
+ - 2
44
+ - 5
45
+ version: "2.5"
46
+ prerelease: false
47
+ name: rspec
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ prerelease: false
61
+ name: sqlite3
62
+ type: :development
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - <
69
+ - !ruby/object:Gem::Version
70
+ hash: 13
71
+ segments:
72
+ - 0
73
+ - 3
74
+ version: "0.3"
75
+ prerelease: false
76
+ name: mysql2
77
+ type: :development
78
+ version_requirements: *id004
79
+ - !ruby/object:Gem::Dependency
80
+ requirement: &id005 !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ prerelease: false
90
+ name: pg
91
+ type: :development
92
+ version_requirements: *id005
93
+ - !ruby/object:Gem::Dependency
94
+ requirement: &id006 !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ prerelease: false
104
+ name: guard
105
+ type: :development
106
+ version_requirements: *id006
107
+ - !ruby/object:Gem::Dependency
108
+ requirement: &id007 !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ hash: 3
114
+ segments:
115
+ - 0
116
+ version: "0"
117
+ prerelease: false
118
+ name: guard-rspec
119
+ type: :development
120
+ version_requirements: *id007
121
+ description: With ActsAsTaggableOn, you can tag a single model on several contexts, such as skills, interests, and awards. It also provides other advanced functionality.
22
122
  email: michael@intridea.com
23
123
  executables: []
24
124
 
25
125
  extensions: []
26
126
 
27
- extra_rdoc_files:
28
- - README.rdoc
127
+ extra_rdoc_files: []
128
+
29
129
  files:
130
+ - .gitignore
131
+ - .rspec
132
+ - .travis.yml
30
133
  - CHANGELOG
31
134
  - Gemfile
135
+ - Guardfile
32
136
  - MIT-LICENSE
33
137
  - README.rdoc
34
138
  - Rakefile
35
139
  - VERSION
140
+ - acts-as-taggable-on.gemspec
36
141
  - generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb
37
142
  - generators/acts_as_taggable_on_migration/templates/migration.rb
38
143
  - lib/acts-as-taggable-on.rb
144
+ - lib/acts-as-taggable-on/version.rb
39
145
  - lib/acts_as_taggable_on/acts_as_taggable_on.rb
40
146
  - lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb
41
147
  - lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb
@@ -45,11 +151,11 @@ files:
45
151
  - lib/acts_as_taggable_on/acts_as_tagger.rb
46
152
  - lib/acts_as_taggable_on/compatibility/Gemfile
47
153
  - lib/acts_as_taggable_on/compatibility/active_record_backports.rb
48
- - lib/acts_as_taggable_on/compatibility/postgresql.rb
49
154
  - lib/acts_as_taggable_on/tag.rb
50
155
  - lib/acts_as_taggable_on/tag_list.rb
51
156
  - lib/acts_as_taggable_on/tagging.rb
52
157
  - lib/acts_as_taggable_on/tags_helper.rb
158
+ - lib/acts_as_taggable_on/utils.rb
53
159
  - lib/generators/acts_as_taggable_on/migration/migration_generator.rb
54
160
  - lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb
55
161
  - rails/init.rb
@@ -61,42 +167,47 @@ files:
61
167
  - spec/acts_as_taggable_on/tagger_spec.rb
62
168
  - spec/acts_as_taggable_on/tagging_spec.rb
63
169
  - spec/acts_as_taggable_on/tags_helper_spec.rb
170
+ - spec/acts_as_taggable_on/utils_spec.rb
64
171
  - spec/bm.rb
65
- - spec/database.yml
66
172
  - spec/database.yml.sample
67
173
  - spec/models.rb
68
174
  - spec/schema.rb
69
175
  - spec/spec_helper.rb
176
+ - uninstall.rb
70
177
  has_rdoc: true
71
- homepage: http://github.com/mbleigh/acts-as-taggable-on
178
+ homepage: ""
72
179
  licenses: []
73
180
 
74
181
  post_install_message:
75
- rdoc_options:
76
- - --charset=UTF-8
182
+ rdoc_options: []
183
+
77
184
  require_paths:
78
185
  - lib
79
186
  required_ruby_version: !ruby/object:Gem::Requirement
187
+ none: false
80
188
  requirements:
81
189
  - - ">="
82
190
  - !ruby/object:Gem::Version
191
+ hash: 3
83
192
  segments:
84
193
  - 0
85
194
  version: "0"
86
195
  required_rubygems_version: !ruby/object:Gem::Requirement
196
+ none: false
87
197
  requirements:
88
198
  - - ">="
89
199
  - !ruby/object:Gem::Version
200
+ hash: 3
90
201
  segments:
91
202
  - 0
92
203
  version: "0"
93
204
  requirements: []
94
205
 
95
206
  rubyforge_project:
96
- rubygems_version: 1.3.6
207
+ rubygems_version: 1.5.2
97
208
  signing_key:
98
209
  specification_version: 3
99
- summary: ActsAsTaggableOn is a tagging plugin for Rails that provides multiple tagging contexts on a single model.
210
+ summary: Advanced tagging for Rails.
100
211
  test_files:
101
212
  - spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb
102
213
  - spec/acts_as_taggable_on/acts_as_tagger_spec.rb
@@ -106,7 +217,9 @@ test_files:
106
217
  - spec/acts_as_taggable_on/tagger_spec.rb
107
218
  - spec/acts_as_taggable_on/tagging_spec.rb
108
219
  - spec/acts_as_taggable_on/tags_helper_spec.rb
220
+ - spec/acts_as_taggable_on/utils_spec.rb
109
221
  - spec/bm.rb
222
+ - spec/database.yml.sample
110
223
  - spec/models.rb
111
224
  - spec/schema.rb
112
225
  - spec/spec_helper.rb
@@ -1,44 +0,0 @@
1
- module ActsAsTaggableOn
2
- module Taggable
3
- module PostgreSQL
4
- def self.included(base)
5
- base.send :include, ActsAsTaggableOn::Taggable::PostgreSQL::InstanceMethods
6
- base.extend ActsAsTaggableOn::Taggable::PostgreSQL::ClassMethods
7
-
8
- ActsAsTaggableOn::Tag.class_eval do
9
- def self.named(name)
10
- where(["name ILIKE ?", name])
11
- end
12
-
13
- def self.named_any(list)
14
- where(list.map { |tag| sanitize_sql(["name ILIKE ?", tag.to_s]) }.join(" OR "))
15
- end
16
-
17
- def self.named_like(name)
18
- where(["name ILIKE ?", "%#{name}%"])
19
- end
20
-
21
- def self.named_like_any(list)
22
- where(list.map { |tag| sanitize_sql(["name ILIKE ?", "%#{tag.to_s}%"]) }.join(" OR "))
23
- end
24
- end
25
- end
26
-
27
- module InstanceMethods
28
- end
29
-
30
- module ClassMethods
31
- # all column names are necessary for PostgreSQL group clause
32
- def grouped_column_names_for(*objects)
33
- object = objects.shift
34
- columns = object.column_names.map { |column| "#{object.table_name}.#{column}" }
35
- columns << objects.map do |object|
36
- "#{object.table_name}.created_at"
37
- end.flatten
38
-
39
- columns.flatten.join(", ")
40
- end
41
- end
42
- end
43
- end
44
- end
@@ -1,17 +0,0 @@
1
- sqlite3:
2
- adapter: sqlite3
3
- database: acts_as_taggable_on.sqlite3
4
-
5
- mysql:
6
- adapter: mysql
7
- hostname: localhost
8
- username: root
9
- password:
10
- database: acts_as_taggable_on
11
-
12
- postgresql:
13
- adapter: postgresql
14
- hostname: localhost
15
- username: postgres
16
- password:
17
- database: acts_as_taggable_on