genki-dm-is-taggable 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +1 -0
- data/LICENSE +20 -0
- data/Manifest.txt +25 -0
- data/README.textile +185 -0
- data/Rakefile +52 -0
- data/TODO +2 -0
- data/lib/dm-is-taggable.rb +31 -0
- data/lib/dm-is-taggable/aggregate_patch.rb +47 -0
- data/lib/dm-is-taggable/is/shared.rb +111 -0
- data/lib/dm-is-taggable/is/tag.rb +189 -0
- data/lib/dm-is-taggable/is/taggable.rb +114 -0
- data/lib/dm-is-taggable/is/tagger.rb +80 -0
- data/lib/dm-is-taggable/is/tagging.rb +30 -0
- data/lib/dm-is-taggable/is/version.rb +8 -0
- data/lib/dm-is-taggable/tag.rb +4 -0
- data/lib/dm-is-taggable/tag_list.rb +113 -0
- data/lib/dm-is-taggable/tagging.rb +4 -0
- data/spec/data/article.rb +5 -0
- data/spec/data/bot.rb +6 -0
- data/spec/data/picture.rb +5 -0
- data/spec/data/user.rb +6 -0
- data/spec/integration/taggable_spec.rb +289 -0
- data/spec/integration/tagger_similarity_spec.rb +40 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +36 -0
- metadata +110 -0
data/History.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Aaron Qian, Maxime Guilbot at Ekohe
|
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/Manifest.txt
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
History.txt
|
2
|
+
LICENSE
|
3
|
+
Manifest.txt
|
4
|
+
README.textile
|
5
|
+
Rakefile
|
6
|
+
TODO
|
7
|
+
lib/dm-is-taggable.rb
|
8
|
+
lib/dm-is-taggable/aggregate_patch.rb
|
9
|
+
lib/dm-is-taggable/tag.rb
|
10
|
+
lib/dm-is-taggable/tagging.rb
|
11
|
+
lib/dm-is-taggable/tag_list.rb
|
12
|
+
lib/dm-is-taggable/is/taggable.rb
|
13
|
+
lib/dm-is-taggable/is/version.rb
|
14
|
+
lib/dm-is-taggable/is/shared.rb
|
15
|
+
lib/dm-is-taggable/is/tag.rb
|
16
|
+
lib/dm-is-taggable/is/tagging.rb
|
17
|
+
lib/dm-is-taggable/is/tagger.rb
|
18
|
+
spec/integration/taggable_spec.rb
|
19
|
+
spec/integration/tagger_similarity_spec.rb
|
20
|
+
spec/data/article.rb
|
21
|
+
spec/data/picture.rb
|
22
|
+
spec/data/bot.rb
|
23
|
+
spec/data/user.rb
|
24
|
+
spec/spec.opts
|
25
|
+
spec/spec_helper.rb
|
data/README.textile
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
h1. dm-is-taggable
|
2
|
+
|
3
|
+
dm-is-taggable is a tagging system built for datamapper. It has supports for multiple tagger types and taggable types.
|
4
|
+
Each tagger can tag different taggable objects.
|
5
|
+
|
6
|
+
|
7
|
+
h2. Installation
|
8
|
+
|
9
|
+
h3. Download the plugin
|
10
|
+
|
11
|
+
In your console:
|
12
|
+
<pre><code>
|
13
|
+
git clone git://github.com/aq1018/dm-is-taggable.git
|
14
|
+
</code></pre>
|
15
|
+
|
16
|
+
h3. Install the gem
|
17
|
+
|
18
|
+
In your console:
|
19
|
+
<pre><code>
|
20
|
+
cd dm-is-taggable
|
21
|
+
sudo rake install
|
22
|
+
</code></pre>
|
23
|
+
|
24
|
+
h3. Include it Merb
|
25
|
+
|
26
|
+
In merb init.rb:
|
27
|
+
|
28
|
+
<pre><code>
|
29
|
+
dependency "dm-is-taggable"
|
30
|
+
</code></pre>
|
31
|
+
|
32
|
+
|
33
|
+
h2. Using dm-is-taggable in your code
|
34
|
+
|
35
|
+
h3. Define taggers
|
36
|
+
|
37
|
+
<pre><code>
|
38
|
+
class User
|
39
|
+
include DataMapper::Resource
|
40
|
+
property :id, Serial
|
41
|
+
property :name, String
|
42
|
+
is :tagger, :on => ["Article", "Picture"]
|
43
|
+
end
|
44
|
+
|
45
|
+
class Bot
|
46
|
+
include DataMapper::Resource
|
47
|
+
property :id, Serial
|
48
|
+
property :name, String
|
49
|
+
is :tagger, :on => ["Article", "Picture"]
|
50
|
+
end
|
51
|
+
</code></pre>
|
52
|
+
|
53
|
+
h3. Define taggables
|
54
|
+
|
55
|
+
<pre><code>
|
56
|
+
class Article
|
57
|
+
include DataMapper::Resource
|
58
|
+
property :id, Serial
|
59
|
+
is :taggable, :by => ["User", "Bot"]
|
60
|
+
end
|
61
|
+
|
62
|
+
class Picture
|
63
|
+
include DataMapper::Resource
|
64
|
+
property :id, Serial
|
65
|
+
is :taggable, :by => ["User", "Bot"]
|
66
|
+
end
|
67
|
+
</code></pre>
|
68
|
+
|
69
|
+
h3. Create tags
|
70
|
+
|
71
|
+
<pre><code>
|
72
|
+
|
73
|
+
@picture = Picture.first
|
74
|
+
@scott = User.first
|
75
|
+
|
76
|
+
# You can tag like this
|
77
|
+
@picture.tag(:with => "shanghai, bar, beer", :by => @scott)
|
78
|
+
|
79
|
+
# or like this
|
80
|
+
# Note: this doesn't remove the previous tags
|
81
|
+
Tag.as(@scott) do
|
82
|
+
@picture.tag(:with => "cool, tag1, tag2")
|
83
|
+
end
|
84
|
+
|
85
|
+
# or like this
|
86
|
+
# Note, this removes all previous tags
|
87
|
+
Tag.as(@scott) do
|
88
|
+
@picture.taglist("cool, tag1, tag2")
|
89
|
+
end
|
90
|
+
</code></pre>
|
91
|
+
|
92
|
+
h3. Retrieve objects with tags
|
93
|
+
|
94
|
+
<pre><code>
|
95
|
+
|
96
|
+
# find pictures tagged with tag1 and tag2
|
97
|
+
Picture.find(:with => "tag1, tag2")
|
98
|
+
|
99
|
+
# find pictures tagged with tag1 or tag2
|
100
|
+
Picture.find(:with => "tag1, tag2", :match => :any)
|
101
|
+
|
102
|
+
# find pictures tagged with tag1 or tag2, tagged by @user1
|
103
|
+
Picture.find(:with => "tag1, tag2", :match => :any, :by => @user1)
|
104
|
+
|
105
|
+
# find pictures tagged with tag1 or tag2, tagged by all users
|
106
|
+
Picture.find(:with => "tag1, tag2", :match => :any, :by => User)
|
107
|
+
|
108
|
+
# or you can do scoped way
|
109
|
+
|
110
|
+
# find pictures tagged with tag1 or tag2, tagged by all users
|
111
|
+
Tag.as(User) do
|
112
|
+
Picture.find(:with => "tag1, tag2", :match => :any)
|
113
|
+
end
|
114
|
+
|
115
|
+
# find pictures tagged with tag1 or tag2, tagged by @user1
|
116
|
+
Tag.as(@user1) do
|
117
|
+
Picture.find(:with => "tag1, tag2", :match => :any)
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
# You can tag like this
|
122
|
+
@picture.tag(:with => "shanghai, bar, beer", :by => @scott)
|
123
|
+
|
124
|
+
# or like this
|
125
|
+
# Note: this doesn't remove the previous tags
|
126
|
+
Tag.as(@scott) do
|
127
|
+
@picture.tag(:with => "cool, tag1, tag2")
|
128
|
+
end
|
129
|
+
|
130
|
+
# or like this
|
131
|
+
# Note, this removes all previous tags
|
132
|
+
Tag.as(@scott) do
|
133
|
+
@picture.taglist("cool, tag1, tag2")
|
134
|
+
end
|
135
|
+
</code></pre>
|
136
|
+
|
137
|
+
h3. Retrieve tags with objects
|
138
|
+
|
139
|
+
<pre><code>
|
140
|
+
|
141
|
+
@picture1 = Picture.first
|
142
|
+
|
143
|
+
# get all tags associated with @picture2 as a string
|
144
|
+
@picture1.taglist
|
145
|
+
|
146
|
+
# or as tag objects
|
147
|
+
@picture1.tags
|
148
|
+
|
149
|
+
# tags tagged by users
|
150
|
+
@picture1.tags_by_users
|
151
|
+
|
152
|
+
# find tags by all users
|
153
|
+
Tag.by(User)
|
154
|
+
|
155
|
+
# find tags by a user
|
156
|
+
Tag.by(@user)
|
157
|
+
|
158
|
+
# find tags on all pictures
|
159
|
+
Tag.on(Picture)
|
160
|
+
|
161
|
+
# find tags on a picture
|
162
|
+
Tag.on(@picture1)
|
163
|
+
|
164
|
+
# find tags by a user, on all pictures
|
165
|
+
Tag.by(@user).on(Pictures)
|
166
|
+
|
167
|
+
# find tags by all users on a picture
|
168
|
+
Tag.by(User).on(@picture)
|
169
|
+
|
170
|
+
# find tags by a user on a picture
|
171
|
+
Tag.by(@user).on(@picture)
|
172
|
+
|
173
|
+
</code></pre>
|
174
|
+
|
175
|
+
h3. Counting tags
|
176
|
+
|
177
|
+
<pre><code>
|
178
|
+
|
179
|
+
# Count how many articles are tagged by @user1
|
180
|
+
Tag.tagged_count(:by => @user1, :on => Article)
|
181
|
+
|
182
|
+
# Count how many articles are tagged by @user1 with "tag1"
|
183
|
+
Tag.tagged_count(:by => @user1, :on => Article, :with => "tag1")
|
184
|
+
|
185
|
+
</code></pre>
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
ROOT = Pathname(__FILE__).dirname.expand_path
|
7
|
+
require ROOT + 'lib/dm-is-taggable/is/version'
|
8
|
+
|
9
|
+
AUTHOR = "Aaron Qian, Maxime Guilbot"
|
10
|
+
EMAIL = "team [a] ekohe [d] com"
|
11
|
+
GEM_NAME = "dm-is-taggable"
|
12
|
+
GEM_VERSION = DataMapper::Is::Taggable::VERSION
|
13
|
+
GEM_DEPENDENCY_VERSION = DataMapper::Is::Taggable::DEPENDENCY_VERSION
|
14
|
+
GEM_DEPENDENCIES = [["dm-core", GEM_DEPENDENCY_VERSION], ["dm-aggregates", GEM_DEPENDENCY_VERSION]]
|
15
|
+
GEM_CLEAN = ["log", "pkg"]
|
16
|
+
GEM_EXTRAS = { :has_rdoc => false, :extra_rdoc_files => %w[ README.textile LICENSE TODO ] }
|
17
|
+
|
18
|
+
PROJECT_NAME = "dm-is-taggable"
|
19
|
+
PROJECT_URL = "http://github.com/aq1018/dm-is-taggable"
|
20
|
+
PROJECT_DESCRIPTION = PROJECT_SUMMARY = "Tagging implementation for DataMapper"
|
21
|
+
|
22
|
+
require 'tasks/hoe'
|
23
|
+
|
24
|
+
task :default => [ :spec ]
|
25
|
+
|
26
|
+
WIN32 = (RUBY_PLATFORM =~ /win32|mingw|cygwin/) rescue nil
|
27
|
+
SUDO = WIN32 ? '' : ('sudo' unless ENV['SUDOLESS'])
|
28
|
+
|
29
|
+
desc "Install #{GEM_NAME} #{GEM_VERSION}"
|
30
|
+
task :install => [ :package ] do
|
31
|
+
sh "#{SUDO} gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources", :verbose => false
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "Uninstall #{GEM_NAME} #{GEM_VERSION} (default ruby)"
|
35
|
+
task :uninstall => [ :clobber ] do
|
36
|
+
sh "#{SUDO} gem uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x", :verbose => false
|
37
|
+
end
|
38
|
+
|
39
|
+
desc 'Run specifications'
|
40
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
41
|
+
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
42
|
+
t.spec_files = Pathname.glob(Pathname.new(__FILE__).dirname + 'spec/**/*_spec.rb')
|
43
|
+
|
44
|
+
begin
|
45
|
+
t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
|
46
|
+
t.rcov_opts << '--exclude' << 'spec'
|
47
|
+
t.rcov_opts << '--text-summary'
|
48
|
+
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
|
49
|
+
rescue Exception
|
50
|
+
# rcov not installed
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Needed to import datamapper and other gems
|
2
|
+
require 'rubygems'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
# Add all external dependencies for the plugin here
|
6
|
+
gem 'dm-core', '=0.9.6'
|
7
|
+
gem 'dm-aggregates', '>=0.9.6'
|
8
|
+
require 'dm-core'
|
9
|
+
require 'dm-aggregates'
|
10
|
+
|
11
|
+
# Require plugin-files
|
12
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-is-taggable' / 'tag_list.rb'
|
13
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-is-taggable' / 'aggregate_patch.rb'
|
14
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-is-taggable' / 'is' / 'shared.rb'
|
15
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-is-taggable' / 'is' / 'taggable.rb'
|
16
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-is-taggable' / 'is' / 'tag.rb'
|
17
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-is-taggable' / 'is' / 'tagging.rb'
|
18
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-is-taggable' / 'is' / 'tagger.rb'
|
19
|
+
|
20
|
+
# Include the plugin in Resource
|
21
|
+
module DataMapper
|
22
|
+
module Resource
|
23
|
+
module ClassMethods
|
24
|
+
include DataMapper::Is::Taggable
|
25
|
+
end # module ClassMethods
|
26
|
+
end # module Resource
|
27
|
+
end # module DataMapper
|
28
|
+
|
29
|
+
# Require the Tag and Tagging Resources
|
30
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-is-taggable' / 'tag.rb'
|
31
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-is-taggable' / 'tagging.rb'
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# we need this unless someone adds the "count(distinct id)" support in dm-aggegrates
|
2
|
+
module DataMapper
|
3
|
+
module Adapters
|
4
|
+
class DataObjectsAdapter
|
5
|
+
module SQL
|
6
|
+
private
|
7
|
+
def fields_statement(query)
|
8
|
+
qualify = query.links.any?
|
9
|
+
query.fields.map { |p| property_to_column_name(query.repository, p, qualify, query.unique?) } * ', '
|
10
|
+
end
|
11
|
+
|
12
|
+
def property_to_column_name(repository, property, qualify, unique=false)
|
13
|
+
case property
|
14
|
+
when Query::Operator
|
15
|
+
aggregate_field_statement(repository, property.operator, property.target, qualify, unique)
|
16
|
+
when Property
|
17
|
+
original_property_to_column_name(repository, property, qualify)
|
18
|
+
when Query::Path
|
19
|
+
original_property_to_column_name(repository, property, qualify)
|
20
|
+
else
|
21
|
+
raise ArgumentError, "+property+ must be a DataMapper::Query::Operator or a DataMapper::Property, but was a #{property.class} (#{property.inspect})"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def aggregate_field_statement(repository, aggregate_function, property, qualify, unique=false)
|
26
|
+
column_name = if aggregate_function == :count && property == :all
|
27
|
+
'*'
|
28
|
+
else
|
29
|
+
unique ? "distinct #{property_to_column_name(repository, property, qualify)}" :
|
30
|
+
property_to_column_name(repository, property, qualify)
|
31
|
+
end
|
32
|
+
|
33
|
+
function_name = case aggregate_function
|
34
|
+
when :count then 'COUNT'
|
35
|
+
when :min then 'MIN'
|
36
|
+
when :max then 'MAX'
|
37
|
+
when :avg then 'AVG'
|
38
|
+
when :sum then 'SUM'
|
39
|
+
else raise "Invalid aggregate function: #{aggregate_function.inspect}"
|
40
|
+
end
|
41
|
+
|
42
|
+
"#{function_name}(#{column_name})"
|
43
|
+
end
|
44
|
+
end # module SQL
|
45
|
+
end # class DataObjectsAdapter
|
46
|
+
end # module Adapters
|
47
|
+
end # module DataMapper
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Is
|
3
|
+
module Taggable
|
4
|
+
module SharedClassMethods
|
5
|
+
|
6
|
+
def extract_class_object(obj)
|
7
|
+
return [] if obj.nil?
|
8
|
+
if obj.is_a?(Class)
|
9
|
+
[obj, nil]
|
10
|
+
else
|
11
|
+
[obj.class, obj]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def by(tagger_class_or_obj)
|
16
|
+
tagger_class, tagger_obj = extract_tagger_class_object(tagger_class_or_obj)
|
17
|
+
query = {:unique => true}
|
18
|
+
query.merge!(Tag.taggings.tagger_type => tagger_class.to_s) if tagger_class
|
19
|
+
query.merge!(Tag.taggings.tagger_id => tagger_obj.id) if tagger_obj
|
20
|
+
all(query)
|
21
|
+
end
|
22
|
+
|
23
|
+
def on(taggable_class_or_obj)
|
24
|
+
taggable_class, taggable_obj = extract_taggable_class_object(taggable_class_or_obj)
|
25
|
+
query = {:unique => true}
|
26
|
+
query.merge!(Tag.taggings.taggable_type => taggable_class.to_s) if taggable_class
|
27
|
+
query.merge!(Tag.taggings.taggable_id => taggable_obj.id) if taggable_obj
|
28
|
+
all(query)
|
29
|
+
end
|
30
|
+
|
31
|
+
def extract_options(options)
|
32
|
+
if options.is_a?(Array) || options.is_a?(String)
|
33
|
+
options = {:with => TagList.from(options)}
|
34
|
+
end
|
35
|
+
options = options.dup
|
36
|
+
|
37
|
+
tagger = options.delete(:by)
|
38
|
+
tagger ||= Tag.tagger
|
39
|
+
taggable = options.delete(:on)
|
40
|
+
tags = TagList.from(options.delete(:with))
|
41
|
+
return [tagger, taggable, tags, options]
|
42
|
+
end
|
43
|
+
|
44
|
+
def extract_tagger_class_object(obj)
|
45
|
+
obj = Extlib::Inflection.constantize(obj.to_s.camel_case.singular) if obj && (obj.is_a?(String) || obj.is_a?(Symbol))
|
46
|
+
return [] if obj.nil?
|
47
|
+
|
48
|
+
if obj.tagger?
|
49
|
+
extract_class_object(obj)
|
50
|
+
else
|
51
|
+
raise Exception.new("#{obj} is not a Tagger class or object!")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def extract_taggable_class_object(obj)
|
56
|
+
obj = Extlib::Inflection.constantize(obj.to_s.camel_case.singular) if obj && (obj.is_a?(String) || obj.is_a?(Symbol))
|
57
|
+
return [] if obj.nil?
|
58
|
+
|
59
|
+
if obj.taggable?
|
60
|
+
extract_class_object(obj)
|
61
|
+
else
|
62
|
+
raise Exception.new("#{obj} is not a Taggable class or object!")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_taggings(tagger, taggable, tags=nil)
|
67
|
+
return if tags.nil? || tags.empty?
|
68
|
+
|
69
|
+
unless tagger.nil? || (tagger.respond_to?(:tagger?) && tagger.tagger? && !tagger.is_a?(Class))
|
70
|
+
raise Exception.new("#{tagger} is not a tagger datamapper resource object!")
|
71
|
+
end
|
72
|
+
|
73
|
+
unless taggable.respond_to?(:taggable?) && taggable.taggable? && !taggable.is_a?(Class)
|
74
|
+
raise Exception.new("#{taggable} is not a taggable datamapper resource object!")
|
75
|
+
end
|
76
|
+
|
77
|
+
raise Exception.new("#{tagger.class} cannot Tag #{taggable.class}") unless tagger.nil? || tagger.can_tag_on?(taggable)
|
78
|
+
|
79
|
+
TagList.from(tags).each do |tag|
|
80
|
+
tag_obj = Tag.fetch(tag)
|
81
|
+
|
82
|
+
# build tagging (tagger could be anonymous)
|
83
|
+
tagging_hash = {:tag_id => tag_obj.id, :taggable_id => taggable.id, :taggable_type => taggable.class.to_s}
|
84
|
+
tagging_hash.merge!(:tagger_id => tagger.id, :tagger_type => tagger.class.to_s) if tagger
|
85
|
+
|
86
|
+
# see if we already have this tagging
|
87
|
+
tagging_obj = Tagging.first(tagging_hash)
|
88
|
+
|
89
|
+
# if we have the tagging already, just skip this one...
|
90
|
+
next if tagging_obj
|
91
|
+
|
92
|
+
# tagging is not in db, let's create one
|
93
|
+
Tagging.create(tagging_hash)
|
94
|
+
end # end of each
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
module SharedInstanceMethods
|
99
|
+
def extract_tagger_class_object(tagger_class_or_object)
|
100
|
+
self.class.extract_tagger_class_object(tagger_class_or_object)
|
101
|
+
end
|
102
|
+
def extract_taggable_class_object(taggable_class_or_object)
|
103
|
+
self.class.extract_taggable_class_object(taggable_class_or_object)
|
104
|
+
end
|
105
|
+
def extract_options(options)
|
106
|
+
self.class.extract_options(options)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|