rylwin-acts-as-taggable-on 2.1.1a

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.gitignore +8 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +10 -0
  4. data/CHANGELOG +35 -0
  5. data/Gemfile +3 -0
  6. data/Guardfile +5 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.rdoc +222 -0
  9. data/Rakefile +13 -0
  10. data/acts-as-taggable-on.gemspec +28 -0
  11. data/generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb +7 -0
  12. data/generators/acts_as_taggable_on_migration/templates/migration.rb +29 -0
  13. data/lib/acts-as-taggable-on/version.rb +4 -0
  14. data/lib/acts-as-taggable-on.rb +36 -0
  15. data/lib/acts_as_taggable_on/acts_as_tagger.rb +67 -0
  16. data/lib/acts_as_taggable_on/compatibility/Gemfile +8 -0
  17. data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +21 -0
  18. data/lib/acts_as_taggable_on/tag.rb +81 -0
  19. data/lib/acts_as_taggable_on/tag_list.rb +96 -0
  20. data/lib/acts_as_taggable_on/taggable/acts_as_taggable_on/cache.rb +53 -0
  21. data/lib/acts_as_taggable_on/taggable/acts_as_taggable_on/collection.rb +139 -0
  22. data/lib/acts_as_taggable_on/taggable/acts_as_taggable_on/core.rb +284 -0
  23. data/lib/acts_as_taggable_on/taggable/acts_as_taggable_on/ownership.rb +105 -0
  24. data/lib/acts_as_taggable_on/taggable/acts_as_taggable_on/related.rb +73 -0
  25. data/lib/acts_as_taggable_on/taggable/acts_as_tagger.rb +67 -0
  26. data/lib/acts_as_taggable_on/taggable/cache.rb +53 -0
  27. data/lib/acts_as_taggable_on/taggable/collection.rb +139 -0
  28. data/lib/acts_as_taggable_on/taggable/compatibility/Gemfile +8 -0
  29. data/lib/acts_as_taggable_on/taggable/compatibility/active_record_backports.rb +21 -0
  30. data/lib/acts_as_taggable_on/taggable/core.rb +284 -0
  31. data/lib/acts_as_taggable_on/taggable/ownership.rb +105 -0
  32. data/lib/acts_as_taggable_on/taggable/related.rb +73 -0
  33. data/lib/acts_as_taggable_on/taggable/tag.rb +81 -0
  34. data/lib/acts_as_taggable_on/taggable/tag_list.rb +96 -0
  35. data/lib/acts_as_taggable_on/taggable/tagging.rb +24 -0
  36. data/lib/acts_as_taggable_on/taggable/tags_helper.rb +17 -0
  37. data/lib/acts_as_taggable_on/taggable/utils.rb +31 -0
  38. data/lib/acts_as_taggable_on/taggable.rb +63 -0
  39. data/lib/acts_as_taggable_on/tagging.rb +24 -0
  40. data/lib/acts_as_taggable_on/tags_helper.rb +17 -0
  41. data/lib/acts_as_taggable_on/utils.rb +31 -0
  42. data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +39 -0
  43. data/lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb +28 -0
  44. data/rails/init.rb +1 -0
  45. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +366 -0
  46. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +114 -0
  47. data/spec/acts_as_taggable_on/tag_list_spec.rb +74 -0
  48. data/spec/acts_as_taggable_on/tag_spec.rb +136 -0
  49. data/spec/acts_as_taggable_on/taggable_spec.rb +410 -0
  50. data/spec/acts_as_taggable_on/tagger_spec.rb +105 -0
  51. data/spec/acts_as_taggable_on/tagging_spec.rb +31 -0
  52. data/spec/acts_as_taggable_on/tags_helper_spec.rb +28 -0
  53. data/spec/acts_as_taggable_on/utils_spec.rb +22 -0
  54. data/spec/bm.rb +52 -0
  55. data/spec/database.yml.sample +19 -0
  56. data/spec/generators/acts_as_taggable_on/migration/migration_generator_spec.rb +22 -0
  57. data/spec/models.rb +44 -0
  58. data/spec/schema.rb +56 -0
  59. data/spec/spec_helper.rb +81 -0
  60. data/uninstall.rb +1 -0
  61. metadata +245 -0
