acts-as-taggable-on 2.0.0.pre4 → 2.0.0.pre5

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.
data/Rakefile CHANGED
@@ -1,4 +1,20 @@
1
1
  begin
2
+ # Rspec 1.3.0
3
+ require 'spec/rake/spectask'
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
10
+
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
2
18
  # Rspec 2.0
3
19
  require 'rspec/core/rake_task'
4
20
 
@@ -12,23 +28,8 @@ begin
12
28
  t.pattern = "spec/**/*_spec.rb"
13
29
  t.rcov = true
14
30
  t.rcov_opts = ['--exclude', 'spec']
15
- end
16
-
17
- rescue LoadError
18
- # Rspec 1.3.0
19
- require 'spec/rake/spectask'
20
-
21
- desc 'Default: run specs'
22
- task :default => :spec
23
- Spec::Rake::SpecTask.new do |t|
24
- t.spec_files = FileList["spec/**/*_spec.rb"]
25
31
  end
26
32
 
27
- Spec::Rake::SpecTask.new('rcov') do |t|
28
- t.spec_files = FileList["spec/**/*_spec.rb"]
29
- t.rcov = true
30
- t.rcov_opts = ['--exclude', 'spec']
31
- end
32
33
  rescue LoadError
33
34
  puts "Rspec not available. Install it with: gem install rspec"
34
35
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0.pre4
1
+ 2.0.0.pre5
@@ -1,15 +1,13 @@
1
1
  require "active_record"
2
2
  require "action_view"
3
3
 
4
- if ActiveRecord::VERSION::MAJOR < 3
5
- require "acts_as_taggable_on/compatibility/active_record_backports"
6
- require "acts_as_taggable_on/compatibility/tag"
7
- require "acts_as_taggable_on/compatibility/tagging"
8
- end
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+
6
+ require "acts_as_taggable_on/compatibility/active_record_backports" if ActiveRecord::VERSION::MAJOR < 3
9
7
 
10
8
  require "acts_as_taggable_on/acts_as_taggable_on"
11
9
  require "acts_as_taggable_on/acts_as_taggable_on/core"
12
- require "acts_as_taggable_on/acts_as_taggable_on/aggregate"
10
+ require "acts_as_taggable_on/acts_as_taggable_on/collection"
13
11
  require "acts_as_taggable_on/acts_as_taggable_on/cache"
14
12
  require "acts_as_taggable_on/acts_as_taggable_on/ownership"
15
13
  require "acts_as_taggable_on/acts_as_taggable_on/related"
@@ -20,6 +18,8 @@ require "acts_as_taggable_on/tag_list"
20
18
  require "acts_as_taggable_on/tags_helper"
21
19
  require "acts_as_taggable_on/tagging"
22
20
 
21
+ $LOAD_PATH.shift
22
+
23
23
  if defined?(ActiveRecord::Base)
24
24
  ActiveRecord::Base.extend ActsAsTaggableOn::Taggable
25
25
  ActiveRecord::Base.send :include, ActsAsTaggableOn::Tagger
@@ -17,7 +17,7 @@ module ActsAsTaggableOn
17
17
  if ::ActiveRecord::VERSION::MAJOR < 3
18
18
  include ActsAsTaggableOn::ActiveRecord::Backports
19
19
  end
20
-
20
+
21
21
  write_inheritable_attribute(:tag_types, tag_types)
22
22
  class_inheritable_reader(:tag_types)
23
23
 
@@ -28,14 +28,14 @@ module ActsAsTaggableOn
28
28
  def self.taggable?
29
29
  true
30
30
  end
31
+
32
+ include ActsAsTaggableOn::Taggable::Core
33
+ include ActsAsTaggableOn::Taggable::Collection
34
+ include ActsAsTaggableOn::Taggable::Cache
35
+ include ActsAsTaggableOn::Taggable::Ownership
36
+ include ActsAsTaggableOn::Taggable::Related
31
37
  end
32
38
  end
33
-
34
- include ActsAsTaggableOn::Taggable::Core
35
- include ActsAsTaggableOn::Taggable::Aggregate
36
- include ActsAsTaggableOn::Taggable::Cache
37
- include ActsAsTaggableOn::Taggable::Ownership
38
- include ActsAsTaggableOn::Taggable::Related
39
39
  end
40
40
  end
41
41
  end
@@ -11,26 +11,43 @@ module ActsAsTaggableOn::Taggable
11
11
  before_save :save_cached_tag_list
12
12
  end
13
13
 
14
- base.tag_types.map(&:to_s).each do |tag_type|
15
- base.class_eval %(
16
- def self.caching_#{tag_type.singularize}_list?
17
- caching_tag_list_on?("#{tag_type}")
18
- end
19
- )
20
- end
14
+ base.intialize_acts_as_taggable_on_cache
21
15
  end
22
16
 
23
17
  module ClassMethods
18
+ def intialize_acts_as_taggable_on_cache
19
+ tag_types.map(&:to_s).each do |tag_type|
20
+ class_eval %(
21
+ def self.caching_#{tag_type.singularize}_list?
22
+ caching_tag_list_on?("#{tag_type}")
23
+ end
24
+ )
25
+ end
26
+ end
27
+
28
+ def acts_as_taggable_on(*args)
29
+ super(*args)
30
+ intialize_acts_as_taggable_on_cache
31
+ end
32
+
24
33
  def caching_tag_list_on?(context)
25
34
  column_names.include?("cached_#{context.to_s.singularize}_list")
26
35
  end
27
36
  end
