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.
- 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
|