rocket_tag 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+ gem "activerecord", ">= 3.1.0"
6
+ gem "squeel", :require => false
7
+
8
+ # Add dependencies to develop your gem here.
9
+ # Include everything needed to run rake, tests, features, etc.
10
+ group :development do
11
+ gem "rspec", "~> 2.3.0"
12
+ gem "yard", "~> 0.6.0"
13
+ gem "bundler", "~> 1.0.0"
14
+ gem "jeweler", "~> 1.6.4"
15
+ gem "rcov", ">= 0"
16
+ gem 'sqlite3'
17
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,58 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.1.0)
5
+ activesupport (= 3.1.0)
6
+ bcrypt-ruby (~> 3.0.0)
7
+ builder (~> 3.0.0)
8
+ i18n (~> 0.6)
9
+ activerecord (3.1.0)
10
+ activemodel (= 3.1.0)
11
+ activesupport (= 3.1.0)
12
+ arel (~> 2.2.1)
13
+ tzinfo (~> 0.3.29)
14
+ activesupport (3.1.0)
15
+ multi_json (~> 1.0)
16
+ arel (2.2.1)
17
+ bcrypt-ruby (3.0.1)
18
+ builder (3.0.0)
19
+ diff-lcs (1.1.3)
20
+ git (1.2.5)
21
+ i18n (0.6.0)
22
+ jeweler (1.6.4)
23
+ bundler (~> 1.0)
24
+ git (>= 1.2.5)
25
+ rake
26
+ multi_json (1.0.3)
27
+ polyamorous (0.5.0)
28
+ activerecord (~> 3.0)
29
+ rake (0.9.2)
30
+ rcov (0.9.10)
31
+ rspec (2.3.0)
32
+ rspec-core (~> 2.3.0)
33
+ rspec-expectations (~> 2.3.0)
34
+ rspec-mocks (~> 2.3.0)
35
+ rspec-core (2.3.1)
36
+ rspec-expectations (2.3.0)
37
+ diff-lcs (~> 1.1.2)
38
+ rspec-mocks (2.3.0)
39
+ sqlite3 (1.3.4)
40
+ squeel (0.9.0)
41
+ activerecord (~> 3.0)
42
+ activesupport (~> 3.0)
43
+ polyamorous (~> 0.5.0)
44
+ tzinfo (0.3.29)
45
+ yard (0.6.8)
46
+
47
+ PLATFORMS
48
+ ruby
49
+
50
+ DEPENDENCIES
51
+ activerecord (>= 3.1.0)
52
+ bundler (~> 1.0.0)
53
+ jeweler (~> 1.6.4)
54
+ rcov
55
+ rspec (~> 2.3.0)
56
+ sqlite3
57
+ squeel
58
+ yard (~> 0.6.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Brad Phelan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ Rocket Tag
2
+ ==========
3
+
4
+ Clean, modern an maintainable, context aware tagging library for rails 3.1 +
5
+
6
+ Installation
7
+ ------------
8
+
9
+ In your gemfile
10
+
11
+ gem "rocket_tag"
12
+
13
+ Then at the command line
14
+
15
+ bundle install
16
+
17
+ Create the migration at the command line
18
+
19
+ rails generate rocket_tag:migration
20
+ rake db:migrate
21
+ rake db:test:prepare
22
+
23
+ Usage
24
+ -----
25
+
26
+ class TaggableModel < ActiveRecord::Base
27
+ attr_taggable :skills, :habits
28
+ end
29
+
30
+ item = TaggableModel.create
31
+
32
+ item.skills = ["kiting", "surfing", "coding"]
33
+ item.habits = ["forking", "talking"]
34
+
35
+
36
+ # Match any tag across any contexts
37
+ TaggableModel.tagged_with ["forking", "kiting"]
38
+
39
+ # Match all tags across any contexts
40
+ TaggableModel.tagged_with ["forking", "kiting"], :all => true
41
+
42
+ # Match any tag on a specific context
43
+ TaggableModel.tagged_with ["math", "kiting"], :on => "skills"
44
+
45
+ # Match all tags on a specific context
46
+ TaggableModel.tagged_with ["math", "kiting"], :all => true, :on => "skills"
47
+
48
+ # Mix with active relation
49
+ TaggableModel.tagged_with(["forking", "kiting"]).where( ["created_at > ?", Time.zone.now.ago(5.hours)])
50
+
51
+
52
+ == Contributing to rocket_tag
53
+
54
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
55
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
56
+ * Fork the project
57
+ * Start a feature/bugfix branch
58
+ * Commit and push until you are happy with your contribution
59
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
60
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
61
+
62
+ == Copyright
63
+
64
+ Copyright (c) 2011 Brad Phelan. See LICENSE.txt for
65
+ further details.
66
+
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "rocket_tag"
18
+ gem.homepage = "http://github.com/bradphelan/rocket_tag"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{A modern fast tagging framework for Rails 3.1+}
21
+ gem.description = %Q{}
22
+ gem.email = "bradphelan@xtargets.com"
23
+ gem.authors = ["Brad Phelan"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'yard'
42
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,39 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module RocketTag
5
+ class MigrationGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ desc "Generates migration for Tag and Tagging models"
9
+
10
+ def self.orm
11
+ Rails::Generators.options[:rails][:orm]
12
+ end
13
+
14
+ def self.source_root
15
+ File.join(File.dirname(__FILE__), 'templates', (orm.to_s unless orm.class.eql?(String)) )
16
+ end
17
+
18
+ def self.orm_has_migration?
19
+ [:active_record].include? orm
20
+ end
21
+
22
+ def self.next_migration_number(dirname)
23
+ if ActiveRecord::Base.timestamped_migrations
24
+ migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
25
+ migration_number += 1
26
+ migration_number.to_s
27
+ else
28
+ "%.3d" % (current_migration_number(dirname) + 1)
29
+ end
30
+ end
31
+
32
+ def create_migration_file
33
+ if self.class.orm_has_migration?
34
+ migration_template 'migration.rb', 'db/migrate/rocket_tag_migration'
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,30 @@
1
+ class RocketTagMigration < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ create_table :tags do |t|
5
+ t.string :name
6
+ end
7
+
8
+ create_table :taggings do |t|
9
+ t.references :tag
10
+
11
+ # You should make sure that the column created is
12
+ # long enough to store the required class names.
13
+ t.references :taggable, :polymorphic => true
14
+ t.references :tagger, :polymorphic => true
15
+
16
+ t.string :context
17
+
18
+ t.datetime :created_at
19
+ end
20
+
21
+ add_index :taggings, :tag_id
22
+ add_index :taggings, [:taggable_id, :taggable_type, :context]
23
+ end
24
+
25
+ def self.down
26
+ drop_table :taggings
27
+ drop_table :tags
28
+ end
29
+
30
+ end
@@ -0,0 +1,9 @@
1
+ module RocketTag
2
+ class Tag < ActiveRecord::Base
3
+ has_many :taggings, :dependent => :destroy, :class_name => 'RocketTag::Tagging'
4
+ attr_accessible :name
5
+
6
+ validates_presence_of :name
7
+ validates_uniqueness_of :name
8
+ end
9
+ end
@@ -0,0 +1,182 @@
1
+ require 'squeel'
2
+
3
+ module RocketTag
4
+ module Taggable
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ class Manager
10
+
11
+ attr_reader :contexts
12
+ attr_writer :contexts
13
+ attr_reader :klass
14
+
15
+ def initialize klass
16
+ @klass = klass
17
+ @contexts = Set.new
18
+ setup_relations
19
+ end
20
+
21
+ def setup_relations
22
+ klass.has_many :taggings , :dependent => :destroy , :as => :taggable, :class_name => "RocketTag::Tagging"
23
+ klass.has_many :tags , :source => :tag, :through => :taggings, :class_name => "RocketTag::Tag"
24
+ end
25
+
26
+
27
+ end
28
+
29
+ def tags_for_context context
30
+ tags.where{taggings.context==my{context}}
31
+ end
32
+
33
+ def taggings_for_context context
34
+ taggings.where{taggings.context==my{context}}
35
+ end
36
+
37
+ def destroy_tags_for_context context
38
+ taggings_for_context(context).delete_all
39
+ end
40
+
41
+ module ClassMethods
42
+
43
+ def rocket_tag
44
+ @rocket_tag ||= RocketTag::Taggable::Manager.new(self)
45
+ end
46
+
47
+ def with_tag_context context
48
+ if context
49
+ where{taggings.context == my{context} }
50
+ else
51
+ where{}
52
+ end
53
+ end
54
+
55
+ def tagged_with tags_list, options = {}
56
+
57
+ on = options.delete :on
58
+ all = options.delete :all
59
+
60
+ q = if all
61
+ joins{tags}.where{
62
+ id.in(
63
+ my{self}.
64
+ select{id}.
65
+ joins{tags}.
66
+ where{tags.name.in(my{tags_list})}.
67
+ group{~id}.
68
+ having{count(~id)==my{tags_list.length}}.
69
+ with_tag_context(my{on})
70
+ )
71
+ }
72
+ else
73
+ joins{tags}.where{tags.name.in(my{tags_list})}.with_tag_context(on)
74
+ end
75
+
76
+ q.select{"distinct #{my{table_name}}.*"}
77
+
78
+ end
79
+
80
+ def attr_taggable *contexts
81
+
82
+ if contexts.blank?
83
+ contexts = [:tag]
84
+ end
85
+
86
+ rocket_tag.contexts += contexts
87
+
88
+ contexts.each do |context|
89
+ class_eval do
90
+
91
+ default_scope do
92
+ preload{taggings}.preload{tags}
93
+ end
94
+
95
+ has_many "#{context}_taggings".to_sym,
96
+ :source => :taggable,
97
+ :as => :taggable,
98
+ :conditions => { :context => context }
99
+
100
+ has_many "#{context}_tags".to_sym,
101
+ :source => :tag,
102
+ :through => :taggings,
103
+ :conditions => [ "taggings.context = ?", context ]
104
+
105
+
106
+ before_save do
107
+ @tag_dirty ||= Set.new
108
+
109
+ @tag_dirty.each do |context|
110
+ # Get the current tags for this context
111
+ list = send(context)
112
+
113
+ # Destroy all taggings
114
+ destroy_tags_for_context context
115
+
116
+ # Find existing tags
117
+ exisiting_tags = Tag.where{name.in(my{list})}
118
+ exisiting_tag_names = exisiting_tags.map &:name
119
+
120
+ # Find missing tags
121
+ tags_names_to_create = list - exisiting_tag_names
122
+
123
+ # Create missing tags
124
+ created_tags = tags_names_to_create.map do |tag_name|
125
+ Tag.create :name => tag_name
126
+ end
127
+
128
+ # Recreate taggings
129
+ tags_to_assign = exisiting_tags + created_tags
130
+
131
+ tags_to_assign.each do |tag|
132
+ tagging = Tagging.new :tag => tag, :taggable => self, :context => context, :tagger => nil
133
+ self.taggings << tagging
134
+ end
135
+ end
136
+ @tag_dirty = Set.new
137
+ end
138
+
139
+ def reload
140
+ super
141
+ self.class.rocket_tag.contexts.each do |context|
142
+ write_attribute context, []
143
+ end
144
+ @tags_cached = false
145
+ cache_tags
146
+ end
147
+
148
+ define_method "cache_tags" do
149
+ unless @tags_cached
150
+ tags_by_context ||= send("taggings").group_by{|f| f.context }
151
+ tags_by_context.each do |context,v|
152
+ write_attribute context, v.map{|t| t.tag.name}
153
+ end
154
+ @tags_cached = true
155
+ end
156
+ end
157
+
158
+ # Return an array of RocketTag::Tags for the context
159
+ define_method "#{context}" do
160
+ cache_tags
161
+ r = read_attribute(context) || []
162
+ end
163
+
164
+
165
+ define_method "#{context}=" do |list|
166
+
167
+ # Ensure the tags are loaded
168
+ cache_tags
169
+ write_attribute(context, list)
170
+
171
+ (@tag_dirty ||= Set.new) << context
172
+
173
+
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ end
180
+
181
+ end
182
+ end
@@ -0,0 +1,23 @@
1
+ module RocketTag
2
+ class Tagging < ::ActiveRecord::Base
3
+
4
+ attr_accessible :tag,
5
+ :tag_id,
6
+ :context,
7
+ :taggable,
8
+ :taggable_type,
9
+ :taggable_id,
10
+ :tagger,
11
+ :tagger_type,
12
+ :tagger_id
13
+
14
+ belongs_to :tag, :class_name => 'RocketTag::Tag'
15
+ belongs_to :taggable, :polymorphic => true
16
+ belongs_to :tagger, :polymorphic => true
17
+
18
+ validates_presence_of :context
19
+ validates_presence_of :tag_id
20
+
21
+ validates_uniqueness_of :tag_id, :scope => [ :taggable_type, :taggable_id, :context, :tagger_id, :tagger_type ]
22
+ end
23
+ end
data/lib/rocket_tag.rb ADDED
@@ -0,0 +1,16 @@
1
+ require "active_record"
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+
5
+ require "rocket_tag/tagging"
6
+ require "rocket_tag/tag"
7
+
8
+ require "rocket_tag/taggable"
9
+
10
+ $LOAD_PATH.shift
11
+
12
+ if defined?(ActiveRecord::Base)
13
+ class ActiveRecord::Base
14
+ include RocketTag::Taggable
15
+ end
16
+ end
@@ -0,0 +1,80 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{rocket_tag}
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = [%q{Brad Phelan}]
12
+ s.date = %q{2011-09-16}
13
+ s.description = %q{}
14
+ s.email = %q{bradphelan@xtargets.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.md",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "lib/generators/rocket_tag/migration/migration_generator.rb",
29
+ "lib/generators/rocket_tag/migration/templates/active_record/migration.rb",
30
+ "lib/rocket_tag.rb",
31
+ "lib/rocket_tag/tag.rb",
32
+ "lib/rocket_tag/taggable.rb",
33
+ "lib/rocket_tag/tagging.rb",
34
+ "rocket_tag.gemspec",
35
+ "spec/database.yml",
36
+ "spec/models.rb",
37
+ "spec/rocket_tag/taggable_spec.rb",
38
+ "spec/schema.rb",
39
+ "spec/spec_helper.rb"
40
+ ]
41
+ s.homepage = %q{http://github.com/bradphelan/rocket_tag}
42
+ s.licenses = [%q{MIT}]
43
+ s.require_paths = [%q{lib}]
44
+ s.rubygems_version = %q{1.8.8}
45
+ s.summary = %q{A modern fast tagging framework for Rails 3.1+}
46
+
47
+ if s.respond_to? :specification_version then
48
+ s.specification_version = 3
49
+
50
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
51
+ s.add_runtime_dependency(%q<activerecord>, [">= 3.1.0"])
52
+ s.add_runtime_dependency(%q<squeel>, [">= 0"])
53
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
54
+ s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
55
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
56
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
57
+ s.add_development_dependency(%q<rcov>, [">= 0"])
58
+ s.add_development_dependency(%q<sqlite3>, [">= 0"])
59
+ else
60
+ s.add_dependency(%q<activerecord>, [">= 3.1.0"])
61
+ s.add_dependency(%q<squeel>, [">= 0"])
62
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
63
+ s.add_dependency(%q<yard>, ["~> 0.6.0"])
64
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
65
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
66
+ s.add_dependency(%q<rcov>, [">= 0"])
67
+ s.add_dependency(%q<sqlite3>, [">= 0"])
68
+ end
69
+ else
70
+ s.add_dependency(%q<activerecord>, [">= 3.1.0"])
71
+ s.add_dependency(%q<squeel>, [">= 0"])
72
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
73
+ s.add_dependency(%q<yard>, ["~> 0.6.0"])
74
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
75
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
76
+ s.add_dependency(%q<rcov>, [">= 0"])
77
+ s.add_dependency(%q<sqlite3>, [">= 0"])
78
+ end
79
+ end
80
+
data/spec/database.yml ADDED
@@ -0,0 +1,19 @@
1
+ sqlite3:
2
+ adapter: sqlite3
3
+ database: rocket_tag.sqlite3
4
+
5
+ mysql:
6
+ adapter: mysql2
7
+ hostname: localhost
8
+ username: root
9
+ password:
10
+ database: acts_as_taggable_on
11
+ charset: utf8
12
+
13
+ postgresql:
14
+ adapter: postgresql
15
+ hostname: localhost
16
+ username: postgres
17
+ password:
18
+ database: acts_as_taggable_on
19
+ encoding: utf8
data/spec/models.rb ADDED
@@ -0,0 +1,45 @@
1
+ class TaggableModel < ActiveRecord::Base
2
+ default_scope do
3
+ preload{taggings}.preload{tags}
4
+ end
5
+
6
+ attr_taggable
7
+ attr_taggable :languages
8
+ attr_taggable :skills
9
+ attr_taggable :needs, :offerings
10
+ has_many :untaggable_models
11
+ end
12
+
13
+ class CachedModel < ActiveRecord::Base
14
+ attr_taggable
15
+ end
16
+
17
+ class OtherCachedModel < ActiveRecord::Base
18
+ attr_taggable :languages, :statuses, :glasses
19
+ end
20
+
21
+ class OtherTaggableModel < ActiveRecord::Base
22
+ attr_taggable :tags, :languages
23
+ attr_taggable :needs, :offerings
24
+ end
25
+
26
+ class InheritingTaggableModel < TaggableModel
27
+ end
28
+
29
+ class AlteredInheritingTaggableModel < TaggableModel
30
+ attr_taggable :parts
31
+ end
32
+
33
+ class UntaggableModel < ActiveRecord::Base
34
+ belongs_to :taggable_model
35
+ end
36
+
37
+ class NonStandardIdTaggableModel < ActiveRecord::Base
38
+ set_primary_key "an_id"
39
+ attr_taggable
40
+ attr_taggable :languages
41
+ attr_taggable :skills
42
+ attr_taggable :needs, :offerings
43
+ has_many :untaggable_models
44
+ end
45
+
@@ -0,0 +1,213 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe TaggableModel do
4
+ before :each do
5
+ clean_database!
6
+ @model = TaggableModel.create
7
+ end
8
+
9
+ describe "#save" do
10
+ it "persists the tags cache to the database" do
11
+ @model.languages = ["a", "b", "c"]
12
+ @model.save
13
+ @model.reload
14
+ @model.languages.should == ["a", "b", "c"]
15
+
16
+ @model.languages = ["x", "y"]
17
+ @model.save
18
+ @model.reload
19
+ @model.languages.should == ["x", "y"]
20
+ end
21
+ end
22
+ describe "#reload" do
23
+ it "resets the tags caches to what is in the database" do
24
+ @model.languages = ["a", "b", "c"]
25
+ @model.reload
26
+ @model.languages.should == []
27
+
28
+ @model.languages = ["x", "y"]
29
+ @model.reload
30
+ @model.languages.should == []
31
+
32
+ @model.needs = ["a"]
33
+ @model.save
34
+ @model.reload
35
+ @model.needs.should == ["a"]
36
+ @model.needs = ["b"]
37
+ @model.needs.should == ["b"]
38
+ @model.reload
39
+ @model.needs.should == ["a"]
40
+ end
41
+ end
42
+
43
+
44
+ it "allows me to get funky with Squeel and ActiveRelation" do
45
+
46
+ sql = <<-EOF.gsub(/\s+/, ' ').strip
47
+ SELECT "taggable_models".*
48
+ FROM "taggable_models"
49
+ INNER JOIN "taggings"
50
+ ON "taggings"."taggable_id" = "taggable_models"."id"
51
+ AND "taggings"."taggable_type" = 'TaggableModel'
52
+ INNER JOIN "tags"
53
+ ON "tags"."id" = "taggings"."tag_id"
54
+ AND taggings.context = 'skills'
55
+ WHERE
56
+ "tags"."name" = 'foo'
57
+ EOF
58
+
59
+ TaggableModel.joins{skills_tags}.where{skills_tags.name == "foo"}.to_sql.should == sql
60
+
61
+ sql = <<-EOF.gsub(/\s+/, ' ').strip
62
+ SELECT distinct taggable_models.*
63
+ FROM "taggable_models"
64
+ INNER JOIN "taggings"
65
+ ON
66
+ "taggings"."taggable_id" = "taggable_models"."id"
67
+ AND
68
+ "taggings"."taggable_type" = 'TaggableModel'
69
+ INNER JOIN "tags"
70
+ ON
71
+ "tags"."id" = "taggings"."tag_id"
72
+ WHERE
73
+ "taggable_models"."id" IN
74
+ (SELECT "taggable_models"."id"
75
+ FROM "taggable_models"
76
+ INNER JOIN "taggings"
77
+ ON "taggings"."taggable_id" = "taggable_models"."id"
78
+ AND "taggings"."taggable_type" = 'TaggableModel'
79
+ INNER JOIN "tags"
80
+ ON "tags"."id" = "taggings"."tag_id" WHERE "tags"."name"
81
+ IN ('a', 'b')
82
+ GROUP BY "taggable_models"."id"
83
+ HAVING count("taggable_models"."id") = 2)
84
+ AND
85
+ (created_at > '2011-09-16 05:41')
86
+ EOF
87
+
88
+ TaggableModel.tagged_with(["a", "b"], :all =>true).where(["created_at > ?", "2011-09-16 05:41"]).to_sql.should == sql
89
+ end
90
+
91
+
92
+ describe "combining with active relation" do
93
+ before :each do
94
+ TaggableModel.create :name => "test 0", :needs => %w[x y z]
95
+ TaggableModel.create :name => "test 1", :needs => %w[a b c]
96
+ TaggableModel.create :name => "test 2", :needs => %w[a b c]
97
+ TaggableModel.create :name => "test 3", :needs => %w[a b c]
98
+
99
+ TaggableModel.create :name => "app 0", :skills => %w[x y z]
100
+ TaggableModel.create :name => "app 1", :skills => %w[a b c]
101
+ TaggableModel.create :name => "app 2", :skills => %w[a b c]
102
+ TaggableModel.create :name => "app 3", :skills => %w[a b c]
103
+ end
104
+
105
+ it "should generate the correct results" do
106
+
107
+ TaggableModel.tagged_with(%w[a b], :all=>true).count(:distinct => true).should == 6
108
+ TaggableModel.tagged_with(%w[a b], :all=>true).where{name.like "app%"}.count(:distinct => true).should == 3
109
+
110
+ TaggableModel.tagged_with(%w[a b], :all=>true).where{name.like "%1"}.count(:distinct => true).should == 2
111
+ TaggableModel.tagged_with(%w[a b], :all=>true, :on => :skills).where{name.like "%1"}.count(:distinct => true).should == 1
112
+
113
+ end
114
+ end
115
+ describe "querying tags" do
116
+
117
+ before :each do
118
+ @t00 = TaggableModel.create :name => "00", :foo => "A"
119
+ @t01 = TaggableModel.create :name => "01", :foo => "B"
120
+
121
+
122
+ @t10 = TaggableModel.create :name => "10", :foo => "A"
123
+ @t11 = TaggableModel.create :name => "11", :foo => "B"
124
+
125
+
126
+ @t20 = TaggableModel.create :name => "20", :foo => "A"
127
+ @t21 = TaggableModel.create :name => "21", :foo => "B"
128
+
129
+ @t00.skills = [ "a" , "b"]
130
+ @t00.languages = [ "german" , "french"]
131
+
132
+ @t01.skills = [ "a" , "b"]
133
+ @t01.languages = [ "german" , "italian"]
134
+
135
+ @t10.skills = [ "a" , "c"]
136
+
137
+ @t11.skills = [ "a" , "c"]
138
+
139
+ @t20.skills = [ "c" , "d"]
140
+
141
+ @t21.skills = [ "c" , "d"]
142
+
143
+ @t21.languages = [ "german" , "jinglish"]
144
+
145
+ @t00.save
146
+ @t01.save
147
+ @t10.save
148
+ @t11.save
149
+ @t20.save
150
+ @t21.save
151
+ end
152
+
153
+ it "allow me to do eager loading on tags" do
154
+ pending "Need to figure out how to verify eager loading other than manually inspect the log file"
155
+ end
156
+
157
+ describe "#tagged_with" do
158
+ describe ":all => true" do
159
+ it "should return records where *all* tags match on any context" do
160
+ q0 = TaggableModel.tagged_with(["a", "german"], :all => true ).all
161
+ q0.length.should == 2
162
+ q0.should include @t00
163
+ q0.should include @t01
164
+ end
165
+ end
166
+ describe ":all => false" do
167
+ it "should return records where *any* tags match on any context" do
168
+ q0 = TaggableModel.tagged_with(["a", "german"] ).all
169
+ q0.length.should == 5
170
+ q0.should include @t00
171
+ q0.should include @t01
172
+ q0.should include @t10
173
+ q0.should include @t11
174
+ q0.should include @t21
175
+
176
+ q0.should_not include @t20 # as it has neither "a" nor "german" tagged
177
+ # on any context
178
+ end
179
+ end
180
+
181
+ describe ":all => false, :on => context" do
182
+ it "should return records where *any* tags match on the specific context" do
183
+ q0 = TaggableModel.tagged_with(["a", "german"], :on => :skills ).all
184
+ q0.length.should == 4
185
+ q0.should include @t00
186
+ q0.should include @t01
187
+ q0.should include @t10
188
+ q0.should include @t11
189
+
190
+ q0.should_not include @t21
191
+ q0.should_not include @t20
192
+ end
193
+ end
194
+
195
+ describe ":all => true, :on => context" do
196
+ it "should return records where *all* tags match on the specific context" do
197
+ q0 = TaggableModel.tagged_with(["a", "german"], :on => :skills, :all => true )
198
+ q0.all.length.should == 0
199
+
200
+ q0 = TaggableModel.tagged_with(["a", "b"], :on => :skills, :all => true ).all
201
+ q0.length.should == 2
202
+ q0.should include @t00
203
+ q0.should include @t01
204
+
205
+ q0 = TaggableModel.tagged_with(["a", "b"], :on => :skills, :all => true ).where{foo=="A"}.all
206
+ q0.length.should == 1
207
+ q0.should include @t00
208
+ q0.should_not include @t01
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
data/spec/schema.rb ADDED
@@ -0,0 +1,57 @@
1
+ ActiveRecord::Schema.define :version => 0 do
2
+ create_table "taggings", :force => true do |t|
3
+ t.integer "tag_id", :limit => 11
4
+ t.integer "taggable_id", :limit => 11
5
+ t.string "taggable_type"
6
+ t.string "context"
7
+ t.datetime "created_at"
8
+ t.integer "tagger_id", :limit => 11
9
+ t.string "tagger_type"
10
+ end
11
+
12
+ add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"
13
+ add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context"
14
+
15
+ create_table "tags", :force => true do |t|
16
+ t.string "name"
17
+ end
18
+
19
+ create_table :taggable_models, :force => true do |t|
20
+ t.column :name, :string
21
+ t.column :type, :string
22
+ t.column :foo, :string
23
+ end
24
+
25
+ create_table :non_standard_id_taggable_models, :primary_key => "an_id", :force => true do |t|
26
+ t.column :name, :string
27
+ t.column :type, :string
28
+ end
29
+
30
+ create_table :untaggable_models, :force => true do |t|
31
+ t.column :taggable_model_id, :integer
32
+ t.column :name, :string
33
+ end
34
+
35
+ create_table :cached_models, :force => true do |t|
36
+ t.column :name, :string
37
+ t.column :type, :string
38
+ t.column :cached_tag_list, :string
39
+ end
40
+
41
+ create_table :other_cached_models, :force => true do |t|
42
+ t.column :name, :string
43
+ t.column :type, :string
44
+ t.column :cached_language_list, :string
45
+ t.column :cached_status_list, :string
46
+ t.column :cached_glass_list, :string
47
+ end
48
+
49
+ create_table :taggable_users, :force => true do |t|
50
+ t.column :name, :string
51
+ end
52
+
53
+ create_table :other_taggable_models, :force => true do |t|
54
+ t.column :name, :string
55
+ t.column :type, :string
56
+ end
57
+ end
@@ -0,0 +1,79 @@
1
+ $LOAD_PATH << "." unless $LOAD_PATH.include?(".")
2
+ require 'logger'
3
+
4
+ begin
5
+ require "rubygems"
6
+ require "bundler"
7
+
8
+ if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.5")
9
+ raise RuntimeError, "Your bundler version is too old." +
10
+ "Run `gem install bundler` to upgrade."
11
+ end
12
+
13
+ # Set up load paths for all bundled gems
14
+ Bundler.setup
15
+ rescue Bundler::GemNotFound
16
+ raise RuntimeError, "Bundler couldn't find some gems." +
17
+ "Did you run \`bundlee install\`?"
18
+ end
19
+
20
+ Bundler.require
21
+ require File.expand_path('../../lib/rocket_tag', __FILE__)
22
+
23
+ unless [].respond_to?(:freq)
24
+ class Array
25
+ def freq
26
+ k=Hash.new(0)
27
+ each {|e| k[e]+=1}
28
+ k
29
+ end
30
+ end
31
+ end
32
+
33
+ db_name = ENV['DB'] || 'sqlite3'
34
+ database_yml = File.expand_path('../database.yml', __FILE__)
35
+
36
+ if File.exists?(database_yml)
37
+ active_record_configuration = YAML.load_file(database_yml)
38
+
39
+ ActiveRecord::Base.configurations = active_record_configuration
40
+ config = ActiveRecord::Base.configurations[db_name]
41
+
42
+ begin
43
+ ActiveRecord::Base.establish_connection(db_name)
44
+ ActiveRecord::Base.connection
45
+ rescue
46
+ case db_name
47
+ when /mysql/
48
+ ActiveRecord::Base.establish_connection(config.merge('database' => nil))
49
+ ActiveRecord::Base.connection.create_database(config['database'], {:charset => 'utf8', :collation => 'utf8_unicode_ci'})
50
+ when 'postgresql'
51
+ ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
52
+ ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => 'utf8'))
53
+ end
54
+
55
+ ActiveRecord::Base.establish_connection(config)
56
+ end
57
+
58
+ ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
59
+ ActiveRecord::Base.default_timezone = :utc
60
+
61
+ ActiveRecord::Base.silence do
62
+ ActiveRecord::Migration.verbose = false
63
+
64
+ load(File.dirname(__FILE__) + '/schema.rb')
65
+ load(File.dirname(__FILE__) + '/models.rb')
66
+ end
67
+
68
+ else
69
+ raise "Please create #{database_yml} first to configure your database. Take a look at: #{database_yml}.sample"
70
+ end
71
+
72
+ def clean_database!
73
+ models = [RocketTag::Tag, RocketTag::Tagging, TaggableModel]
74
+ models.each do |model|
75
+ ActiveRecord::Base.connection.execute "DELETE FROM #{model.table_name}"
76
+ end
77
+ end
78
+
79
+ clean_database!
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rocket_tag
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.0
6
+ platform: ruby
7
+ authors:
8
+ - Brad Phelan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-09-16 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ requirement: &id001 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 3.1.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: squeel
28
+ requirement: &id002 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *id002
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ requirement: &id003 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 2.3.0
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *id003
48
+ - !ruby/object:Gem::Dependency
49
+ name: yard
50
+ requirement: &id004 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: 0.6.0
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *id004
59
+ - !ruby/object:Gem::Dependency
60
+ name: bundler
61
+ requirement: &id005 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ~>
65
+ - !ruby/object:Gem::Version
66
+ version: 1.0.0
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *id005
70
+ - !ruby/object:Gem::Dependency
71
+ name: jeweler
72
+ requirement: &id006 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 1.6.4
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: *id006
81
+ - !ruby/object:Gem::Dependency
82
+ name: rcov
83
+ requirement: &id007 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: *id007
92
+ - !ruby/object:Gem::Dependency
93
+ name: sqlite3
94
+ requirement: &id008 !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: "0"
100
+ type: :development
101
+ prerelease: false
102
+ version_requirements: *id008
103
+ description: ""
104
+ email: bradphelan@xtargets.com
105
+ executables: []
106
+
107
+ extensions: []
108
+
109
+ extra_rdoc_files:
110
+ - LICENSE.txt
111
+ - README.md
112
+ files:
113
+ - .document
114
+ - .rspec
115
+ - Gemfile
116
+ - Gemfile.lock
117
+ - LICENSE.txt
118
+ - README.md
119
+ - Rakefile
120
+ - VERSION
121
+ - lib/generators/rocket_tag/migration/migration_generator.rb
122
+ - lib/generators/rocket_tag/migration/templates/active_record/migration.rb
123
+ - lib/rocket_tag.rb
124
+ - lib/rocket_tag/tag.rb
125
+ - lib/rocket_tag/taggable.rb
126
+ - lib/rocket_tag/tagging.rb
127
+ - rocket_tag.gemspec
128
+ - spec/database.yml
129
+ - spec/models.rb
130
+ - spec/rocket_tag/taggable_spec.rb
131
+ - spec/schema.rb
132
+ - spec/spec_helper.rb
133
+ homepage: http://github.com/bradphelan/rocket_tag
134
+ licenses:
135
+ - MIT
136
+ post_install_message:
137
+ rdoc_options: []
138
+
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ none: false
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ hash: -3991953909619882683
147
+ segments:
148
+ - 0
149
+ version: "0"
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ none: false
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: "0"
156
+ requirements: []
157
+
158
+ rubyforge_project:
159
+ rubygems_version: 1.8.8
160
+ signing_key:
161
+ specification_version: 3
162
+ summary: A modern fast tagging framework for Rails 3.1+
163
+ test_files: []
164
+