28
37
 
29
38
  module InstanceMethods
39
+ def tag_list_cache_set_on(context)
40
+ variable_name = "@#{context.to_s.singularize}_list"
41
+ !instance_variable_get(variable_name).nil?
42
+ end
43
+
30
44
  def save_cached_tag_list
31
45
  tag_types.map(&:to_s).each do |tag_type|
32
46
  if self.class.send("caching_#{tag_type.singularize}_list?")
33
- self["cached_#{tag_type.singularize}_list"] = tag_list_cache_on(tag_type.singularize).to_a.flatten.compact.join(', ')
47
+ if tag_list_cache_set_on(tag_type)
48
+ list = tag_list_cache_on(tag_type.singularize).to_a.flatten.compact.join(', ')
49
+ self["cached_#{tag_type.singularize}_list"] = list
50
+ end
34
51
  end
35
52
  end
36
53
  end
@@ -1,35 +1,41 @@
1
1
  module ActsAsTaggableOn::Taggable
2
- module Aggregate
2
+ module Collection
3
3
  def self.included(base)
4
- unless base.ancestors.include?(ActsAsTaggableOn::Taggable::Aggregate::InstanceMethods)
5
- base.send :include, ActsAsTaggableOn::Taggable::Aggregate::InstanceMethods
6
- base.extend ActsAsTaggableOn::Taggable::Aggregate::ClassMethods
7
- end
8
-
9
- base.tag_types.map(&:to_s).each do |tag_type|
10
- base.class_eval %(
11
- def #{tag_type.singularize}_counts(options = {})
12
- tag_counts_on('#{tag_type}', options)
13
- end
14
-
15
- def top_#{tag_type}(limit = 10)
16
- tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
17
- end
18
-
19
- def self.top_#{tag_type}(limit = 10)
20
- tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
21
- end
22
- )
23
- end
4
+ base.send :include, ActsAsTaggableOn::Taggable::Collection::InstanceMethods
5
+ base.extend ActsAsTaggableOn::Taggable::Collection::ClassMethods
6
+ base.initialize_acts_as_taggable_on_collection
24
7
  end
25
8
 
26
9
  module ClassMethods
27
- def tag_counts_on(context, options = {})
28
- find_for_tag_counts(options.merge({:on => context.to_s}))
29
- end
10
+ def initialize_acts_as_taggable_on_collection
11
+ tag_types.map(&:to_s).each do |tag_type|
12
+ class_eval %(
13
+ def self.#{tag_type.singularize}_counts(options={})
14
+ tag_counts_on('#{tag_type}', options)
15
+ end
30
16
 
31
- def all_tag_counts(options = {})
32
- find_for_tag_counts(options)
17
+ def #{tag_type.singularize}_counts(options = {})
18
+ tag_counts_on('#{tag_type}', options)
19
+ end
20
+
21
+ def top_#{tag_type}(limit = 10)
22
+ tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
23
+ end
24
+
25
+ def self.top_#{tag_type}(limit = 10)
26
+ tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
27
+ end
28
+ )
29
+ end
30
+ end
31
+
32
+ def acts_as_taggable_on(*args)
33
+ super(*args)
34
+ initialize_acts_as_taggable_on_collection
35
+ end
36
+
37
+ def tag_counts_on(context, options = {})
38
+ all_tag_counts(options.merge({:on => context.to_s}))
33
39
  end
34
40
 
35
41
  # Calculate the tag counts for all tags.
@@ -43,7 +49,7 @@ module ActsAsTaggableOn::Taggable
43
49
  # :at_least - Exclude tags with a frequency less than the given value
44
50
  # :at_most - Exclude tags with a frequency greater than the given value
45
51
  # :on - Scope the find to only include a certain context
46
- def find_for_tag_counts(options = {})
52
+ def all_tag_counts(options = {})
47
53
  options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :on, :id
48
54
 
