mbleigh-acts-as-taggable-on 1.0.0 → 1.0.1
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.
- 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
|