is_taggable_rails3 0.3.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/README.rdoc ADDED
@@ -0,0 +1,56 @@
1
+ = is_taggable
2
+
3
+ At last, a short and sweet tagging implementation that you can easily modify and extend.
4
+
5
+ Most of the plugins out there are on steroids or messing directly with SQL, a known gateway drug.
6
+
7
+ We wanted a more sober plugin that would handle new functionality without breaking a sweat. Some plugins had minimal or no tests *gasp*. They were so messed up that operating would likely cause internal bleeding.
8
+
9
+ So, we crafted the plugin we needed with the functionality we were looking for: tag kinds. It's small and healthy. So, it should be a good base to build on.
10
+
11
+ == Usage
12
+
13
+ After generating the migration:
14
+
15
+ $ script/generate is_taggable_migration
16
+ $ rake db:migrate
17
+
18
+ All you need is the 'is_taggable' declaration in your models:
19
+
20
+ class User < ActiveRecord::Base
21
+ is_taggable :tags, :languages
22
+ end
23
+
24
+ In your forms, add a text fields for "tag_list" and/or "language_list" (matching the example model above):
25
+
26
+ <%= f.text_field :tag_list %>
27
+
28
+ Calling is_taggable with any arguments defaults to a tag_list. Instantiating our polyglot user is easy:
29
+
30
+ User.new :tag_list => "rails, giraffesoft", :language_list => "english, french, spanish, latin, esperanto, tlhIngan Hol"
31
+
32
+ A comma is the default tag separator, but this can be easily changed:
33
+
34
+ IsTaggable::TagList.delimiter = " "
35
+
36
+ == Get it
37
+
38
+ $ sudo gem install is_taggable
39
+
40
+ As a rails gem dependency:
41
+
42
+ config.gem 'is_taggable'
43
+
44
+ Or get the source from github:
45
+
46
+ $ git clone git://github.com/giraffesoft/is_taggable.git
47
+
48
+ (or fork it at http://github.com/giraffesoft/is_taggable)
49
+
50
+ == Credits
51
+
52
+ is_taggable was created, and is maintained by Daniel Haran and James Golick.
53
+
54
+ == License
55
+
56
+ is_taggable is available under the MIT License
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 1
3
+ :patch: 0
4
+ :major: 0
@@ -0,0 +1,7 @@
1
+ class IsTaggableMigrationGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => 'is_taggable_migration'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,24 @@
1
+ class IsTaggableMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :tags do |t|
4
+ t.string :name, :default => ''
5
+ t.string :kind, :default => ''
6
+ end
7
+
8
+ create_table :taggings do |t|
9
+ t.integer :tag_id
10
+
11
+ t.string :taggable_type, :default => ''
12
+ t.integer :taggable_id
13
+ end
14
+
15
+ add_index :tags, [:name, :kind]
16
+ add_index :taggings, :tag_id
17
+ add_index :taggings, [:taggable_id, :taggable_type]
18
+ end
19
+
20
+ def self.down
21
+ drop_table :taggings
22
+ drop_table :tags
23
+ end
24
+ end
@@ -0,0 +1,92 @@
1
+ require 'tag'
2
+ require 'tagging'
3
+
4
+ module IsTaggable
5
+ class TagList < Array
6
+ cattr_accessor :delimiter
7
+ @@delimiter = ','
8
+
9
+ def initialize(list)
10
+ list = list.is_a?(Array) ? list : list.split(@@delimiter).collect(&:strip).reject(&:blank?)
11
+ super
12
+ end
13
+
14
+ def to_s
15
+ join(@@delimiter)
16
+ end
17
+ end
18
+
19
+ module ActiveRecordExtension
20
+ def is_taggable(*kinds)
21
+ if respond_to?(:class_attribute)
22
+ class_attribute :tag_kinds
23
+ else
24
+ class_inheritable_accessor :tag_kinds
25
+ end
26
+ self.tag_kinds = kinds.map(&:to_s).map(&:singularize)
27
+ self.tag_kinds << :tag if kinds.empty?
28
+
29
+ include IsTaggable::TaggableMethods
30
+ end
31
+ end
32
+
33
+ module TaggableMethods
34
+ def self.included(klass)
35
+ klass.class_eval do
36
+ include IsTaggable::TaggableMethods::InstanceMethods
37
+
38
+ has_many :taggings, :as => :taggable, :dependent => :destroy
39
+ has_many :tags, :through => :taggings
40
+ after_save :save_tags
41
+
42
+ tag_kinds.each do |k|
43
+ define_method("#{k}_list") { get_tag_list(k) }
44
+ define_method("#{k}_list=") { |new_list| set_tag_list(k, new_list) }
45
+ end
46
+ end
47
+ end
48
+
49
+ module InstanceMethods
50
+ def set_tag_list(kind, list)
51
+ tag_list = TagList.new(list)
52
+ instance_variable_set(tag_list_name_for_kind(kind), tag_list)
53
+ end
54
+
55
+ def get_tag_list(kind)
56
+ set_tag_list(kind, tags.of_kind(kind).map(&:name)) if tag_list_instance_variable(kind).nil?
57
+ tag_list_instance_variable(kind)
58
+ end
59
+
60
+ protected
61
+ def tag_list_name_for_kind(kind)
62
+ "@#{kind}_list"
63
+ end
64
+
65
+ def tag_list_instance_variable(kind)
66
+ instance_variable_get(tag_list_name_for_kind(kind))
67
+ end
68
+
69
+ def save_tags
70
+ tag_kinds.each do |tag_kind|
71
+ delete_unused_tags(tag_kind)
72
+ add_new_tags(tag_kind)
73
+ end
74
+
75
+ taggings.each(&:save)
76
+ end
77
+
78
+ def delete_unused_tags(tag_kind)
79
+ tags.of_kind(tag_kind).each { |t| tags.delete(t) unless get_tag_list(tag_kind).include?(t.name) }
80
+ end
81
+
82
+ def add_new_tags(tag_kind)
83
+ tag_names = tags.of_kind(tag_kind).map(&:name)
84
+ get_tag_list(tag_kind).each do |tag_name|
85
+ tags << Tag.find_or_initialize_with_name_like_and_kind(tag_name, tag_kind) unless tag_names.include?(tag_name)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ ActiveRecord::Base.send(:extend, IsTaggable::ActiveRecordExtension)
data/lib/tag.rb ADDED
@@ -0,0 +1,24 @@
1
+ class Tag < ActiveRecord::Base
2
+ class << self
3
+ def find_or_initialize_with_name_like_and_kind(name, kind)
4
+ with_name_like_and_kind(name, kind).first || new(:name => name, :kind => kind)
5
+ end
6
+ end
7
+
8
+ has_many :taggings, :dependent => :destroy
9
+
10
+ validates_presence_of :name
11
+ validates_uniqueness_of :name, :scope => :kind
12
+
13
+ if Tag.respond_to? :scope
14
+ scope :with_name_like_and_kind, lambda {|name, kind|
15
+ where(:name => name.to_s.downcase, :kind => kind.to_s.singularize)
16
+ }
17
+ scope :of_kind, lambda {|kind| where(:kind => kind.to_s.singularize) }
18
+ else
19
+ named_scope :with_name_like_and_kind, lambda {|name, kind|
20
+ { :conditions => {:name => name.to_s.downcase, :kind => kind.to_s.singularize} }
21
+ }
22
+ named_scope :of_kind, lambda {|kind| { :conditions => {:kind => kind} } }
23
+ end
24
+ end
data/lib/tagging.rb ADDED
@@ -0,0 +1,4 @@
1
+ class Tagging < ActiveRecord::Base
2
+ belongs_to :tag
3
+ belongs_to :taggable, :polymorphic => true
4
+ end
@@ -0,0 +1,83 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ Expectations do
4
+ expect Tag do
5
+ Post.new.tags.build
6
+ end
7
+
8
+ expect Tagging do
9
+ Post.new.taggings.build
10
+ end
11
+
12
+ expect ["is_taggable", "has 'tags' by default"] do
13
+ n = Comment.new :tag_list => "is_taggable, has 'tags' by default"
14
+ n.tag_list
15
+ end
16
+
17
+ expect ["one", "two"] do
18
+ IsTaggable::TagList.delimiter = " "
19
+ n = Comment.new :tag_list => "one two"
20
+ IsTaggable::TagList.delimiter = "," # puts things back to avoid breaking following tests
21
+ n.tag_list
22
+ end
23
+
24
+ expect ["something cool", "something else cool"] do
25
+ p = Post.new :tag_list => "something cool, something else cool"
26
+ p.tag_list
27
+ end
28
+
29
+ expect ["something cool", "something new"] do
30
+ p = Post.new :tag_list => "something cool, something else cool"
31
+ p.save!
32
+ p.tag_list = "something cool, something new"
33
+ p.save!
34
+ p.tags.reload
35
+ p.instance_variable_set("@tag_list", nil)
36
+ p.tag_list
37
+ end
38
+
39
+ expect ["english", "french"] do
40
+ p = Post.new :language_list => "english, french"
41
+ p.save!
42
+ p.tags.reload
43
+ p.instance_variable_set("@language_list", nil)
44
+ p.language_list
45
+ end
46
+
47
+ expect ["english", "french"] do
48
+ p = Post.new :language_list => "english, french"
49
+ p.language_list
50
+ end
51
+
52
+ expect "english,french" do
53
+ p = Post.new :language_list => "english, french"
54
+ p.language_list.to_s
55
+ end
56
+
57
+ # added - should clean up strings with arbitrary spaces around commas
58
+ expect ["spaces","should","not","matter"] do
59
+ p = Post.new
60
+ p.tag_list = "spaces,should, not,matter"
61
+ p.save!
62
+ p.tags.reload
63
+ p.tag_list
64
+ end
65
+
66
+ expect ["blank","topics","should be ignored"] do
67
+ p = Post.new
68
+ p.tag_list = "blank, topics, should be ignored, "
69
+ p.save!
70
+ p.tags.reload
71
+ p.tag_list
72
+ end
73
+
74
+ expect 2 do
75
+ p = Post.new :language_list => "english, french"
76
+ p.save!
77
+ p.tags.length
78
+ end
79
+
80
+ expect 1 do
81
+ Tag.with_name_like_and_kind('english', :languages).count
82
+ end
83
+ end
data/test/tag_test.rb ADDED
@@ -0,0 +1,41 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ Expectations do
4
+ expect Tagging do
5
+ t = Tag.new
6
+ if t.respond_to?(:association)
7
+ t.association(:taggings).reflection.klass
8
+ else
9
+ t.taggings.proxy_reflection.klass
10
+ end
11
+ end
12
+
13
+ expect Tag.new(:name => "duplicate").not.to.be.valid? do
14
+ Tag.create!(:name => "duplicate")
15
+ end
16
+
17
+ expect Tag.new(:name => "not dup").to.be.valid? do
18
+ Tag.create!(:name => "not dup", :kind => "something")
19
+ end
20
+
21
+ expect Tag.new.not.to.be.valid?
22
+ expect String do
23
+ t = Tag.new
24
+ t.valid?
25
+ m = t.errors[:name]
26
+ m.is_a?(Array) ? m.first : m
27
+ end
28
+
29
+ expect Tag.create!(:name => "iamawesome", :kind => "awesomestuff") do
30
+ Tag.find_or_initialize_with_name_like_and_kind("iaMawesome", "awesomestuff")
31
+ end
32
+
33
+ expect true do
34
+ Tag.create!(:name => "iamawesome", :kind => "stuff")
35
+ Tag.find_or_initialize_with_name_like_and_kind("iaMawesome", "otherstuff").new_record?
36
+ end
37
+
38
+ expect Tag.create!(:kind => "language", :name => "french") do
39
+ Tag.of_kind("language").first
40
+ end
41
+ end
@@ -0,0 +1,38 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ Expectations do
4
+ expect Tag do
5
+ t = Tagging.new :tag => Tag.new(:name => 'some_tag')
6
+ t.tag
7
+ end
8
+
9
+ expect Post do
10
+ t = Tagging.new :taggable => Post.new
11
+ t.taggable
12
+ end
13
+
14
+ expect 2 do
15
+ 2.times { Post.create(:tag_list => "interesting") }
16
+ Tag.find_by_name("interesting").taggings.count
17
+ end
18
+
19
+ expect 1 do
20
+ p1 = Post.create(:tag_list => "witty")
21
+ p2 = Post.create(:tag_list => "witty")
22
+
23
+ p2.destroy
24
+ Tag.find_by_name("witty").taggings.count
25
+ end
26
+
27
+ expect 2 do
28
+ p1 = Post.create(:tag_list => "smart, pretty")
29
+ p1.taggings.count
30
+ end
31
+
32
+ expect 1 do
33
+ p1 = Post.create(:tag_list => "mildly, inappropriate")
34
+
35
+ Tag.find_by_name('inappropriate').destroy
36
+ p1.taggings.count
37
+ end
38
+ end
@@ -0,0 +1,41 @@
1
+ $:.unshift File.expand_path('../lib', File.dirname(__FILE__))
2
+
3
+ require 'active_record'
4
+ require 'is_taggable'
5
+ require 'expectations'
6
+ require 'logger'
7
+
8
+ ActiveRecord::Base.configurations = {'sqlite3' => {:adapter => 'sqlite3', :database => ':memory:'}}
9
+ ActiveRecord::Base.establish_connection('sqlite3')
10
+
11
+ ActiveRecord::Base.logger = Logger.new(STDERR)
12
+ ActiveRecord::Base.logger.level = Logger::WARN
13
+
14
+ ActiveRecord::Schema.define(:version => 0) do
15
+ create_table :comments do |t|
16
+ end
17
+
18
+ create_table :posts do |t|
19
+ t.string :title, :default => ''
20
+ end
21
+
22
+ create_table :tags do |t|
23
+ t.string :name, :default => ''
24
+ t.string :kind, :default => ''
25
+ end
26
+
27
+ create_table :taggings do |t|
28
+ t.integer :tag_id
29
+
30
+ t.string :taggable_type, :default => ''
31
+ t.integer :taggable_id
32
+ end
33
+ end
34
+
35
+ class Post < ActiveRecord::Base
36
+ is_taggable :tags, :languages
37
+ end
38
+
39
+ class Comment < ActiveRecord::Base
40
+ is_taggable
41
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: is_taggable_rails3
3
+ version: !ruby/object:Gem::Version
4
+ hash: 19
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
11
+ platform: ruby
12
+ authors:
13
+ - Daniel Haran
14
+ - James Golick
15
+ - GiraffeSoft Inc.
16
+ autorequire:
17
+ bindir: bin
18
+ cert_chain: []
19
+
20
+ date: 2012-01-10 00:00:00 Z
21
+ dependencies: []
22
+
23
+ description: ""
24
+ email: chebuctonian@mgmail.com
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files: []
30
+
31
+ files:
32
+ - README.rdoc
33
+ - VERSION.yml
34
+ - generators/is_taggable_migration/is_taggable_migration_generator.rb
35
+ - generators/is_taggable_migration/templates/migration.rb
36
+ - lib/is_taggable.rb
37
+ - lib/tag.rb
38
+ - lib/tagging.rb
39
+ - test/is_taggable_test.rb
40
+ - test/tag_test.rb
41
+ - test/tagging_test.rb
42
+ - test/test_helper.rb
43
+ homepage: http://github.com/sbeam/is_taggable_rails3
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --inline-source
49
+ - --charset=UTF-8
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ hash: 3
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 1.7.2
74
+ signing_key:
75
+ specification_version: 2
76
+ summary: tagging that doesn't want to be on steroids. it's skinny and happy to stay that way.
77
+ test_files: []
78
+