acts-as-taggable-on 2.0.6 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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