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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/{spec/spec.opts → .rspec} +0 -0
  4. data/.travis.yml +40 -0
  5. data/Appraisals +16 -0
  6. data/CHANGELOG.md +208 -0
  7. data/CONTRIBUTING.md +44 -0
  8. data/Gemfile +10 -5
  9. data/Guardfile +5 -0
  10. data/{MIT-LICENSE → LICENSE.md} +1 -1
  11. data/README.md +477 -0
  12. data/Rakefile +14 -52
  13. data/UPGRADING.md +8 -0
  14. data/acts-as-taggable-on.gemspec +36 -0
  15. data/{lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb → db/migrate/1_acts_as_taggable_on_migration.rb} +5 -3
  16. data/db/migrate/2_add_missing_unique_indices.rb +19 -0
  17. data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +14 -0
  18. data/db/migrate/4_add_missing_taggable_index.rb +9 -0
  19. data/db/migrate/5_change_collation_for_tag_names.rb +9 -0
  20. data/gemfiles/activerecord_3.2.gemfile +15 -0
  21. data/gemfiles/activerecord_4.0.gemfile +15 -0
  22. data/gemfiles/activerecord_4.1.gemfile +15 -0
  23. data/gemfiles/activerecord_4.2.gemfile +16 -0
  24. data/lib/acts-as-taggable-on.rb +117 -22
  25. data/lib/acts_as_taggable_on/compatibility.rb +35 -0
  26. data/lib/acts_as_taggable_on/default_parser.rb +79 -0
  27. data/lib/acts_as_taggable_on/engine.rb +5 -0
  28. data/lib/acts_as_taggable_on/generic_parser.rb +19 -0
  29. data/lib/acts_as_taggable_on/tag.rb +137 -61
  30. data/lib/acts_as_taggable_on/tag_list.rb +96 -75
  31. data/lib/acts_as_taggable_on/tag_list_parser.rb +21 -0
  32. data/lib/acts_as_taggable_on/taggable/cache.rb +86 -0
  33. data/lib/acts_as_taggable_on/taggable/collection.rb +178 -0
  34. data/lib/acts_as_taggable_on/taggable/core.rb +459 -0
  35. data/lib/acts_as_taggable_on/taggable/dirty.rb +36 -0
  36. data/lib/acts_as_taggable_on/taggable/ownership.rb +125 -0
  37. data/lib/acts_as_taggable_on/taggable/related.rb +71 -0
  38. data/lib/acts_as_taggable_on/taggable.rb +102 -0
  39. data/lib/acts_as_taggable_on/tagger.rb +88 -0
  40. data/lib/acts_as_taggable_on/tagging.rb +38 -18
  41. data/lib/acts_as_taggable_on/tags_helper.rb +12 -14
  42. data/lib/acts_as_taggable_on/utils.rb +38 -0
  43. data/lib/acts_as_taggable_on/version.rb +4 -0
  44. data/lib/acts_as_taggable_on.rb +6 -0
  45. data/lib/tasks/tags_collate_utf8.rake +21 -0
  46. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +205 -195
  47. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +79 -81
  48. data/spec/acts_as_taggable_on/caching_spec.rb +83 -0
  49. data/spec/acts_as_taggable_on/default_parser_spec.rb +47 -0
  50. data/spec/acts_as_taggable_on/generic_parser_spec.rb +14 -0
  51. data/spec/acts_as_taggable_on/related_spec.rb +99 -0
  52. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +211 -0
  53. data/spec/acts_as_taggable_on/tag_list_parser_spec.rb +46 -0
  54. data/spec/acts_as_taggable_on/tag_list_spec.rb +142 -62
  55. data/spec/acts_as_taggable_on/tag_spec.rb +274 -64
  56. data/spec/acts_as_taggable_on/taggable/dirty_spec.rb +127 -0
  57. data/spec/acts_as_taggable_on/taggable_spec.rb +704 -181
  58. data/spec/acts_as_taggable_on/tagger_spec.rb +134 -56
  59. data/spec/acts_as_taggable_on/tagging_spec.rb +54 -22
  60. data/spec/acts_as_taggable_on/tags_helper_spec.rb +39 -22
  61. data/spec/acts_as_taggable_on/utils_spec.rb +23 -0
  62. data/spec/internal/app/models/altered_inheriting_taggable_model.rb +3 -0
  63. data/spec/internal/app/models/cached_model.rb +3 -0
  64. data/spec/internal/app/models/cached_model_with_array.rb +5 -0
  65. data/spec/internal/app/models/company.rb +15 -0
  66. data/spec/internal/app/models/inheriting_taggable_model.rb +2 -0
  67. data/spec/internal/app/models/market.rb +2 -0
  68. data/spec/internal/app/models/models.rb +90 -0
  69. data/spec/internal/app/models/non_standard_id_taggable_model.rb +8 -0
  70. data/spec/internal/app/models/ordered_taggable_model.rb +4 -0
  71. data/spec/internal/app/models/other_cached_model.rb +3 -0
  72. data/spec/internal/app/models/other_taggable_model.rb +4 -0
  73. data/spec/internal/app/models/student.rb +2 -0
  74. data/spec/internal/app/models/taggable_model.rb +13 -0
  75. data/spec/internal/app/models/untaggable_model.rb +3 -0
  76. data/spec/internal/app/models/user.rb +3 -0
  77. data/spec/internal/config/database.yml.sample +19 -0
  78. data/spec/internal/db/schema.rb +97 -0
  79. data/spec/spec_helper.rb +12 -38
  80. data/spec/support/0-helpers.rb +32 -0
  81. data/spec/support/array.rb +9 -0
  82. data/spec/support/database.rb +42 -0
  83. data/spec/support/database_cleaner.rb +21 -0
  84. metadata +268 -73
  85. data/CHANGELOG +0 -25
  86. data/README.rdoc +0 -212
  87. data/VERSION +0 -1
  88. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +0 -56
  89. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +0 -97
  90. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +0 -220
  91. data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +0 -29
  92. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +0 -101
  93. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +0 -64
  94. data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +0 -41
  95. data/lib/acts_as_taggable_on/acts_as_tagger.rb +0 -47
  96. data/lib/acts_as_taggable_on/compatibility/Gemfile +0 -6
  97. data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +0 -17
  98. data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +0 -31
  99. data/rails/init.rb +0 -1
  100. data/spec/bm.rb +0 -52
  101. data/spec/models.rb +0 -36
  102. data/spec/schema.rb +0 -42
