acts_as_taggable_simple 0.0.4 → 0.1.0
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/.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
|