tagalong 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.rvmrc +172 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +106 -0
- data/Rakefile +2 -0
- data/lib/generators/tagalong/migration/migration_generator.rb +39 -0
- data/lib/generators/tagalong/migration/templates/active_record/migration.rb +34 -0
- data/lib/tagalong/exceptions.rb +4 -0
- data/lib/tagalong/tag_manager.rb +63 -0
- data/lib/tagalong/tagalong_tag.rb +13 -0
- data/lib/tagalong/tagalong_tagging.rb +9 -0
- data/lib/tagalong/taggable.rb +27 -0
- data/lib/tagalong/tagger.rb +44 -0
- data/lib/tagalong/version.rb +3 -0
- data/lib/tagalong.rb +16 -0
- data/spec/database.yml +4 -0
- data/spec/integration/tag_manager_integration_spec.rb +30 -0
- data/spec/lib/tagalong/tag_manager_spec.rb +196 -0
- data/spec/lib/tagalong/taggable_spec.rb +44 -0
- data/spec/lib/tagalong/tagger_spec.rb +114 -0
- data/spec/models.rb +7 -0
- data/spec/schema.rb +37 -0
- data/spec/spec_helper.rb +53 -0
- data/tagalong.gemspec +21 -0
- metadata +111 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# Check to see if brew is installed
|
4
|
+
function check_for_brew() {
|
5
|
+
printf "Checking for brew - "
|
6
|
+
which brew > /dev/null
|
7
|
+
if [ $? -ne 0 ]; then
|
8
|
+
echo "NOT found"
|
9
|
+
echo "brew, could not be found!"
|
10
|
+
echo "Please install it using the instructions found on their site (http://mxcl.github.com/homebrew/).\n"
|
11
|
+
return 1
|
12
|
+
else
|
13
|
+
echo "found"
|
14
|
+
fi
|
15
|
+
return 0
|
16
|
+
}
|
17
|
+
|
18
|
+
# Given a brew package as the first argument check if that package has already been installed.
|
19
|
+
function check_for_brew_package() {
|
20
|
+
printf "Checking for $1 - "
|
21
|
+
brew list | grep $1 > /dev/null
|
22
|
+
if [ $? -ne 0 ]; then
|
23
|
+
echo "NOT found"
|
24
|
+
echo "$1, could not be found."
|
25
|
+
echo "Please run brew update && brew install $1 to install it.\n"
|
26
|
+
return 1
|
27
|
+
else
|
28
|
+
echo "found"
|
29
|
+
fi
|
30
|
+
return 0
|
31
|
+
}
|
32
|
+
|
33
|
+
# This method takes two arguments. The first argument is the name of the gem to check for and install. The second argument is
|
34
|
+
# the version of the gem to check for and install. Both arguments are required. If the gem is already installed an exit status
|
35
|
+
# of 0 is set, if an install is required an exit status of 1 is set.
|
36
|
+
function install_gem_if_not_found() {
|
37
|
+
printf "Checking for gem $1 ($2) - "
|
38
|
+
gem list | grep "$1.*$2.*" > /dev/null
|
39
|
+
if [ $? -ne 0 ]; then
|
40
|
+
echo "NOT found, installing for you"
|
41
|
+
gem install $1 -v $2
|
42
|
+
return 1
|
43
|
+
else
|
44
|
+
echo "found"
|
45
|
+
fi
|
46
|
+
return 0
|
47
|
+
}
|
48
|
+
|
49
|
+
function load_rvm_as_a_function() {
|
50
|
+
# Here I load rvm as a bash function rather than a binary. I do
|
51
|
+
# this because the binary version is limited and won't properly
|
52
|
+
# create or switch to gemsets. This was added specifically to
|
53
|
+
# handle the case where .rvmrc is loaded via rvm rvmrc load in
|
54
|
+
# a Capistrano deploy. However, it doesn't seem to hurt the
|
55
|
+
# normal dev workflow so I am just leaving it here. For more info
|
56
|
+
# on binary/function mode of RVM refer to the following url:
|
57
|
+
# http://beginrescueend.com/workflow/scripting/
|
58
|
+
if [[ -s "$HOME/.rvm/scripts/rvm" ]] ; then
|
59
|
+
# First try to load from a user install
|
60
|
+
source "$HOME/.rvm/scripts/rvm"
|
61
|
+
elif [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then
|
62
|
+
# Then try to load from a root install
|
63
|
+
source "/usr/local/rvm/scripts/rvm"
|
64
|
+
else
|
65
|
+
printf "ERROR: An RVM installation was not found.\n"
|
66
|
+
return 1
|
67
|
+
fi
|
68
|
+
return 0
|
69
|
+
}
|
70
|
+
|
71
|
+
# Attempt to load the specified RVM environment where the RVM environment
|
72
|
+
# is the first argument passed to this method when called in the form of
|
73
|
+
# "ruby-1.9.3-p0@rpp_web_app"
|
74
|
+
function load_rvm_environment() {
|
75
|
+
#
|
76
|
+
# First we attempt to load the desired environment directly from the environment
|
77
|
+
# file. This is very fast and efficient compared to running through the entire
|
78
|
+
# CLI and selector. If you want feedback on which environment was used then
|
79
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
80
|
+
#
|
81
|
+
|
82
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" && -s "${rvm_path:-$HOME/.rvm}/environments/$1" ]]
|
83
|
+
then
|
84
|
+
source "${rvm_path:-$HOME/.rvm}/environments/$1"
|
85
|
+
|
86
|
+
if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
|
87
|
+
then
|
88
|
+
source "${rvm_path:-$HOME/.rvm}/hooks/after_use"
|
89
|
+
fi
|
90
|
+
return 0
|
91
|
+
else
|
92
|
+
#If the environment file has not yet been created, use the RVM CLI to select.
|
93
|
+
if ! rvm --create "$1"
|
94
|
+
then
|
95
|
+
echo "Failed to create RVM environment '$1'."
|
96
|
+
return 1
|
97
|
+
fi
|
98
|
+
return 0
|
99
|
+
fi
|
100
|
+
|
101
|
+
#
|
102
|
+
# If you use an RVM gemset file to install a list of gems (*.gems), you can have
|
103
|
+
# it be automatically loaded. Uncomment the following and adjust the filename if
|
104
|
+
# necessary.
|
105
|
+
#
|
106
|
+
# filename=".gems"
|
107
|
+
# if [[ -s "$filename" ]]
|
108
|
+
# then
|
109
|
+
# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
|
110
|
+
# fi
|
111
|
+
}
|
112
|
+
|
113
|
+
# if [ `uname -s` == "Darwin" ]; then # we assume we are on a Mac OS X dev box
|
114
|
+
# missing_brew=0
|
115
|
+
# missing_brew_packages=0
|
116
|
+
|
117
|
+
# check_for_brew; if [ $? -ne 0 ]; then missing_brew=1; fi
|
118
|
+
# check_for_brew_package "readline"; if [ $? -ne 0 ]; then missing_brew_packages=1; fi
|
119
|
+
# check_for_brew_package "imagemagick"; if [ $? -ne 0 ]; then missing_brew_packages=1; fi
|
120
|
+
# check_for_brew_package "qt"; if [ $? -ne 0 ]; then missing_brew_packages=1; fi
|
121
|
+
# check_for_brew_package "wkhtmltopdf"; if [ $? -ne 0 ]; then missing_brew_packages=1; fi
|
122
|
+
# check_for_brew_package "mysql"; if [ $? -ne 0 ]; then missing_brew_packages=1; fi
|
123
|
+
# check_for_brew_package "redis"; if [ $? -ne 0 ]; then missing_brew_packages=1; fi
|
124
|
+
|
125
|
+
# if [ $missing_brew_packages -ne 0 ] || [ $missing_brew -ne 0 ]; then
|
126
|
+
# echo "YOU ARE MISSING THE ABOVE PACKAGES! YOU ARE NOT IN THE CORRECT RVM RUBY VERSION GEMSET COMBO!"
|
127
|
+
# cur_rvm=$(rvm current)
|
128
|
+
# echo "The rvm you are currently using is: $cur_rvm\n"
|
129
|
+
|
130
|
+
# echo "Please install them using the provided instructions above."
|
131
|
+
# echo "Once completed cd out of this directory and back into this directory to have this project specific .rvmrc script run again."
|
132
|
+
# return 1;
|
133
|
+
# fi
|
134
|
+
# fi
|
135
|
+
|
136
|
+
load_rvm_as_a_function; if [ $? -ne 0 ]; then return 1; fi
|
137
|
+
#
|
138
|
+
# Note: In order for this script to work, specifically the creation
|
139
|
+
# of the gemset below with the rvm --create call, the user executing
|
140
|
+
# this needs to have the following in their ~/.rvmrc:
|
141
|
+
#
|
142
|
+
# export rvm_is_not_a_shell_function=0
|
143
|
+
#
|
144
|
+
# This is only really needed for when it is being run via Capistrano
|
145
|
+
# so it only needs to be in the deployment users ~/.rvmrc on
|
146
|
+
# boxes you will be deploying to. This with the function loading
|
147
|
+
# above is the magic that makes the rvm --create work in this script
|
148
|
+
# when it is loaded via Capistrano.
|
149
|
+
|
150
|
+
#
|
151
|
+
# Note: We also use the following options in our deployment users ~/.rvmrc
|
152
|
+
# files handle trusting, auto install of new ruby versions, auto creation
|
153
|
+
# of gemsets, etc. The example of the full ~/.rvmrc file is as follows:
|
154
|
+
#
|
155
|
+
# export rvm_path="/home/rpi/.rvm"
|
156
|
+
# export rvm_is_not_a_shell_function=0
|
157
|
+
# rvm_trust_rvmrcs_flag=1
|
158
|
+
# rvm_install_on_use_flag=1
|
159
|
+
# rvm_gemset_create_on_use_flag=1
|
160
|
+
#
|
161
|
+
|
162
|
+
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
163
|
+
# development environment upon cd'ing into the directory
|
164
|
+
|
165
|
+
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
|
166
|
+
environment_id="ruby-1.9.3-p125@tagalong"
|
167
|
+
|
168
|
+
load_rvm_environment $environment_id; if [ $? -ne 0 ]; then return 1; fi
|
169
|
+
|
170
|
+
# At this point we should have the proper RVM environment and gemset loaded
|
171
|
+
cur_rvm=$(rvm current)
|
172
|
+
echo "Entered RVM environment '$cur_rvm'"
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Andrew De Ponte
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# Tagalong
|
2
|
+
|
3
|
+
Tagalong is a Rails plugin that is intended to be a clean, efficient, and simple. I have tried very hard to have the API make sense in terms of OOP as I have seen many other tagging libraries that I don't think do a great job of this.
|
4
|
+
|
5
|
+
The other key differentiation between Tagalong and many of the other tagging libraries out there is the relational database structure behind the scenes. This allows us to differentiate this tagging plugin in the following ways:
|
6
|
+
|
7
|
+
* clean object oriented API
|
8
|
+
* does NOT require saving of the model being tagged
|
9
|
+
* keeps history of tags Taggers have used
|
10
|
+
* allows defining multiple Taggers and Taggables
|
11
|
+
* tracks number of times tags are used
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
gem 'tagalong'
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install tagalong
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
### Migration Setup
|
30
|
+
|
31
|
+
In order to use `tagalong` you have to generate the proper migrations so that the tags can be stored in the database. You can do this with the following command:
|
32
|
+
|
33
|
+
rails generate tagalong:migration
|
34
|
+
|
35
|
+
The above will generate the migration and place it appropriately in the db/migrate/ project path so that the next time you `rake db:migrate` it will make the changes to the database.
|
36
|
+
|
37
|
+
### Declaring Taggers and Taggables
|
38
|
+
|
39
|
+
In order to use `tagalong` you need to first declare at least one Tagger and at least one Taggable. You do this as follows:
|
40
|
+
|
41
|
+
class Contact < ActiveRecord::Base
|
42
|
+
tagalong_taggable
|
43
|
+
end
|
44
|
+
|
45
|
+
class User < ActiveRecord::Base
|
46
|
+
tagalong_tagger
|
47
|
+
end
|
48
|
+
|
49
|
+
### Tag things
|
50
|
+
|
51
|
+
To tag things you must use the Tagger object and hand it a persisted Taggable object with the given tag that you want to apply as follows:
|
52
|
+
|
53
|
+
@user.tag(@contact, "sometag")
|
54
|
+
|
55
|
+
### Untag things
|
56
|
+
|
57
|
+
To untag things you must use the Tagger object and hand it a persisted Taggable object with the given tag that you want to untag as follows:
|
58
|
+
|
59
|
+
@user.untag(@contact, "sometag")
|
60
|
+
|
61
|
+
### List tags
|
62
|
+
|
63
|
+
You can get the list of tags for either a Tagger or a Taggable.
|
64
|
+
|
65
|
+
When you get the tags from a Tagger you are getting a list of all tags that tagger has ever used. This can be done as follows:
|
66
|
+
|
67
|
+
@user.tags
|
68
|
+
# => ['some_tag', 'another_tag', 'woot_tag']
|
69
|
+
|
70
|
+
When you get the tags from a Taggable you are getting a list of all the tags that taggable currently has applied. This can be done as follows:
|
71
|
+
|
72
|
+
@contact.tags
|
73
|
+
# => ['some_tag', 'woot_tag']
|
74
|
+
|
75
|
+
Tags are returned ordered by how often the tags are used.
|
76
|
+
|
77
|
+
### List tags with usage info
|
78
|
+
|
79
|
+
Passing a taggable object to the tags method on the Tagger will return a list of hash objects containing the tag (`tag`), a boolean representing if the tag is applied to the passed taggable (`used`), and the number of applications of that tag by the Tagger (`number_of_references`).
|
80
|
+
|
81
|
+
@user.tags(@contact)
|
82
|
+
# => [
|
83
|
+
{ tag: 'some_tag', :used => true, :number_of_references => 23 },
|
84
|
+
{ tag: 'another_tag', :used => false, :number_of_references => 42 }
|
85
|
+
]
|
86
|
+
|
87
|
+
### List taggables that have a tag
|
88
|
+
|
89
|
+
You can aquire an array of taggable objects that have a given tag using the `taggables_with` method on the Tagger object as follows:
|
90
|
+
|
91
|
+
@user.taggables_with('some_tag')
|
92
|
+
# => [Taggable Object, Taggable Object] (in this case Taggable Objects would be Contacts)
|
93
|
+
|
94
|
+
## Credits
|
95
|
+
|
96
|
+
I just wanted to thank all of the other open source Rails tagging plugins out there. Especially, acts-as-taggable-on, I learned a lot from you all. Thanks!
|
97
|
+
|
98
|
+
## Contributing
|
99
|
+
|
100
|
+
If you are interested in contributing code please follow the process below and please include tests. Also, please fill out issues if you have discovered a bug or simply want to request a feature on our GitHub issues page.
|
101
|
+
|
102
|
+
1. Fork it
|
103
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
104
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
105
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
106
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
module Tagalong
|
5
|
+
class MigrationGenerator < Rails::Generators::Base
|
6
|
+
include Rails::Generators::Migration
|
7
|
+
|
8
|
+
desc "Generates migration for TagalongTag and TagalongTagging models"
|
9
|
+
|
10
|
+
def self.orm
|
11
|
+
Rails::Generators.options[:rails][:orm]
|
12
|
+
end
|
13
|
+
|
14
|
+
#source_root File.expand_path('../templates', __FILE__)
|
15
|
+
def self.source_root
|
16
|
+
File.join(File.dirname(__FILE__), 'templates', (orm.to_s unless orm.class.eql?(String)))
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.orm_has_migration?
|
20
|
+
[:active_record].include? orm
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.next_migration_number(dirname)
|
24
|
+
if ActiveRecord::Base.timestamped_migrations
|
25
|
+
migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
|
26
|
+
migration_number += 1
|
27
|
+
migration_number.to_s
|
28
|
+
else
|
29
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_migration_file
|
34
|
+
if self.class.orm_has_migration?
|
35
|
+
migration_template 'migration.rb', 'db/migrate/tagalong_migration'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class TagalongMigration < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :tagalong_tags do |t|
|
4
|
+
t.string :name
|
5
|
+
t.integer :number_of_references
|
6
|
+
t.integer :tagger_id
|
7
|
+
t.string :tagger_type
|
8
|
+
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
|
12
|
+
add_index :tagalong_tags, :tagger_id
|
13
|
+
add_index :tagalong_tags, :tagger_type
|
14
|
+
add_index :tagalong_tags, [:tagger_id, :tagger_type]
|
15
|
+
|
16
|
+
create_table :tagalong_taggings do |t|
|
17
|
+
t.integer :tagalong_tag_id
|
18
|
+
t.integer :taggable_id
|
19
|
+
t.string :taggable_type
|
20
|
+
|
21
|
+
t.timestamps
|
22
|
+
end
|
23
|
+
|
24
|
+
add_index :tagalong_taggings, :tagalong_tag_id
|
25
|
+
add_index :tagalong_taggings, :taggable_id
|
26
|
+
add_index :tagalong_taggings, :taggable_type
|
27
|
+
add_index :tagalong_taggings, [:taggable_id, :taggable_type]
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.down
|
31
|
+
drop_table :tagalong_tags
|
32
|
+
drop_table :tagalong_taggings
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Tagalong
|
2
|
+
class TagManager
|
3
|
+
def initialize(taggable, tagger)
|
4
|
+
@taggable = taggable
|
5
|
+
@tagger = tagger
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_tag(name)
|
9
|
+
tagger_tag = tagger_used_tag?(name)
|
10
|
+
if (tagger_tag != nil)
|
11
|
+
if !taggable_has_tag?(name)
|
12
|
+
associate_tag_with_taggable(tagger_tag, @taggable)
|
13
|
+
end
|
14
|
+
else
|
15
|
+
tagger_tag = create_tag_for_tagger(name, @tagger)
|
16
|
+
associate_tag_with_taggable(tagger_tag, @taggable)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove_tag(name)
|
21
|
+
tagger_tag = tagger_used_tag?(name)
|
22
|
+
if tagger_tag && taggable_has_tag?(name)
|
23
|
+
disassociate_tag_from_taggable(tagger_tag, @taggable)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def tagger_used_tag?(name)
|
28
|
+
@tagger.tagalong_tags.find_by_name(name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def taggable_has_tag?(name)
|
32
|
+
@taggable.tagalong_tags.find_by_name(name)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def create_tag_for_tagger(name, tagger)
|
38
|
+
return TagalongTag.create!(:tagger_id => tagger.id, :tagger_type => tagger.class.to_s, :name => name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def associate_tag_with_taggable(tag, taggable)
|
42
|
+
TagalongTagging.create!(:taggable_id => taggable.id, :taggable_type => taggable.class.to_s, :tagalong_tag_id => tag.id)
|
43
|
+
increment_tag_number_of_references(tag)
|
44
|
+
end
|
45
|
+
|
46
|
+
def disassociate_tag_from_taggable(tag, taggable)
|
47
|
+
taggable_tagging = TagalongTagging.find_by_tagalong_tag_id_and_taggable_id(tag.id, taggable.id)
|
48
|
+
TagalongTagging.destroy(taggable_tagging.id)
|
49
|
+
taggable.tagalong_tags(true)
|
50
|
+
decrement_tag_number_of_references(tag)
|
51
|
+
end
|
52
|
+
|
53
|
+
def increment_tag_number_of_references(tag)
|
54
|
+
tag.number_of_references = (tag.number_of_references || 0) + 1
|
55
|
+
tag.save!
|
56
|
+
end
|
57
|
+
|
58
|
+
def decrement_tag_number_of_references(tag)
|
59
|
+
tag.number_of_references = tag.number_of_references ? tag.number_of_references - 1 : 0
|
60
|
+
tag.save!
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Tagalong
|
2
|
+
class TagalongTag < ::ActiveRecord::Base
|
3
|
+
has_many :tagalong_taggings, :dependent => :destroy, :class_name => 'Tagalong::TagalongTagging'
|
4
|
+
belongs_to :tagger, :polymorphic => true
|
5
|
+
|
6
|
+
validates_presence_of :name
|
7
|
+
validates_uniqueness_of :name, :scope => [ :tagger_id, :tagger_type ]
|
8
|
+
|
9
|
+
def taggables
|
10
|
+
tagalong_taggings.map { |tagging| tagging.taggable }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Tagalong
|
2
|
+
class TagalongTagging < ::ActiveRecord::Base
|
3
|
+
belongs_to :tagalong_tag, :class_name => 'Tagalong::TagalongTag'
|
4
|
+
belongs_to :taggable, :polymorphic => true
|
5
|
+
|
6
|
+
validates_presence_of :tagalong_tag_id
|
7
|
+
validates_presence_of :tagalong_tag_id, :scope => [ :taggable_type, :taggable_id ]
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Tagalong
|
2
|
+
module Taggable
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def tagalong_taggable
|
9
|
+
class_eval do
|
10
|
+
has_many :tagalong_taggings, :class_name => 'Tagalong::TagalongTagging', :as => :taggable
|
11
|
+
has_many :tagalong_tags, :class_name => 'Tagalong::TagalongTag', :through => :tagalong_taggings
|
12
|
+
include Tagalong::Taggable::InstanceMethods
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
def has_tag?(name)
|
19
|
+
return self.tagalong_tags.map { |r| r.name }.include?(name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def tags
|
23
|
+
return self.tagalong_tags.order("number_of_references DESC").map { |r| r.name }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Tagalong
|
2
|
+
module Tagger
|
3
|
+
def self.included(base)
|
4
|
+
base.extend Tagalong::Tagger::ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def tagalong_tagger
|
9
|
+
class_eval do
|
10
|
+
has_many :tagalong_tags, :class_name => 'Tagalong::TagalongTag', :as => :tagger
|
11
|
+
include Tagalong::Tagger::InstanceMethods
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module InstanceMethods
|
17
|
+
def tag(taggable_obj, tag_name)
|
18
|
+
raise Tagalong::TaggableNotPersisted, "Taggable must be persisted to tag it." if !taggable_obj.persisted?
|
19
|
+
tag_manager = Tagalong::TagManager.new(taggable_obj, self)
|
20
|
+
tag_manager.add_tag(tag_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def untag(taggable_obj, tag_name)
|
24
|
+
raise Tagalong::TaggableNotPersisted, "Taggable must be persisted to untag it." if !taggable_obj.persisted?
|
25
|
+
tag_manager = Tagalong::TagManager.new(taggable_obj, self)
|
26
|
+
tag_manager.remove_tag(tag_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def tags(taggable = nil)
|
30
|
+
if taggable == nil
|
31
|
+
return self.tagalong_tags.order("number_of_references DESC").map { |r| r.name }
|
32
|
+
else
|
33
|
+
return self.tagalong_tags.joins("LEFT OUTER JOIN tagalong_taggings ON tagalong_taggings.tagalong_tag_id = tagalong_tags.id").select("tagalong_tags.id, tagalong_tags.name, tagalong_tags.number_of_references, tagalong_taggings.id as used").order("number_of_references DESC").map { |r| { :tag => r.name, :used => !r.used.nil?, :number_of_references => r.number_of_references } }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def taggables_with(name)
|
38
|
+
self.tagalong_tags.where(:name => name).each do |t|
|
39
|
+
return t.taggables
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/tagalong.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "active_record"
|
2
|
+
|
3
|
+
require "tagalong/version"
|
4
|
+
require "tagalong/exceptions"
|
5
|
+
require "tagalong/tagalong_tag"
|
6
|
+
require "tagalong/tagalong_tagging"
|
7
|
+
require "tagalong/tag_manager"
|
8
|
+
require "tagalong/taggable"
|
9
|
+
require "tagalong/tagger"
|
10
|
+
|
11
|
+
if defined?(ActiveRecord::Base)
|
12
|
+
class ActiveRecord::Base
|
13
|
+
include Tagalong::Taggable
|
14
|
+
include Tagalong::Tagger
|
15
|
+
end
|
16
|
+
end
|
data/spec/database.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "TagManager Integration" do
|
4
|
+
before(:each) do
|
5
|
+
@contact = Contact.create!(:name => "Bob Bobster")
|
6
|
+
@user = User.create!(:name => "My User")
|
7
|
+
@contact_tag_manager = Tagalong::TagManager.new(@contact, @user)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#add_tag" do
|
11
|
+
it "add new tag to contact" do
|
12
|
+
@contact_tag_manager.add_tag("fool")
|
13
|
+
@contact.tagalong_tags.map { |r| r.name }.should include("fool")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "add an existing tag to contact" do
|
17
|
+
@contact_tag_manager.add_tag("bar")
|
18
|
+
@contact_tag_manager.add_tag("bar")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#remove_tag" do
|
23
|
+
it "remove tag from contact" do
|
24
|
+
cm_contact_tag = @user.tagalong_tags.create!(:name => "crazy")
|
25
|
+
@contact.tagalong_taggings.create!(:tagalong_tag_id => cm_contact_tag.id)
|
26
|
+
@contact_tag_manager.remove_tag("crazy")
|
27
|
+
@contact.tagalong_tags.map { |r| r.name }.should_not include("crazy")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# class TagalongTag; end
|
4
|
+
# class TagalongTagging; end
|
5
|
+
|
6
|
+
describe Tagalong::TagManager do
|
7
|
+
before(:each) do
|
8
|
+
@taggable = stub('taggable', :id => 58, :class => "Contact", :reload => nil)
|
9
|
+
@tagger = stub('tagger', :id => 23, :class => "User")
|
10
|
+
@tag_manager = Tagalong::TagManager.new(@taggable, @tagger)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#add_tag" do
|
14
|
+
context "tagger does NOT already have the tag" do
|
15
|
+
before(:each) do
|
16
|
+
@tag_manager.stub(:tagger_used_tag?).and_return(nil)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "creates a tag for the tagger" do
|
20
|
+
@tag_manager.should_receive(:create_tag_for_tagger).with("foo_tag", @tagger)
|
21
|
+
@tag_manager.stub(:associate_tag_with_taggable)
|
22
|
+
@tag_manager.add_tag("foo_tag")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "associates the created tagger tag with the taggable" do
|
26
|
+
tag = stub('tag', :id => 98)
|
27
|
+
@tag_manager.stub(:create_tag_for_tagger).and_return(tag)
|
28
|
+
@tag_manager.should_receive(:associate_tag_with_taggable).with(tag, @taggable)
|
29
|
+
@tag_manager.add_tag("foo_tag")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "tagger already has the tag" do
|
34
|
+
before(:each) do
|
35
|
+
@tag = stub('tag', :id => 108)
|
36
|
+
@tag_manager.stub(:tagger_used_tag?).and_return(@tag)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "does NOT create a new tag for the tagger" do
|
40
|
+
@tag_manager.stub(:taggable_has_tag?).and_return(@tag)
|
41
|
+
@tag_manager.should_not_receive(:create_tag_for_tagger)
|
42
|
+
@tag_manager.add_tag("foo_tag")
|
43
|
+
end
|
44
|
+
|
45
|
+
context "tag is already associated to the taggable" do
|
46
|
+
before(:each) do
|
47
|
+
@tag_manager.stub(:taggable_has_tag?).and_return(@tag)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "does NOT associated the matching tag record with taggable" do
|
51
|
+
@tag_manager.should_not_receive(:associate_tag_with_taggable)
|
52
|
+
@tag_manager.add_tag("foo_tag")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "tag is NOT associated to the taggable" do
|
57
|
+
before(:each) do
|
58
|
+
@tag_manager.stub(:taggable_has_tag?).and_return(nil)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "associates the tagger matched tag record with the taggable" do
|
62
|
+
@tag_manager.should_receive(:associate_tag_with_taggable).with(@tag, @taggable)
|
63
|
+
@tag_manager.add_tag("foo_tag")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#remove_tag" do
|
70
|
+
it "disassociates the tag from the taggable if the tag belongs to tagger" do
|
71
|
+
tag = stub('tag')
|
72
|
+
@tag_manager.stub(:tagger_used_tag?).and_return(tag)
|
73
|
+
@tag_manager.stub(:taggable_has_tag?).and_return(tag)
|
74
|
+
@tag_manager.should_receive(:disassociate_tag_from_taggable).with(tag, @taggable)
|
75
|
+
@tag_manager.remove_tag("foo_tag")
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should NOT dissassociate the tag from the taggable if it does NOT belong to the tagger" do
|
79
|
+
@tag_manager.stub(:tagger_used_tag?).and_return(stub('tag'))
|
80
|
+
@tag_manager.stub(:taggable_has_tag?).and_return(nil)
|
81
|
+
@tag_manager.should_not_receive(:disassociate_tag_from_taggable)
|
82
|
+
@tag_manager.remove_tag("foo_tag")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#tagger_used_tag?" do
|
87
|
+
it "returns the matching TagalongTag if the tagger has the tag" do
|
88
|
+
tag = stub('tag')
|
89
|
+
@tagger.stub_chain(:tagalong_tags, :find_by_name).and_return(tag)
|
90
|
+
@tag_manager.tagger_used_tag?("foo_tag").should == tag
|
91
|
+
end
|
92
|
+
|
93
|
+
it "returns nil if the tagger does NOT have the tag" do
|
94
|
+
@tagger.stub_chain(:tagalong_tags, :find_by_name).and_return(nil)
|
95
|
+
@tag_manager.tagger_used_tag?("foo_tag").should be_nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "#taggable_has_tag?" do
|
100
|
+
it "returns matching TagalongTag if the taggable is already associated with the tag" do
|
101
|
+
tag = stub('tag')
|
102
|
+
@taggable.stub_chain(:tagalong_tags, :find_by_name).and_return(tag)
|
103
|
+
@tag_manager.taggable_has_tag?("foo_tag").should == tag
|
104
|
+
end
|
105
|
+
|
106
|
+
it "returns nil, if the tag is NOT already associated with the taggable" do
|
107
|
+
@taggable.stub_chain(:tagalong_tags, :find_by_name).and_return(nil)
|
108
|
+
@tag_manager.taggable_has_tag?("foo_tag").should be_nil
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "#create_tag_for_tagger" do
|
113
|
+
it "creates a TagalongTag record with the given name, associated with the tagger object" do
|
114
|
+
Tagalong::TagalongTag.should_receive(:create!).with({ :tagger_id => @tagger.id, :tagger_type => "User", :name => "hoopty" })
|
115
|
+
@tag_manager.send(:create_tag_for_tagger, "hoopty", @tagger)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "#associate_tag_with_taggable" do
|
120
|
+
it "creates a TagalongTagging record associated with the given taggable and tag" do
|
121
|
+
tag = stub('tag', :id => 214)
|
122
|
+
@tag_manager.stub(:increment_tag_number_of_references)
|
123
|
+
Tagalong::TagalongTagging.should_receive(:create!).with({ :taggable_id => @taggable.id, :taggable_type => "Contact", :tagalong_tag_id => 214 })
|
124
|
+
@tag_manager.send(:associate_tag_with_taggable, tag, @taggable)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "increments the reference count for the tag" do
|
128
|
+
tag = stub('tag', :id => stub)
|
129
|
+
Tagalong::TagalongTagging.stub(:create!)
|
130
|
+
@tag_manager.should_receive(:increment_tag_number_of_references).with(tag)
|
131
|
+
@tag_manager.send(:associate_tag_with_taggable, tag, @taggable)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#disassociate_tag_from_taggable" do
|
136
|
+
it "destroys the TagalongTagging record that associates the given tag with the given taggable" do
|
137
|
+
tag = stub('tag', :id => 111)
|
138
|
+
tagging = mock('tagging', :id => 283)
|
139
|
+
@taggable.stub(:tagalong_tags)
|
140
|
+
Tagalong::TagalongTagging.stub(:find_by_tagalong_tag_id_and_taggable_id).with(111, @taggable.id).and_return(tagging)
|
141
|
+
@tag_manager.stub(:decrement_tag_number_of_references)
|
142
|
+
Tagalong::TagalongTagging.should_receive(:destroy).with(283)
|
143
|
+
@tag_manager.send(:disassociate_tag_from_taggable, tag, @taggable)
|
144
|
+
end
|
145
|
+
|
146
|
+
it "decrements the reference count of the tag" do
|
147
|
+
tag = stub('tag', :id => 111)
|
148
|
+
tagging = stub('tagging', :id => 283)
|
149
|
+
@taggable.stub(:tagalong_tags)
|
150
|
+
Tagalong::TagalongTagging.stub(:find_by_tagalong_tag_id_and_taggable_id).and_return(tagging)
|
151
|
+
Tagalong::TagalongTagging.stub(:destroy)
|
152
|
+
@tag_manager.should_receive(:decrement_tag_number_of_references).with(tag)
|
153
|
+
@tag_manager.send(:disassociate_tag_from_taggable, tag, @taggable)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe "#increment_tag_number_of_references" do
|
158
|
+
it "increments the number of references for the tag" do
|
159
|
+
tag = mock('tag', :number_of_references => 13, :save! => nil)
|
160
|
+
tag.should_receive(:number_of_references=).with(14)
|
161
|
+
@tag_manager.send(:increment_tag_number_of_references, tag)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "initializes number of referencs to 1 if number of references is nil" do
|
165
|
+
tag = mock('tag', :number_of_references => nil, :save! => nil)
|
166
|
+
tag.should_receive(:number_of_references=).with(1)
|
167
|
+
@tag_manager.send(:increment_tag_number_of_references, tag)
|
168
|
+
end
|
169
|
+
|
170
|
+
it "saves the changes to the database" do
|
171
|
+
tag = mock('tag', :number_of_references => 13, :number_of_references= => nil)
|
172
|
+
tag.should_receive(:save!)
|
173
|
+
@tag_manager.send(:increment_tag_number_of_references, tag)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
describe "#decrement_tag_number_of_references" do
|
178
|
+
it "decrements the number of references for the tag" do
|
179
|
+
tag = mock('tag', :number_of_references => 13, :save! => nil)
|
180
|
+
tag.should_receive(:number_of_references=).with(12)
|
181
|
+
@tag_manager.send(:decrement_tag_number_of_references, tag)
|
182
|
+
end
|
183
|
+
|
184
|
+
it "initializes number of references to zero if number of references is nil" do
|
185
|
+
tag = mock('tag', :number_of_references => nil, :save! => nil)
|
186
|
+
tag.should_receive(:number_of_references=).with(0)
|
187
|
+
@tag_manager.send(:decrement_tag_number_of_references, tag)
|
188
|
+
end
|
189
|
+
|
190
|
+
it "saves the changes to the database" do
|
191
|
+
tag = mock('tag', :number_of_references => 13, :number_of_references= => nil)
|
192
|
+
tag.should_receive(:save!)
|
193
|
+
@tag_manager.send(:decrement_tag_number_of_references, tag)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# The Contact class which is used in this as our taggable model
|
4
|
+
# is defined in the spec/models.rb file.
|
5
|
+
|
6
|
+
describe "Taggable" do
|
7
|
+
before(:each) do
|
8
|
+
@user = User.create!(:name => "My Owner")
|
9
|
+
@contact = Contact.create!(:name => "My Taggable")
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "Integration" do
|
13
|
+
describe "#has_tag?" do
|
14
|
+
it "returns true if the taggable has the given tag" do
|
15
|
+
tag = @user.tagalong_tags.create!(:name => "foo")
|
16
|
+
@contact.tagalong_taggings.create!(:tagalong_tag_id => tag.id)
|
17
|
+
@contact.has_tag?("foo").should be_true
|
18
|
+
end
|
19
|
+
|
20
|
+
it "returns false if the taggable does NOT have the given tag" do
|
21
|
+
@contact.has_tag?("bar").should be_false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#tags" do
|
26
|
+
it "returns list of tags currently applied to this taggable" do
|
27
|
+
@contact.tagalong_tags.create!(:name => "foo")
|
28
|
+
@contact.tagalong_tags.create!(:name => "bar")
|
29
|
+
@contact.tagalong_tags.create!(:name => "car")
|
30
|
+
@contact.tags.should == ["foo", "bar", "car"]
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns list of tags currently applied in descending order of references" do
|
34
|
+
@contact.tagalong_tags.create!(:name => "hoopty", :number_of_references => 5)
|
35
|
+
@contact.tagalong_tags.create!(:name => "doopty", :number_of_references => 99)
|
36
|
+
@contact.tagalong_tags.create!(:name => "toopty", :number_of_references => 4)
|
37
|
+
@contact.tags.should == ["doopty", "hoopty", "toopty"]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "Isolation" do
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# The User class which is used in this spec as our tagger
|
4
|
+
# model, it is defined in the spec/models.rb file.
|
5
|
+
|
6
|
+
describe "Tagger" do
|
7
|
+
before(:each) do
|
8
|
+
@user = User.create!(:name => "My Owner")
|
9
|
+
@contact = Contact.create!(:name => "My Taggable")
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "Integration" do
|
13
|
+
describe "#tag" do
|
14
|
+
it "tags the given taggable object with the given tag name" do
|
15
|
+
@user.tag(@contact, "foo")
|
16
|
+
@contact.tagalong_tags.map { |r| r.name }.should include("foo")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#untag" do
|
21
|
+
it "untags the tag from the given taggable object for the tagger" do
|
22
|
+
@contact.tagalong_tags.create!(:name => "bar", :tagger_id => @user.id, :tagger_type => @user.class.to_s)
|
23
|
+
@user.untag(@contact, "bar")
|
24
|
+
@contact.tagalong_tags.map { |r| r.name }.should_not include("bar")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#tags" do
|
29
|
+
context "without a taggable" do
|
30
|
+
it "returns list of tags the tagger has used" do
|
31
|
+
@user.tagalong_tags.create!(:name => "foo")
|
32
|
+
@user.tagalong_tags.create!(:name => "bar")
|
33
|
+
@user.tagalong_tags.create!(:name => "car")
|
34
|
+
@user.tags.should == ["foo", "bar", "car"]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns the list of tags in descending order of number of references" do
|
38
|
+
@user.tagalong_tags.create!(:name => "foo", :number_of_references => 20)
|
39
|
+
@user.tagalong_tags.create!(:name => "bar", :number_of_references => 100)
|
40
|
+
@user.tagalong_tags.create!(:name => "car", :number_of_references => 8)
|
41
|
+
@user.tags.should == ["bar", "foo", "car"]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "with a taggable" do
|
46
|
+
it "returns a hash of the tags with usage information about the passed taggable" do
|
47
|
+
tag = @user.tagalong_tags.create!(:name => "foo", :number_of_references => 1)
|
48
|
+
@contact.tagalong_taggings.create!(:tagalong_tag_id => tag.id)
|
49
|
+
@user.tagalong_tags.create!(:name => "bar", :number_of_references => 0)
|
50
|
+
tag = @user.tagalong_tags.create!(:name => "car", :number_of_references => 1)
|
51
|
+
@contact.tagalong_taggings.create!(:tagalong_tag_id => tag.id)
|
52
|
+
@user.tags(@contact).should == [
|
53
|
+
{ :tag => "foo", :used => true, :number_of_references => 1 },
|
54
|
+
{ :tag => "car", :used => true, :number_of_references => 1 },
|
55
|
+
{ :tag => "bar", :used => false, :number_of_references => 0 }
|
56
|
+
]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "#taggables_with" do
|
62
|
+
it "returns a collection of the taggables tagged with the given tag" do
|
63
|
+
@user.tag(@contact, "jackson")
|
64
|
+
@user.taggables_with("jackson").should == [@contact]
|
65
|
+
end
|
66
|
+
|
67
|
+
it "returns an empty array if it has no matching taggables" do
|
68
|
+
@user.taggables_with("jackson_five").should == []
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "Isolation" do
|
74
|
+
describe "#tag" do
|
75
|
+
it "creates an instance of the tag manager" do
|
76
|
+
tag_manager = stub('tag_manager', :add_tag => nil)
|
77
|
+
Tagalong::TagManager.should_receive(:new).with(@contact, @user).and_return(tag_manager)
|
78
|
+
@user.tag(@contact, "foo")
|
79
|
+
end
|
80
|
+
|
81
|
+
it "tells the tag manager instance to tag the given taggable for tagger (self)" do
|
82
|
+
tag_manager = mock('tag_manager')
|
83
|
+
Tagalong::TagManager.stub(:new).with(@contact, @user).and_return(tag_manager)
|
84
|
+
tag_manager.should_receive(:add_tag).with("foo")
|
85
|
+
@user.tag(@contact, "foo")
|
86
|
+
end
|
87
|
+
|
88
|
+
it "raises taggable not persisted exception if attempting to tag a non-persisted taggable" do
|
89
|
+
new_contact = Contact.new
|
90
|
+
lambda { @user.tag(new_contact, "bar") }.should raise_error(Tagalong::TaggableNotPersisted)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#untag" do
|
95
|
+
it "creates an instance of the tag manager" do
|
96
|
+
tag_manager = stub('tag_manager', :remove_tag => nil)
|
97
|
+
Tagalong::TagManager.should_receive(:new).with(@contact, @user).and_return(tag_manager)
|
98
|
+
@user.untag(@contact, "bar")
|
99
|
+
end
|
100
|
+
|
101
|
+
it "tells the tag manager instance to untag the given taggable for tagger (self)" do
|
102
|
+
tag_manager = mock('tag_manager')
|
103
|
+
Tagalong::TagManager.stub(:new).with(@contact, @user).and_return(tag_manager)
|
104
|
+
tag_manager.should_receive(:remove_tag).with("bar")
|
105
|
+
@user.untag(@contact, "bar")
|
106
|
+
end
|
107
|
+
|
108
|
+
it "raises taggable not persisted exception if attempting to untag a non-persisted taggable" do
|
109
|
+
new_contact = Contact.new
|
110
|
+
lambda { @user.untag(new_contact, "bar") }.should raise_error(Tagalong::TaggableNotPersisted)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/spec/models.rb
ADDED
data/spec/schema.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
ActiveRecord::Schema.define :version => 0 do
|
2
|
+
create_table "tagalong_taggings", :force => true do |t|
|
3
|
+
t.integer "tagalong_tag_id", :limit => 11
|
4
|
+
t.integer "taggable_id", :limit => 11
|
5
|
+
t.string "taggable_type"
|
6
|
+
t.datetime "created_at"
|
7
|
+
t.datetime "updated_at"
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index "tagalong_taggings", ["tagalong_tag_id"], :name => "index_tagalong_taggings_on_tagalong_tag_id"
|
11
|
+
add_index "tagalong_taggings", ["taggable_id", "taggable_type"], :name => "index_tagalong_taggings_on_taggable_id_and_taggable_type"
|
12
|
+
|
13
|
+
create_table "tagalong_tags", :force => true do |t|
|
14
|
+
t.integer "tagger_id", :limit => 11
|
15
|
+
t.string "tagger_type"
|
16
|
+
t.string "name"
|
17
|
+
t.integer "number_of_references", :limit => 11
|
18
|
+
t.datetime "created_at"
|
19
|
+
t.datetime "updated_at"
|
20
|
+
end
|
21
|
+
|
22
|
+
add_index "tagalong_tags", ["tagger_id", "tagger_type"], :name => "index_tagalong_tags_on_tagger_id_and_tagger_type"
|
23
|
+
|
24
|
+
create_table "contacts", :force => true do |t|
|
25
|
+
t.string "name"
|
26
|
+
t.string "phone"
|
27
|
+
t.datetime "created_at"
|
28
|
+
t.datetime "udpated_at"
|
29
|
+
end
|
30
|
+
|
31
|
+
create_table "users", :force => true do |t|
|
32
|
+
t.string "email"
|
33
|
+
t.string "name"
|
34
|
+
t.datetime "created_at"
|
35
|
+
t.datetime "updated_at"
|
36
|
+
end
|
37
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/setup'
|
5
|
+
|
6
|
+
require File.expand_path('../../lib/tagalong', __FILE__)
|
7
|
+
|
8
|
+
db_name = ENV['DB'] || 'sqlite3'
|
9
|
+
database_yml = File.expand_path('../database.yml', __FILE__)
|
10
|
+
|
11
|
+
if File.exists?(database_yml)
|
12
|
+
active_record_configuration = YAML.load_file(database_yml)
|
13
|
+
|
14
|
+
ActiveRecord::Base.configurations = active_record_configuration
|
15
|
+
config = ActiveRecord::Base.configurations[db_name]
|
16
|
+
|
17
|
+
begin
|
18
|
+
ActiveRecord::Base.establish_connection(db_name)
|
19
|
+
ActiveRecord::Base.connection
|
20
|
+
rescue
|
21
|
+
case db_name
|
22
|
+
when /mysql/
|
23
|
+
ActiveRecord::Base.establish_connection(config.merge('database' => nil))
|
24
|
+
ActiveRecord::Base.connection.create_database(config['database'], {:charset => 'utf8', :collation => 'utf8_unicode_ci'})
|
25
|
+
when 'postgresql'
|
26
|
+
ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
|
27
|
+
ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => 'utf8'))
|
28
|
+
end
|
29
|
+
|
30
|
+
ActiveRecord::Base.establish_connection(config)
|
31
|
+
end
|
32
|
+
|
33
|
+
ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
|
34
|
+
ActiveRecord::Base.default_timezone = :utc
|
35
|
+
|
36
|
+
ActiveRecord::Base.silence do
|
37
|
+
ActiveRecord::Migration.verbose = false
|
38
|
+
|
39
|
+
load(File.dirname(__FILE__) + '/schema.rb')
|
40
|
+
load(File.dirname(__FILE__) + '/models.rb')
|
41
|
+
end
|
42
|
+
else
|
43
|
+
raise "Please create #{database_yml} first to configure your database. Take a look at: #{database_yml}.sample"
|
44
|
+
end
|
45
|
+
|
46
|
+
def clean_database!
|
47
|
+
models = [Tagalong::TagalongTag, Tagalong::TagalongTagging]
|
48
|
+
models.each do |model|
|
49
|
+
ActiveRecord::Base.connection.execute "DELETE FROM #{model.table_name}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
clean_database!
|
data/tagalong.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/tagalong/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Andrew De Ponte"]
|
6
|
+
gem.email = ["cyphactor@gmail.com"]
|
7
|
+
gem.description = %q{A Rails tagging plugin that makes sense.}
|
8
|
+
gem.summary = %q{A Rails tagging plugin that makes sense.}
|
9
|
+
gem.homepage = "http://github.com/cyphactor/tagalong"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "tagalong"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Tagalong::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency "activerecord"
|
19
|
+
gem.add_development_dependency "sqlite3"
|
20
|
+
gem.add_development_dependency "rspec"
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tagalong
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andrew De Ponte
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: &70241519058460 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70241519058460
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: sqlite3
|
27
|
+
requirement: &70241519058000 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70241519058000
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
requirement: &70241519057520 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70241519057520
|
47
|
+
description: A Rails tagging plugin that makes sense.
|
48
|
+
email:
|
49
|
+
- cyphactor@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- .rvmrc
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- lib/generators/tagalong/migration/migration_generator.rb
|
61
|
+
- lib/generators/tagalong/migration/templates/active_record/migration.rb
|
62
|
+
- lib/tagalong.rb
|
63
|
+
- lib/tagalong/exceptions.rb
|
64
|
+
- lib/tagalong/tag_manager.rb
|
65
|
+
- lib/tagalong/tagalong_tag.rb
|
66
|
+
- lib/tagalong/tagalong_tagging.rb
|
67
|
+
- lib/tagalong/taggable.rb
|
68
|
+
- lib/tagalong/tagger.rb
|
69
|
+
- lib/tagalong/version.rb
|
70
|
+
- spec/database.yml
|
71
|
+
- spec/integration/tag_manager_integration_spec.rb
|
72
|
+
- spec/lib/tagalong/tag_manager_spec.rb
|
73
|
+
- spec/lib/tagalong/taggable_spec.rb
|
74
|
+
- spec/lib/tagalong/tagger_spec.rb
|
75
|
+
- spec/models.rb
|
76
|
+
- spec/schema.rb
|
77
|
+
- spec/spec_helper.rb
|
78
|
+
- tagalong.gemspec
|
79
|
+
homepage: http://github.com/cyphactor/tagalong
|
80
|
+
licenses: []
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 1.8.16
|
100
|
+
signing_key:
|
101
|
+
specification_version: 3
|
102
|
+
summary: A Rails tagging plugin that makes sense.
|
103
|
+
test_files:
|
104
|
+
- spec/database.yml
|
105
|
+
- spec/integration/tag_manager_integration_spec.rb
|
106
|
+
- spec/lib/tagalong/tag_manager_spec.rb
|
107
|
+
- spec/lib/tagalong/taggable_spec.rb
|
108
|
+
- spec/lib/tagalong/tagger_spec.rb
|
109
|
+
- spec/models.rb
|
110
|
+
- spec/schema.rb
|
111
|
+
- spec/spec_helper.rb
|