tagtical 1.0.6

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.
Files changed (41) hide show
  1. data/CHANGELOG +25 -0
  2. data/Gemfile +20 -0
  3. data/Gemfile.lock +25 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.rdoc +306 -0
  6. data/Rakefile +59 -0
  7. data/VERSION +1 -0
  8. data/generators/tagtical_migration/tagtical_migration_generator.rb +7 -0
  9. data/generators/tagtical_migration/templates/migration.rb +34 -0
  10. data/lib/generators/tagtical/migration/migration_generator.rb +32 -0
  11. data/lib/generators/tagtical/migration/templates/active_record/migration.rb +35 -0
  12. data/lib/tagtical/acts_as_tagger.rb +69 -0
  13. data/lib/tagtical/compatibility/Gemfile +8 -0
  14. data/lib/tagtical/compatibility/active_record_backports.rb +21 -0
  15. data/lib/tagtical/tag.rb +314 -0
  16. data/lib/tagtical/tag_list.rb +133 -0
  17. data/lib/tagtical/taggable/cache.rb +53 -0
  18. data/lib/tagtical/taggable/collection.rb +141 -0
  19. data/lib/tagtical/taggable/core.rb +317 -0
  20. data/lib/tagtical/taggable/ownership.rb +110 -0
  21. data/lib/tagtical/taggable/related.rb +60 -0
  22. data/lib/tagtical/taggable.rb +51 -0
  23. data/lib/tagtical/tagging.rb +42 -0
  24. data/lib/tagtical/tags_helper.rb +17 -0
  25. data/lib/tagtical.rb +47 -0
  26. data/rails/init.rb +1 -0
  27. data/spec/bm.rb +53 -0
  28. data/spec/database.yml +17 -0
  29. data/spec/database.yml.sample +17 -0
  30. data/spec/models.rb +60 -0
  31. data/spec/schema.rb +46 -0
  32. data/spec/spec_helper.rb +159 -0
  33. data/spec/tagtical/acts_as_tagger_spec.rb +94 -0
  34. data/spec/tagtical/tag_list_spec.rb +102 -0
  35. data/spec/tagtical/tag_spec.rb +301 -0
  36. data/spec/tagtical/taggable_spec.rb +460 -0
  37. data/spec/tagtical/tagger_spec.rb +76 -0
  38. data/spec/tagtical/tagging_spec.rb +52 -0
  39. data/spec/tagtical/tags_helper_spec.rb +28 -0
  40. data/spec/tagtical/tagtical_spec.rb +340 -0
  41. metadata +132 -0
