tagtical 1.0.6

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