49
55
  start_at = sanitize_sql(["#{Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
@@ -79,7 +85,6 @@ module ActsAsTaggableOn::Taggable
79
85
  group_by << " AND #{having}" unless having.blank?
80
86
 
81
87
  Tag.select("#{Tag.table_name}.*, COUNT(*) AS count").joins(joins.join(" ")).where(conditions).group(group_by).limit(options[:limit]).order(options[:order])
82
-
83
88
  end
84
89
  end
85
90
 
@@ -6,51 +6,51 @@ module ActsAsTaggableOn::Taggable
6
6
 
7
7
  base.class_eval do
8
8
  attr_writer :custom_contexts
9
-
10
9
  after_save :save_tags
11
-
12
- def self.tagged_with(*args)
13
- find_options_for_find_tagged_with(*args)
14
- end
15
10
  end
16
11
 
17
- base.tag_types.map(&:to_s).each do |tag_type|
18
- context_taggings = "#{tag_type.singularize}_taggings".to_sym
19
- context_tags = tag_type.to_sym
20
-
21
- base.class_eval do
22
- has_many context_taggings, :as => :taggable, :dependent => :destroy, :include => :tag,
23
- :conditions => ['#{Tagging.table_name}.context = ?', tag_type], :class_name => "Tagging"
24
- has_many context_tags, :through => context_taggings, :source => :tag
25
- end
26
-
27
- base.class_eval %(
28
- def self.#{tag_type.singularize}_counts(options={})
29
- tag_counts_on('#{tag_type}', options)
12
+ base.initialize_acts_as_taggable_on_core
13
+ end
14
+
15
+ module ClassMethods
16
+ def initialize_acts_as_taggable_on_core
17
+ tag_types.map(&:to_s).each do |tag_type|
18
+ context_taggings = "#{tag_type.singularize}_taggings".to_sym
19
+ context_tags = tag_type.to_sym
20
+
21
+ class_eval do
22
+ has_many context_taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "Tagging",
23
+ :conditions => ['#{Tagging.table_name}.tagger_id IS NULL AND #{Tagging.table_name}.context = ?', tag_type]
24
+ has_many context_tags, :through => context_taggings, :source => :tag
30
25
  end
31
26
 
32
- def #{tag_type.singularize}_list
33
- tag_list_on('#{tag_type}')
34
- end
27
+ class_eval %(
28
+ def #{tag_type.singularize}_list
29
+ tag_list_on('#{tag_type}')
30
+ end
35
31
 
36
- def #{tag_type.singularize}_list=(new_tags)
37
- set_tag_list_on('#{tag_type}', new_tags)
38
- end
39
-
40
- def all_#{tag_type}_list
41
- all_tags_list_on('#{tag_type}')
42
- end
43
- )
32
+ def #{tag_type.singularize}_list=(new_tags)
33
+ set_tag_list_on('#{tag_type}', new_tags)
34
+ end
35
+
36
+ def all_#{tag_type}_list
37
+ all_tags_list_on('#{tag_type}')
38
+ end
39
+ )
40
+ end
44
41
  end
45
- end
46
-
47
- module ClassMethods
42
+
43
+ def acts_as_taggable_on(*args)
44
+ super(*args)
45
+ initialize_acts_as_taggable_on_core
46
+ end
47
+
48
48
  # all column names are necessary for PostgreSQL group clause
49
49
  def grouped_column_names_for(object)
50
50
  object.column_names.map { |column| "#{object.table_name}.#{column}" }.join(", ")
51
51
  end
52
52
 
53
- def find_options_for_find_tagged_with(tags, options = {})
53
+ def tagged_with(tags, options = {})
54
54
  tag_list = TagList.from(tags)
55
55
 
56
56
  return {} if tag_list.empty?
@@ -98,19 +98,19 @@ module ActsAsTaggableOn::Taggable
98
98
  group = "#{grouped_column_names_for(self)} HAVING COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
99
99
  end
100
100
 
101
+
101
102
  joins(joins.join(" ")).group(group).where(conditions.join(" AND ")).readonly(false)
102
103
  end
103
104
 
104
105
  def is_taggable?
105
106
  true
106
107
  end
107
-
108
108
  end
109
109
 
110
110
  module InstanceMethods
111
111
  # all column names are necessary for PostgreSQL group clause
112
112
  def grouped_column_names_for(object)
113
- object.column_names.map { |column| "#{object.table_name}.#{column}" }.join(", ")
113
+ self.class.grouped_column_names_for(object)
114
114
  end
115
115
 
116
116
  def custom_contexts
@@ -156,7 +156,13 @@ module ActsAsTaggableOn::Taggable
156
156
  ##
157
157
  # Returns all tags that are not owned of a given context
158
158
  def tags_on(context)
159
- base_tags.where(["#{Tagging.table_name}.context = ? AND #{Tagging.table_name}.tagger_id IS NULL", context.to_s]).all
159
+ if respond_to?(context)
160
+ # If the association is available, use it:
161
+ send(context).all
162
+ else
163
+ # If the association is not available, query it the old fashioned way
164
+ base_tags.where(["#{Tagging.table_name}.context = ? AND #{Tagging.table_name}.tagger_id IS NULL", context.to_s]).all
165
+ end
160
166
  end
161
167
 
162
168
  def set_tag_list_on(context, new_list)
@@ -180,7 +186,7 @@ module ActsAsTaggableOn::Taggable
180
186
  end
181
187
 
182
188
  def save_tags
183
- transaction do
189
+ transaction do
184
190
  tagging_contexts.each do |context|
185
191
  tag_list = tag_list_cache_on(context).uniq
186
192
 
@@ -192,11 +198,13 @@ module ActsAsTaggableOn::Taggable
192
198
  new_tags = tag_list - current_tags
193
199
 
194
200
  # Find taggings to remove:
195
- old_taggings = Tagging.where(:taggable_id => self.id, :taggable_type => self.class.base_class.to_s,
196
- :tagger_type => nil, :tagger_id => nil,
197
- :context => context.to_s, :tag_id => old_tags)
201
+ old_taggings = taggings.where(:tagger_type => nil, :tagger_id => nil,
202
+ :context => context.to_s, :tag_id => old_tags).all
198
203
 
199
- Tagging.destroy_all :id => old_taggings.map(&:id)
204
+ if old_taggings.present?
205
+ # Destroy old taggings:
206
+ Tagging.destroy_all :id => old_taggings.map(&:id)
207
+ end
200
208
 
201
209
  # Create new taggings:
202
210
  new_tags.each do |tag|
@@ -0,0 +1,29 @@
1
+ module ActsAsTaggableOn::Taggable
2
+ module Dirty
3
+ def self.included(base)
4
+ include ActsAsTaggableOn::Taggable::Dirty::InstanceMethods
5
+
6
+ base.tag_types.map(&:to_s).each do |tag_type|
7
+ base.class_eval %(
8
+ def #{tag_type.singularize}_list_changed?
9
+ tag_list_changed_on?('#{tag_type}')
10
+ tag_list_on('#{tag_type}')
11
+ end
12
+
13
+ def #{tag_type.singularize}_list=(new_tags)
14
+ change_tag_list_on('#{tag_type}', new_tags)
15
+ super(new_tags)
16
+ end
17
+ )
18
+ end
19
+ end
20
+
21
+ module InstanceMethods
22
+ def tag_list_changed_on?(context)
23
+ end
24
+
25
+ def change_tag_list_on(context, new_tags)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -6,18 +6,26 @@ module ActsAsTaggableOn::Taggable
6
6
 
7
7
  base.class_eval do
8
8
  after_save :save_owned_tags
9
- end
10
-
11
- base.tag_types.map(&:to_s).each do |tag_type|
12
- base.class_eval %(
13
- def #{tag_type}_from(owner)
14
- owner_tag_list_on(owner, '#{tag_type}')
15
- end
16
- )
17
9
  end
10
+
11
+ base.initialize_acts_as_taggable_on_ownership
18
12
  end
19
13
 
20
14
  module ClassMethods
15
+ def acts_as_taggable_on(*args)
16
+ initialize_acts_as_taggable_on_ownership
17
+ super(*args)
18
+ end
19
+
20
+ def initialize_acts_as_taggable_on_ownership
21
+ tag_types.map(&:to_s).each do |tag_type|
22
+ class_eval %(
23
+ def #{tag_type}_from(owner)
24
+ owner_tag_list_on(owner, '#{tag_type}')
25
+ end
26
+ )
27
+ end
28
+ end
21
29
  end
22
30
 
23
31
  module InstanceMethods
@@ -65,16 +73,15 @@ module ActsAsTaggableOn::Taggable
65
73
  # Find existing tags or create non-existing tags:
66
74
  tag_list = Tag.find_or_create_all_with_like_by_name(tag_list.uniq)
67
75
 
68
- owned_tags = owner_tags_on(owner, context)
69
-
70
- old_tags = owned_tags - tag_list
71
- new_tags = tag_list - owned_tags
76
+ owned_tags = owner_tags_on(owner, context)
77
+ old_tags = owned_tags - tag_list
78
+ new_tags = tag_list - owned_tags
72
79
 
73
80
  # Find all taggings that belong to the taggable (self), are owned by the owner,
74
81
  # have the correct context, and are removed from the list.
75
82
  old_taggings = Tagging.where(:taggable_id => id, :taggable_type => self.class.base_class.to_s,
76
83
  :tagger_type => owner.class.to_s, :tagger_id => owner.id,
77
- :tag_id => old_tags, :context => context)
84
+ :tag_id => old_tags, :context => context).all
78
85
 
79
86
  if old_taggings.present?
80
87
  # Destroy old taggings:
@@ -1,75 +1,63 @@
1
1
  module ActsAsTaggableOn::Taggable
2
2
  module Related
3
3
  def self.included(base)
4
- unless base.ancestors.include?(ActsAsTaggableOn::Taggable::Related::InstanceMethods)
5
- base.send :include, ActsAsTaggableOn::Taggable::Related::InstanceMethods
6
- base.extend ActsAsTaggableOn::Taggable::Related::ClassMethods
7
- end
8
-
9
- base.tag_types.map(&:to_s).each do |tag_type|
10
- base.class_eval %(
11
- def find_related_#{tag_type}(options = {})
12
- related_tags_for('#{tag_type}', self.class, options)
13
- end
14
- alias_method :find_related_on_#{tag_type}, :find_related_#{tag_type}
4
+ base.send :include, ActsAsTaggableOn::Taggable::Related::InstanceMethods
5
+ base.extend ActsAsTaggableOn::Taggable::Related::ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def initialize_acts_as_taggable_on_related
10
+ tag_types.map(&:to_s).each do |tag_type|
11
+ class_eval %(
12
+ def find_related_#{tag_type}(options = {})
13
+ related_tags_for('#{tag_type}', self.class, options)
14
+ end
15
+ alias_method :find_related_on_#{tag_type}, :find_related_#{tag_type}
15
16
 
16
- def find_related_#{tag_type}_for(klass, options = {})
17
- related_tags_for('#{tag_type}', klass, options)
18
- end
17
+ def find_related_#{tag_type}_for(klass, options = {})
18
+ related_tags_for('#{tag_type}', klass, options)
19
+ end
19
20
 
20
- def find_matching_contexts(search_context, result_context, options = {})
21
- matching_contexts_for(search_context.to_s, result_context.to_s, self.class, options)
22
- end
21
+ def find_matching_contexts(search_context, result_context, options = {})
22
+ matching_contexts_for(search_context.to_s, result_context.to_s, self.class, options)
23
+ end
23
24
 
24
- def find_matching_contexts_for(klass, search_context, result_context, options = {})
25
- matching_contexts_for(search_context.to_s, result_context.to_s, klass, options)
26
- end
27
- )
25
+ def find_matching_contexts_for(klass, search_context, result_context, options = {})
26
+ matching_contexts_for(search_context.to_s, result_context.to_s, klass, options)
27
+ end
28
+ )
29
+ end
30
+ end
31
+
32
+ def acts_as_taggable_on(*args)
33
+ super(*args)
34
+ initialize_acts_as_taggable_on_related
28
35
  end
29
- end
30
-
31
- module ClassMethods
32
36
  end
33
37
 
34
38
  module InstanceMethods
35
39
  def matching_contexts_for(search_context, result_context, klass, options = {})
36
- search_conditions = matching_context_search_options(search_context, result_context, klass, options)
37
-
38
- # klass.select(search_conditions[:select]).from(search_conditions[:from]).where(search_conditions[:conditions]).group(search_conditions[:group]).order(search_conditions[:order])
39
- klass.scoped(search_conditions)
40
- end
41
-
42
- def matching_context_search_options(search_context, result_context, klass, options = {})
43
40
  tags_to_find = tags_on(search_context).collect { |t| t.name }
44
41
 
45
42
  exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
46
-
47
- { :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
48
- :from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
49
- :conditions => ["#{exclude_self} #{klass.table_name}.id = #{Tagging.table_name}.taggable_id AND #{Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND #{Tag.table_name}.name IN (?) AND #{Tagging.table_name}.context = ?", tags_to_find, result_context],
50
- :group => grouped_column_names_for(klass),
51
- :order => "count DESC"
52
- }.update(options)
43
+
44
+ klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
45
+ :from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
46
+ :conditions => ["#{exclude_self} #{klass.table_name}.id = #{Tagging.table_name}.taggable_id AND #{Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND #{Tag.table_name}.name IN (?) AND #{Tagging.table_name}.context = ?", tags_to_find, result_context],
47
+ :group => grouped_column_names_for(klass),
48
+ :order => "count DESC" }.update(options))
53
49
  end
