acts-as-taggable-on 1.1.9 → 2.0.0.pre1

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.
@@ -1,52 +1,60 @@
1
1
  module ActiveRecord
2
2
  module Acts
3
3
  module Tagger
4
+
4
5
  def self.included(base)
5
6
  base.extend ClassMethods
6
7
  end
7
-
8
+
8
9
  module ClassMethods
10
+
9
11
  def acts_as_tagger(opts={})
10
- has_many :owned_taggings, opts.merge(:as => :tagger, :dependent => :destroy,
12
+ has_many :owned_taggings, opts.merge(:as => :tagger, :dependent => :destroy,
11
13
  :include => :tag, :class_name => "Tagging")
12
14
  has_many :owned_tags, :through => :owned_taggings, :source => :tag, :uniq => true
15
+
13
16
  include ActiveRecord::Acts::Tagger::InstanceMethods
14
- extend ActiveRecord::Acts::Tagger::SingletonMethods
17
+ extend ActiveRecord::Acts::Tagger::SingletonMethods
15
18
  end
16
-
19
+
17
20
  def is_tagger?
18
21
  false
19
22
  end
23
+
20
24
  end
21
-
25
+
22
26
  module InstanceMethods
27
+
23
28
  def self.included(base)
24
29
  end
25
-
30
+
26
31
  def tag(taggable, opts={})
27
32
  opts.reverse_merge!(:force => true)
28
33
 
29
34
  return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
30
- raise "You need to specify a tag context using :on" unless opts.has_key?(:on)
31
- raise "You need to specify some tags using :with" unless opts.has_key?(:with)
32
- raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless
33
- ( opts[:force] || taggable.tag_types.include?(opts[:on]) )
35
+
36
+ raise "You need to specify a tag context using :on" unless opts.has_key?(:on)
37
+ raise "You need to specify some tags using :with" unless opts.has_key?(:with)
38
+ raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless (opts[:force] || taggable.tag_types.include?(opts[:on]))
34
39
 
35
40
  taggable.set_tag_list_on(opts[:on].to_s, opts[:with], self)
36
41
  taggable.save
37
42
  end
38
-
43
+
39
44
  def is_tagger?
40
45
  self.class.is_tagger?
41
46
  end
47
+
42
48
  end
43
-
49
+
44
50
  module SingletonMethods
51
+
45
52
  def is_tagger?
46
53
  true
47
54
  end
55
+
48
56
  end
49
-
57
+
50
58
  end
51
59
  end
52
60
  end
@@ -2,10 +2,12 @@ module ActiveRecord
2
2
  module Acts
3
3
  module TaggableOn
4
4
  module GroupHelper
5
+
5
6
  # all column names are necessary for PostgreSQL group clause
6
7
  def grouped_column_names_for(object)
7
8
  object.column_names.map { |column| "#{object.table_name}.#{column}" }.join(", ")
8
9
  end
10
+
9
11
  end
10
12
  end
11
13
  end
@@ -1,52 +1,53 @@
1
1
  class Tag < ActiveRecord::Base
2
-
2
+
3
3
  attr_accessible :name
4
-
4
+
5
5
  ### ASSOCIATIONS:
6
-
6
+
7
7
  has_many :taggings, :dependent => :destroy
8
-
8
+
9
9
  ### VALIDATIONS:
10
-
10
+
11
11
  validates_presence_of :name
12
12
  validates_uniqueness_of :name
13
-
13
+
14
14
  ### NAMED SCOPES:
15
-
16
- named_scope :named, lambda { |name| { :conditions => ["name LIKE ?", name] } }
17
- named_scope :named_any, lambda { |list| { :conditions => list.map { |tag| sanitize_sql(["name LIKE ?", tag.to_s]) }.join(" OR ") } }
18
- named_scope :named_like, lambda { |name| { :conditions => ["name LIKE ?", "%#{name}%"] } }
19
- named_scope :named_like_any, lambda { |list| { :conditions => list.map { |tag| sanitize_sql(["name LIKE ?", "%#{tag.to_s}%"]) }.join(" OR ") } }
20
-
15
+
16
+ scope :named, lambda { |name| { :conditions => ["name LIKE ?", name] } }
17
+ scope :named_any, lambda { |list| { :conditions => list.map { |tag| sanitize_sql(["name LIKE ?", tag.to_s]) }.join(" OR ") } }
18
+ scope :named_like, lambda { |name| { :conditions => ["name LIKE ?", "%#{name}%"] } }
19
+ scope :named_like_any, lambda { |list| { :conditions => list.map { |tag| sanitize_sql(["name LIKE ?", "%#{tag.to_s}%"]) }.join(" OR ") } }
20
+
21
21
  ### CLASS METHODS:
22
-
22
+
23
23
  def self.find_or_create_with_like_by_name(name)
24
24
  named_like(name).first || create(:name => name)
25
25
  end
26
-
26
+
27
27
  def self.find_or_create_all_with_like_by_name(*list)
28
28
  list = [list].flatten
29
-
29
+
30
30
  return [] if list.empty?
31
31
 
32
32
  existing_tags = Tag.named_any(list).all
33
- new_tag_names = list.reject { |name| existing_tags.any? { |tag| tag.name.mb_chars.downcase == name.mb_chars.downcase } }
33
+ new_tag_names = list.reject { |name| existing_tags.any? { |tag| tag.name.downcase == name.downcase } }
34
34
  created_tags = new_tag_names.map { |name| Tag.create(:name => name) }
35
-
36
- existing_tags + created_tags
35
+
36
+ existing_tags + created_tags
37
37
  end
38
-
38
+
39
39
  ### INSTANCE METHODS:
40
-
40
+
41
41
  def ==(object)
42
42
  super || (object.is_a?(Tag) && name == object.name)
43
43
  end
44
-
44
+
45
45
  def to_s
46
46
  name
47
47
  end
48
-
48
+
49
49
  def count
50
50
  read_attribute(:count).to_i
51
51
  end
52
+
52
53
  end
@@ -1,17 +1,19 @@
1
1
  class TagList < Array
2
+
2
3
  cattr_accessor :delimiter
4
+
3
5
  self.delimiter = ','
4
-
6
+
5
7
  def initialize(*args)
6
8
  add(*args)
7
9
  end
8
-
10
+
9
11
  attr_accessor :owner
10
-
12
+
11
13
  # Add tags to the tag_list. Duplicate or blank tags will be ignored.
12
14
  #
13
15
  # tag_list.add("Fun", "Happy")
14
- #
16
+ #
15
17
  # Use the <tt>:parse</tt> option to add an unparsed tag string.
16
18
  #
17
19
  # tag_list.add("Fun, Happy", :parse => true)
@@ -21,20 +23,20 @@ class TagList < Array
21
23
  clean!
22
24
  self
23
25
  end
24
-
26
+
25
27
  # Remove specific tags from the tag_list.
26
- #
28
+ #
27
29
  # tag_list.remove("Sad", "Lonely")
28
30
  #
29
31
  # Like #add, the <tt>:parse</tt> option can be used to remove multiple tags in a string.
30
- #
32
+ #
31
33
  # tag_list.remove("Sad, Lonely", :parse => true)
32
34
  def remove(*names)
33
35
  extract_and_apply_options!(names)
34
36
  delete_if { |name| names.include?(name) }
35
37
  self
36
38
  end
37
-
39
+
38
40
  # Transform the tag_list into a tag string suitable for edting in a form.
39
41
  # The tags are joined with <tt>TagList.delimiter</tt> and quoted if necessary.
40
42
  #
@@ -43,12 +45,12 @@ class TagList < Array
43
45
  def to_s
44
46
  tags = frozen? ? self.dup : self
45
47
  tags.send(:clean!)
46
-
48
+
47
49
  tags.map do |name|
48
50
  name.include?(delimiter) ? "\"#{name}\"" : name
49
51
  end.join(delimiter.ends_with?(" ") ? delimiter : "#{delimiter} ")
50
52
  end
51
-
53
+
52
54
  private
53
55
  # Remove whitespace, duplicates, and blanks.
54
56
  def clean!
@@ -56,35 +58,38 @@ class TagList < Array
56
58
  map!(&:strip)
57
59
  uniq!
58
60
  end
59
-
61
+
60
62
  def extract_and_apply_options!(args)
61
63
  options = args.last.is_a?(Hash) ? args.pop : {}
62
64
  options.assert_valid_keys :parse
63
-
65
+
64
66
  if options[:parse]
65
67
  args.map! { |a| self.class.from(a) }
66
68
  end
67
-
69
+
68
70
  args.flatten!
69
71
  end
70
-
72
+
71
73
  class << self
74
+
72
75
  # Returns a new TagList using the given tag string.
73
- #
76
+ #
74
77
  # tag_list = TagList.from("One , Two, Three")
75
78
  # tag_list # ["One", "Two", "Three"]
76
79
  def from(string)
77
80
  string = string.join(", ") if string.respond_to?(:join)
78
81
 
79
- returning new do |tag_list|
82
+ new.tap do |tag_list|
80
83
  string = string.to_s.dup
81
-
84
+
82
85
  # Parse the quoted tags
83
86
  string.gsub!(/(\A|#{delimiter})\s*"(.*?)"\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
84
87
  string.gsub!(/(\A|#{delimiter})\s*'(.*?)'\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
85
-
88
+
86
89
  tag_list.add(string.split(delimiter))
87
90
  end
88
91
  end
92
+
89
93
  end
90
- end
94
+
95
+ end
@@ -1,14 +1,22 @@
1
1
  class Tagging < ActiveRecord::Base #:nodoc:
2
- attr_accessible :tag, :tag_id, :context,
3
- :taggable, :taggable_type, :taggable_id,
4
- :tagger, :tagger_type, :tagger_id
2
+
3
+ attr_accessible :tag,
4
+ :tag_id,
5
+ :context,
6
+ :taggable,
7
+ :taggable_type,
8
+ :taggable_id,
9
+ :tagger,
10
+ :tagger_type,
11
+ :tagger_id
5
12
 
6
13
  belongs_to :tag
7
14
  belongs_to :taggable, :polymorphic => true
8
- belongs_to :tagger, :polymorphic => true
9
-
15
+ belongs_to :tagger, :polymorphic => true
16
+
10
17
  validates_presence_of :context
11
18
  validates_presence_of :tag_id
12
-
13
- validates_uniqueness_of :tag_id, :scope => [:taggable_type, :taggable_id, :context, :tagger_id, :tagger_type]
19
+
20
+ validates_uniqueness_of :tag_id, :scope => [ :taggable_type, :taggable_id, :context, :tagger_id, :tagger_type ]
21
+
14
22
  end
@@ -1,13 +1,17 @@
1
1
  module TagsHelper
2
+
2
3
  # See the README for an example using tag_cloud.
3
4
  def tag_cloud(tags, classes)
5
+ tags = tags.all if tags.is_a? ActiveRecord::Relation
6
+
4
7
  return [] if tags.empty?
5
8
 
6
9
  max_count = tags.sort_by(&:count).last.count.to_f
7
-
10
+
8
11
  tags.each do |tag|
9
12
  index = ((tag.count / max_count) * (classes.size - 1)).round
10
13
  yield tag, classes[index]
11
14
  end
12
15
  end
13
- end
16
+
17
+ end
@@ -0,0 +1,31 @@
1
+ require 'rails/generators/migration'
2
+
3
+ module ActsAsTaggableOn
4
+ class MigrationGenerator < Rails::Generators::Base
5
+ include Rails::Generators::Migration
6
+
7
+ desc "Generates migration for Tag and Tagging models"
8
+
9
+ def self.orm
10
+ Rails::Generators.options[:rails][:orm]
11
+ end
12
+
13
+ def self.source_root
14
+ File.join(File.dirname(__FILE__), 'templates', orm)
15
+ end
16
+
17
+ def self.orm_has_migration?
18
+ [:active_record].include? orm
19
+ end
20
+
21
+ def self.next_migration_number(path)
22
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
23
+ end
24
+
25
+ def create_migration_file
26
+ if self.class.orm_has_migration?
27
+ migration_template 'migration.rb', 'db/migrate/acts_as_taggable_on_migration'
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,27 +1,26 @@
1
1
  class ActsAsTaggableOnMigration < ActiveRecord::Migration
2
2
  def self.up
3
3
  create_table :tags do |t|
4
- t.column :name, :string
4
+ t.string :name
5
5
  end
6
-
6
+
7
7
  create_table :taggings do |t|
8
- t.column :tag_id, :integer
9
- t.column :taggable_id, :integer
10
- t.column :tagger_id, :integer
11
- t.column :tagger_type, :string
12
-
8
+ t.references :tag
9
+
13
10
  # You should make sure that the column created is
14
11
  # long enough to store the required class names.
15
- t.column :taggable_type, :string
16
- t.column :context, :string
17
-
18
- t.column :created_at, :datetime
12
+ t.references :taggable, :polymorphic => true
13
+ t.references :tagger, :polymorphic => true
14
+
15
+ t.string :context
16
+
17
+ t.datetime :created_at
19
18
  end
20
-
19
+
21
20
  add_index :taggings, :tag_id
22
21
  add_index :taggings, [:taggable_id, :taggable_type, :context]
23
22
  end
24
-
23
+
25
24
  def self.down
26
25
  drop_table :taggings
27
26
  drop_table :tags
@@ -2,9 +2,9 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe "Acts As Taggable On" do
4
4
  before(:each) do
5
- clean_database!
5
+ clean_database!
6
6
  end
7
-
7
+
8
8
  it "should provide a class method 'taggable?' that is false for untaggable models" do
9
9
  UntaggableModel.should_not be_taggable
10
10
  end
@@ -22,7 +22,7 @@ describe "Acts As Taggable On" do
22
22
  it "should create a class attribute for tag types" do
23
23
  @taggable.class.should respond_to(:tag_types)
24
24
  end
25
-
25
+
26
26
  it "should create an instance attribute for tag types" do
27
27
  @taggable.should respond_to(:tag_types)
28
28
  end
@@ -36,7 +36,7 @@ describe "Acts As Taggable On" do
36
36
  end
37
37
 
38
38
  it "should add tagged_with and tag_counts to singleton" do
39
- TaggableModel.should respond_to(:find_tagged_with, :tag_counts)
39
+ TaggableModel.should respond_to(:tagged_with, :tag_counts)
40
40
  end
41
41
 
42
42
  it "should add saving of tag lists and cached tag lists to the instance" do
@@ -48,10 +48,6 @@ describe "Acts As Taggable On" do
48
48
  @taggable.should respond_to(:tag_list, :skill_list, :language_list)
49
49
  @taggable.should respond_to(:tag_list=, :skill_list=, :language_list=)
50
50
  end
51
-
52
- it "should generate a tag_list accessor for getting all tags for each tag type" do
53
- @taggable.should respond_to(:all_tags_list, :all_skills_list, :all_languages_list)
54
- end
55
51
  end
56
52
 
57
53
  describe "Single Table Inheritance" do
@@ -212,4 +208,4 @@ describe "Acts As Taggable On" do
212
208
  end
213
209
  end
214
210
 
215
- end
211
+ end
@@ -15,7 +15,7 @@ describe "Group Helper" do
15
15
  end
16
16
 
17
17
  it "should return all column names joined for TaggableModel GROUP clause" do
18
- @taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.id, taggable_models.name, taggable_models.type, taggable_models.cached_tag_list"
18
+ @taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.id, taggable_models.name, taggable_models.type"
19
19
  end
20
20
  end
21
21
  end