@@ -0,0 +1,105 @@
1
+ module ActsAsTaggableOn::Taggable
2
+ module Ownership
3
+ def self.included(base)
4
+ base.send :include, ActsAsTaggableOn::Taggable::Ownership::InstanceMethods
5
+ base.extend ActsAsTaggableOn::Taggable::Ownership::ClassMethods
6
+
7
+ base.class_eval do
8
+ after_save :save_owned_tags
9
+ end
10
+
11
+ base.initialize_acts_as_taggable_on_ownership
12
+ end
13
+
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
29
+ end
30
+
31
+ module InstanceMethods
32
+ def owner_tags_on(owner, context)
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
40
+ end
41
+
42
+ def cached_owned_tag_list_on(context)
43
+ variable_name = "@owned_#{context}_list"
44
+ cache = instance_variable_get(variable_name) || instance_variable_set(variable_name, {})
45
+ end
46
+
47
+ def owner_tag_list_on(owner, context)
48
+ add_custom_context(context)
49
+
50
+ cache = cached_owned_tag_list_on(context)
51
+ cache.delete_if { |key, value| key.id == owner.id && key.class == owner.class }
52
+
53
+ cache[owner] ||= ActsAsTaggableOn::TagList.new(*owner_tags_on(owner, context).map(&:name))
54
+ end
55
+
56
+ def set_owner_tag_list_on(owner, context, new_list)
57
+ add_custom_context(context)
58
+
59
+ cache = cached_owned_tag_list_on(context)
60
+ cache.delete_if { |key, value| key.id == owner.id && key.class == owner.class }
61
+
62
+ cache[owner] = ActsAsTaggableOn::TagList.from(new_list)
63
+ end
64
+
65
+ def reload(*args)
66
+ self.class.tag_types.each do |context|
67
+ instance_variable_set("@owned_#{context}_list", nil)
68
+ end
69
+
70
+ super(*args)
71
+ end
72
+
73
+ def save_owned_tags
74
+ tagging_contexts.each do |context|
75
+ cached_owned_tag_list_on(context).each do |owner, tag_list|
76
+ # Find existing tags or create non-existing tags:
77
+ tag_list = ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_list.uniq)
78
+
79
+ owned_tags = owner_tags_on(owner, context)
80
+ old_tags = owned_tags - tag_list
81
+ new_tags = tag_list - owned_tags
82
+
83
+ # Find all taggings that belong to the taggable (self), are owned by the owner,
84
+ # have the correct context, and are removed from the list.
85
+ old_taggings = ActsAsTaggableOn::Tagging.where(:taggable_id => id, :taggable_type => self.class.base_class.to_s,
86
+ :tagger_type => owner.class.to_s, :tagger_id => owner.id,
87
+ :tag_id => old_tags, :context => context).all
88
+
89
+ if old_taggings.present?
90
+ # Destroy old taggings:
91
+ ActsAsTaggableOn::Tagging.destroy_all(:id => old_taggings.map(&:id))
92
+ end
93
+
94
+ # Create new taggings:
95
+ new_tags.each do |tag|
96
+ taggings.create!(:tag_id => tag.id, :context => context.to_s, :tagger => owner, :taggable => self)
97
+ end
98
+ end
99
+ end
100
+
101
+ true
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,73 @@
1
+ module ActsAsTaggableOn::Taggable
2
+ module Related
3
+ def self.included(base)
4
+ base.send :include, ActsAsTaggableOn::Taggable::Related::InstanceMethods
5
+ base.extend ActsAsTaggableOn::Taggable::Related::ClassMethods
6
+ base.initialize_acts_as_taggable_on_related
7
+ end
8
+
9
+ module ClassMethods
10
+ def initialize_acts_as_taggable_on_related
11
+ tag_types.map(&:to_s).each do |tag_type|
12
+ class_eval %(
13
+ def find_related_#{tag_type}(options = {})
14
+ related_tags_for('#{tag_type}', self.class, options)
15
+ end
16
+ alias_method :find_related_on_#{tag_type}, :find_related_#{tag_type}
17
+
18
+ def find_related_#{tag_type}_for(klass, options = {})
19
+ related_tags_for('#{tag_type}', klass, options)
20
+ end
21
+ )
22
+ end
23
+
24
+ unless tag_types.empty?
25
+ class_eval %(
26
+ def find_matching_contexts(search_context, result_context, options = {})
27
+ matching_contexts_for(search_context.to_s, result_context.to_s, self.class, options)
28
+ end
29
+
30
+ def find_matching_contexts_for(klass, search_context, result_context, options = {})
31
+ matching_contexts_for(search_context.to_s, result_context.to_s, klass, options)
32
+ end
33
+ )
34
+ end
35
+ end
36
+
37
+ def acts_as_taggable_on(*args)
38
+ super(*args)
39
+ initialize_acts_as_taggable_on_related
40
+ end
41
+ end
42
+
43
+ module InstanceMethods
44
+ def matching_contexts_for(search_context, result_context, klass, options = {})
45
+ tags_to_find = tags_on(search_context).collect { |t| t.name }
46
+
47
+ exclude_self = "#{klass.table_name}.#{klass.primary_key} != #{id} AND" if self.class == klass
48
+
49
+ group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(klass) : "#{klass.table_name}.#{klass.primary_key}"
50
+
51
+ klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count",
52
+ :from => "#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}",
53
+ :conditions => ["#{exclude_self} #{klass.table_name}.#{klass.primary_key} = #{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}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, result_context],
54
+ :group => group_columns,
55
+ :order => "count DESC" }.update(options))
56
+ end
57
+
58
+ def related_tags_for(context, klass, options = {})
59
+ tags_to_find = tags_on(context).collect { |t| t.name }
60
+
61
+ exclude_self = "#{klass.table_name}.#{klass.primary_key} != #{id} AND" if self.class == klass
62
+
63
+ group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(klass) : "#{klass.table_name}.#{klass.primary_key}"
64
+
65
+ klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count",
66
+ :from => "#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}",
67
+ :conditions => ["#{exclude_self} #{klass.table_name}.#{klass.primary_key} = #{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}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?)", tags_to_find],
68
+ :group => group_columns,
69
+ :order => "count DESC" }.update(options))
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,81 @@
1
+ module ActsAsTaggableOn
2
+ class Tag < ::ActiveRecord::Base
3
+ include ActsAsTaggableOn::ActiveRecord::Backports if ::ActiveRecord::VERSION::MAJOR < 3
4
+ include ActsAsTaggableOn::Utils
5
+
6
+ attr_accessible :name
7
+
8
+ ### ASSOCIATIONS:
9
+
10
+ has_many :taggings, :dependent => :destroy, :class_name => 'ActsAsTaggableOn::Tagging'
11
+
12
+ ### VALIDATIONS:
13
+
14
+ validates_presence_of :name
15
+ validates_uniqueness_of :name
16
+
17
+ ### SCOPES:
18
+
19
+ def self.named(name)
20
+ where(["name #{like_operator} ?", escape_like(name)])
21
+ end
22
+
23
+ def self.named_any(list)
24
+ where(list.map { |tag| sanitize_sql(["name #{like_operator} ?", escape_like(tag.to_s)]) }.join(" OR "))
25
+ end
26
+
27
+ def self.named_like(name)
28
+ where(["name #{like_operator} ?", "%#{escape_like(name)}%"])
29
+ end
30
+
31
+ def self.named_like_any(list)
32
+ where(list.map { |tag| sanitize_sql(["name #{like_operator} ?", "%#{escape_like(tag.to_s)}%"]) }.join(" OR "))
33
+ end
34
+
35
+ ### CLASS METHODS:
36
+
37
+ def self.find_or_create_with_like_by_name(name)
38
+ named_like(name).first || create(:name => name)
39
+ end
40
+
41
+ def self.find_or_create_all_with_like_by_name(*list)
42
+ list = [list].flatten
43
+
44
+ return [] if list.empty?
45
+
46
+ existing_tags = Tag.named_any(list).all
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
51
+ created_tags = new_tag_names.map { |name| Tag.create(:name => name) }
52
+
53
+ existing_tags + created_tags
54
+ end
55
+
56
+ ### INSTANCE METHODS:
57
+
58
+ def ==(object)
59
+ super || (object.is_a?(Tag) && name == object.name)
60
+ end
61
+
62
+ def to_s
63
+ name
64
+ end
65
+
66
+ def count
67
+ read_attribute(:count).to_i
68
+ end
69
+
70
+ def safe_name
71
+ name.gsub(/[^a-zA-Z0-9]/, '')
72
+ end
73
+
74
+ class << self
75
+ private
76
+ def comparable_name(str)
77
+ RUBY_VERSION >= "1.9" ? str.downcase : str.mb_chars.downcase
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,96 @@
1
+ module ActsAsTaggableOn
2
+ class TagList < Array
3
+ cattr_accessor :delimiter
4
+ self.delimiter = ','
5
+
6
+ attr_accessor :owner
7
+
8
+ def initialize(*args)
9
+ add(*args)
10
+ end
11
+
12
+ ##
13
+ # Returns a new TagList using the given tag string.
14
+ #
15
+ # Example:
16
+ # tag_list = TagList.from("One , Two, Three")
17
+ # tag_list # ["One", "Two", "Three"]
18
+ def self.from(string)
19
+ glue = delimiter.ends_with?(" ") ? delimiter : "#{delimiter} "
20
+ string = string.join(glue) if string.respond_to?(:join)
21
+
22
+ new.tap do |tag_list|
23
+ string = string.to_s.dup
24
+
25
+ # Parse the quoted tags
26
+ string.gsub!(/(\A|#{delimiter})\s*"(.*?)"\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
27
+ string.gsub!(/(\A|#{delimiter})\s*'(.*?)'\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
28
+
29
+ tag_list.add(string.split(delimiter))
30
+ end
31
+ end
32
+
33
+ ##
34
+ # Add tags to the tag_list. Duplicate or blank tags will be ignored.
35
+ # Use the <tt>:parse</tt> option to add an unparsed tag string.
36
+ #
37
+ # Example:
38
+ # tag_list.add("Fun", "Happy")
39
+ # tag_list.add("Fun, Happy", :parse => true)
40
+ def add(*names)
41
+ extract_and_apply_options!(names)
42
+ concat(names)
43
+ clean!
44
+ self
45
+ end
46
+
47
+ ##
48
+ # Remove specific tags from the tag_list.
49
+ # Use the <tt>:parse</tt> option to add an unparsed tag string.
50
+ #
51
+ # Example:
52
+ # tag_list.remove("Sad", "Lonely")
53
+ # tag_list.remove("Sad, Lonely", :parse => true)
54
+ def remove(*names)
55
+ extract_and_apply_options!(names)
56
+ delete_if { |name| names.include?(name) }
57
+ self
58
+ end
59
+
60
+ ##
61
+ # Transform the tag_list into a tag string suitable for edting in a form.
62
+ # The tags are joined with <tt>TagList.delimiter</tt> and quoted if necessary.
63
+ #
64
+ # Example:
65
+ # tag_list = TagList.new("Round", "Square,Cube")
66
+ # tag_list.to_s # 'Round, "Square,Cube"'
67
+ def to_s
68
+ tags = frozen? ? self.dup : self
69
+ tags.send(:clean!)
70
+
71
+ tags.map do |name|
72
+ name.include?(delimiter) ? "\"#{name}\"" : name
73
+ end.join(delimiter.ends_with?(" ") ? delimiter : "#{delimiter} ")
74
+ end
75
+
76
+ private
77
+
78
+ # Remove whitespace, duplicates, and blanks.
79
+ def clean!
80
+ reject!(&:blank?)
81
+ map!(&:strip)
82
+ uniq!
83
+ end
84
+
85
+ def extract_and_apply_options!(args)
86
+ options = args.last.is_a?(Hash) ? args.pop : {}
87
+ options.assert_valid_keys :parse
88
+
89
+ if options[:parse]
90
+ args.map! { |a| self.class.from(a) }
91
+ end
92
+
93
+ args.flatten!
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,24 @@
1
+ module ActsAsTaggableOn
2
+ class Tagging < ::ActiveRecord::Base #:nodoc:
3
+ include ActsAsTaggableOn::ActiveRecord::Backports if ::ActiveRecord::VERSION::MAJOR < 3
4
+
5
+ attr_accessible :tag,
6
+ :tag_id,
7
+ :context,
8
+ :taggable,
9
+ :taggable_type,
10
+ :taggable_id,
11
+ :tagger,
12
+ :tagger_type,
13
+ :tagger_id
14
+
15
+ belongs_to :tag, :class_name => 'ActsAsTaggableOn::Tag'
16
+ belongs_to :taggable, :polymorphic => true
17
+ belongs_to :tagger, :polymorphic => true
18
+
19
+ validates_presence_of :context
20
+ validates_presence_of :tag_id
21
+
22
+ validates_uniqueness_of :tag_id, :scope => [ :taggable_type, :taggable_id, :context, :tagger_id, :tagger_type ]
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module ActsAsTaggableOn
2
+ module TagsHelper
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]
14
+ end
15
+ end
16
+ end
17
+ 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
@@ -0,0 +1,63 @@
1
+ module ActsAsTaggableOn
2
+ module Taggable
3
+ def taggable?
4
+ false
5
+ end
6
+
7
+ ##
8
+ # This is an alias for calling <tt>acts_as_taggable_on :tags</tt>.
9
+ #
10
+ # Example:
11
+ # class Book < ActiveRecord::Base
12
+ # acts_as_taggable
13
+ # end
14
+ def acts_as_taggable
15
+ acts_as_taggable_on :tags
16
+ end
17
+
18
+ ##
19
+ # Make a model taggable on specified contexts.
20
+ #
21
+ # @param [Array] tag_types An array of taggable contexts
22
+ #
23
+ # Example:
24
+ # class User < ActiveRecord::Base
25
+ # acts_as_taggable_on :languages, :skills
26
+ # end
27
+ def acts_as_taggable_on(*tag_types)
28
+ tag_types = tag_types.to_a.flatten.compact.map(&:to_sym)
29
+
30
+ if taggable?
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
36
+ else
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
44
+
45
+ class_eval do
46
+ has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "ActsAsTaggableOn::Tagging"
47
+ has_many :base_tags, :through => :taggings, :source => :tag, :class_name => "ActsAsTaggableOn::Tag"
48
+
49
+ def self.taggable?
50
+ true
51
+ end
52
+
53
+ include ActsAsTaggableOn::Utils
54
+ include ActsAsTaggableOn::Taggable::Core
55
+ include ActsAsTaggableOn::Taggable::Collection
56
+ include ActsAsTaggableOn::Taggable::Cache
57
+ include ActsAsTaggableOn::Taggable::Ownership
58
+ include ActsAsTaggableOn::Taggable::Related
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,24 @@
1
+ module ActsAsTaggableOn
2
+ class Tagging < ::ActiveRecord::Base #:nodoc:
3
+ include ActsAsTaggableOn::ActiveRecord::Backports if ::ActiveRecord::VERSION::MAJOR < 3
4
+
5
+ attr_accessible :tag,
6
+ :tag_id,
7
+ :context,
8
+ :taggable,
9
+ :taggable_type,
10
+ :taggable_id,
11
+ :tagger,
12
+ :tagger_type,
13
+ :tagger_id
14
+
15
+ belongs_to :tag, :class_name => 'ActsAsTaggableOn::Tag'
16
+ belongs_to :taggable, :polymorphic => true
17
+ belongs_to :tagger, :polymorphic => true
18
+
19
+ validates_presence_of :context
20
+ validates_presence_of :tag_id
21
+
22
+ validates_uniqueness_of :tag_id, :scope => [ :taggable_type, :taggable_id, :context, :tagger_id, :tagger_type ]
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module ActsAsTaggableOn
2
+ module TagsHelper
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]
14
+ end
15
+ end
16
+ end
17
+ 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
@@ -0,0 +1,39 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module ActsAsTaggableOn
5
+ class MigrationGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ desc "Generates migration for Tag and Tagging models"
9
+
10
+ def self.orm
11
+ Rails::Generators.options[:rails][:orm]
12
+ end
13
+
14
+ def self.source_root
15
+ File.join(File.dirname(__FILE__), 'templates', (orm.to_s unless orm.class.eql?(String)) )
16
+ end
17
+
18
+ def self.orm_has_migration?
19
+ [:active_record].include? orm
20
+ end
21
+
22
+ def self.next_migration_number(dirname)
23
+ if ActiveRecord::Base.timestamped_migrations
24
+ migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
25
+ migration_number += 1
26
+ migration_number.to_s
27
+ else
28
+ "%.3d" % (current_migration_number(dirname) + 1)
29
+ end
30
+ end
31
+
32
+ def create_migration_file
33
+ if self.class.orm_has_migration?
34
+ migration_template 'migration.rb', 'db/migrate/acts_as_taggable_on_migration'
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,28 @@
1
+ class ActsAsTaggableOnMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :tags do |t|
4
+ t.string :name
5
+ end
6
+
7
+ create_table :taggings do |t|
8
+ t.references :tag
9
+
10
+ # You should make sure that the column created is
11
+ # long enough to store the required class names.
12
+ t.references :taggable, :polymorphic => true
13
+ t.references :tagger, :polymorphic => true
14
+
15
+ t.string :context
16
+
17
+ t.datetime :created_at
18
+ end
19
+
20
+ add_index :taggings, :tag_id
21
+ add_index :taggings, [:taggable_id, :taggable_type, :context]
22
+ end
23
+
24
+ def self.down
25
+ drop_table :taggings
26
+ drop_table :tags
27
+ end
28
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'acts-as-taggable-on'