54
50
 
55
51
  def related_tags_for(context, klass, options = {})
56
- search_conditions = related_search_options(context, klass, options)
57
-
58
- # klass.select(search_conditions[:select]).from(search_conditions[:from]).where(search_conditions[:conditions]).group(search_conditions[:group]).order(search_conditions[:order])
59
- klass.scoped(search_conditions)
60
- end
61
-
62
- def related_search_options(context, klass, options = {})
63
52
  tags_to_find = tags_on(context).collect { |t| t.name }
64
53
 
65
54
  exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
66
55
 
67
- { :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
68
- :from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
69
- :conditions => ["#{exclude_self} #{klass.table_name}.id = #{Tagging.table_name}.taggable_id AND #{Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND #{Tag.table_name}.name IN (?)", tags_to_find],
70
- :group => grouped_column_names_for(klass),
71
- :order => "count DESC"
72
- }.update(options)
56
+ klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
57
+ :from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
58
+ :conditions => ["#{exclude_self} #{klass.table_name}.id = #{Tagging.table_name}.taggable_id AND #{Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND #{Tag.table_name}.name IN (?)", tags_to_find],
59
+ :group => grouped_column_names_for(klass),
60
+ :order => "count DESC" }.update(options))
73
61
  end
74
62
  end
75
63
  end
