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

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