giraffesoft-is_taggable 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/README.rdoc ADDED
@@ -0,0 +1,52 @@
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
+ Calling is_taggable with any arguments defaults to a tag_list. Instantiating our polyglot user is easy:
25
+
26
+ User.new :tag_list => "rails, giraffesoft", :language_list => "english, french, spanish, latin, esperanto, tlhIngan Hol"
27
+
28
+ A comma is the default tag separator, but this can be easily changed:
29
+
30
+ IsTaggable::TagList.delimiter = " "
31
+
32
+ == Get it
33
+
34
+ $ sudo gem install is_taggable
35
+
36
+ As a rails gem dependency:
37
+
38
+ config.gem 'is_taggable'
39
+
40
+ Or get the source from github:
41
+
42
+ $ git clone git://github.com/giraffesoft/is_taggable.git
43
+
44
+ (or fork it at http://github.com/giraffesoft/is_taggable)
45
+
46
+ == Credits
47
+
48
+ is_taggable was created, and is maintained by Daniel Haran and James Golick.
49
+
50
+ == License
51
+
52
+ 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,90 @@
1
+ path = File.expand_path(File.dirname(__FILE__))
2
+ $LOAD_PATH << path unless $LOAD_PATH.include?(path)
3
+ require 'tag'
4
+ require 'tagging'
5
+
6
+ module IsTaggable
7
+ class TagList < Array
8
+ cattr_accessor :delimiter
9
+ @@delimiter = ','
10
+
11
+ def initialize(list)
12
+ list = list.is_a?(Array) ? list : list.split(@@delimiter).collect(&:strip)
13
+ super
14
+ end
15
+
16
+ def to_s
17
+ join(', ')
18
+ end
19
+ end
20
+
21
+ module ActiveRecordExtension
22
+ def is_taggable(*kinds)
23
+ class_inheritable_accessor :tag_kinds
24
+ self.tag_kinds = kinds.map(&:to_s).map(&:singularize)
25
+ self.tag_kinds << :tag if kinds.empty?
26
+
27
+ include IsTaggable::TaggableMethods
28
+ end
29
+ end
30
+
31
+ module TaggableMethods
32
+ def self.included(klass)
33
+ klass.class_eval do
34
+ include IsTaggable::TaggableMethods::InstanceMethods
35
+
36
+ has_many :taggings, :as => :taggable
37
+ has_many :tags, :through => :taggings
38
+ after_save :save_tags
39
+
40
+ tag_kinds.each do |k|
41
+ define_method("#{k}_list") { get_tag_list(k) }
42
+ define_method("#{k}_list=") { |new_list| set_tag_list(k, new_list) }
43
+ end
44
+ end
45
+ end
46
+
47
+ module InstanceMethods
48
+ def set_tag_list(kind, list)
49
+ tag_list = TagList.new(list)
50
+ instance_variable_set(tag_list_name_for_kind(kind), tag_list)
51
+ end
52
+
53
+ def get_tag_list(kind)
54
+ set_tag_list(kind, tags.of_kind(kind).map(&:name)) if tag_list_instance_variable(kind).nil?
55
+ tag_list_instance_variable(kind)
56
+ end
57
+
58
+ protected
59
+ def tag_list_name_for_kind(kind)
60
+ "@#{kind}_list"
61
+ end
62
+
63
+ def tag_list_instance_variable(kind)
64
+ instance_variable_get(tag_list_name_for_kind(kind))
65
+ end
66
+
67
+ def save_tags
68
+ tag_kinds.each do |tag_kind|
69
+ delete_unused_tags(tag_kind)
70
+ add_new_tags(tag_kind)
71
+ end
72
+
73
+ taggings.each(&:save)
74
+ end
75
+
76
+ def delete_unused_tags(tag_kind)
77
+ tags.of_kind(tag_kind).each { |t| tags.delete(t) unless get_tag_list(tag_kind).include?(t.name) }
78
+ end
79
+
80
+ def add_new_tags(tag_kind)
81
+ tag_names = tags.of_kind(tag_kind).map(&:name)
82
+ get_tag_list(tag_kind).each do |tag_name|
83
+ tags << Tag.find_or_initialize_with_name_like_and_kind(tag_name, tag_kind) unless tag_names.include?(tag_name)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ ActiveRecord::Base.send(:extend, IsTaggable::ActiveRecordExtension)
data/lib/tag.rb ADDED
@@ -0,0 +1,15 @@
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
9
+
10
+ validates_presence_of :name
11
+ validates_uniqueness_of :name, :scope => :kind
12
+
13
+ named_scope :with_name_like_and_kind, lambda { |name, kind| { :conditions => ["name like ? AND kind = ?", name, kind] } }
14
+ named_scope :of_kind, lambda { |kind| { :conditions => {:kind => kind} } }
15
+ end
data/lib/tagging.rb ADDED
@@ -0,0 +1,3 @@
1
+ class Tagging < ActiveRecord::Base
2
+ belongs_to :tag
3
+ end
@@ -0,0 +1,71 @@
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 2 do
67
+ p = Post.new :language_list => "english, french"
68
+ p.save!
69
+ p.tags.length
70
+ end
71
+ end
data/test/tag_test.rb ADDED
@@ -0,0 +1,35 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ Expectations do
4
+ expect Tagging do
5
+ Tag.new.taggings.proxy_reflection.klass
6
+ end
7
+
8
+ expect Tag.new(:name => "duplicate").not.to.be.valid? do
9
+ Tag.create!(:name => "duplicate")
10
+ end
11
+
12
+ expect Tag.new(:name => "not dup").to.be.valid? do
13
+ Tag.create!(:name => "not dup", :kind => "something")
14
+ end
15
+
16
+ expect Tag.new.not.to.be.valid?
17
+ expect String do
18
+ t = Tag.new
19
+ t.valid?
20
+ t.errors[:name]
21
+ end
22
+
23
+ expect Tag.create!(:name => "iamawesome", :kind => "awesomestuff") do
24
+ Tag.find_or_initialize_with_name_like_and_kind("iaMawesome", "awesomestuff")
25
+ end
26
+
27
+ expect true do
28
+ Tag.create!(:name => "iamawesome", :kind => "stuff")
29
+ Tag.find_or_initialize_with_name_like_and_kind("iaMawesome", "otherstuff").new_record?
30
+ end
31
+
32
+ expect Tag.create!(:kind => "language", :name => "french") do
33
+ Tag.of_kind("language").first
34
+ end
35
+ end
@@ -0,0 +1,8 @@
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
+ end
@@ -0,0 +1,40 @@
1
+ require 'rubygems'
2
+ require 'activerecord'
3
+ require File.dirname(__FILE__)+'/../lib/is_taggable'
4
+ require 'expectations'
5
+ require 'logger'
6
+
7
+ ActiveRecord::Base.configurations = {'sqlite3' => {:adapter => 'sqlite3', :database => ':memory:'}}
8
+ ActiveRecord::Base.establish_connection('sqlite3')
9
+
10
+ ActiveRecord::Base.logger = Logger.new(STDERR)
11
+ ActiveRecord::Base.logger.level = Logger::WARN
12
+
13
+ ActiveRecord::Schema.define(:version => 0) do
14
+ create_table :comments do |t|
15
+ end
16
+
17
+ create_table :posts do |t|
18
+ t.string :title, :default => ''
19
+ end
20
+
21
+ create_table :tags do |t|
22
+ t.string :name, :default => ''
23
+ t.string :kind, :default => ''
24
+ end
25
+
26
+ create_table :taggings do |t|
27
+ t.integer :tag_id
28
+
29
+ t.string :taggable_type, :default => ''
30
+ t.integer :taggable_id
31
+ end
32
+ end
33
+
34
+ class Post < ActiveRecord::Base
35
+ is_taggable :tags, :languages
36
+ end
37
+
38
+ class Comment < ActiveRecord::Base
39
+ is_taggable
40
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: giraffesoft-is_taggable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Haran
8
+ - James Golick
9
+ - GiraffeSoft Inc.
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+
14
+ date: 2009-02-16 00:00:00 -08:00
15
+ default_executable:
16
+ dependencies: []
17
+
18
+ description:
19
+ email: chebuctonian@mgmail.com
20
+ executables: []
21
+
22
+ extensions: []
23
+
24
+ extra_rdoc_files: []
25
+
26
+ files:
27
+ - README.rdoc
28
+ - VERSION.yml
29
+ - generators/is_taggable_migration
30
+ - generators/is_taggable_migration/is_taggable_migration_generator.rb
31
+ - generators/is_taggable_migration/templates
32
+ - generators/is_taggable_migration/templates/migration.rb
33
+ - lib/is_taggable.rb
34
+ - lib/tag.rb
35
+ - lib/tagging.rb
36
+ - test/is_taggable_test.rb
37
+ - test/tag_test.rb
38
+ - test/tagging_test.rb
39
+ - test/test_helper.rb
40
+ has_rdoc: true
41
+ homepage: http://github.com/giraffesoft/is_taggable
42
+ post_install_message:
43
+ rdoc_options:
44
+ - --inline-source
45
+ - --charset=UTF-8
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.2.0
64
+ signing_key:
65
+ specification_version: 2
66
+ summary: tagging that doesn't want to be on steroids. it's skinny and happy to stay that way.
67
+ test_files: []
68
+