tagger 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source :rubygems
2
+
3
+ gem "rails", "3.0.0"
4
+ gem "rspec-rails", "2.0.0.rc"
5
+ gem "activerecord", "3.0.0"
6
+ gem "sqlite3-ruby"
7
+
8
+ platforms :mri_19 do
9
+ gem "ruby-debug19", :require => "ruby-debug"
10
+ end
11
+
12
+ platforms :mri_18 do
13
+ gem "ruby-debug"
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,110 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ abstract (1.0.0)
5
+ actionmailer (3.0.0)
6
+ actionpack (= 3.0.0)
7
+ mail (~> 2.2.5)
8
+ actionpack (3.0.0)
9
+ activemodel (= 3.0.0)
10
+ activesupport (= 3.0.0)
11
+ builder (~> 2.1.2)
12
+ erubis (~> 2.6.6)
13
+ i18n (~> 0.4.1)
14
+ rack (~> 1.2.1)
15
+ rack-mount (~> 0.6.12)
16
+ rack-test (~> 0.5.4)
17
+ tzinfo (~> 0.3.23)
18
+ activemodel (3.0.0)
19
+ activesupport (= 3.0.0)
20
+ builder (~> 2.1.2)
21
+ i18n (~> 0.4.1)
22
+ activerecord (3.0.0)
23
+ activemodel (= 3.0.0)
24
+ activesupport (= 3.0.0)
25
+ arel (~> 1.0.0)
26
+ tzinfo (~> 0.3.23)
27
+ activeresource (3.0.0)
28
+ activemodel (= 3.0.0)
29
+ activesupport (= 3.0.0)
30
+ activesupport (3.0.0)
31
+ archive-tar-minitar (0.5.2)
32
+ arel (1.0.1)
33
+ activesupport (~> 3.0.0)
34
+ builder (2.1.2)
35
+ columnize (0.3.1)
36
+ diff-lcs (1.1.2)
37
+ erubis (2.6.6)
38
+ abstract (>= 1.0.0)
39
+ i18n (0.4.1)
40
+ linecache (0.43)
41
+ linecache19 (0.5.11)
42
+ ruby_core_source (>= 0.1.4)
43
+ mail (2.2.5)
44
+ activesupport (>= 2.3.6)
45
+ mime-types
46
+ treetop (>= 1.4.5)
47
+ mime-types (1.16)
48
+ polyglot (0.3.1)
49
+ rack (1.2.1)
50
+ rack-mount (0.6.13)
51
+ rack (>= 1.0.0)
52
+ rack-test (0.5.4)
53
+ rack (>= 1.0)
54
+ rails (3.0.0)
55
+ actionmailer (= 3.0.0)
56
+ actionpack (= 3.0.0)
57
+ activerecord (= 3.0.0)
58
+ activeresource (= 3.0.0)
59
+ activesupport (= 3.0.0)
60
+ bundler (~> 1.0.0)
61
+ railties (= 3.0.0)
62
+ railties (3.0.0)
63
+ actionpack (= 3.0.0)
64
+ activesupport (= 3.0.0)
65
+ rake (>= 0.8.4)
66
+ thor (~> 0.14.0)
67
+ rake (0.8.7)
68
+ rspec (2.0.0.rc)
69
+ rspec-core (= 2.0.0.rc)
70
+ rspec-expectations (= 2.0.0.rc)
71
+ rspec-mocks (= 2.0.0.rc)
72
+ rspec-core (2.0.0.rc)
73
+ rspec-expectations (2.0.0.rc)
74
+ diff-lcs (>= 1.1.2)
75
+ rspec-mocks (2.0.0.rc)
76
+ rspec-core (= 2.0.0.rc)
77
+ rspec-expectations (= 2.0.0.rc)
78
+ rspec-rails (2.0.0.rc)
79
+ rspec (= 2.0.0.rc)
80
+ ruby-debug (0.10.3)
81
+ columnize (>= 0.1)
82
+ ruby-debug-base (~> 0.10.3.0)
83
+ ruby-debug-base (0.10.3)
84
+ linecache (>= 0.3)
85
+ ruby-debug-base19 (0.11.24)
86
+ columnize (>= 0.3.1)
87
+ linecache19 (>= 0.5.11)
88
+ ruby_core_source (>= 0.1.4)
89
+ ruby-debug19 (0.11.6)
90
+ columnize (>= 0.3.1)
91
+ linecache19 (>= 0.5.11)
92
+ ruby-debug-base19 (>= 0.11.19)
93
+ ruby_core_source (0.1.4)
94
+ archive-tar-minitar (>= 0.5.2)
95
+ sqlite3-ruby (1.3.1)
96
+ thor (0.14.0)
97
+ treetop (1.4.8)
98
+ polyglot (>= 0.3.1)
99
+ tzinfo (0.3.23)
100
+
101
+ PLATFORMS
102
+ ruby
103
+
104
+ DEPENDENCIES
105
+ activerecord (= 3.0.0)
106
+ rails (= 3.0.0)
107
+ rspec-rails (= 2.0.0.rc)
108
+ ruby-debug
109
+ ruby-debug19
110
+ sqlite3-ruby
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Nando Vieira
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.rdoc ADDED
@@ -0,0 +1,45 @@
1
+ = Tagger
2
+
3
+ == Installing
4
+
5
+ 1. Install the plugin with <tt>script/plugin install git://github.com/fnando/has_tags.git</tt>
6
+ 2. Generate the migration files with <tt>rails generate tagger:install</tt>.
7
+ 3. Finally, run the migrations with <tt>rake db:migrate</tt>.
8
+
9
+ == Usage
10
+
11
+ Add the method call <tt>taggable</tt> to your model.
12
+
13
+ class Link < ActiveRecord::Base
14
+ taggable :scope => :category
15
+ belongs_to :category
16
+ end
17
+
18
+ class Category < ActiveRecord::Base
19
+ has_many :links
20
+ end
21
+
22
+ @post = Post.first
23
+ @category = Category.first
24
+
25
+ # set tags using the chosen tag separator
26
+ @post.tagged_with = "rails, activerecord, models"
27
+
28
+ # get tags joined with the chosen tag separator
29
+ @post.tagged_with
30
+
31
+ # get all tags as objects
32
+ @post.tags
33
+
34
+ # check if an user tagged this object
35
+ @post.tag_owner?(current_user)
36
+
37
+ # generate a tag cloud
38
+ @tags = Tag.cloud(:link, :limit => 100, :scope => @category)
39
+
40
+ # at the view, call the helper tag_cloud
41
+ tag_cloud @tags do |tag_name, css|
42
+ <%= link_to tag_name, tag_path(tag_name), :class => css
43
+ end
44
+
45
+ Copyright (c) 2008 Nando Vieira, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require "rspec/core/rake_task"
2
+ require "./lib/tagger/version"
3
+
4
+ RSpec::Core::RakeTask.new do |t|
5
+ t.ruby_opts = %w[-Ilib -Ispec]
6
+ end
7
+
8
+ begin
9
+ require "jeweler"
10
+
11
+ JEWEL = Jeweler::Tasks.new do |gem|
12
+ gem.name = "tagger"
13
+ gem.version = Tagger::Version::STRING
14
+ gem.summary = "Add tagging support for Rails apps"
15
+ gem.description = "Add tagging support for Rails apps"
16
+ gem.authors = ["Nando Vieira"]
17
+ gem.email = "fnando.vieira@gmail.com"
18
+ gem.homepage = "http://github.com/fnando/tagger"
19
+ gem.has_rdoc = false
20
+ gem.add_dependency "rails", ">= 3.0.0"
21
+ gem.files = FileList["{Gemfile,Gemfile.lock,Rakefile,MIT-LICENSE,tagger.gemspec,README.rdoc}", "{lib,spec,templates}/**/*"]
22
+ end
23
+
24
+ Jeweler::GemcutterTasks.new
25
+ rescue LoadError => e
26
+ puts "You don't Jeweler installed, so you won't be able to build gems."
27
+ end
@@ -0,0 +1,78 @@
1
+ module Tagger
2
+ module ActiveRecord
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+
6
+ class << base
7
+ attr_accessor :taggable_options
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+ def taggable(options={})
13
+ include InstanceMethods
14
+
15
+ self.taggable_options = {
16
+ :separator => options[:separator],
17
+ :scope => options[:scope]
18
+ }
19
+
20
+ has_many :taggings, :as => :taggable, :dependent => :destroy
21
+ has_many :tags, :through => :taggings, :order => "tags.name asc"
22
+
23
+ after_save :save_tags
24
+ end
25
+ end
26
+
27
+ module InstanceMethods
28
+ def tagged_with=(tags)
29
+ @tagged_with = joined_tags(parsed_tags(tags))
30
+ end
31
+
32
+ def tagged_with
33
+ @tagged_with ||= joined_tags(tags.all.collect(&:name))
34
+ end
35
+
36
+ private
37
+ # Parse a tag string using the preferred tag separator.
38
+ #
39
+ # parsed_tags("a, d, c, b")
40
+ # #=> ["a", "b", "c", "d"]
41
+ #
42
+ def parsed_tags(tags)
43
+ Tag.parse(tags, self.class.taggable_options[:separator])
44
+ end
45
+
46
+ # Receive an array of tags and join them using the
47
+ # preferred tag separator.
48
+ #
49
+ # joined_tags %w[b c d a]
50
+ # #=> a, b, c, d
51
+ #
52
+ def joined_tags(tags)
53
+ tags = tags.sort_by {|tag| tag.downcase}
54
+
55
+ if self.class.taggable_options[:separator] == :space
56
+ tags.collect {|tag| tag.include?(" ") ? %["#{tag}"] : tag }.join(" ")
57
+ else
58
+ tags.join(", ")
59
+ end
60
+ end
61
+
62
+ # Remove all associated tags and re-create them.
63
+ def save_tags
64
+ Tag.transaction do
65
+ taggings.delete_all
66
+
67
+ scope = nil
68
+ scope = send(self.class.taggable_options[:scope]) if self.class.taggable_options[:scope]
69
+
70
+ parsed_tags(tagged_with).each do |tag_name|
71
+ tag = Tag.find_or_create_by_name(tag_name)
72
+ self.taggings.create(:tag => tag, :scope => scope)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,13 @@
1
+ require "rails/generators/base"
2
+
3
+ module Tagger
4
+ class InstallGenerator < ::Rails::Generators::Base
5
+ source_root File.dirname(__FILE__) + "/../../templates"
6
+
7
+ def copy_migrations
8
+ stamp = proc {|time| time.utc.strftime("%Y%m%d%H%M%S")}
9
+ copy_file "tags.rb", "db/migrations/#{stamp[Time.now]}_create_tags.rb"
10
+ copy_file "taggings.rb", "db/migrations/#{stamp[1.second.from_now]}_create_taggings.rb"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ module Tagger
2
+ module Helper
3
+ # Build a tag cloud based on the provided tags.
4
+ #
5
+ # tag_cloud @tags do {|tag_name, css_class|}
6
+ # tag_cloud @tags, %w(t1 t2 t3 t4 t5) do {|tag_name, css_class| #=> do something }
7
+ #
8
+ def tag_cloud(tags, css_classes = nil, &block)
9
+ # set css classes if not specified
10
+ css_classes ||= %w[s1 s2 s3 s4 s5]
11
+
12
+ # collect all tag totals
13
+ totals = tags.collect(&:total)
14
+
15
+ # get max and min totals
16
+ max = totals.max.to_i
17
+ min = totals.min.to_i
18
+
19
+ divisor = ((max - min) / css_classes.size) + 1
20
+
21
+ tags.each do |tag|
22
+ yield tag.name, css_classes[(tag.total - min) / divisor] if divisor != 0
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,12 @@
1
+ module Tagger
2
+ class Railtie < Rails::Railtie
3
+ generators do
4
+ require "tagger/generator"
5
+ end
6
+
7
+ initializer "tagger.initializer" do |app|
8
+ ::ActiveRecord::Base.send :include, ActiveRecord
9
+ ::ActionView::Base.send :include, Helper
10
+ end
11
+ end
12
+ end
data/lib/tagger/tag.rb ADDED
@@ -0,0 +1,51 @@
1
+ class Tag < ActiveRecord::Base
2
+ SEPARATORS = {
3
+ :comma => ",",
4
+ :space => " "
5
+ }
6
+
7
+ has_many :taggings
8
+ attr_reader :weight
9
+
10
+ # Tag.cloud(:post)
11
+ # Tag.cloud(:post, :scope => @category)
12
+ # Tag.cloud(:post, :limit => 50)
13
+ def self.cloud(type, options = {})
14
+ options[:limit] ||= 100
15
+
16
+ scope_condition = "
17
+ AND scopable_type = #{options[:scope].class.to_s.inspect}
18
+ AND scopable_id = #{options[:scope].id}" if options[:scope]
19
+
20
+ tags = Tag.find_by_sql [%(
21
+ SELECT tags.id, tags.name, COUNT(*) AS total
22
+ FROM tags, taggings
23
+ WHERE
24
+ tags.id == taggings.tag_id AND
25
+ taggings.taggable_type = ?
26
+ #{scope_condition}
27
+ GROUP BY taggings.tag_id
28
+ ORDER BY total DESC, name ASC
29
+ LIMIT ?
30
+ ), type.to_s.classify, options[:limit]]
31
+ end
32
+
33
+ # Tag.parse(tags_list, :comma)
34
+ # Tag.parse(tags_list, :space)
35
+ def self.parse(tag_list, separator)
36
+ separator = SEPARATORS[separator] || SEPARATORS[:comma]
37
+ tags = []
38
+ tag_list = tag_list.dup
39
+ tag_list.gsub!(/(["'])(.*?)(\1)/ms) {|m| tags << $2; "" }
40
+ tags += tag_list.split(separator)
41
+ tags.collect(&:squish).reject(&:blank?).sort_by {|tag| tag.downcase}
42
+ end
43
+
44
+ def total
45
+ read_attribute(:total).to_i
46
+ end
47
+
48
+ def to_s
49
+ name
50
+ end
51
+ end
@@ -0,0 +1,22 @@
1
+ class Tagging < ActiveRecord::Base
2
+ belongs_to :tag
3
+ belongs_to :taggable, :polymorphic => true
4
+
5
+ before_save :define_scope
6
+
7
+ def scope=(object)
8
+ @scope = object
9
+ end
10
+
11
+ def scope
12
+ @scope = scopable_type.constantize.find(scopable_id) if !!scopable_id
13
+ end
14
+
15
+ private
16
+ def define_scope
17
+ unless @scope.blank?
18
+ write_attribute(:scopable_type, @scope.class.to_s)
19
+ write_attribute(:scopable_id, @scope.id)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ module Tagger
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ PATCH = 0
6
+ STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
+ end
8
+ end
data/lib/tagger.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "rails/railtie"
2
+ require "active_record"
3
+
4
+ require "tagger/helper"
5
+ require "tagger/tag"
6
+ require "tagger/tagging"
7
+ require "tagger/active_record"
8
+ require "tagger/version"
9
+ require "tagger/railtie"
@@ -0,0 +1,5 @@
1
+ programming:
2
+ name: Programming
3
+
4
+ essays:
5
+ name: Essays
@@ -0,0 +1,3 @@
1
+ simples_ideias:
2
+ title: Simples Idéias
3
+ url: http://simplesideias.com.br
@@ -0,0 +1,7 @@
1
+ all_about_rails:
2
+ title: All About Rails
3
+ category: programming
4
+
5
+ ideas:
6
+ title: Ideas for Startups
7
+ category: essays
@@ -0,0 +1,2 @@
1
+ ruby:
2
+ name: ruby
@@ -0,0 +1,4 @@
1
+ require "spec_helper"
2
+
3
+ describe Tagger::Helper do
4
+ end
data/spec/schema.rb ADDED
@@ -0,0 +1,23 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :categories do |t|
3
+ t.string :name
4
+ end
5
+
6
+ create_table :posts do |t|
7
+ t.references :category
8
+ t.string :title
9
+ end
10
+
11
+ create_table :links do |t|
12
+ t.string :title, :url
13
+ end
14
+
15
+ create_table :taggings do |t|
16
+ t.references :tag, :user
17
+ t.references :scopable, :taggable, :polymorphic => true
18
+ end
19
+
20
+ create_table :tags do |t|
21
+ t.string :name
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require "rails"
3
+ require "tagger"
4
+ require File.dirname(__FILE__) + "/support/config/boot"
5
+ require "rspec/rails"
6
+
7
+ require "support/models"
8
+
9
+ # Load database schema
10
+ load File.dirname(__FILE__) + "/schema.rb"
11
+
12
+ RSpec.configure do |config|
13
+ config.mock_with :rspec
14
+ config.fixture_path = File.dirname(__FILE__) + "/fixtures"
15
+ config.use_transactional_fixtures = true
16
+ end
@@ -0,0 +1,14 @@
1
+ ENV["BUNDLE_GEMFILE"] = File.dirname(__FILE__) + "/../../../Gemfile"
2
+ require "bundler"
3
+ Bundler.setup
4
+ require "rails/all"
5
+ Bundler.require(:default)
6
+
7
+ module Tagger
8
+ class Application < Rails::Application
9
+ config.root = File.dirname(__FILE__) + "/.."
10
+ config.active_support.deprecation = :log
11
+ end
12
+ end
13
+
14
+ Tagger::Application.initialize!
@@ -0,0 +1,3 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: ":memory:"