gutentag 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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +3 -0
- data/LICENCE +20 -0
- data/README.md +38 -0
- data/Rakefile +3 -0
- data/app/models/gutentag/tag.rb +6 -0
- data/app/models/gutentag/tagging.rb +7 -0
- data/db/migrate/1_gutentag_tables.rb +27 -0
- data/gutentag.gemspec +19 -0
- data/lib/gutentag.rb +7 -0
- data/lib/gutentag/active_record.rb +26 -0
- data/lib/gutentag/engine.rb +9 -0
- data/lib/gutentag/tag_names.rb +51 -0
- data/spec/acceptance/tag_names_spec.rb +66 -0
- data/spec/acceptance/tags_spec.rb +38 -0
- data/spec/internal/app/models/article.rb +3 -0
- data/spec/internal/config/database.yml +3 -0
- data/spec/internal/db/schema.rb +6 -0
- data/spec/internal/log/.gitignore +1 -0
- data/spec/spec_helper.rb +11 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
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.
|
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
data/gutentag.gemspec
ADDED
@@ -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
|
data/lib/gutentag.rb
ADDED
@@ -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,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 @@
|
|
1
|
+
*.log
|
data/spec/spec_helper.rb
ADDED
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: []
|