taglish 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ .bundle
2
+ *.sqlite3
3
+ tmp/*
4
+ tmp/**/*
5
+ .*.swp
6
+ .*.swo
7
+ spec/database.yml
8
+ spec/debug.log
9
+ README.html
10
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
3
+
@@ -0,0 +1,107 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ taglish (0.1.0)
5
+ rails (~> 3.0)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ actionmailer (3.2.8)
11
+ actionpack (= 3.2.8)
12
+ mail (~> 2.4.4)
13
+ actionpack (3.2.8)
14
+ activemodel (= 3.2.8)
15
+ activesupport (= 3.2.8)
16
+ builder (~> 3.0.0)
17
+ erubis (~> 2.7.0)
18
+ journey (~> 1.0.4)
19
+ rack (~> 1.4.0)
20
+ rack-cache (~> 1.2)
21
+ rack-test (~> 0.6.1)
22
+ sprockets (~> 2.1.3)
23
+ activemodel (3.2.8)
24
+ activesupport (= 3.2.8)
25
+ builder (~> 3.0.0)
26
+ activerecord (3.2.8)
27
+ activemodel (= 3.2.8)
28
+ activesupport (= 3.2.8)
29
+ arel (~> 3.0.2)
30
+ tzinfo (~> 0.3.29)
31
+ activeresource (3.2.8)
32
+ activemodel (= 3.2.8)
33
+ activesupport (= 3.2.8)
34
+ activesupport (3.2.8)
35
+ i18n (~> 0.6)
36
+ multi_json (~> 1.0)
37
+ arel (3.0.2)
38
+ builder (3.0.3)
39
+ diff-lcs (1.1.3)
40
+ erubis (2.7.0)
41
+ hike (1.2.1)
42
+ i18n (0.6.1)
43
+ journey (1.0.4)
44
+ json (1.7.5)
45
+ mail (2.4.4)
46
+ i18n (>= 0.4.0)
47
+ mime-types (~> 1.16)
48
+ treetop (~> 1.4.8)
49
+ mime-types (1.19)
50
+ multi_json (1.3.6)
51
+ mysql2 (0.3.11)
52
+ pg (0.14.1)
53
+ polyglot (0.3.3)
54
+ rack (1.4.1)
55
+ rack-cache (1.2)
56
+ rack (>= 0.4)
57
+ rack-ssl (1.3.2)
58
+ rack
59
+ rack-test (0.6.2)
60
+ rack (>= 1.0)
61
+ rails (3.2.8)
62
+ actionmailer (= 3.2.8)
63
+ actionpack (= 3.2.8)
64
+ activerecord (= 3.2.8)
65
+ activeresource (= 3.2.8)
66
+ activesupport (= 3.2.8)
67
+ bundler (~> 1.0)
68
+ railties (= 3.2.8)
69
+ railties (3.2.8)
70
+ actionpack (= 3.2.8)
71
+ activesupport (= 3.2.8)
72
+ rack-ssl (~> 1.3.2)
73
+ rake (>= 0.8.7)
74
+ rdoc (~> 3.4)
75
+ thor (>= 0.14.6, < 2.0)
76
+ rake (0.9.2.2)
77
+ rdoc (3.12)
78
+ json (~> 1.4)
79
+ rspec (2.11.0)
80
+ rspec-core (~> 2.11.0)
81
+ rspec-expectations (~> 2.11.0)
82
+ rspec-mocks (~> 2.11.0)
83
+ rspec-core (2.11.1)
84
+ rspec-expectations (2.11.3)
85
+ diff-lcs (~> 1.1.3)
86
+ rspec-mocks (2.11.3)
87
+ sprockets (2.1.3)
88
+ hike (~> 1.2)
89
+ rack (~> 1.0)
90
+ tilt (~> 1.1, != 1.3.0)
91
+ sqlite3 (1.3.6)
92
+ thor (0.16.0)
93
+ tilt (1.3.3)
94
+ treetop (1.4.10)
95
+ polyglot
96
+ polyglot (>= 0.3.1)
97
+ tzinfo (0.3.33)
98
+
99
+ PLATFORMS
100
+ ruby
101
+
102
+ DEPENDENCIES
103
+ mysql2 (~> 0.3.7)
104
+ pg
105
+ rspec (~> 2.6)
106
+ sqlite3
107
+ taglish!
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Paul A. Jungwirth and Illuminated Computing Inc.
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,11 @@
1
+ Taglish
2
+ =======
3
+
4
+ Taglish is yet another tagging library for Rails.
5
+ It borrows heavily from [acts-as-taggable-on](https://github.com/mbleigh/acts-as-taggable-on), but it has a different feature set, the reflection stuff is refactored for greater comprehension, and it aims to remove [acts-as-taggable-on's memory leaks](https://github.com/mbleigh/acts-as-taggable-on/issues/94).
6
+ It is also faster for large tag sets.
7
+ Its major new feature is support for "scored" tags, so a model can have tags like `ruby:9, sql:5, js:4`.
8
+
9
+ It is still in heavy development, so lots of things are changing and I don't make any promises.
10
+
11
+
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.setup :default, :development
4
+
5
+ desc 'Default: run specs'
6
+ task :default => :spec
7
+
8
+ require 'rspec/core/rake_task'
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.pattern = "spec/**/*_spec.rb"
11
+ end
12
+
13
+ Bundler::GemHelper.install_tasks
14
+
15
+ task :readme => [] do |task|
16
+ `markdown README.md >README.html`
17
+ end
@@ -0,0 +1,39 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module Taglish
5
+ class MigrationGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ desc "Generates migration for Tag and Tagging models"
9
+
10
+ def self.orm
11
+ Rails::Generators.options[:rails][:orm]
12
+ end
13
+
14
+ def self.source_root
15
+ File.join(File.dirname(__FILE__), 'templates', (orm.to_s unless orm.class.eql?(String)) )
16
+ end
17
+
18
+ def self.orm_has_migration?
19
+ [:active_record].include? orm
20
+ end
21
+
22
+ def self.next_migration_number(dirname)
23
+ if ActiveRecord::Base.timestamped_migrations
24
+ migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
25
+ migration_number += 1
26
+ migration_number.to_s
27
+ else
28
+ "%.3d" % (current_migration_number(dirname) + 1)
29
+ end
30
+ end
31
+
32
+ def create_migration_file
33
+ if self.class.orm_has_migration?
34
+ migration_template 'migration.rb', 'db/migrate/taglish_migration''
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,31 @@
1
+ class TaglishMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :tags do |t|
4
+ t.string :name
5
+ end
6
+
7
+ create_table :taggings do |t|
8
+ t.references :tag
9
+
10
+ # You should make sure that the column created is
11
+ # long enough to store the required class names.
12
+ t.references :taggable, :polymorphic => true
13
+ t.references :tagger, :polymorphic => true
14
+
15
+ # Limit is created to prevent MySQL error on index
16
+ # length for MyISAM table type: http://bit.ly/vgW2Ql
17
+ t.string :context, :limit => 128
18
+ t.integer :score
19
+
20
+ t.datetime :created_at
21
+ end
22
+
23
+ add_index :taggings, :tag_id
24
+ add_index :taggings, [:taggable_id, :taggable_type, :context, :tag_id]
25
+ end
26
+
27
+ def self.down
28
+ drop_table :taggings
29
+ drop_table :tags
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ require "active_record"
2
+ require "active_record/version"
3
+ require "action_view"
4
+
5
+ require "digest/sha1"
6
+
7
+ module Taglish
8
+ DEFAULT_DELIMITER = ','
9
+ DEFAULT_SCORE_DELIMITER = ':'
10
+
11
+ end
12
+
13
+
14
+ require 'taglish/util'
15
+ require 'taglish/tag_type'
16
+ require 'taglish/tag'
17
+ require 'taglish/taggable'
18
+ require 'taglish/tagging'
19
+ require 'taglish/tag_list'
20
+ require 'taglish/core'
21
+
22
+
23
+ if defined?(ActiveRecord::Base)
24
+ ActiveRecord::Base.extend Taglish::Taggable
25
+ # TODO LATER:
26
+ # ActiveRecord::Base.send :include, Taglish::Tagger
27
+ end
28
+
29
+ if defined?(ActionView::Base)
30
+ # TODO:
31
+ # ActionView::Base.send :include, Taglish::TagsHelper
32
+ end
33
+
@@ -0,0 +1,157 @@
1
+ module Taglish::Core
2
+
3
+ def self.included(base)
4
+ base.class_eval do
5
+ after_save :save_tags
6
+ end
7
+ end
8
+
9
+ # Returns an array of Tags
10
+ # context should be plural
11
+ def tags_on(context)
12
+ taggings.on(context).map{|tg| tg.tag}
13
+ end
14
+
15
+ # Returns an array of Taggings
16
+ # context should be plural
17
+ def taggings_on(context)
18
+ q = taggings.includes(:tag).where(%Q{
19
+ #{Taglish::Tagging.table_name}.context = ?
20
+ AND #{Taglish::Tagging.table_name}.tagger_id IS NULL}, context.to_s)
21
+ q = q.order("#{Taglish::Tagging.table_name}.id") if tags_have_order?(context)
22
+ q
23
+ end
24
+
25
+ # Returns an array of strings
26
+ def tag_list_on(tag_type)
27
+ n = tag_list_variable_name_for(tag_type)
28
+ instance_variable_get(n) ||
29
+ instance_variable_set(n,
30
+ Taglish::TagList.new(tag_type, *taggings_on(tag_type.name).map(&:to_s)))
31
+ end
32
+
33
+ def set_tag_list_on(tag_type, new_list)
34
+ n = tag_list_variable_name_for(tag_type)
35
+ mark_tag_list_as_changed(tag_type, new_list)
36
+ instance_variable_set(n, Taglish::TagList.from(tag_type, new_list))
37
+ end
38
+
39
+ def taggings_by_name(context)
40
+ Hash[taggings_on(context).map{|tg| [tg.name, tg]}]
41
+ end
42
+
43
+ def mark_tag_list_as_changed(tag_type, new_list)
44
+ value = new_list.is_a?(Array) ? new_list.join(', ') : new_list
45
+ attrib = tag_list_attribute_name_for(tag_type)
46
+
47
+ old = changed_attributes[attrib]
48
+ if old.nil?
49
+ old = tag_list_on(tag_type).to_s
50
+ if old.to_s != value.to_s
51
+ changed_attributes[attrib] = old
52
+ tag_list_changed!(tag_type)
53
+ end
54
+ else
55
+ if old.to_s == value.to_s
56
+ changed_attributes.delete(attrib)
57
+ tag_list_changed!(tag_type, false)
58
+ end
59
+ end
60
+ end
61
+
62
+ def all_tags_list_on(context)
63
+ raise "TODO LATER"
64
+ end
65
+
66
+ def add_tag_on(context, tag)
67
+ end
68
+
69
+ # context is plural
70
+ def tags_have_score?(context)
71
+ tag_types[context].scored?
72
+ end
73
+
74
+ # context is plural
75
+ def tags_have_order?(context)
76
+ tag_types[context].ordered?
77
+ end
78
+
79
+ def taggable?
80
+ self.class.taggable?
81
+ end
82
+
83
+ def tag_list_changed?(tag_type)
84
+ instance_variable_get(dirty_tag_list_variable_name_for(tag_type))
85
+ end
86
+
87
+ def tag_list_changed!(tag_type, changed=true)
88
+ instance_variable_set(dirty_tag_list_variable_name_for(tag_type), changed)
89
+ end
90
+
91
+ def reload(*args)
92
+ self.class.tag_types.each do |tt_name, tt|
93
+ instance_variable_set(tag_list_variable_name_for(tt), nil)
94
+ tag_list_changed!(tt, false)
95
+ end
96
+
97
+ super(*args)
98
+ end
99
+
100
+ def save_tags
101
+ self.class.tag_types.each do |tag_type_name, tag_type|
102
+ # next unless changed_attributes[tag_list_attribute_name_for(tag_type)]
103
+ next unless tag_list_changed?(tag_type)
104
+
105
+ new_tag_list = instance_variable_get(tag_list_variable_name_for(tag_type))
106
+
107
+ # Tag objects for the new set of taggings:
108
+ # tags = tag_type.find_or_create_tags(*new_tags)
109
+
110
+ # Tagging objects for the new set of taggings (not persisted):
111
+ # new_taggings = Hash[new_tag_list.to_tagging_array.map{|tg| [tg.name, tg]}]
112
+ new_taggings = new_tag_list.to_tagging_array
113
+
114
+ # Tagging objects for the previous set of taggings:
115
+ current_taggings = taggings_by_name(tag_type_name)
116
+
117
+ if tag_type.ordered?
118
+ raise "TODO: Ordering not supported yet"
119
+ else
120
+ new_taggings.each do |tg|
121
+ old_tg = current_taggings[tg.name]
122
+ if old_tg
123
+ if old_tg.score != tg.score
124
+ old_tg.update_attribute(:score, tg.score)
125
+ end
126
+ current_taggings.delete tg.name
127
+ else
128
+ tg.taggable = self
129
+ tg.tag = Taglish::Tag.find_or_create_by_name(tg.name)
130
+ tg.context = tag_type_name
131
+ tg.save!
132
+ end
133
+ end
134
+ end
135
+
136
+ # Remove unused taggings:
137
+ Taglish::Tagging.destroy_all :id => current_taggings.values.map(&:id)
138
+ end
139
+
140
+ true
141
+ end
142
+
143
+ private
144
+
145
+ def tag_list_variable_name_for(tag_type)
146
+ "@#{tag_type.name}_list"
147
+ end
148
+
149
+ def tag_list_attribute_name_for(tag_type)
150
+ "#{tag_type.name.to_s.singularize}_list"
151
+ end
152
+
153
+ def dirty_tag_list_variable_name_for(tag_type)
154
+ "#{tag_list_variable_name_for(tag_type)}_changed"
155
+ end
156
+
157
+ end
@@ -0,0 +1,31 @@
1
+ class Taglish::Tag < ActiveRecord::Base
2
+ include Taglish::Util
3
+
4
+ attr_accessible :name
5
+
6
+ has_many :taggings, :dependent => :destroy, :class_name => 'Taglish::Tagging'
7
+
8
+ validates_presence_of :name
9
+ validates_uniqueness_of :name
10
+ validates_length_of :name, :maximum => 255
11
+
12
+ def ==(object)
13
+ super || (object.is_a?(Tag) && name == object.name)
14
+ end
15
+
16
+ def to_s
17
+ name
18
+ end
19
+
20
+ def count
21
+ read_attribute(:count).to_i
22
+ end
23
+
24
+ class << self
25
+ private
26
+ def comparable_name(str)
27
+ str.mb_chars.downcase.to_s
28
+ end
29
+ end
30
+
31
+ end