@@ -0,0 +1,71 @@
1
+ module ActsAsTaggableOn::Taggable
2
+ module Related
3
+ def self.included(base)
4
+ base.extend ActsAsTaggableOn::Taggable::Related::ClassMethods
5
+ base.initialize_acts_as_taggable_on_related
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 <<-RUBY, __FILE__, __LINE__ + 1
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}
16
+
17
+ def find_related_#{tag_type}_for(klass, options = {})
18
+ related_tags_for('#{tag_type}', klass, options)
19
+ end
20
+ RUBY
21
+ end
22
+ end
23
+
24
+ def acts_as_taggable_on(*args)
25
+ super(*args)
26
+ initialize_acts_as_taggable_on_related
27
+ end
28
+ end
29
+
30
+ def find_matching_contexts(search_context, result_context, options = {})
31
+ matching_contexts_for(search_context.to_s, result_context.to_s, self.class, options)
32
+ end
33
+
34
+ def find_matching_contexts_for(klass, search_context, result_context, options = {})
35
+ matching_contexts_for(search_context.to_s, result_context.to_s, klass, options)
36
+ end
37
+
38
+ def matching_contexts_for(search_context, result_context, klass, options = {})
39
+ tags_to_find = tags_on(search_context).map { |t| t.name }
40
+ related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, result_context])
41
+ end
42
+
43
+ def related_tags_for(context, klass, options = {})
44
+ tags_to_ignore = Array.wrap(options[:ignore]).map(&:to_s) || []
45
+ tags_to_find = tags_on(context).map { |t| t.name }.reject { |t| tags_to_ignore.include? t }
46
+ related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, context])
47
+ end
48
+
49
+ private
50
+
51
+ def exclude_self(klass, id)
52
+ "#{klass.table_name}.#{klass.primary_key} != #{id} AND" if [self.class.base_class, self.class].include? klass
53
+ end
54
+
55
+ def group_columns(klass)
56
+ if ActsAsTaggableOn::Utils.using_postgresql?
57
+ grouped_column_names_for(klass)
58
+ else
59
+ "#{klass.table_name}.#{klass.primary_key}"
60
+ end
61
+ end
62
+
63
+ def related_where(klass, conditions)
64
+ klass.select("#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count")
65
+ .from("#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}")
66
+ .group(group_columns(klass))
67
+ .order('count DESC')
68
+ .where(conditions)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,102 @@
1
+ module ActsAsTaggableOn
2
+ module Taggable
3
+
4
+ def taggable?
5
+ false
6
+ end
7
+
8
+ ##
9
+ # This is an alias for calling <tt>acts_as_taggable_on :tags</tt>.
10
+ #
11
+ # Example:
12
+ # class Book < ActiveRecord::Base
13
+ # acts_as_taggable
14
+ # end
15
+ def acts_as_taggable
16
+ acts_as_taggable_on :tags
17
+ end
18
+
19
+ ##
20
+ # This is an alias for calling <tt>acts_as_ordered_taggable_on :tags</tt>.
21
+ #
22
+ # Example:
23
+ # class Book < ActiveRecord::Base
24
+ # acts_as_ordered_taggable
25
+ # end
26
+ def acts_as_ordered_taggable
27
+ acts_as_ordered_taggable_on :tags
28
+ end
29
+
30
+ ##
31
+ # Make a model taggable on specified contexts.
32
+ #
33
+ # @param [Array] tag_types An array of taggable contexts
34
+ #
35
+ # Example:
36
+ # class User < ActiveRecord::Base
37
+ # acts_as_taggable_on :languages, :skills
38
+ # end
39
+ def acts_as_taggable_on(*tag_types)
40
+ taggable_on(false, tag_types)
41
+ end
42
+
43
+ ##
44
+ # Make a model taggable on specified contexts
45
+ # and preserves the order in which tags are created
46
+ #
47
+ # @param [Array] tag_types An array of taggable contexts
48
+ #
49
+ # Example:
50
+ # class User < ActiveRecord::Base
51
+ # acts_as_ordered_taggable_on :languages, :skills
52
+ # end
53
+ def acts_as_ordered_taggable_on(*tag_types)
54
+ taggable_on(true, tag_types)
55
+ end
56
+
57
+ private
58
+
59
+ # Make a model taggable on specified contexts
60
+ # and optionally preserves the order in which tags are created
61
+ #
62
+ # Separate methods used above for backwards compatibility
63
+ # so that the original acts_as_taggable_on method is unaffected
64
+ # as it's not possible to add another argument to the method
65
+ # without the tag_types being enclosed in square brackets
66
+ #
67
+ # NB: method overridden in core module in order to create tag type
68
+ # associations and methods after this logic has executed
69
+ #
70
+ def taggable_on(preserve_tag_order, *tag_types)
71
+ tag_types = tag_types.to_a.flatten.compact.map(&:to_sym)
72
+
73
+ if taggable?
74
+ self.tag_types = (self.tag_types + tag_types).uniq
75
+ self.preserve_tag_order = preserve_tag_order
76
+ else
77
+ class_attribute :tag_types
78
+ self.tag_types = tag_types
79
+ class_attribute :preserve_tag_order
80
+ self.preserve_tag_order = preserve_tag_order
81
+
82
+ class_eval do
83
+ has_many :taggings, as: :taggable, dependent: :destroy, class_name: '::ActsAsTaggableOn::Tagging'
84
+ has_many :base_tags, through: :taggings, source: :tag, class_name: '::ActsAsTaggableOn::Tag'
85
+
86
+ def self.taggable?
87
+ true
88
+ end
89
+ end
90
+ end
91
+
92
+ # each of these add context-specific methods and must be
93
+ # called on each call of taggable_on
94
+ include Core
95
+ include Collection
96
+ include Cache
97
+ include Ownership
98
+ include Related
99
+ include Dirty
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,88 @@
1
+ module ActsAsTaggableOn
2
+ module Tagger
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ ##
9
+ # Make a model a tagger. This allows an instance of a model to claim ownership
10
+ # of tags.
11
+ #
12
+ # Example:
13
+ # class User < ActiveRecord::Base
14
+ # acts_as_tagger
15
+ # end
16
+ def acts_as_tagger(opts={})
17
+ class_eval do
18
+ has_many_with_taggable_compatibility :owned_taggings,
19
+ opts.merge(
20
+ as: :tagger,
21
+ dependent: :destroy,
22
+ class_name: '::ActsAsTaggableOn::Tagging'
23
+ )
24
+
25
+ has_many_with_taggable_compatibility :owned_tags,
26
+ through: :owned_taggings,
27
+ source: :tag,
28
+ class_name: '::ActsAsTaggableOn::Tag',
29
+ uniq: true
30
+ end
31
+
32
+ include ActsAsTaggableOn::Tagger::InstanceMethods
33
+ extend ActsAsTaggableOn::Tagger::SingletonMethods
34
+ end
35
+
36
+ def tagger?
37
+ false
38
+ end
39
+
40
+ def is_tagger?
41
+ tagger?
42
+ end
43
+ end
44
+
45
+ module InstanceMethods
46
+ ##
47
+ # Tag a taggable model with tags that are owned by the tagger.
48
+ #
49
+ # @param taggable The object that will be tagged
50
+ # @param [Hash] options An hash with options. Available options are:
51
+ # * <tt>:with</tt> - The tags that you want to
52
+ # * <tt>:on</tt> - The context on which you want to tag
53
+ #
54
+ # Example:
55
+ # @user.tag(@photo, :with => "paris, normandy", :on => :locations)
56
+ def tag(taggable, opts={})
57
+ opts.reverse_merge!(force: true)
58
+ skip_save = opts.delete(:skip_save)
59
+ return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
60
+
61
+ fail 'You need to specify a tag context using :on' unless opts.key?(:on)
62
+ fail 'You need to specify some tags using :with' unless opts.key?(:with)
63
+ fail "No context :#{opts[:on]} defined in #{taggable.class}" unless opts[:force] || taggable.tag_types.include?(opts[:on])
64
+
65
+ taggable.set_owner_tag_list_on(self, opts[:on].to_s, opts[:with])
66
+ taggable.save unless skip_save
67
+ end
68
+
69
+ def tagger?
70
+ self.class.is_tagger?
71
+ end
72
+
73
+ def is_tagger?
74
+ tagger?
75
+ end
76
+ end
77
+
78
+ module SingletonMethods
79
+ def tagger?
80
+ true
81
+ end
82
+
83
+ def is_tagger?
84
+ tagger?
85
+ end
86
+ end
87
+ end
88
+ end
@@ -1,23 +1,43 @@
1
- class Tagging < ActiveRecord::Base #:nodoc:
2
- include ActsAsTaggableOn::ActiveRecord::Backports if ActiveRecord::VERSION::MAJOR < 3
1
+ module ActsAsTaggableOn
2
+ class Tagging < ::ActiveRecord::Base #:nodoc:
3
+ #TODO, remove from 4.0.0
4
+ attr_accessible :tag,
5
+ :tag_id,
6
+ :context,
7
+ :taggable,
8
+ :taggable_type,
9
+ :taggable_id,
10
+ :tagger,
11
+ :tagger_type,
12
+ :tagger_id if defined?(ActiveModel::MassAssignmentSecurity)
3
13
 
