acts_as_taggable_simple 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.rvmrc +1 -0
- data/README.rdoc +16 -16
- data/Rakefile +1 -14
- data/acts_as_taggable_simple.gemspec +3 -0
- data/lib/active_model/acts/taggable.rb +100 -0
- data/lib/active_record/active_record.rb +4 -0
- data/lib/{tag.rb → active_record/tag.rb} +4 -0
- data/lib/{tagging.rb → active_record/tagging.rb} +0 -0
- data/lib/acts_as_taggable_simple.rb +4 -3
- data/lib/acts_as_taggable_simple/version.rb +1 -1
- data/lib/generators/acts_as_taggable_simple/migration/templates/active_record/migration.rb +2 -0
- data/lib/mongoid/mongoid.rb +4 -0
- data/lib/mongoid/tag.rb +44 -0
- data/lib/mongoid/tagging.rb +11 -0
- data/spec/active_record/models.rb +18 -0
- data/spec/database.yml +1 -1
- data/spec/factories.rb +11 -0
- data/spec/lib/acts_as_taggable_simple/acts_as_taggable_simple_spec.rb +33 -0
- data/spec/lib/acts_as_taggable_simple/taggable_spec.rb +146 -0
- data/spec/mongoid.yml +4 -0
- data/spec/mongoid/models.rb +27 -0
- data/spec/schema.rb +11 -3
- data/spec/spec_helper.rb +47 -24
- metadata +60 -19
- data/lib/active_record/acts/taggable.rb +0 -67
- data/spec/acts_as_taggable_simple/acts_as_taggable_simple_spec.rb +0 -14
- data/spec/acts_as_taggable_simple/spec_helper.rb +0 -1
- data/spec/acts_as_taggable_simple/taggable_spec.rb +0 -90
- data/spec/debug.log +0 -1955
- data/spec/models.rb +0 -10
data/.gitignore
CHANGED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.2@acts_as_taggable_simple --create
|
data/README.rdoc
CHANGED
@@ -9,11 +9,6 @@ the following methods to a taggable model:
|
|
9
9
|
* tag_list - returns an Array of tag String objects
|
10
10
|
* tag_list= - takes either a String of tags or an array of tag String objects and sets the tags for the object
|
11
11
|
|
12
|
-
acts_as_taggable provides two new attributes for a model:
|
13
|
-
|
14
|
-
* tags - the ActsAsTaggable::Tag objects associated with the taggable item
|
15
|
-
* taggings - the ActsAsTaggable::Tagging join objects associated with the taggable item
|
16
|
-
|
17
12
|
|
18
13
|
=== Installation
|
19
14
|
|
@@ -79,17 +74,6 @@ Note that our note will not be tagged until we save it.
|
|
79
74
|
Now when we retrieve our note from active record,
|
80
75
|
it will retain the tags we have given it.
|
81
76
|
|
82
|
-
If necessary we can directly manipulate a note's tag objects.
|
83
|
-
This should not be necessary though, and for normal use we can
|
84
|
-
do all of our tag editing through tag_list.
|
85
|
-
|
86
|
-
note.tags << ActsAsTaggableSimple::Tag.new :name => 'why_did_we_do_this?'
|
87
|
-
|
88
|
-
We can also manipulate the join objects by accessing taggings on a note.
|
89
|
-
Again, there is usually never a need to do this.
|
90
|
-
|
91
|
-
note.taggings # returns all of the ActsAsTaggableSimple::Tagging objects for our note
|
92
|
-
|
93
77
|
This is all we can do with this gem. Things like searching are left to
|
94
78
|
the user or solutions like:
|
95
79
|
http://github.com/ernie/meta_search
|
@@ -97,6 +81,22 @@ http://github.com/ernie/meta_search
|
|
97
81
|
If you want to do cloud calculations on tags, this task is left to you.
|
98
82
|
|
99
83
|
|
84
|
+
=== Contexts
|
85
|
+
|
86
|
+
class Note < ActiveRecord::Base
|
87
|
+
attr_accessible :content
|
88
|
+
acts_as_taggable :tag, :color
|
89
|
+
end
|
90
|
+
|
91
|
+
note = Note.new
|
92
|
+
note.tag_list = "new important"
|
93
|
+
note.color_list = "red blue purple"
|
94
|
+
note.save
|
95
|
+
|
96
|
+
note.tag_list # returns ["new", "important"]
|
97
|
+
note.color_list # returns ["red", "blue", "purple"]
|
98
|
+
|
99
|
+
|
100
100
|
=== Example
|
101
101
|
|
102
102
|
app/models/taggable_model.rb
|
data/Rakefile
CHANGED
@@ -1,23 +1,10 @@
|
|
1
1
|
require 'bundler'
|
2
2
|
require 'rspec/core/rake_task'
|
3
3
|
|
4
|
-
require 'rake'
|
5
|
-
|
6
4
|
Bundler::GemHelper.install_tasks
|
7
5
|
|
8
6
|
RSpec::Core::RakeTask.new do |t|
|
9
7
|
pattern = "spec/**/*_spec.rb"
|
10
8
|
end
|
11
9
|
|
12
|
-
task :default => :spec
|
13
|
-
|
14
|
-
require 'rake/rdoctask'
|
15
|
-
require 'acts_as_taggable_simple/version'
|
16
|
-
Rake::RDocTask.new do |rdoc|
|
17
|
-
version = ActsAsTaggableSimple::VERSION
|
18
|
-
|
19
|
-
rdoc.rdoc_dir = 'rdoc'
|
20
|
-
rdoc.title = "acts_as_taggable_simple #{version}"
|
21
|
-
rdoc.rdoc_files.include('README*')
|
22
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
-
end
|
10
|
+
task :default => :spec
|
@@ -17,6 +17,9 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.add_development_dependency 'activerecord'
|
18
18
|
s.add_development_dependency 'activesupport'
|
19
19
|
s.add_development_dependency 'sqlite3-ruby'
|
20
|
+
s.add_development_dependency 'mongoid'
|
21
|
+
s.add_development_dependency 'bson_ext'
|
22
|
+
s.add_development_dependency 'factory_girl'
|
20
23
|
|
21
24
|
s.files = `git ls-files`.split("\n")
|
22
25
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module ActiveModel #:nodoc:
|
2
|
+
module Acts #:nodoc:
|
3
|
+
|
4
|
+
# This +acts_as+ extension provides capabilities for adding and removing tags to ActiveRecord objects.
|
5
|
+
#
|
6
|
+
# Basic note example:
|
7
|
+
#
|
8
|
+
# class Note < ActiveRecord::Base
|
9
|
+
# acts_as_taggable
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# note.tag_list = "rails css javascript"
|
13
|
+
# note.tag_list # returns ["rails", "css", "javascript"]
|
14
|
+
#
|
15
|
+
module Taggable
|
16
|
+
extend ActiveSupport::Concern
|
17
|
+
|
18
|
+
# Provides one method: ActiveRecord::Base#acts_as_taggable.
|
19
|
+
module ClassMethods
|
20
|
+
|
21
|
+
# Call this method to make one of your models taggable.
|
22
|
+
#
|
23
|
+
# Future support for a :delimeter option for specifying the tag delimeter
|
24
|
+
def acts_as_taggable(*args)
|
25
|
+
has_many :taggings, as: :taggable, class_name: "ActsAsTaggableSimple::Tagging"
|
26
|
+
after_save :save_tags
|
27
|
+
|
28
|
+
args << "tag" if args.empty?
|
29
|
+
contexts = args.map(&:to_s).map(&:singularize)
|
30
|
+
self.class_variable_set(:@@tagging_contexts, contexts)
|
31
|
+
|
32
|
+
contexts.each do |context|
|
33
|
+
class_eval <<-EOV
|
34
|
+
def #{context}_list
|
35
|
+
self.instance_variable_get('@#{context}_list') || self.instance_variable_set('@#{context}_list', TagList.new(tags_on("#{context}").map(&:name)))
|
36
|
+
end
|
37
|
+
|
38
|
+
def #{context}_list=(list)
|
39
|
+
self.instance_variable_set('@#{context}_list', list.is_a?(String) ? TagList.new(list.split(" ")) : list)
|
40
|
+
end
|
41
|
+
EOV
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Provides methods to manage tags for a taggable model:
|
47
|
+
# * tag_list - returns an Array of tag String's
|
48
|
+
# * tag_list= - takes and Array of tag Strings or a String of tags separated by spaces
|
49
|
+
# * save_tags - updates the ActsAsTaggableSimple::Tag objects after saving a taggable model
|
50
|
+
module InstanceMethods
|
51
|
+
def tagging_contexts
|
52
|
+
self.class.class_variable_get(:@@tagging_contexts)
|
53
|
+
end
|
54
|
+
|
55
|
+
def tag_list_on(context)
|
56
|
+
try("#{context}_list")
|
57
|
+
end
|
58
|
+
|
59
|
+
def tags_on(context)
|
60
|
+
taggings.where(context: context).map(&:tag)
|
61
|
+
end
|
62
|
+
|
63
|
+
def taggings_on(context)
|
64
|
+
taggings.where(context: context)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Called right before saving the model to update the associated ActsAsTaggableSimple::Tag objects
|
68
|
+
# Once saved this also necessarily updates the ActsAsTaggableSimple::Tagging objects for this record
|
69
|
+
def save_tags
|
70
|
+
tagging_contexts.each do |context|
|
71
|
+
tag_list = tag_list_on(context)
|
72
|
+
|
73
|
+
ActsAsTaggableSimple::Tag.find_or_create_all_tags(tag_list)
|
74
|
+
|
75
|
+
existing_tags = taggings_on(context).collect do |tagging|
|
76
|
+
tagging.tag.name
|
77
|
+
end
|
78
|
+
new_tags = tag_list - existing_tags
|
79
|
+
old_tags = existing_tags - tag_list
|
80
|
+
|
81
|
+
destroyed_taggings = []
|
82
|
+
taggings_on(context).each do |tagging|
|
83
|
+
if old_tags.include? tagging.tag.name
|
84
|
+
tagging.destroy
|
85
|
+
destroyed_taggings << tagging
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
self.taggings -= destroyed_taggings
|
90
|
+
|
91
|
+
new_tags = ActsAsTaggableSimple::Tag.find_by_names(new_tags)
|
92
|
+
new_tags.each do |tag|
|
93
|
+
taggings.create! tag: tag, context: context
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
File without changes
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# ActsAsTaggableSimple
|
2
2
|
|
3
3
|
require 'tag_list'
|
4
|
-
require '
|
5
|
-
|
6
|
-
require 'active_record/
|
4
|
+
require 'active_model/acts/taggable'
|
5
|
+
|
6
|
+
require 'active_record/active_record' if defined? ::ActiveRecord
|
7
|
+
require 'mongoid/mongoid' if defined? ::Mongoid
|
data/lib/mongoid/tag.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module ActsAsTaggableSimple #:nodoc:
|
2
|
+
|
3
|
+
# ActiveRecord model for storing a tag in the database.
|
4
|
+
#
|
5
|
+
# Tags have only one attribute: +name+, which is just the text representation of the tag.
|
6
|
+
# +name+ must be unique for each instance.
|
7
|
+
class Tag
|
8
|
+
include Mongoid::Document
|
9
|
+
|
10
|
+
attr_accessible :name
|
11
|
+
|
12
|
+
validates_uniqueness_of :name
|
13
|
+
|
14
|
+
# Finds or creates all of the tags contained in +list+ and returns them.
|
15
|
+
#
|
16
|
+
# +list+ is an Array of String's containing the +name+'s of the desired tags.
|
17
|
+
# If an ActsAsTaggableSimple::Tag does not yet exist for a tag, then it is created, otherwise the already existing tag is used.
|
18
|
+
#
|
19
|
+
# Empty database example:
|
20
|
+
#
|
21
|
+
# tags = ActsAsTaggableSimple::Tag.find_or_create_all_tags(%w{rails css html})
|
22
|
+
#
|
23
|
+
# Will create 3 new ActsAsTaggableSimple::Tag objects and save them to the database.
|
24
|
+
#
|
25
|
+
# If +rails+, +css+, and +html+ tags already exist in the database, then the following example will only create 1 new tag.
|
26
|
+
#
|
27
|
+
# tags = ActsAsTaggableSimple::Tag.find_or_create_all_tags(%w{rails css html javascript})
|
28
|
+
#
|
29
|
+
def self.find_or_create_all_tags(list)
|
30
|
+
existing_tags = Tag.where(:name.in => list)
|
31
|
+
create_tags_list = list - existing_tags.map(&:name)
|
32
|
+
|
33
|
+
create_tags = create_tags_list.collect do |tag|
|
34
|
+
Tag.create!(:name => tag)
|
35
|
+
end
|
36
|
+
|
37
|
+
create_tags + existing_tags.all
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.find_by_names(names)
|
41
|
+
where(:name.in => names)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module ActsAsTaggableSimple #:nodoc:
|
2
|
+
|
3
|
+
# Model for storing a join between a taggable object and an ActsAsTaggableSimple::Tag object.
|
4
|
+
class Tagging
|
5
|
+
include Mongoid::Document
|
6
|
+
|
7
|
+
belongs_to :taggable, :polymorphic => true
|
8
|
+
belongs_to :tag, :class_name => "ActsAsTaggableSimple::Tag"
|
9
|
+
has_one :name, :through => :tag
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Todo < ActiveRecord::Base
|
2
|
+
acts_as_taggable
|
3
|
+
end
|
4
|
+
|
5
|
+
class Other < ActiveRecord::Base
|
6
|
+
acts_as_taggable
|
7
|
+
end
|
8
|
+
|
9
|
+
class Note < ActiveRecord::Base
|
10
|
+
acts_as_taggable :country
|
11
|
+
end
|
12
|
+
|
13
|
+
class Multi < ActiveRecord::Base
|
14
|
+
acts_as_taggable :country, :city
|
15
|
+
end
|
16
|
+
|
17
|
+
class Untaggable < ActiveRecord::Base
|
18
|
+
end
|
data/spec/database.yml
CHANGED
data/spec/factories.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Acts As Taggable Simple" do
|
4
|
+
describe "Taggable Method Generation" do
|
5
|
+
describe "with a context tagging" do
|
6
|
+
before do
|
7
|
+
@taggable = Factory.build :note
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should generate a country_list method" do
|
11
|
+
@taggable.should respond_to(:country_list)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should generate a country_list= method" do
|
15
|
+
@taggable.should respond_to(:country_list=)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "without a context tagging" do
|
20
|
+
before do
|
21
|
+
@taggable = Factory.build :todo
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should generate a tag_list method" do
|
25
|
+
@taggable.should respond_to(:tag_list)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should generate a tag_list= method" do
|
29
|
+
@taggable.should respond_to(:tag_list=)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Taggable" do
|
4
|
+
describe "with multiple context taggings" do
|
5
|
+
before do
|
6
|
+
@taggable = Factory.create :multi
|
7
|
+
end
|
8
|
+
|
9
|
+
it "creates tags" do
|
10
|
+
@taggable.country_list = "mexico usa canada"
|
11
|
+
@taggable.city_list = "tijuana ithaca montreal"
|
12
|
+
|
13
|
+
lambda {
|
14
|
+
@taggable.save
|
15
|
+
}.should change(ActsAsTaggableSimple::Tag, :count).by(6)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "doesn't create tags that already exist" do
|
19
|
+
@taggable.country_list = "tag2 tag3 tag1"
|
20
|
+
@taggable.city_list = "tijuana ithaca montreal"
|
21
|
+
@taggable.save
|
22
|
+
|
23
|
+
taggable1 = Factory.build(:note, :country_list => "tag2 tag1 rails css tijuana")
|
24
|
+
|
25
|
+
lambda {
|
26
|
+
taggable1.save
|
27
|
+
}.should change(ActsAsTaggableSimple::Tag, :count).by(2)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "with a context tagging" do
|
32
|
+
before do
|
33
|
+
@taggable = Factory.create :note
|
34
|
+
end
|
35
|
+
|
36
|
+
it "create tags" do
|
37
|
+
@taggable.country_list = "mexico usa canada"
|
38
|
+
|
39
|
+
lambda {
|
40
|
+
@taggable.save
|
41
|
+
}.should change(ActsAsTaggableSimple::Tag, :count).by(3)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "doesn't create tags that already exist" do
|
45
|
+
@taggable.country_list = "tag2 tag3 tag1"
|
46
|
+
@taggable.save
|
47
|
+
|
48
|
+
taggable1 = Factory.build(:note, :country_list => "tag2 tag1 rails css")
|
49
|
+
|
50
|
+
lambda {
|
51
|
+
taggable1.save
|
52
|
+
}.should change(ActsAsTaggableSimple::Tag, :count).by(2)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "deletes old taggings" do
|
56
|
+
@taggable.country_list = "ruby rails css"
|
57
|
+
@taggable.save
|
58
|
+
|
59
|
+
@taggable.country_list = "ruby rails"
|
60
|
+
|
61
|
+
lambda {
|
62
|
+
@taggable.save
|
63
|
+
}.should change(@taggable.taggings, :count).by(-1)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "without a context tagging" do
|
68
|
+
before do
|
69
|
+
@taggable = Factory.create :todo
|
70
|
+
@other = Factory.create :other
|
71
|
+
end
|
72
|
+
|
73
|
+
it "creates tags" do
|
74
|
+
@taggable.tag_list = "tag2 tag3 tag1"
|
75
|
+
@taggable.tag_list.should be_kind_of(Array)
|
76
|
+
|
77
|
+
lambda {
|
78
|
+
@taggable.save
|
79
|
+
}.should change(ActsAsTaggableSimple::Tag, :count).by(3)
|
80
|
+
|
81
|
+
@taggable.tag_list.sort.should == %w(tag2 tag3 tag1).sort
|
82
|
+
end
|
83
|
+
|
84
|
+
it "does not create tags that already exist and create tags that do not yet exist when saving" do
|
85
|
+
@taggable.tag_list = "tag2 tag3 tag1"
|
86
|
+
@taggable.save
|
87
|
+
|
88
|
+
taggable1 = Factory.build(:todo, :tag_list => "tag2 tag1 rails css")
|
89
|
+
taggable2 = Factory.build(:todo, :tag_list => "tag2 tag1 rails css")
|
90
|
+
|
91
|
+
lambda {
|
92
|
+
taggable1.save
|
93
|
+
}.should change(ActsAsTaggableSimple::Tag, :count).by(2)
|
94
|
+
|
95
|
+
lambda {
|
96
|
+
taggable2.save
|
97
|
+
}.should change(ActsAsTaggableSimple::Tag, :count).by(0)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "deletes old taggings when saving" do
|
101
|
+
@taggable.tag_list = "ruby rails css"
|
102
|
+
@taggable.save
|
103
|
+
|
104
|
+
@taggable.tag_list = "ruby rails"
|
105
|
+
|
106
|
+
lambda {
|
107
|
+
@taggable.save
|
108
|
+
}.should change(@taggable.taggings, :count).by(-1)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "retains unchanged taggings when saving" do
|
112
|
+
@taggable.tag_list = "ruby rails css"
|
113
|
+
@taggable.save
|
114
|
+
old_taggings = @taggable.taggings
|
115
|
+
|
116
|
+
@taggable.tag_list = "ruby rails"
|
117
|
+
@taggable.save
|
118
|
+
|
119
|
+
@taggable.taggings.should == old_taggings[0..1]
|
120
|
+
end
|
121
|
+
|
122
|
+
it "tags multiple models simultaneously" do
|
123
|
+
@taggable.tag_list = "ruby rails css"
|
124
|
+
@taggable.save
|
125
|
+
|
126
|
+
@other.tag_list = "ruby rails css"
|
127
|
+
@other.save
|
128
|
+
|
129
|
+
taggable1 = Factory.build(:todo, :tag_list => "ruby tag1 tag2")
|
130
|
+
other1 = Factory.build(:other, :tag_list => "rails tag1 tag3 tag4")
|
131
|
+
|
132
|
+
taggable1.save
|
133
|
+
other1.save
|
134
|
+
|
135
|
+
@taggable = Todo.find @taggable.id
|
136
|
+
@other = Other.find @other.id
|
137
|
+
taggable1 = Todo.find taggable1.id
|
138
|
+
other1 = Other.find other1.id
|
139
|
+
|
140
|
+
@taggable.tag_list.sort.should == %w{ruby rails css}.sort
|
141
|
+
@other.tag_list.sort.should == %w{ruby rails css}.sort
|
142
|
+
taggable1.tag_list.sort.should == %w{ruby tag1 tag2}.sort
|
143
|
+
other1.tag_list.sort.should == %w{rails tag1 tag3 tag4}.sort
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|