taglish 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.
@@ -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