4
- attr_accessible :tag,
5
- :tag_id,
6
- :context,
7
- :taggable,
8
- :taggable_type,
9
- :taggable_id,
10
- :tagger,
11
- :tagger_type,
12
- :tagger_id
14
+ belongs_to :tag, class_name: '::ActsAsTaggableOn::Tag', counter_cache: ActsAsTaggableOn.tags_counter
15
+ belongs_to :taggable, polymorphic: true
16
+ belongs_to :tagger, polymorphic: true
13
17
 
14
- belongs_to :tag
15
- belongs_to :taggable, :polymorphic => true
16
- belongs_to :tagger, :polymorphic => true
18
+ scope :owned_by, ->(owner) { where(tagger: owner) }
19
+ scope :not_owned, -> { where(tagger_id: nil, tagger_type: nil) }
17
20
 
18
- validates_presence_of :context
19
- validates_presence_of :tag_id
21
+ scope :by_contexts, ->(contexts = ['tags']) { where(context: contexts) }
22
+ scope :by_context, ->(context= 'tags') { by_contexts(context.to_s) }
20
23
 
21
- validates_uniqueness_of :tag_id, :scope => [ :taggable_type, :taggable_id, :context, :tagger_id, :tagger_type ]
24
+ validates_presence_of :context
25
+ validates_presence_of :tag_id
22
26
 
