gutentag 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 69d6508868235e19bd72405fac2df27f9c54ecb9
4
+ data.tar.gz: 836f4a7342bb7846878bb679e18473a99c671f21
5
+ SHA512:
6
+ metadata.gz: 45c8b280565560bfb936d517f9169402f6790b7057705fac454e4e06404c0a60eac90abbdbc45be19b159a7f7390a5d5633d541cb46a3a4e00e1e85ecebc7d89
7
+ data.tar.gz: d720d6e90ba3450fc55e3a231b4a784c6957864fd02f8cd58c050d026b26ad47d6bb1caf89f9365f08db75f8803b33bbed2f885d47336b8c50f6e35b46a2a415
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ spec/internal/db/*.sqlite
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ script: bundle exec rspec spec
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENCE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Pat Allan
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.
@@ -0,0 +1,38 @@
1
+ # Gutentag
2
+
3
+ A good, simple, solid tagging extension for ActiveRecord.
4
+
5
+ This was built partly as a proof-of-concept, and partly to see how a tagging gem could work when it's not all stuffed within models, and partly just because I wanted a simpler tagging library.
6
+
7
+ ## Installation
8
+
9
+ Get it into your Gemfile - and don't forget the version constraint!
10
+
11
+ gem 'gutentag', '~> 0.1.0'
12
+
13
+ Next: your tags get persisted to your database, so let's import and run the migrations to get the tables set up:
14
+
15
+ rake gutentag:install:migrations
16
+ rake db:migrate
17
+
18
+ ## Usage
19
+
20
+ The first step is easy: add the tag associations to whichever models should have tags (in these examples, the Article model):
21
+
22
+ class Article < ActiveRecord::Base
23
+ # ...
24
+ has_many_tags
25
+ # ...
26
+ end
27
+
28
+ That's all it takes to get a tags association on each article. Of course, populating tags can be a little frustrating, unless you want to manage Gutentag::Tag instances yourself? As an alternative, just use the tag_names accessor to get/set tags via string representations.
29
+
30
+ article.tag_names #=> ['pancakes', 'melbourne', 'ruby']
31
+ article.tag_names << 'portland'
32
+ article.tag_names #=> ['pancakes', 'melbourne', 'ruby', 'portland']
33
+ article.tag_names -= ['ruby']
34
+ article.tag_names #=> ['pancakes', 'melbourne', 'portland']
35
+
36
+ ## Licence
37
+
38
+ Copyright (c) 2013, Gutentag is developed and maintained by Pat Allan, and is released under the open MIT Licence.
@@ -0,0 +1,3 @@
1
+ require 'bundler'
2
+
3
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,6 @@
1
+ class Gutentag::Tag < ActiveRecord::Base
2
+ has_many :taggings, :class_name => 'Gutentag::Tagging',
3
+ :dependent => :destroy
4
+
5
+ validates :name, :presence => true, :uniqueness => true
6
+ end
@@ -0,0 +1,7 @@
1
+ class Gutentag::Tagging < ActiveRecord::Base
2
+ belongs_to :taggable, :polymorphic => true
3
+ belongs_to :tag, :class_name => 'Gutentag::Tag'
4
+
5
+ validates :taggable, :presence => true
6
+ validates :tag, :presence => true
7
+ end
@@ -0,0 +1,27 @@
1
+ class GutentagTables < ActiveRecord::Migration
2
+ def up
3
+ create_table :taggings do |t|
4
+ t.integer :tag_id, :null => false
5
+ t.integer :taggable_id, :null => false
6
+ t.string :taggable_type, :null => false
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :taggings, :tag_id
11
+ add_index :taggings, [:taggable_type, :taggable_id]
12
+ add_index :taggings, [:taggable_type, :taggable_id, :tag_id],
13
+ :unique => true, :name => 'unique_taggings'
14
+
15
+ create_table :tags do |t|
16
+ t.string :name, :null => false
17
+ t.timestamps
18
+ end
19
+
20
+ add_index :tags, :name, :unique => true
21
+ end
22
+
23
+ def down
24
+ drop_table :tags
25
+ drop_table :taggings
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |s|
3
+ s.name = 'gutentag'
4
+ s.version = '0.1.0'
5
+ s.authors = ['Pat Allan']
6
+ s.email = ['pat@freelancing-gods.com']
7
+ s.homepage = 'https://github.com/pat/gutentag'
8
+ s.summary = 'Good Tags'
9
+ s.description = 'A good, simple, solid tagging extension for ActiveRecord'
10
+
11
+ s.files = `git ls-files`.split("\n")
12
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
13
+ s.require_paths = ['lib']
14
+
15
+ s.add_runtime_dependency 'activerecord', '>= 3.2.0'
16
+ s.add_development_dependency 'combustion', '~> 0.4.0'
17
+ s.add_development_dependency 'rspec-rails', '~> 2.13'
18
+ s.add_development_dependency 'sqlite3', '~> 1.3.7'
19
+ end
@@ -0,0 +1,7 @@
1
+ module Gutentag
2
+ #
3
+ end
4
+
5
+ require 'gutentag/active_record'
6
+ require 'gutentag/engine'
7
+ require 'gutentag/tag_names'
@@ -0,0 +1,26 @@
1
+ require 'active_support/concern'
2
+
3
+ module Gutentag::ActiveRecord
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def has_many_tags
8
+ has_many :taggings, :class_name => 'Gutentag::Tagging', :as => :taggable,
9
+ :dependent => :destroy
10
+ has_many :tags, :class_name => 'Gutentag::Tag',
11
+ :through => :taggings
12
+ end
13
+ end
14
+
15
+ def tag_names
16
+ @tag_names ||= Gutentag::TagNames.new self
17
+ end
18
+
19
+ def tag_names=(names)
20
+ if names.is_a?(Gutentag::TagNames)
21
+ @tag_names = names
22
+ else
23
+ @tag_names = Gutentag::TagNames.new_with_names self, names
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ require 'rails/engine'
2
+
3
+ class Gutentag::Engine < Rails::Engine
4
+ engine_name :gutentag
5
+
6
+ ActiveSupport.on_load :active_record do
7
+ include Gutentag::ActiveRecord
8
+ end
9
+ end
@@ -0,0 +1,51 @@
1
+ class Gutentag::TagNames
2
+ include Enumerable
3
+
4
+ def self.new_with_names(taggable, names)
5
+ tag_names = new(taggable)
6
+ tag_names.clear
7
+ names.each { |name| tag_names << name }
8
+ tag_names
9
+ end
10
+
11
+ def initialize(taggable)
12
+ @taggable = taggable
13
+ end
14
+
15
+ def to_a
16
+ taggable.tags.collect &:name
17
+ end
18
+
19
+ def +(array)
20
+ array.each { |name| self.<< name }
21
+ self
22
+ end
23
+
24
+ def -(array)
25
+ array.each { |name| self.delete name }
26
+ self
27
+ end
28
+
29
+ def <<(name)
30
+ tag = Gutentag::Tag.where(:name => name).first ||
31
+ Gutentag::Tag.create(:name => name)
32
+
33
+ taggable.tags << tag
34
+ end
35
+
36
+ def clear
37
+ taggable.tags.clear
38
+ end
39
+
40
+ def delete(name)
41
+ taggable.tags.delete Gutentag::Tag.where(:name => name).first
42
+ end
43
+
44
+ def each(&block)
45
+ to_a.each &block
46
+ end
47
+
48
+ private
49
+
50
+ attr_reader :taggable
51
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Managing tags via names" do
4
+ let(:article) { Article.create }
5
+
6
+ it "returns tag names" do
7
+ melbourne = Gutentag::Tag.create :name => 'melbourne'
8
+
9
+ article.tags << melbourne
10
+
11
+ article.tag_names.to_a.should == ['melbourne']
12
+ end
13
+
14
+ it "adds tags via their names" do
15
+ article.tag_names << 'melbourne'
16
+
17
+ article.tags.collect(&:name).should == ['melbourne']
18
+ end
19
+
20
+ it "accepts a completely new set of tags" do
21
+ article.tag_names = ['portland', 'oregon']
22
+
23
+ article.tags.collect(&:name).should == ['portland', 'oregon']
24
+ end
25
+
26
+ it "enumerates through tag names" do
27
+ article.tag_names = ['melbourne', 'victoria']
28
+ names = []
29
+
30
+ article.tag_names.each do |name|
31
+ names << name
32
+ end
33
+
34
+ names.should == ['melbourne', 'victoria']
35
+ end
36
+
37
+ it "does not allow duplication of tags" do
38
+ existing = Article.create
39
+ existing.tags << Gutentag::Tag.create(:name => 'portland')
40
+
41
+ article.tag_names = ['portland']
42
+
43
+ existing.tag_ids.should == article.tag_ids
44
+ end
45
+
46
+ it "appends tag names" do
47
+ article.tag_names = ['portland']
48
+ article.tag_names += ['oregon', 'ruby']
49
+
50
+ article.tags.collect(&:name).should == ['portland', 'oregon', 'ruby']
51
+ end
52
+
53
+ it "removes a single tag name" do
54
+ article.tag_names = ['portland', 'oregon']
55
+ article.tag_names.delete 'oregon'
56
+
57
+ article.tags.collect(&:name).should == ['portland']
58
+ end
59
+
60
+ it "removes tag names" do
61
+ article.tag_names = ['portland', 'oregon', 'ruby']
62
+ article.tag_names -= ['oregon', 'ruby']
63
+
64
+ article.tags.collect(&:name).should == ['portland']
65
+ end
66
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Adding and removing tags' do
4
+ let(:article) { Article.create }
5
+ let(:pancakes) { Gutentag::Tag.create :name => 'pancakes' }
6
+
7
+ it "stores new tags" do
8
+ article.tags << pancakes
9
+
10
+ article.tags.reload.should == [pancakes]
11
+ end
12
+
13
+ it "removes existing tags" do
14
+ article.tags << pancakes
15
+
16
+ article.tags.delete pancakes
17
+
18
+ article.tags.reload.should == []
19
+ end
20
+
21
+ it "removes taggings when an article is deleted" do
22
+ article.tags << pancakes
23
+
24
+ article.destroy
25
+
26
+ Gutentag::Tagging.where(
27
+ :taggable_type => 'Article', :taggable_id => article.id
28
+ ).count.should be_zero
29
+ end
30
+
31
+ it "removes taggings when a tag is deleted" do
32
+ article.tags << pancakes
33
+
34
+ pancakes.destroy
35
+
36
+ Gutentag::Tagging.where(:tag_id => pancakes.id).count.should be_zero
37
+ end
38
+ end
@@ -0,0 +1,3 @@
1
+ class Article < ActiveRecord::Base
2
+ has_many_tags
3
+ end
@@ -0,0 +1,3 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: db/combustion_test.sqlite
@@ -0,0 +1,6 @@
1
+ ActiveRecord::Schema.define do
2
+ create_table :articles, :force => true do |t|
3
+ t.string :title
4
+ t.timestamps
5
+ end
6
+ end
@@ -0,0 +1 @@
1
+ *.log
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+
3
+ Bundler.require :default, :development
4
+
5
+ Combustion.initialize! :active_record
6
+
7
+ require 'rspec/rails'
8
+
9
+ RSpec.configure do |config|
10
+ config.use_transactional_fixtures = true
11
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gutentag
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Pat Allan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 3.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 3.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: combustion
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.4.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 0.4.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '2.13'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '2.13'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 1.3.7
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 1.3.7
69
+ description: A good, simple, solid tagging extension for ActiveRecord
70
+ email:
71
+ - pat@freelancing-gods.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .travis.yml
78
+ - Gemfile
79
+ - LICENCE
80
+ - README.md
81
+ - Rakefile
82
+ - app/models/gutentag/tag.rb
83
+ - app/models/gutentag/tagging.rb
84
+ - db/migrate/1_gutentag_tables.rb
85
+ - gutentag.gemspec
86
+ - lib/gutentag.rb
87
+ - lib/gutentag/active_record.rb
88
+ - lib/gutentag/engine.rb
89
+ - lib/gutentag/tag_names.rb
90
+ - spec/acceptance/tag_names_spec.rb
91
+ - spec/acceptance/tags_spec.rb
92
+ - spec/internal/app/models/article.rb
93
+ - spec/internal/config/database.yml
94
+ - spec/internal/db/schema.rb
95
+ - spec/internal/log/.gitignore
96
+ - spec/spec_helper.rb
97
+ homepage: https://github.com/pat/gutentag
98
+ licenses: []
99
+ metadata: {}
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - '>='
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubyforge_project:
116
+ rubygems_version: 2.0.2
117
+ signing_key:
118
+ specification_version: 4
119
+ summary: Good Tags
120
+ test_files: []