@@ -6,10 +6,10 @@ module ActsAsTaggableOn
6
6
  named_scope :where, lambda { |conditions| { :conditions => conditions } }
7
7
  named_scope :joins, lambda { |joins| { :joins => joins } }
8
8
  named_scope :group, lambda { |group| { :group => group } }
9
- named_scope :readonly, lambda { |readonly| { :readonly => readonly } }
10
9
  named_scope :order, lambda { |order| { :order => order } }
11
10
  named_scope :select, lambda { |select| { :select => select } }
12
- named_scope :limit, lambda { |limit| { :limit => limit } }
11
+ named_scope :limit, lambda { |limit| { :limit => limit } }
12
+ named_scope :readonly, lambda { |readonly| { :readonly => readonly } }
13
13
  end
14
14
  end
15
15
  end
@@ -1,5 +1,6 @@
1
1
  class Tag < ActiveRecord::Base
2
-
2
+ include ActsAsTaggableOn::ActiveRecord::Backports if ActiveRecord::VERSION::MAJOR < 3
3
+
3
4
  attr_accessible :name
4
5
 
5
6
  ### ASSOCIATIONS:
@@ -1,4 +1,5 @@
1
1
  class Tagging < ActiveRecord::Base #:nodoc:
2
+ include ActsAsTaggableOn::ActiveRecord::Backports if ActiveRecord::VERSION::MAJOR < 3
2
3
 
3
4
  attr_accessible :tag,
4
5
  :tag_id,
@@ -35,19 +35,10 @@ describe "Acts As Taggable On" do
35
35
  @taggable.should respond_to(:tags, :skills, :languages)
36
36
  end
37
37
 
38
- it "should generate a cached column checker for each tag type" do
39
- TaggableModel.should respond_to(:caching_tag_list?, :caching_skill_list?, :caching_language_list?)
40
- end
41
-
42
38
  it "should add tagged_with and tag_counts to singleton" do
43
39
  TaggableModel.should respond_to(:tagged_with, :tag_counts)
44
40
  end
45
41
 
46
- it "should add saving of tag lists and cached tag lists to the instance" do
47
- @taggable.should respond_to(:save_cached_tag_list)
48
- @taggable.should respond_to(:save_tags)
49
- end
50
-
51
42
  it "should generate a tag_list accessor/setter for each tag type" do
52
43
  @taggable.should respond_to(:tag_list, :skill_list, :language_list)
53
44
  @taggable.should respond_to(:tag_list=, :skill_list=, :language_list=)
@@ -215,5 +206,61 @@ describe "Acts As Taggable On" do
215
206
  }.should_not raise_error
216
207
  end
217
208
  end