23
- end
27
+ validates_uniqueness_of :tag_id, scope: [:taggable_type, :taggable_id, :context, :tagger_id, :tagger_type]
28
+
29
+ after_destroy :remove_unused_tags
30
+
31
+ private
32
+
33
+ def remove_unused_tags
34
+ if ActsAsTaggableOn.remove_unused_tags
35
+ if ActsAsTaggableOn.tags_counter
36
+ tag.destroy if tag.reload.taggings_count.zero?
37
+ else
38
+ tag.destroy if tag.reload.taggings.count.zero?
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,17 +1,15 @@
1
- module TagsHelper
2
-
3
- # See the README for an example using tag_cloud.
4
- def tag_cloud(tags, classes)
5
- tags = tags.all if tags.respond_to?(:all)
6
-
7
- return [] if tags.empty?
8
-
9
- max_count = tags.sort_by(&:count).last.count.to_f
10
-
11
- tags.each do |tag|
12
- index = ((tag.count / max_count) * (classes.size - 1)).round
13
- yield tag, classes[index]
1
+ module ActsAsTaggableOn
2
+ module TagsHelper
3
+ # See the wiki for an example using tag_cloud.
4
+ def tag_cloud(tags, classes)
5
+ return [] if tags.empty?
6
+
7
+ max_count = tags.sort_by(&:taggings_count).last.taggings_count.to_f
8
+
9
+ tags.each do |tag|
10
+ index = ((tag.taggings_count / max_count) * (classes.size - 1))
11
+ yield tag, classes[index.nan? ? 0 : index.round]
12
+ end
14
13
  end
