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 CHANGED
@@ -8,3 +8,5 @@ doc/**/*
8
8
  rdoc/**/*
9
9
  Gemfile.lock
10
10
  Manifest
11
+ .idea/
12
+ spec/*.log
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
@@ -0,0 +1,4 @@
1
+ require 'active_record/tag'
2
+ require 'active_record/tagging'
3
+
4
+ ActiveRecord::Base.send :include, ActiveModel::Acts::Taggable
@@ -34,5 +34,9 @@ module ActsAsTaggableSimple #:nodoc:
34
34
 
35
35
  create_tags + existing_tags.all
36
36
  end
37
+
38
+ def self.find_by_names(names)
39
+ where(name: names)
40
+ end
37
41
  end
38
42
  end
File without changes
@@ -1,6 +1,7 @@
1
1
  # ActsAsTaggableSimple
2
2
 
3
3
  require 'tag_list'
4
- require 'tag'
5
- require 'tagging'
6
- require 'active_record/acts/taggable'
4
+ require 'active_model/acts/taggable'
5
+
6
+ require 'active_record/active_record' if defined? ::ActiveRecord
7
+ require 'mongoid/mongoid' if defined? ::Mongoid
@@ -1,3 +1,3 @@
1
1
  module ActsAsTaggableSimple
2
- VERSION = "0.0.4"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -20,6 +20,8 @@ class ActsAsTaggableSimpleMigration < ActiveRecord::Migration
20
20
  create_table :taggings do |t|
21
21
  t.references :taggable, :polymorphic => true
22
22
  t.references :tag
23
+
24
+ t.string :context
23
25
  end
24
26
  end
25
27
 
@@ -0,0 +1,4 @@
1
+ require 'mongoid/tag'
2
+ require 'mongoid/tagging'
3
+
4
+ Mongoid::Document.send :include, ActiveModel::Acts::Taggable
@@ -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
@@ -1,3 +1,3 @@
1
1
  sqlite3:
2
2
  adapter: sqlite3
3
- database: ":memory:"
3
+ database: ""
data/spec/factories.rb ADDED
@@ -0,0 +1,11 @@
1
+ Factory.define :note do |f|
2
+ end
3
+
4
+ Factory.define :todo do |f|
5
+ end
6
+
7
+ Factory.define :other do |f|
8
+ end
9
+
10
+ Factory.define :multi do |f|
11
+ end
@@ -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