209
+
210
+ describe 'Caching' do
211
+ before(:each) do
212
+ @taggable = CachedModel.new(:name => "Bob Jones")
213
+ end
214
+
215
+ it "should add saving of tag lists and cached tag lists to the instance" do
216
+ @taggable.should respond_to(:save_cached_tag_list)
217
+ @taggable.should respond_to(:save_tags)
218
+ end
219
+
220
+ it "should generate a cached column checker for each tag type" do
221
+ CachedModel.should respond_to(:caching_tag_list?)
222
+ end
223
+
224
+ it 'should not have cached tags' do
225
+ @taggable.cached_tag_list.should be_blank
226
+ end
227
+
228
+ it 'should cache tags' do
229
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
230
+ @taggable.cached_tag_list.should == 'awesome, epic'
231
+ end
232
+
233
+ it 'should keep the cache' do
234
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
235
+ @taggable = CachedModel.find(@taggable)
236
+ @taggable.save!
237
+ @taggable.cached_tag_list.should == 'awesome, epic'
238
+ end
239
+
240
+ it 'should update the cache' do
241
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
242
+ @taggable.update_attributes(:tag_list => 'awesome')
243
+ @taggable.cached_tag_list.should == 'awesome'
244
+ end
245
+
246
+ it 'should remove the cache' do
247
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
248
+ @taggable.update_attributes(:tag_list => '')
249
+ @taggable.cached_tag_list.should be_blank
250
+ end
251
+
252
+ it 'should have a tag list' do
253
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
254
+ @taggable = CachedModel.find(@taggable.id)
255
+ @taggable.tag_list.sort.should == %w(awesome epic).sort
256
+ end
257
+
258
+ it 'should keep the tag list' do
259
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
260
+ @taggable = CachedModel.find(@taggable.id)
261
+ @taggable.save!
262
+ @taggable.tag_list.sort.should == %w(awesome epic).sort
263
+ end
264
+ end
218
265
 
219
266
  end
@@ -88,7 +88,7 @@ describe "Taggable" do
88
88
  frank = TaggableModel.create(:name => "Frank", :tag_list => "Ruby")
89
89
 
90
90
  Tag.find(:all).size.should == 1
91
- TaggableModel.tagged_with("ruby").all.should == TaggableModel.tagged_with("Ruby").all
91
+ TaggableModel.tagged_with("ruby").to_a.should == TaggableModel.tagged_with("Ruby").to_a
92
92
  end
93
93
 
94
94
  it "should be able to get tag counts on model as a whole" do
@@ -108,10 +108,20 @@ describe "Taggable" do
108
108
  TaggableModel.all_tag_counts.first.count.should == 3 # ruby
109
109
  end
110
110
 
111
- it "should not return read-only records" do
112
- TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
113
-
114
- TaggableModel.tagged_with("ruby").first.should_not be_readonly
111
+ if ActiveRecord::VERSION::MAJOR >= 3
112
+ it "should not return read-only records" do
113
+ TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
114
+ TaggableModel.tagged_with("ruby").first.should_not be_readonly
115
+ end
116
+ else
117
+ xit "should not return read-only records" do
118
+ # apparantly, there is no way to set readonly to false in a scope if joins are made
119
+ end
120
+
121
+ it "should be possible to return writable records" do
122
+ TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
123
+ TaggableModel.tagged_with("ruby").first(:readonly => false).should_not be_readonly
124
+ end
115
125
  end
116
126
 
117
127
  it "should be able to get scoped tag counts" do
@@ -145,9 +155,9 @@ describe "Taggable" do
145
155
  frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
146
156
  steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
147
157
 
148
- TaggableModel.tagged_with("ruby", :order => 'taggable_models.name').all.should == [bob, frank, steve]
149
- TaggableModel.tagged_with("ruby, rails", :order => 'taggable_models.name').all.should == [bob, frank]
150
- TaggableModel.tagged_with(["ruby", "rails"], :order => 'taggable_models.name').all.should == [bob, frank]
158
+ TaggableModel.tagged_with("ruby", :order => 'taggable_models.name').to_a.should == [bob, frank, steve]
159
+ TaggableModel.tagged_with("ruby, rails", :order => 'taggable_models.name').to_a.should == [bob, frank]
160
+ TaggableModel.tagged_with(["ruby", "rails"], :order => 'taggable_models.name').to_a.should == [bob, frank]
151
161
  end
152
162
 
153
163
  it "should be able to find tagged with any tag" do
@@ -155,9 +165,9 @@ describe "Taggable" do
155
165
  frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
156
166
  steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
157
167
 
