tagtical 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +25 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +25 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +306 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/generators/tagtical_migration/tagtical_migration_generator.rb +7 -0
- data/generators/tagtical_migration/templates/migration.rb +34 -0
- data/lib/generators/tagtical/migration/migration_generator.rb +32 -0
- data/lib/generators/tagtical/migration/templates/active_record/migration.rb +35 -0
- data/lib/tagtical/acts_as_tagger.rb +69 -0
- data/lib/tagtical/compatibility/Gemfile +8 -0
- data/lib/tagtical/compatibility/active_record_backports.rb +21 -0
- data/lib/tagtical/tag.rb +314 -0
- data/lib/tagtical/tag_list.rb +133 -0
- data/lib/tagtical/taggable/cache.rb +53 -0
- data/lib/tagtical/taggable/collection.rb +141 -0
- data/lib/tagtical/taggable/core.rb +317 -0
- data/lib/tagtical/taggable/ownership.rb +110 -0
- data/lib/tagtical/taggable/related.rb +60 -0
- data/lib/tagtical/taggable.rb +51 -0
- data/lib/tagtical/tagging.rb +42 -0
- data/lib/tagtical/tags_helper.rb +17 -0
- data/lib/tagtical.rb +47 -0
- data/rails/init.rb +1 -0
- data/spec/bm.rb +53 -0
- data/spec/database.yml +17 -0
- data/spec/database.yml.sample +17 -0
- data/spec/models.rb +60 -0
- data/spec/schema.rb +46 -0
- data/spec/spec_helper.rb +159 -0
- data/spec/tagtical/acts_as_tagger_spec.rb +94 -0
- data/spec/tagtical/tag_list_spec.rb +102 -0
- data/spec/tagtical/tag_spec.rb +301 -0
- data/spec/tagtical/taggable_spec.rb +460 -0
- data/spec/tagtical/tagger_spec.rb +76 -0
- data/spec/tagtical/tagging_spec.rb +52 -0
- data/spec/tagtical/tags_helper_spec.rb +28 -0
- data/spec/tagtical/tagtical_spec.rb +340 -0
- 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,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,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
|