15
14
  end
16
-
17
15
  end
@@ -0,0 +1,38 @@
1
+ # This module is deprecated and will be removed in the incoming versions
2
+
3
+ module ActsAsTaggableOn
4
+ module Utils
5
+ class << self
6
+ # Use ActsAsTaggableOn::Tag connection
7
+ def connection
8
+ ActsAsTaggableOn::Tag.connection
9
+ end
10
+
11
+ def using_postgresql?
12
+ connection && connection.adapter_name == 'PostgreSQL'
13
+ end
14
+
15
+ def using_mysql?
16
+ #We should probably use regex for mysql to support prehistoric adapters
17
+ connection && connection.adapter_name == 'Mysql2'
18
+ end
19
+
20
+ def sha_prefix(string)
21
+ Digest::SHA1.hexdigest(string)[0..6]
22
+ end
23
+
24
+ def active_record4?
25
+ ::ActiveRecord::VERSION::MAJOR == 4
26
+ end
27
+
28
+ def like_operator
29
+ using_postgresql? ? 'ILIKE' : 'LIKE'
30
+ end
31
+
32
+ # escape _ and % characters in strings, since these are wildcards in SQL.
33
+ def escape_like(str)
34
+ str.gsub(/[!%_]/) { |x| '!' + x }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,4 @@
1
+ module ActsAsTaggableOn
2
+ VERSION = '3.5.0'
3
+ end
4
+
@@ -0,0 +1,6 @@
1
+ $stderr.puts <<EOF
2
+ You are requiring 'acts_as_taggable_on' in the 'acts-as-taggable-on' gem.
3
+
4
+ Please change your require to 'acts-as-taggable-on'.
5
+ EOF
6
+ require_relative 'acts-as-taggable-on'
@@ -0,0 +1,21 @@
1
+ # These rake tasks are to be run by MySql users only, they fix the management of
2
+ # binary-encoded strings for tag 'names'. Issues:
3
+ # https://github.com/mbleigh/acts-as-taggable-on/issues/623
4
+
5
+ namespace :acts_as_taggable_on_engine do
6
+
7
+ namespace :tag_names do
8
+
9
+ desc "Forcing collate of tag names to utf8_bin"
10
+ task :collate_bin => [:environment] do |t, args|
11
+ ActsAsTaggableOn::Configuration.apply_binary_collation(true)
12
+ end
13
+
14
+ desc "Forcing collate of tag names to utf8_general_ci"
15
+ task :collate_ci => [:environment] do |t, args|
16
+ ActsAsTaggableOn::Configuration.apply_binary_collation(false)
17
+ end
18
+
19
+ end
20
+
21
+ end