158
- TaggableModel.tagged_with(["ruby", "java"], :order => 'taggable_models.name', :any => true).all.should == [bob, frank, steve]
159
- TaggableModel.tagged_with(["c++", "fitter"], :order => 'taggable_models.name', :any => true).all.should == [bob, steve]
160
- TaggableModel.tagged_with(["depressed", "css"], :order => 'taggable_models.name', :any => true).all.should == [bob, frank]
168
+ TaggableModel.tagged_with(["ruby", "java"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank, steve]
169
+ TaggableModel.tagged_with(["c++", "fitter"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, steve]
170
+ TaggableModel.tagged_with(["depressed", "css"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank]
161
171
  end
162
172
 
163
173
  it "should be able to find tagged on a custom tag context" do
@@ -166,7 +176,7 @@ describe "Taggable" do
166
176
  bob.tag_list_on(:rotors).should == ["spinning","jumping"]
167
177
  bob.save
168
178
 
169
- TaggableModel.tagged_with("spinning", :on => :rotors).all.should == [bob]
179
+ TaggableModel.tagged_with("spinning", :on => :rotors).to_a.should == [bob]
170
180
  end
171
181
 
172
182
  it "should be able to use named scopes to chain tag finds" do
@@ -175,10 +185,10 @@ describe "Taggable" do
175
185
  steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, python')
176
186
 
177
187
  # Let's only find those productive Rails developers
178
- TaggableModel.tagged_with('rails', :on => :skills, :order => 'taggable_models.name').all.should == [bob, frank]
179
- TaggableModel.tagged_with('happier', :on => :tags, :order => 'taggable_models.name').all.should == [bob, steve]
180
- TaggableModel.tagged_with('rails', :on => :skills).tagged_with('happier', :on => :tags).all.should == [bob]
181
- TaggableModel.tagged_with('rails').tagged_with('happier', :on => :tags).all.should == [bob]
188
+ TaggableModel.tagged_with('rails', :on => :skills, :order => 'taggable_models.name').to_a.should == [bob, frank]
189
+ TaggableModel.tagged_with('happier', :on => :tags, :order => 'taggable_models.name').to_a.should == [bob, steve]
190
+ TaggableModel.tagged_with('rails', :on => :skills).tagged_with('happier', :on => :tags).to_a.should == [bob]
191
+ TaggableModel.tagged_with('rails').tagged_with('happier', :on => :tags).to_a.should == [bob]
182
192
  end
183
193
 
184
194
  it "should be able to find tagged with only the matching tags" do
@@ -186,7 +196,7 @@ describe "Taggable" do
186
196
  frank = TaggableModel.create(:name => "Frank", :tag_list => "fitter, happier, inefficient")
187
197
  steve = TaggableModel.create(:name => 'Steve', :tag_list => "fitter, happier")
188
198
 
189
- TaggableModel.tagged_with("fitter, happier", :match_all => true).all.should == [steve]
199
+ TaggableModel.tagged_with("fitter, happier", :match_all => true).to_a.should == [steve]
190
200
  end
191
201
 
192
202
  it "should be able to find tagged with some excluded tags" do
@@ -194,7 +204,7 @@ describe "Taggable" do
194
204
  frank = TaggableModel.create(:name => "Frank", :tag_list => "happier")
195
205
  steve = TaggableModel.create(:name => 'Steve', :tag_list => "happier")
196
206
 
197
- TaggableModel.tagged_with("lazy", :exclude => true).all.should == [frank, steve]
207
+ TaggableModel.tagged_with("lazy", :exclude => true).to_a.should == [frank, steve]
198
208
  end
199
209
 
200
210
  it "should not create duplicate taggings" do
@@ -212,7 +222,7 @@ describe "Taggable" do
212
222
  end
213
223
 
214
224
  it "should return all column names joined for TaggableModel GROUP clause" do
215
- @taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.id, taggable_models.name, taggable_models.type, taggable_models.cached_tag_list"
225
+ @taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.id, taggable_models.name, taggable_models.type"
216
226
  end
217
227
  end
218
228
 
data/spec/bm.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'active_record'
2
+ require 'action_view'
3
+ require File.expand_path('../../lib/acts-as-taggable-on', __FILE__)
4
+
5
+ if defined?(ActiveRecord::Acts::TaggableOn)
6
+ ActiveRecord::Base.send :include, ActiveRecord::Acts::TaggableOn
7
+ ActiveRecord::Base.send :include, ActiveRecord::Acts::Tagger
8
+ ActionView::Base.send :include, TagsHelper if defined?(ActionView::Base)
9
+ end
10
+
11
+ TEST_DATABASE_FILE = File.join(File.dirname(__FILE__), '..', 'test.sqlite3')
12
+ File.unlink(TEST_DATABASE_FILE) if File.exist?(TEST_DATABASE_FILE)
13
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => TEST_DATABASE_FILE
14
+
15
+ ActiveRecord::Base.silence do
16
+ ActiveRecord::Migration.verbose = false
17
+ ActiveRecord::Schema.define :version => 0 do
18
+ create_table "taggings", :force => true do |t|
19
+ t.integer "tag_id", :limit => 11
20
+ t.integer "taggable_id", :limit => 11
21
+ t.string "taggable_type"
22
+ t.string "context"
23
+ t.datetime "created_at"
24
+ t.integer "tagger_id", :limit => 11
25
+ t.string "tagger_type"
26
+ end
27
+
28
+ add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"
29
+ add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context"
30
+
31
+ create_table "tags", :force => true do |t|
32
+ t.string "name"
33
+ end
34
+
35
+ create_table :taggable_models, :force => true do |t|
36
+ t.column :name, :string
37
+ t.column :type, :string
38
+ t.column :cached_tag_list, :string
39
+ end
40
+ end
41
+
42
+ class TaggableModel < ActiveRecord::Base
43
+ acts_as_taggable
44
+ acts_as_taggable_on :languages
45
+ acts_as_taggable_on :skills
46
+ acts_as_taggable_on :needs, :offerings
47
+ end
48
+ end
49
+
50
+ puts Benchmark.measure {
51
+ 1000.times { TaggableModel.create :tag_list => "awesome, epic, neat" }
52
+ }
data/spec/models.rb CHANGED
@@ -5,6 +5,10 @@ class TaggableModel < ActiveRecord::Base
5
5
  acts_as_taggable_on :needs, :offerings
6
6
  end
7
7
 
8
+ class CachedModel < ActiveRecord::Base
9
+ acts_as_taggable
10
+ end
11
+
8
12
  class OtherTaggableModel < ActiveRecord::Base
9
13
  acts_as_taggable_on :tags, :languages
10
14
  acts_as_taggable_on :needs, :offerings
@@ -24,9 +28,9 @@ end
24
28
  class UntaggableModel < ActiveRecord::Base
25
29
  end
26
30
 
27
- if ActiveRecord::VERSION::MAJOR < 3
28
- [TaggableModel, OtherTaggableModel, InheritingTaggableModel,
29
- AlteredInheritingTaggableModel, TaggableUser, UntaggableModel].each do |klass|
30
- klass.send(:include, ActsAsTaggableOn::ActiveRecord::Backports)
31
- end
32
- end
31
+ # if ActiveRecord::VERSION::MAJOR < 3
32
+ # [TaggableModel, CachedModel, OtherTaggableModel, InheritingTaggableModel,
33
+ # AlteredInheritingTaggableModel, TaggableUser, UntaggableModel].each do |klass|
34
+ # klass.send(:include, ActsAsTaggableOn::ActiveRecord::Backports)
35
+ # end
36
+ # end
data/spec/schema.rb CHANGED
@@ -19,6 +19,11 @@ ActiveRecord::Schema.define :version => 0 do
19
19
  create_table :taggable_models, :force => true do |t|
20
20
  t.column :name, :string
21
21
  t.column :type, :string
22
+ end
23
+
24
+ create_table :cached_models, :force => true do |t|
25
+ t.column :name, :string
26
+ t.column :type, :string
22
27
  t.column :cached_tag_list, :string
23
28
  end
24
29
 
@@ -29,7 +34,6 @@ ActiveRecord::Schema.define :version => 0 do
29
34
  create_table :other_taggable_models, :force => true do |t|
30
35
  t.column :name, :string
31
36
  t.column :type, :string
32
- #t.column :cached_tag_list, :string
33
37
  end
34
38
 
35
39
  create_table :untaggable_models, :force => true do |t|
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts-as-taggable-on
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre4
4
+ prerelease: true
5
+ segments:
6
+ - 2
7
+ - 0
8
+ - 0
9
+ - pre5
10
+ version: 2.0.0.pre5
5
11
  platform: ruby
6
12
  authors:
7
13
  - Michael Bleigh
@@ -9,7 +15,7 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2010-03-22 00:00:00 +01:00
18
+ date: 2010-03-25 00:00:00 +01:00
13
19
  default_executable:
14
20
  dependencies: []
15
21
 
@@ -30,16 +36,15 @@ files:
30
36
  - VERSION
31
37
  - lib/acts-as-taggable-on.rb
32
38
  - lib/acts_as_taggable_on/acts_as_taggable_on.rb
33
- - lib/acts_as_taggable_on/acts_as_taggable_on/aggregate.rb
34
39
  - lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb
40
+ - lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb
35
41
  - lib/acts_as_taggable_on/acts_as_taggable_on/core.rb
42
+ - lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb
36
43
  - lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb
37
44
  - lib/acts_as_taggable_on/acts_as_taggable_on/related.rb
38
45
  - lib/acts_as_taggable_on/acts_as_tagger.rb
39
46
  - lib/acts_as_taggable_on/compatibility/Gemfile
40
47
  - lib/acts_as_taggable_on/compatibility/active_record_backports.rb
41
- - lib/acts_as_taggable_on/compatibility/tag.rb
42
- - lib/acts_as_taggable_on/compatibility/tagging.rb
43
48
  - lib/acts_as_taggable_on/tag.rb
44
49
  - lib/acts_as_taggable_on/tag_list.rb
45
50
  - lib/acts_as_taggable_on/tagging.rb
@@ -55,6 +60,7 @@ files:
55
60
  - spec/acts_as_taggable_on/tagger_spec.rb
56
61
  - spec/acts_as_taggable_on/tagging_spec.rb
57
62
  - spec/acts_as_taggable_on/tags_helper_spec.rb
63
+ - spec/bm.rb
58
64
  - spec/models.rb
59
65
  - spec/schema.rb
60
66
  - spec/spec.opts
@@ -72,18 +78,22 @@ required_ruby_version: !ruby/object:Gem::Requirement
72
78
  requirements:
73
79
  - - ">="
74
80
  - !ruby/object:Gem::Version
81
+ segments:
82
+ - 0
75
83
  version: "0"
76
- version:
77
84
  required_rubygems_version: !ruby/object:Gem::Requirement
78
85
  requirements:
79
86
  - - ">"
80
87
  - !ruby/object:Gem::Version
88
+ segments:
89
+ - 1
90
+ - 3
91
+ - 1
81
92
  version: 1.3.1
82
- version:
83
93
  requirements: []
84
94
 
85
95
  rubyforge_project:
86
- rubygems_version: 1.3.5
96
+ rubygems_version: 1.3.6
87
97
  signing_key:
88
98
  specification_version: 3
89
99
  summary: ActsAsTaggableOn is a tagging plugin for Rails that provides multiple tagging contexts on a single model.
@@ -96,6 +106,7 @@ test_files:
96
106
  - spec/acts_as_taggable_on/tagger_spec.rb
97
107
  - spec/acts_as_taggable_on/tagging_spec.rb
98
108
  - spec/acts_as_taggable_on/tags_helper_spec.rb
109
+ - spec/bm.rb
99
110
  - spec/models.rb
100
111
  - spec/schema.rb
101
112
  - spec/spec_helper.rb
@@ -1,3 +0,0 @@
1
- class Tag < ActiveRecord::Base
2
- include ActsAsTaggableOn::ActiveRecord::Backports
3
- end
@@ -1,3 +0,0 @@
1
- class Tagging < ActiveRecord::Base
2
- include ActsAsTaggableOn::ActiveRecord::Backports
3
- end