data/CHANGELOG ADDED
@@ -0,0 +1,25 @@
1
+ == 2010-02-17
2
+ * Converted the plugin to be compatible with Rails3
3
+
4
+ == 2009-12-02
5
+
6
+ * PostgreSQL is now supported (via morgoth)
7
+
8
+ == 2008-07-17
9
+
10
+ * Can now use a named_scope to find tags!
11
+
12
+ == 2008-06-23
13
+
14
+ * Can now find related objects of another class (tristanzdunn)
15
+ * Removed extraneous down migration cruft (azabaj)
16
+
17
+ == 2008-06-09
18
+
19
+ * Added support for Single Table Inheritance
20
+ * Adding gemspec and rails/init.rb for gemified plugin
21
+
22
+ == 2007-12-12
23
+
24
+ * Added ability to use dynamic tag contexts
25
+ * Fixed missing migration generator
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ source :gemcutter
2
+
3
+ # Rails 3.0
4
+
5
+ # Cannot require these as dependency until there the fix is released:
6
+ #
7
+ #gem 'rails', '3.0.5'
8
+ #gem 'rspec', '2.6.0'
9
+ # http://rubyforge.org/tracker/?func=detail&atid=575&aid=29163&group_id=126
10
+
11
+ gem 'sqlite3-ruby', :require => 'sqlite3'
12
+ gem 'mysql'
13
+
14
+ #gem 'pg'
15
+ gem 'jeweler'
16
+ gem 'rcov'
17
+
18
+ group :test do
19
+ gem "mocha"
20
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,25 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ git (1.2.5)
5
+ jeweler (1.6.2)
6
+ bundler (~> 1.0)
7
+ git (>= 1.2.5)
8
+ rake
9
+ mocha (0.9.12)
10
+ mysql (2.8.1)
11
+ rake (0.9.2)
12
+ rcov (0.9.9)
13
+ sqlite3 (1.3.3)
14
+ sqlite3-ruby (1.3.3)
15
+ sqlite3 (>= 1.3.3)
16
+
17
+ PLATFORMS
18
+ ruby
19
+
20
+ DEPENDENCIES
21
+ jeweler
22
+ mocha
23
+ mysql
24
+ rcov
25
+ sqlite3-ruby
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Michael Bleigh and Intridea Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,306 @@
1
+ = Tagtical
2
+
3
+ This plugin was originally based on acts_as_taggable_on by Michael Bleigh (http://mbleigh.com/). That plugin
4
+ was based on acts_as_taggable_on_steroids by Jonathan Viney.
5
+
6
+ While a lot concepts are the same (taggings + tags tables using polymorphism), this adaption introduces
7
+ the concept of "relevance" for a tag and allows for the creation of subclasses on Tag.
8
+
9
+ For instance, if you want to tag a photo with "Mood Tags". You would simply subclass Tag with
10
+ Tag::Mood and you could add functionality specific to that model. This involves moving the "context"
11
+ off of Tagging and moving it onto Tag as a "type" column. It acts not only as a "context", but also
12
+ as a designator for the STI class. Subsequently, you could also add a "relevance" for how applicable
13
+ that mood is.
14
+
15
+ Tagtical allows for an arbitrary number of Tag subclasses, each of which can be extended to the needs
16
+ of the application. However, a tag subclass is not required! If you have many custom tag types that you
17
+ create on the fly, you can wait until a later time to create a tag subclass.
18
+
19
+ Here are the main differences between tagtical and acts_as_taggable_on:
20
+
21
+ 1. Add "relevance" to the Tagging class so you can weight the tags to the object.
22
+ 2. Tagtical removes "context" off the taggings table and adds "type" onto the tags table.
23
+ 3. The traditional functionality of tags is preserved while laying the foundation for STI on the Tag class.
24
+ You can choose to extend the Tag class at a later time.
25
+ 4. Tag "name" now becomes tag "value". The difference is small, but significant. If you had
26
+ GeoTag's, for example, you wouldn't refer to its "name", you would refer to its "value". The value
27
+ could be a serialized field of long and lat if you wanted.
28
+ 5. Support a config/tagtical.yml to further configure the application. For example, since most people
29
+ usually have one User class for their application, there is no reason to do polymorphic on "tagger",
30
+ so I give the user the option to specify the class_name specifically for tagger.
31
+
32
+ Additions include:
33
+ 1. Scopes are created on Tag so you can do photo.tags.color and grab all the tags of type Tag::Color, for example.
34
+ 2. Scopes are also created on the Taggable model so you could do Model.with_colors("red", "blue") and it would return everything tagged with those colors.
35
+
36
+ == Installation
37
+
38
+ === Rails & Ruby Versions
39
+
40
+ Tagtical was developed on Rails 3.05 and Ruby 1.9.2
41
+
42
+ It can probably work with older versions, but would take a few tweaks.
43
+
44
+ ==== Plugin
45
+
46
+ Tagtical is available both as a gem and as a traditional plugin. For the
47
+ traditional plugin you can install like so:
48
+
49
+ script/plugin install git://github.com/Mixbook/tagtical.git
50
+
51
+ ==== Post Installation
52
+
53
+ 1. script/generate tagtical_migration
54
+ 2. rake db:migrate
55
+
56
+ === Rails 3.0
57
+ To use it, add it to your Gemfile:
58
+
59
+ gem 'tagtical'
60
+
61
+ ==== Post Installation
62
+
63
+ 1. rails generate tagtical:migration
64
+ 2. rake db:migrate
65
+
66
+ == Testing
67
+
68
+ Tagtical uses RSpec for its test coverage. Inside the plugin
69
+ directory, you can run the specs for RoR 3.0.0 with:
70
+
71
+ rake spec
72
+
73
+ Rails 2.3 is not supported, however I left the stub code from acts_as_taggable_on in there in case
74
+ someone wants to try to get it working:
75
+
76
+ rake rails2.3:spec
77
+
78
+ If you already have RSpec on your application, the specs will run while using:
79
+
80
+ rake spec:plugins
81
+
82
+
83
+ == Usage
84
+
85
+ class User < ActiveRecord::Base
86
+ # Alias for <tt>tagtical :tags</tt>:
87
+ acts_as_taggable :activities, :interests, :sports # top level, generic :tags is already included.
88
+ end
89
+ module Tag
90
+ class Activity < Tagtical::Tag
91
+ end
92
+ class Sport < Activity
93
+ def ball?
94
+ value=~/ball$/i
95
+ end
96
+ end
97
+ end
98
+
99
+ @user = User.new(:name => "Bobby")
100
+ @user.tag_list = "awesome, slick, hefty" # this should be familiar
101
+ @user.activity_list = "joking, clowning, boxing" # but you can do it for any context!
102
+ @user.activity_list # => ["joking","clowning","boxing"] as TagList
103
+ @user.save
104
+
105
+ @user.tags # => [<Tag value:"awesome">,<Tag value:"slick">,<Tag value:"hefty">]
106
+ @user.activities # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">,<Tag::Activity value:"boxing">]
107
+ @user.activities.first.athletic? # => false
108
+
109
+ @frankie = User.create(:name => "Frankie", :activity_list => "joking, flying, eating")
110
+ User.activity_counts # => [<Tag::Activity value="joking" count=2>,<Tag::Activity value="clowning" count=1>...]
111
+ @frankie.activity_counts
112
+
113
+ @user.sport_list = {"boxing" => 4.5 # Since Sport's parent is Activity, it will move "boxing" down
114
+ # from Activity to Sport and give it a relevance of 4.5.
115
+ @user.save
116
+
117
+ @user.activities # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">,<Tag::Sport value:"boxing">]
118
+ @user.activities(:only => :children) # => [<Tag::Sport value:"boxing">] - look at only the STI subclasses
119
+ @user.activities(:only => :current) # => [<Tag::Activity value:"joking">,<Tag::Activity value:"clowning">] - look at only the current STI class
120
+ @user.activities.first.athletic? # => false
121
+ @user.sports.all(&:ball?) # => true
122
+
123
+ --- Defining Subclasses
124
+
125
+ There is a lot of flexibility when it comes to naming subclasses. Lets say the type column had a value
126
+ of "color". You could define the subclass any of these ways:
127
+
128
+ module Tagtical
129
+ module Tag
130
+ class Color < Tagtical::Tag
131
+ end
132
+ end
133
+ end
134
+ module Tag
135
+ class Color < Tagtical::Tag
136
+ end
137
+ end
138
+ class Color < Tagtical::Tag
139
+ end
140
+ module Tagtical
141
+ module Tag
142
+ class ColorTag < Tagtical::Tag
143
+ end
144
+ end
145
+ end
146
+ module Tag
147
+ class ColorTag < Tagtical::Tag
148
+ end
149
+ end
150
+ class ColorTag < Tagtical::Tag
151
+ end
152
+
153
+ This allows for a wide range of folder structures. You could nest files (with corresponding models) like this:
154
+ app/models/tagtical/tag/color.rb
155
+ app/models/tag/color.rb
156
+ app/models/color.rb
157
+ app/models/tagtical/tag/color_tag.rb
158
+ app/models/tag/color_tag.rb
159
+ app/models/color_tag.rb
160
+
161
+ === Finding Tagged Objects
162
+
163
+ Tagtical utilizes named_scopes to create an association for tags.
164
+ This way you can mix and match to filter down your results, and it also improves
165
+ compatibility with the will_paginate gem:
166
+
167
+ class User < ActiveRecord::Base
168
+ acts_as_taggable
169
+ named_scope :by_join_date, :order => "created_at DESC"
170
+ end
171
+
172
+ User.tagged_with("awesome").by_date
173
+ User.tagged_with("awesome").by_date.paginate(:page => params[:page], :per_page => 20)
174
+
175
+ # Find a user with matching all tags, not just one
176
+ User.tagged_with(["awesome", "cool"], :match_all => :true)
177
+
178
+ # Find a user with any of the tags:
179
+ User.tagged_with(["awesome", "cool"], :any => true)
180
+
181
+ === Relationships
182
+
183
+ You can find objects of the same type based on similar tags on certain contexts.
184
+ Also, objects will be returned in descending order based on the total number of
185
+ matched tags.
186
+
187
+ @bobby = User.find_by_name("Bobby")
188
+ @bobby.activity_list # => ["jogging", "diving"]
189
+
190
+ @frankie = User.find_by_name("Frankie")
191
+ @frankie.activity_list # => ["hacking"]
192
+
193
+ @tom = User.find_by_name("Tom")
194
+ @tom.activity_list # => ["hacking", "jogging", "diving"]
195
+
196
+ @tom.find_related_activities # => [<User name="Bobby">,<User name="Frankie">]
197
+ @bobby.find_related_activities # => [<User name="Tom">]
198
+ @frankie.find_related_activities # => [<User name="Tom">]
199
+
200
+ === Dynamic Tag Contexts
201
+
202
+ In addition to the generated tag contexts in the definition, it is also possible
203
+ to allow for dynamic tag contexts (this could be user generated tag contexts!)
204
+
205
+ @user = User.new(:name => "Bobby")
206
+ @user.set_tag_list_on(:customs, "same, as, tag, list")
207
+ @user.tag_list_on(:customs) # => ["same","as","tag","list"]
208
+ @user.save
209
+ @user.tags_on(:customs) # => [<Tag value='same'>,...]
210
+ @user.tag_counts_on(:customs)
211
+ User.tagged_with("same", :on => :customs) # => [@user]
212
+
213
+ In the future, lets say you wanted to add additional methods for these specific tags. You would simply
214
+ just define the subclass and the code will automatically instantiate it as that class. Just do:
215
+
216
+ class CustomTag < Tagtical::Tag
217
+ def some_custom_function
218
+ end
219
+ end
220
+
221
+ Now moving forward, these classes will be instantiated with this model. Wow cool!
222
+
223
+
224
+ === Tag Ownership
225
+
226
+ Tags can have owners:
227
+
228
+ class User < ActiveRecord::Base
229
+ acts_as_tagger
230
+ end
231
+
232
+ class Photo < ActiveRecord::Base
233
+ acts_as_taggable :locations
234
+ end
235
+
236
+ @some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations)
237
+ @some_user.owned_taggings
238
+ @some_user.owned_tags
239
+ @some_photo.locations_from(@some_user)
240
+
241
+ === Tag cloud calculations
242
+
243
+ To construct tag clouds, the frequency of each tag needs to be calculated.
244
+ Because we specified +tagtical+ on the <tt>User</tt> class, we can
245
+ get a calculation of all the tag counts by using <tt>User.tag_counts_on(:customs)</tt>. But what if we wanted a tag count for
246
+ an single user's posts? To achieve this we call tag_counts on the association:
247
+
248
+ User.find(:first).posts.tag_counts_on(:tags)
249
+
250
+ A helper is included to assist with generating tag clouds.
251
+
252
+ Here is an example that generates a tag cloud.
253
+
254
+ Helper:
255
+
256
+ module PostsHelper
257
+ include Tagtical::TagsHelper
258
+ end
259
+
260
+ Controller:
261
+
262
+ class PostController < ApplicationController
263
+ def tag_cloud
264
+ @tags = Post.tag_counts_on(:tags)
265
+ end
266
+ end
267
+
268
+ View:
269
+
270
+ <% tag_cloud(@tags, %w(css1 css2 css3 css4)) do |tag, css_class| %>
271
+ <%= link_to tag.value, { :action => :tag, :id => tag.value }, :class => css_class %>
272
+ <% end %>
273
+
274
+ CSS:
275
+
276
+ .css1 { font-size: 1.0em; }
277
+ .css2 { font-size: 1.2em; }
278
+ .css3 { font-size: 1.4em; }
279
+ .css4 { font-size: 1.6em; }
280
+
281
+ == Contributors
282
+
283
+ * Aryk Grosz - Original Author
284
+ * Jonathan Nevelson - Taggable Scopes
285
+
286
+ == Contributors (from the acts_as_taggable_on project)
287
+
288
+ * TomEric (i76) - Maintainer
289
+ * Michael Bleigh - Original Author of acts_as_taggable_on
290
+ * Szymon Nowak - Rails 3.0 compatibility
291
+ * Jelle Vandebeeck - Rails 3.0 compatibility
292
+ * Brendan Lim - Related Objects
293
+ * Pradeep Elankumaran - Taggers
294
+ * Sinclair Bain - Patch King
295
+
296
+ === Patch Contributors (from the acts_as_taggable_on project)
297
+
298
+ * tristanzdunn - Related objects of other classes
299
+ * azabaj - Fixed migrate down
300
+ * Peter Cooper - named_scope fix
301
+ * slainer68 - STI fix
302
+ * harrylove - migration instructions and fix-ups
303
+ * lawrencepit - cached tag work
304
+ * sobrinho - fixed tag_cloud helper
305
+
306
+ Copyright (c) 2011 Aryk Grosz (http://mixbook.com/) released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,59 @@
1
+ begin
2
+ # RSpec 1.3.0
3
+ require 'spec/rake/spectask'
4
+
5
+ desc 'Default: run specs'
6
+ task :default => :spec
7
+ Spec::Rake::SpecTask.new do |t|
8
+ t.spec_files = FileList["spec/**/*_spec.rb"]
9
+ end
10
+
11
+ Spec::Rake::SpecTask.new('rcov') do |t|
12
+ t.spec_files = FileList["spec/**/*_spec.rb"]
13
+ t.rcov = true
14
+ t.rcov_opts = ['--exclude', 'spec']
15
+ end
16
+
17
+ rescue LoadError
18
+ # RSpec 2.0
19
+ require 'rspec/core/rake_task'
20
+
21
+ desc 'Default: run specs'
22
+ task :default => :spec
23
+ RSpec::Core::RakeTask.new do |t|
24
+ t.pattern = "spec/**/*_spec.rb"
25
+ end
26
+
27
+ RSpec::Core::RakeTask.new('rcov') do |t|
28
+ t.pattern = "spec/**/*_spec.rb"
29
+ t.rcov = true
30
+ t.rcov_opts = ['--exclude', 'spec']
31
+ end
32
+
33
+ rescue LoadError
34
+ puts "RSpec not available. Install it with: gem install rspec"
35
+ end
36
+
37
+ namespace 'rails2.3' do
38
+ task :spec do
39
+ gemfile = File.join(File.dirname(__FILE__), 'lib', 'tagtical', 'compatibility', 'Gemfile')
40
+ ENV['BUNDLE_GEMFILE'] = gemfile
41
+ Rake::Task['spec'].invoke
42
+ end
43
+ end
44
+
45
+ begin
46
+ require 'jeweler'
47
+ Jeweler::Tasks.new do |gemspec|
48
+ gemspec.name = "tagtical"
49
+ gemspec.summary = "Tagtical is a tagging plugin for Rails that provides weighting, contexts, and inheritance for tags."
50
+ gemspec.description = "Tagtical allows you do create subclasses for Tag and add additional functionality in an STI fashion. For example. You could do Tag::Color.find_by_name('blue').to_rgb. It also supports storing weights or relevance on the taggings."
51
+ gemspec.email = "aryk@mixbook.com"
52
+ gemspec.homepage = "https://github.com/Mixbook/tagtical"
53
+ gemspec.authors = ["Aryk Grosz"]
54
+ gemspec.files = FileList["[A-Z]*", "{generators,lib,spec,rails}/**/*"] - FileList["**/*.log"]
55
+ end
56
+ Jeweler::GemcutterTasks.new
57
+ rescue LoadError
58
+ puts "Jeweler not available. Install it with: gem install jeweler"
59
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.6
@@ -0,0 +1,7 @@
1
+ class TagticalMigrationGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => "tagtical_migration"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ class TagticalMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :tags do |t|
4
+ t.column :value, :string
5
+ t.column :type, :string
6
+ end
7
+ add_index :tags, [:type, :value], :unique => true
8
+ add_index :tags, :value
9
+
10
+ create_table :taggings do |t|
11
+ t.column :relevance, :float
12
+ t.column :tag_id, :integer
13
+ t.column :taggable_id, :integer
14
+ t.column :tagger_id, :integer
15
+ t.column :tagger_type, :string if Tagtical.config.polymorphic_tagger?
16
+
17
+ # You should make sure that the column created is
18
+ # long enough to store the required class names.
19
+ t.column :taggable_type, :string
20
+
21
+ t.column :created_at, :datetime
22
+ end
23
+
24
+ add_index :taggings, :tag_id
25
+ add_index :taggings, [:taggable_id, :taggable_type]
26
+ add_index :taggings, Tagtical.config.polymorphic_tagger? ? [:tagger_id, :tagger_type] : [:tagger_id]
27
+ end
28
+
29
+ def self.down
30
+ drop_table :taggings
31
+ drop_table :tags
32
+ end
33
+
34
+ end
@@ -0,0 +1,32 @@
1
+ require 'rails/generators/migration'
2
+
3
+ module Tagtical
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.to_s unless orm.class.eql?(String)) )
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/tagtical_migration'
28
+ end
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,35 @@
1
+ class TagticalMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :tags do |t|
4
+ t.string :value
5
+ t.string :type
6
+ end
7
+ add_index :tags, [:type, :value], :unique => true
8
+ add_index :tags, :value
9
+
10
+ create_table :taggings do |t|
11
+ t.float :relevance
12
+ t.references :tag
13
+
14
+ # You should make sure that the column created is
15
+ # long enough to store the required class names.
16
+ t.references :taggable, :polymorphic => true
17
+ if Tagtical.config.polymorphic_tagger?
18
+ t.references :tagger, :polymorphic => true
19
+ else
20
+ t.integer :tagger_id
21
+ end
22
+
23
+ t.datetime :created_at
24
+ end
25
+
26
+ add_index :taggings, :tag_id
27
+ add_index :taggings, [:taggable_id, :taggable_type]
28
+ add_index :taggings, Tagtical.config.polymorphic_tagger? ? [:tagger_id, :tagger_type] : [:tagger_id]
29
+ end
30
+
31
+ def self.down
32
+ drop_table :taggings
33
+ drop_table :tags
34
+ end
35
+ end
@@ -0,0 +1,69 @@
1
+ module Tagtical
2
+ module Tagger
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ ##
9
+ # Make a model a tagger. This allows an instance of a model to claim ownership
10
+ # of tags.
11
+ #
12
+ # Example:
13
+ # class User < ActiveRecord::Base
14
+ # acts_as_tagger
15
+ # end
16
+ def acts_as_tagger(opts={})
17
+ class_eval do
18
+ opts.update(:as => :tagger) if Tagtical.config.polymorphic_tagger?
19
+ has_many :owned_taggings, opts.merge(:dependent => :destroy,
20
+ :include => :tag, :class_name => "Tagtical::Tagging")
21
+ has_many :owned_tags, :through => :owned_taggings, :source => :tag, :uniq => true, :class_name => "Tagtical::Tag"
22
+ end
23
+
24
+ include Tagtical::Tagger::InstanceMethods
25
+ extend Tagtical::Tagger::SingletonMethods
26
+ end
27
+
28
+ def is_tagger?
29
+ false
30
+ end
31
+ end
32
+
33
+ module InstanceMethods
34
+ ##
35
+ # Tag a taggable model with tags that are owned by the tagger.
36
+ #
37
+ # @param taggable The object that will be tagged
38
+ # @param [Hash] options An hash with options. Available options are:
39
+ # * <tt>:with</tt> - The tags that you want to
40
+ # * <tt>:overwrite</tt> - true if you want to replace the tags with this new list.
41
+ # * <tt>:on</tt> - The context on which you want to tag
42
+ #
43
+ # Example:
44
+ # @user.tag(@photo, :with => "paris, normandy", :on => :locations)
45
+ def tag(taggable, opts={})
46
+ opts.reverse_merge!(:force => true)
47
+
48
+ return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
49
+
50
+ raise "You need to specify a tag context using :on" unless opts.has_key?(:on)
51
+ raise "You need to specify some tags using :with" unless opts.has_key?(:with)
52
+ raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless (opts[:force] || taggable.tag_types.include?(opts[:on]))
53
+
54
+ taggable.set_owner_tag_list_on(self, opts[:on].to_s, opts[:with])
55
+ taggable.save
56
+ end
57
+
58
+ def is_tagger?
59
+ self.class.is_tagger?
60
+ end
61
+ end
62
+
63
+ module SingletonMethods
64
+ def is_tagger?
65
+ true
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,8 @@
1
+ source :gemcutter
2
+
3
+ # Rails 2.3
4
+ gem 'rails', '2.3.5'
5
+ gem 'rspec', '1.3.0', :require => 'spec'
6
+ gem 'sqlite3-ruby', '1.2.5', :require => 'sqlite3'
7
+ gem 'mysql'
8
+ gem 'pg'
@@ -0,0 +1,21 @@
1
+ module Tagtical
2
+ module ActiveRecord
3
+ module Backports
4
+ def self.included(base)
5
+ base.class_eval do
6
+ named_scope :where, lambda { |conditions| { :conditions => conditions } }
7
+ named_scope :joins, lambda { |joins| { :joins => joins } }
8
+ named_scope :group, lambda { |group| { :group => group } }
9
+ named_scope :order, lambda { |order| { :order => order } }
10
+ named_scope :select, lambda { |select| { :select => select } }
11
+ named_scope :limit, lambda { |limit| { :limit => limit } }
12
+ named_scope :readonly, lambda { |readonly| { :readonly => readonly } }
13
+
14
+ def self.to_sql
15
+ construct_finder_sql({})
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end