mbleigh-acts-as-taggable-on 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +5 -0
- data/README +18 -2
- data/generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb +1 -2
- data/generators/acts_as_taggable_on_migration/templates/migration.rb +2 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +33 -16
- data/lib/acts_as_taggable_on/acts_as_tagger.rb +5 -1
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +76 -5
- data/spec/acts_as_taggable_on/taggable_spec.rb +10 -1
- data/spec/acts_as_taggable_on/tagger_spec.rb +1 -0
- data/spec/schema.rb +19 -11
- data/spec/spec_helper.rb +11 -0
- metadata +2 -2
- data/generators/acts_as_taggable_on_migration/templates/add_users_migration.rb +0 -11
data/CHANGELOG
CHANGED
data/README
CHANGED
@@ -35,12 +35,23 @@ GemPlugin
|
|
35
35
|
Acts As Taggable On is also available as a gem plugin using Rails 2.1's gem dependencies.
|
36
36
|
To install the gem, add this to your config/environment.rb:
|
37
37
|
|
38
|
-
config.gem "mbleigh-acts-as-taggable-on", :source => "http://gems.github.com"
|
38
|
+
config.gem "mbleigh-acts-as-taggable-on", :source => "http://gems.github.com", :lib => "acts-as-taggable-on"
|
39
39
|
|
40
40
|
After that, you can run "rake gems:install" to install the gem if you don't already have it.
|
41
41
|
See http://ryandaigle.com/articles/2008/4/1/what-s-new-in-edge-rails-gem-dependencies for
|
42
42
|
additional details about gem dependencies in Rails.
|
43
43
|
|
44
|
+
** NOTE **
|
45
|
+
Some issues have been experienced with "rake gems:install". If that doesn't work to install the gem,
|
46
|
+
try just installing it as a normal gem:
|
47
|
+
|
48
|
+
gem install mbleigh-acts-as-taggable-on --source http://gems.github.com
|
49
|
+
|
50
|
+
Post Installation (Rails)
|
51
|
+
-------------------------
|
52
|
+
1. script/generate acts_as_taggable_on_migration
|
53
|
+
2. rake db/migrate
|
54
|
+
|
44
55
|
Testing
|
45
56
|
=======
|
46
57
|
|
@@ -135,14 +146,19 @@ Contributors
|
|
135
146
|
============
|
136
147
|
|
137
148
|
* Michael Bleigh - Original Author
|
138
|
-
* Brendan Lim - Related
|
149
|
+
* Brendan Lim - Related Objects
|
139
150
|
* Pradeep Elankumaran - Taggers
|
151
|
+
* Sinclair Bain - Patch King
|
140
152
|
|
141
153
|
Patch Contributors
|
142
154
|
------------------
|
143
155
|
|
156
|
+
* tristanzdunn - Related objects of other classes
|
157
|
+
* azabaj - Fixed migrate down
|
144
158
|
* Peter Cooper - named_scope fix
|
145
159
|
* slainer68 - STI fix
|
160
|
+
* harrylove - migration instructions and fix-ups
|
161
|
+
* lawrencepit - cached tag work
|
146
162
|
|
147
163
|
Resources
|
148
164
|
=========
|
@@ -2,7 +2,6 @@ class ActsAsTaggableOnMigrationGenerator < Rails::Generator::Base
|
|
2
2
|
def manifest
|
3
3
|
record do |m|
|
4
4
|
m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => "acts_as_taggable_on_migration"
|
5
|
-
|
6
|
-
end
|
5
|
+
end
|
7
6
|
end
|
8
7
|
end
|
@@ -7,6 +7,8 @@ class ActsAsTaggableOnMigration < ActiveRecord::Migration
|
|
7
7
|
create_table :taggings do |t|
|
8
8
|
t.column :tag_id, :integer
|
9
9
|
t.column :taggable_id, :integer
|
10
|
+
t.column :tagger_id, :integer
|
11
|
+
t.column :tagger_type, :string
|
10
12
|
|
11
13
|
# You should make sure that the column created is
|
12
14
|
# long enough to store the required class names.
|
@@ -6,12 +6,17 @@ module ActiveRecord
|
|
6
6
|
end
|
7
7
|
|
8
8
|
module ClassMethods
|
9
|
+
def taggable?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
9
13
|
def acts_as_taggable
|
10
14
|
acts_as_taggable_on :tags
|
11
15
|
end
|
12
16
|
|
13
17
|
def acts_as_taggable_on(*args)
|
14
|
-
|
18
|
+
args.flatten! if args
|
19
|
+
args.compact! if args
|
15
20
|
for tag_type in args
|
16
21
|
tag_type = tag_type.to_s
|
17
22
|
self.class_eval do
|
@@ -21,6 +26,10 @@ module ActiveRecord
|
|
21
26
|
end
|
22
27
|
|
23
28
|
self.class_eval <<-RUBY
|
29
|
+
def self.taggable?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
24
33
|
def self.caching_#{tag_type.singularize}_list?
|
25
34
|
caching_tag_list_on?("#{tag_type}")
|
26
35
|
end
|
@@ -46,18 +55,21 @@ module ActiveRecord
|
|
46
55
|
end
|
47
56
|
|
48
57
|
def find_related_#{tag_type}(options = {})
|
49
|
-
|
58
|
+
related_tags_for('#{tag_type}', self.class, options)
|
50
59
|
end
|
51
60
|
alias_method :find_related_on_#{tag_type}, :find_related_#{tag_type}
|
61
|
+
|
62
|
+
def find_related_#{tag_type}_for(klass, options = {})
|
63
|
+
related_tags_for('#{tag_type}', klass, options)
|
64
|
+
end
|
52
65
|
RUBY
|
53
66
|
end
|
54
67
|
|
55
68
|
if respond_to?(:tag_types)
|
56
|
-
|
57
|
-
write_inheritable_attribute(:tag_types, tag_types + args)
|
69
|
+
write_inheritable_attribute( :tag_types, (tag_types + args).uniq )
|
58
70
|
else
|
59
71
|
self.class_eval do
|
60
|
-
write_inheritable_attribute(:tag_types, args)
|
72
|
+
write_inheritable_attribute(:tag_types, args.uniq)
|
61
73
|
class_inheritable_reader :tag_types
|
62
74
|
|
63
75
|
has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag
|
@@ -207,9 +219,10 @@ module ActiveRecord
|
|
207
219
|
|
208
220
|
def tag_list_on(context, owner=nil)
|
209
221
|
var_name = context.to_s.singularize + "_list"
|
222
|
+
add_custom_context(context)
|
210
223
|
return instance_variable_get("@#{var_name}") unless instance_variable_get("@#{var_name}").nil?
|
211
224
|
|
212
|
-
if !owner && self.class.caching_tag_list_on?(context) and !(cached_value = cached_tag_list_on(context
|
225
|
+
if !owner && self.class.caching_tag_list_on?(context) and !(cached_value = cached_tag_list_on(context)).nil?
|
213
226
|
instance_variable_set("@#{var_name}", TagList.from(self["cached_#{var_name}"]))
|
214
227
|
else
|
215
228
|
instance_variable_set("@#{var_name}", TagList.new(*tags_on(context, owner).map(&:name)))
|
@@ -238,18 +251,22 @@ module ActiveRecord
|
|
238
251
|
def tag_counts_on(context,options={})
|
239
252
|
self.class.tag_counts_on(context,{:conditions => ["#{Tag.table_name}.name IN (?)", tag_list_on(context)]}.reverse_merge!(options))
|
240
253
|
end
|
241
|
-
|
242
|
-
def
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
254
|
+
|
255
|
+
def related_tags_for(context, klass, options = {})
|
256
|
+
search_conditions = related_search_options(context, klass, options)
|
257
|
+
|
258
|
+
klass.find(:all, search_conditions)
|
259
|
+
end
|
260
|
+
|
261
|
+
def related_search_options(context, klass, options = {})
|
262
|
+
tags_to_find = self.tags_on(context).collect { |t| t.name }
|
263
|
+
|
264
|
+
{ :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
|
265
|
+
:from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
|
266
|
+
:conditions => ["#{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],
|
267
|
+
:group => "#{klass.table_name}.id",
|
249
268
|
:order => "count DESC"
|
250
269
|
}.update(options)
|
251
|
-
|
252
|
-
self.class.find(:all, search_conditions)
|
253
270
|
end
|
254
271
|
|
255
272
|
def save_cached_tag_list
|
@@ -24,10 +24,14 @@ module ActiveRecord
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def tag(taggable, opts={})
|
27
|
+
opts.reverse_merge!(:force => true)
|
28
|
+
|
27
29
|
return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
|
28
30
|
raise "You need to specify a tag context using :on" unless opts.has_key?(:on)
|
29
31
|
raise "You need to specify some tags using :with" unless opts.has_key?(:with)
|
30
|
-
raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless
|
32
|
+
raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless
|
33
|
+
( opts[:force] || taggable.tag_types.include?(opts[:on]) )
|
34
|
+
|
31
35
|
taggable.set_tag_list_on(opts[:on].to_s, opts[:with], self)
|
32
36
|
taggable.save
|
33
37
|
end
|
@@ -1,11 +1,20 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../spec_helper'
|
2
2
|
|
3
|
-
describe "
|
4
|
-
|
3
|
+
describe "Acts As Taggable On" do
|
4
|
+
it "should provide a class method 'taggable?' that is false for untaggable models" do
|
5
|
+
UntaggableModel.should_not be_taggable
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "Taggable Method Generation" do
|
5
9
|
before(:each) do
|
10
|
+
[TaggableModel, Tag, Tagging, TaggableUser].each(&:delete_all)
|
6
11
|
@taggable = TaggableModel.new(:name => "Bob Jones")
|
7
12
|
end
|
8
13
|
|
14
|
+
it "should respond 'true' to taggable?" do
|
15
|
+
@taggable.class.should be_taggable
|
16
|
+
end
|
17
|
+
|
9
18
|
it "should create a class attribute for tag types" do
|
10
19
|
@taggable.class.should respond_to(:tag_types)
|
11
20
|
end
|
@@ -33,7 +42,7 @@ describe "acts_as_taggable_on" do
|
|
33
42
|
end
|
34
43
|
end
|
35
44
|
|
36
|
-
|
45
|
+
describe "Single Table Inheritance" do
|
37
46
|
before do
|
38
47
|
@taggable = TaggableModel.new(:name => "taggable")
|
39
48
|
@inherited_same = InheritingTaggableModel.new(:name => "inherited same")
|
@@ -50,7 +59,7 @@ describe "acts_as_taggable_on" do
|
|
50
59
|
end
|
51
60
|
end
|
52
61
|
|
53
|
-
|
62
|
+
describe "Reloading" do
|
54
63
|
it "should save a model instantiated by Model.find" do
|
55
64
|
taggable = TaggableModel.create!(:name => "Taggable")
|
56
65
|
found_taggable = TaggableModel.find(taggable.id)
|
@@ -58,7 +67,7 @@ describe "acts_as_taggable_on" do
|
|
58
67
|
end
|
59
68
|
end
|
60
69
|
|
61
|
-
|
70
|
+
describe "Related Objects" do
|
62
71
|
it "should find related objects based on tag names on context" do
|
63
72
|
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
64
73
|
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
@@ -76,5 +85,67 @@ describe "acts_as_taggable_on" do
|
|
76
85
|
taggable1.find_related_tags.should include(taggable3)
|
77
86
|
taggable1.find_related_tags.should_not include(taggable2)
|
78
87
|
end
|
88
|
+
|
89
|
+
it "should find other related objects based on tag names on context" do
|
90
|
+
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
91
|
+
taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
|
92
|
+
taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
|
93
|
+
|
94
|
+
taggable1.tag_list = "one, two"
|
95
|
+
taggable1.save
|
96
|
+
|
97
|
+
taggable2.tag_list = "three, four"
|
98
|
+
taggable2.save
|
99
|
+
|
100
|
+
taggable3.tag_list = "one, four"
|
101
|
+
taggable3.save
|
102
|
+
|
103
|
+
taggable1.find_related_tags_for(OtherTaggableModel).should include(taggable3)
|
104
|
+
taggable1.find_related_tags_for(OtherTaggableModel).should_not include(taggable2)
|
105
|
+
end
|
79
106
|
end
|
107
|
+
|
108
|
+
describe 'Tagging Contexts' do
|
109
|
+
before(:all) do
|
110
|
+
class Array
|
111
|
+
def freq
|
112
|
+
k=Hash.new(0)
|
113
|
+
self.each {|e| k[e]+=1}
|
114
|
+
k
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should eliminate duplicate tagging contexts ' do
|
120
|
+
TaggableModel.acts_as_taggable_on(:skills, :skills)
|
121
|
+
TaggableModel.tag_types.freq[:skills].should_not == 3
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should not contain embedded/nested arrays" do
|
125
|
+
TaggableModel.acts_as_taggable_on([:array], [:array])
|
126
|
+
TaggableModel.tag_types.freq[[:array]].should == 0
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should _flatten_ the content of arrays" do
|
130
|
+
TaggableModel.acts_as_taggable_on([:array], [:array])
|
131
|
+
TaggableModel.tag_types.freq[:array].should == 1
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should not raise an error when passed nil" do
|
135
|
+
lambda {
|
136
|
+
TaggableModel.acts_as_taggable_on()
|
137
|
+
}.should_not raise_error
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should not raise an error when passed [nil]" do
|
141
|
+
lambda {
|
142
|
+
TaggableModel.acts_as_taggable_on([nil])
|
143
|
+
}.should_not raise_error
|
144
|
+
end
|
145
|
+
|
146
|
+
after(:all) do
|
147
|
+
class Array; remove_method :freq; end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
80
151
|
end
|
@@ -2,6 +2,7 @@ require File.dirname(__FILE__) + '/../spec_helper'
|
|
2
2
|
|
3
3
|
describe "Taggable" do
|
4
4
|
before(:each) do
|
5
|
+
[TaggableModel, Tag, Tagging, TaggableUser].each(&:delete_all)
|
5
6
|
@taggable = TaggableModel.new(:name => "Bob Jones")
|
6
7
|
end
|
7
8
|
|
@@ -13,6 +14,13 @@ describe "Taggable" do
|
|
13
14
|
Tag.find(:all).size.should == 3
|
14
15
|
end
|
15
16
|
|
17
|
+
it "should be able to create tags through the tag list directly" do
|
18
|
+
@taggable.tag_list_on(:test).add("hello")
|
19
|
+
@taggable.save
|
20
|
+
@taggable.reload
|
21
|
+
@taggable.tag_list_on(:test).should == ["hello"]
|
22
|
+
end
|
23
|
+
|
16
24
|
it "should differentiate between contexts" do
|
17
25
|
@taggable.skill_list = "ruby, rails, css"
|
18
26
|
@taggable.tag_list = "ruby, bob, charlie"
|
@@ -89,8 +97,9 @@ describe "Taggable" do
|
|
89
97
|
TaggableModel.find_tagged_with("spinning", :on => :rotors).should_not be_empty
|
90
98
|
end
|
91
99
|
|
92
|
-
|
100
|
+
describe "Single Table Inheritance" do
|
93
101
|
before do
|
102
|
+
[TaggableModel, Tag, Tagging, TaggableUser].each(&:delete_all)
|
94
103
|
@taggable = TaggableModel.new(:name => "taggable")
|
95
104
|
@inherited_same = InheritingTaggableModel.new(:name => "inherited same")
|
96
105
|
@inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
|
data/spec/schema.rb
CHANGED
@@ -1,16 +1,19 @@
|
|
1
1
|
ActiveRecord::Schema.define :version => 0 do
|
2
|
-
create_table
|
3
|
-
t.
|
2
|
+
create_table "taggings", :force => true do |t|
|
3
|
+
t.integer "tag_id", :limit => 11
|
4
|
+
t.integer "taggable_id", :limit => 11
|
5
|
+
t.string "taggable_type"
|
6
|
+
t.string "context"
|
7
|
+
t.datetime "created_at"
|
8
|
+
t.integer "tagger_id", :limit => 11
|
9
|
+
t.string "tagger_type"
|
4
10
|
end
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
t.
|
11
|
-
t.column :created_at, :datetime
|
12
|
-
t.column :tagger_id, :integer
|
13
|
-
t.column :tagger_type, :string
|
11
|
+
|
12
|
+
add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"
|
13
|
+
add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context"
|
14
|
+
|
15
|
+
create_table "tags", :force => true do |t|
|
16
|
+
t.string "name"
|
14
17
|
end
|
15
18
|
|
16
19
|
create_table :taggable_models, :force => true do |t|
|
@@ -21,4 +24,9 @@ ActiveRecord::Schema.define :version => 0 do
|
|
21
24
|
create_table :taggable_users, :force => true do |t|
|
22
25
|
t.column :name, :string
|
23
26
|
end
|
27
|
+
create_table :other_taggable_models, :force => true do |t|
|
28
|
+
t.column :name, :string
|
29
|
+
t.column :type, :string
|
30
|
+
#t.column :cached_tag_list, :string
|
31
|
+
end
|
24
32
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../../../../spec/spec_helper'
|
2
2
|
|
3
|
+
module Spec::Example::ExampleGroupMethods
|
4
|
+
alias :context :describe
|
5
|
+
end
|
6
|
+
|
3
7
|
plugin_spec_dir = File.dirname(__FILE__)
|
4
8
|
ActiveRecord::Base.logger = Logger.new(plugin_spec_dir + "/debug.log")
|
5
9
|
|
@@ -10,6 +14,10 @@ class TaggableModel < ActiveRecord::Base
|
|
10
14
|
acts_as_taggable_on :skills
|
11
15
|
end
|
12
16
|
|
17
|
+
class OtherTaggableModel < ActiveRecord::Base
|
18
|
+
acts_as_taggable_on :tags, :languages
|
19
|
+
end
|
20
|
+
|
13
21
|
class InheritingTaggableModel < TaggableModel
|
14
22
|
end
|
15
23
|
|
@@ -19,4 +27,7 @@ end
|
|
19
27
|
|
20
28
|
class TaggableUser < ActiveRecord::Base
|
21
29
|
acts_as_tagger
|
30
|
+
end
|
31
|
+
|
32
|
+
class UntaggableModel < ActiveRecord::Base
|
22
33
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mbleigh-acts-as-taggable-on
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Bleigh
|
@@ -72,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements: []
|
73
73
|
|
74
74
|
rubyforge_project:
|
75
|
-
rubygems_version: 1.0
|
75
|
+
rubygems_version: 1.2.0
|
76
76
|
signing_key:
|
77
77
|
specification_version: 2
|
78
78
|
summary: Tagging for ActiveRecord with custom contexts and advanced features.
|
@@ -1,11 +0,0 @@
|
|
1
|
-
class AddUsersToActsAsTaggableOnMigration < ActiveRecord::Migration
|
2
|
-
def self.up
|
3
|
-
add_column :taggings, :tagger_id, :integer
|
4
|
-
add_column :taggings, :tagger_type, :string
|
5
|
-
end
|
6
|
-
|
7
|
-
def self.down
|
8
|
-
remove_column :taggings, :tagger_type
|
9
|
-
remove_column :taggings, :tagger_id
|
10
|
-
end
|
11
|
-
end
|