gnugeek-is_msfte_taggable 0.0.2

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,6 @@
1
+ [master]
2
+
3
+ *
4
+
5
+ [0.0.1]
6
+ * (14 Apr 2009) Gemified
@@ -0,0 +1,4 @@
1
+ = Is Msfte Taggable
2
+
3
+ MS Sql Server specific tagging plugin for Rails that uses the MS Sql Server full
4
+ text search engine.
@@ -0,0 +1,14 @@
1
+ Description:
2
+ Generate migrations for +is_taggable+ plugin
3
+
4
+ Example:
5
+ ./script/generate is_taggable .
6
+
7
+ This will create:
8
+ db/migrate/XXXXXXXXXX_create_taggables.rb
9
+
10
+ If the --with-autocomplete options is passed, it will generate this as well:
11
+ create app/controllers/taggable_controller.rb
12
+ create app/views/taggable
13
+ create app/views/taggable/autocomplete.js.erb
14
+ create app/helpers/taggable_helper.rb
@@ -0,0 +1,30 @@
1
+ class IsMsfteTaggableGenerator < Rails::Generator::NamedBase
2
+
3
+ def manifest
4
+ record do |m|
5
+
6
+ m.migration_template 'migration.rb', "db/migrate", {
7
+ :assigns => { :migration_name => 'CreateTaggables' },
8
+ :migration_file_name => 'create_taggables'
9
+ }
10
+
11
+ if options[:with_tasks]
12
+ m.file 'blah', :collison => :skip
13
+ end
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def banner
20
+ "\nUsage: script/generate is_taggable . [options]\n\n"
21
+ end
22
+
23
+ def add_options!(opt)
24
+ opt.separator ''
25
+ opt.separator 'Options:'
26
+ opt.on("--with-tasks",
27
+ "Generate tasks for MSFTE catalog setup") { |v| options[:with_tasks] = v }
28
+ end
29
+
30
+ end
@@ -0,0 +1,5 @@
1
+ <ul>
2
+ <%- @autocomplete_tags.each do |tag| -%>
3
+ <li id="<%= tag.id %>"><%= tag.name %></li>
4
+ <%- end -%>
5
+ </ul>
@@ -0,0 +1,31 @@
1
+ # = Migration for the is_taggable plugin
2
+ #
3
+ class CreateTaggables < ActiveRecord::Migration
4
+ def self.up
5
+
6
+ create_table :tags do |t|
7
+ t.string :name, :default => ''
8
+ t.string :kind, :default => ''
9
+ t.timestamps
10
+ end
11
+
12
+ create_table :taggings do |t|
13
+ t.integer :tag_id
14
+ # Interface +taggable+
15
+ t.string :taggable_type
16
+ t.integer :taggable_id
17
+ t.timestamps
18
+ end
19
+
20
+ # Add indices
21
+ add_index :tags, :name
22
+ add_index :tags, [:name, :kind], :name => "name_and_kind"
23
+ add_index :taggings, [:taggable_type, :taggable_id], :name => "taggable_interface_index"
24
+
25
+ end
26
+
27
+ def self.down
28
+ drop_table :tags
29
+ drop_table :taggings
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ # = Return autocomplete results for the Tag class from is_taggable plugin
2
+ # Add before_filters, copy it somewhere else, etc, as you like.
3
+ # Just don't forget the <tt>app/views/taggable/autocomplete.js.erb</tt> file as well
4
+ #
5
+ class TaggableController < ApplicationController
6
+
7
+ # Return autocomplete results
8
+ def autocomplete
9
+ @autocomplete_tags = Tag.all( :conditions => ['name LIKE ?', "#{params[:autocomplete_tags]}%"], :order => 'name', :limit => 50)
10
+ respond_to do |format|
11
+ format.js { render :template => "autocomplete", :layout => false }
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,32 @@
1
+ # = Encapsulating Script.aculo.us <tt>Ajax.Autocompleter</tt> method
2
+ # Over-ride the CSS in your stylesheets with a more specific selector, like <tt>.my-page div.autocomplete { color: red }</tt>
3
+
4
+ module TaggableHelper
5
+
6
+ # Returns <tt><div></tt>, CSS and JavaScript code needed for autocompleting tags
7
+ # See http://github.com/madrobby/scriptaculous/wikis/ajax-autocompleter
8
+ def taggable_autocompleter_for(field_name, options={})
9
+ authenticity_token_param = protect_against_forgery? ? "authenticity_token=#{form_authenticity_token}" : '' # Beware of tests :)
10
+ autocompleter=<<HTML
11
+ <style type="text/css" media="screen">
12
+ div.autocomplete {
13
+ position:absolute; width:250px;
14
+ background-color: white;
15
+ border:1px solid #888; margin:0; padding:0; }
16
+ div.autocomplete ul { list-style-type:none; margin:0; padding:0; }
17
+ div.autocomplete ul li.selected { background-color: #ecf4fe;}
18
+ div.autocomplete ul li { display: block; margin: 0; padding: 0.2em; height: 1em; cursor: pointer; }
19
+ </style>
20
+ <div id="#{field_name}_autocomplete_choices" class="autocomplete"></div>
21
+ <script type="text/javascript">
22
+ new Ajax.Autocompleter("#{field_name}",
23
+ "#{field_name}_autocomplete_choices",
24
+ "/taggable/autocomplete?#{authenticity_token_param}",
25
+ { paramName: 'autocomplete_tag', tokens: ',' }
26
+ );
27
+ </script>
28
+ HTML
29
+ return autocompleter
30
+ end
31
+
32
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'is_taggable'
@@ -0,0 +1,111 @@
1
+ path = File.expand_path(File.dirname(__FILE__))
2
+ $LOAD_PATH << path unless $LOAD_PATH.include?(path)
3
+ require 'tag'
4
+ require 'tagging'
5
+
6
+ module IsMsfteTaggable
7
+ class TagList < Array
8
+ def to_s
9
+ join(', ')
10
+ end
11
+ end
12
+
13
+ module ActiveRecordExtension
14
+ def is_msfte_taggable(*kinds)
15
+ class_inheritable_accessor :tag_kinds
16
+ self.tag_kinds = kinds.map(&:to_s).map(&:singularize)
17
+ self.tag_kinds << :tag if kinds.empty?
18
+
19
+ include IsMsfteTaggable::TaggableMethods
20
+ end
21
+ end
22
+
23
+ module TaggableMethods
24
+ def self.included(klass)
25
+ klass.class_eval do
26
+ include IsMsfteTaggable::TaggableMethods::InstanceMethods
27
+
28
+ has_many :taggings, :as => :taggable
29
+ has_many :tags, :through => :taggings
30
+ after_save :save_tags
31
+
32
+ @@type = self#class
33
+
34
+ def self.make_search_string(search_string, boolean="OR")
35
+ search_string.split(/ +/).map {|term| '"' + term + '*"'}.join(" #{boolean} ")
36
+ end
37
+
38
+ tag_kinds.each do |k|
39
+ define_method("#{k}_list") { get_tag_list(k) }
40
+ define_method("#{k}_list=") { |new_list| set_tag_list(k, new_list) }
41
+
42
+ named_scope "with_#{k}_tags".to_sym, lambda { |tag_or_tags|
43
+ if tag_or_tags.blank?
44
+ {}
45
+ else
46
+ start_condition = "id IN (SELECT taggable_id FROM #{Tagging.table_name}, #{Tag.table_name}
47
+ WHERE #{Tagging.table_name}.taggable_type = '#{@@type}' AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND
48
+ CONTAINS (#{Tag.table_name}.name, '#{make_search_string(tag_or_tags, 'OR')}*'))"
49
+ { :conditions => start_condition }
50
+ end
51
+ }
52
+ end
53
+
54
+ named_scope "with_tags".to_sym, lambda { |tag_or_tags|
55
+ if tag_or_tags.blank?
56
+ {}
57
+ else
58
+ start_condition = "id IN (SELECT taggable_id FROM #{Tagging.table_name}, #{Tag.table_name}
59
+ WHERE #{Tagging.table_name}.taggable_type = '#{@@type}' AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND
60
+ CONTAINS (#{Tag.table_name}.name, '#{make_search_string(tag_or_tags,'OR')}*'))"
61
+ { :conditions => start_condition }
62
+ end
63
+ }
64
+ end
65
+ end
66
+
67
+ module InstanceMethods
68
+ def set_tag_list(kind, list)
69
+ list.gsub!(/ *, */,',') unless list.is_a?(Array)
70
+ tag_list = TagList.new(list.is_a?(Array) ? list : list.split(','))
71
+ instance_variable_set(tag_list_name_for_kind(kind), tag_list)
72
+ end
73
+
74
+ def get_tag_list(kind)
75
+ set_tag_list(kind, tags.of_kind(kind).map(&:name)) if tag_list_instance_variable(kind).nil?
76
+ tag_list_instance_variable(kind)
77
+ end
78
+
79
+ protected
80
+ def tag_list_name_for_kind(kind)
81
+ "@#{kind}_list"
82
+ end
83
+
84
+ def tag_list_instance_variable(kind)
85
+ instance_variable_get(tag_list_name_for_kind(kind))
86
+ end
87
+
88
+ def save_tags
89
+ tag_kinds.each do |tag_kind|
90
+ delete_unused_tags(tag_kind)
91
+ add_new_tags(tag_kind)
92
+ end
93
+
94
+ taggings.each(&:save)
95
+ end
96
+
97
+ def delete_unused_tags(tag_kind)
98
+ tags.of_kind(tag_kind).each { |t| tags.delete(t) unless get_tag_list(tag_kind).include?(t.name) }
99
+ end
100
+
101
+ def add_new_tags(tag_kind)
102
+ tag_names = tags.of_kind(tag_kind).map(&:name)
103
+ get_tag_list(tag_kind).each do |tag_name|
104
+ tags << Tag.find_or_initialize_with_name_like_and_kind(tag_name, tag_kind) unless tag_names.include?(tag_name)
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ ActiveRecord::Base.send(:extend, IsMsfteTaggable::ActiveRecordExtension)
@@ -0,0 +1,20 @@
1
+ class Tag < ActiveRecord::Base
2
+ class << self
3
+ def find_or_initialize_with_name_like_and_kind(name, kind)
4
+ with_name_like_and_kind(name, kind).first || new(:name => name, :kind => kind)
5
+ end
6
+ end
7
+
8
+ has_many :taggings
9
+
10
+ validates_presence_of :name
11
+ validates_uniqueness_of :name, :scope => :kind
12
+
13
+ named_scope :with_name_like_and_kind, lambda { |name, kind| { :conditions => ["name like ? AND kind = ?", name, kind] } }
14
+ named_scope :of_kind, lambda { |kind| { :conditions => {:kind => kind} } }
15
+ named_scope :unique_by_name_for_kind, lambda { |kind| { :conditions => {:kind => kind}, :group => 'id,name,kind,created_at,updated_at' } }
16
+
17
+ def self.unique_tag_list_by_kind(kind)
18
+ unique_by_name_for_kind('available_service').map(&:name)
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ class Tagging < ActiveRecord::Base
2
+ belongs_to :tag
3
+ end
@@ -0,0 +1,59 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ Expectations do
4
+ expect Tag do
5
+ Post.new.tags.build
6
+ end
7
+
8
+ expect Tagging do
9
+ Post.new.taggings.build
10
+ end
11
+
12
+ expect ["something cool", "something else cool"] do
13
+ p = Post.new :tag_list => "something cool, something else cool"
14
+ p.tag_list
15
+ end
16
+
17
+ expect ["something cool", "something new"] do
18
+ p = Post.new :tag_list => "something cool, something else cool"
19
+ p.save!
20
+ p.tag_list = "something cool, something new"
21
+ p.save!
22
+ p.tags.reload
23
+ p.instance_variable_set("@tag_list", nil)
24
+ p.tag_list
25
+ end
26
+
27
+ expect ["english", "french"] do
28
+ p = Post.new :language_list => "english, french"
29
+ p.save!
30
+ p.tags.reload
31
+ p.instance_variable_set("@language_list", nil)
32
+ p.language_list
33
+ end
34
+
35
+ expect ["english", "french"] do
36
+ p = Post.new :language_list => "english, french"
37
+ p.language_list
38
+ end
39
+
40
+ expect "english, french" do
41
+ p = Post.new :language_list => "english, french"
42
+ p.language_list.to_s
43
+ end
44
+
45
+ # added - should clean up strings with arbitrary spaces around commas
46
+ expect ["spaces","should","not","matter"] do
47
+ p = Post.new
48
+ p.tag_list = "spaces,should, not,matter"
49
+ p.save!
50
+ p.tags.reload
51
+ p.tag_list
52
+ end
53
+
54
+ expect 2 do
55
+ p = Post.new :language_list => "english, french"
56
+ p.save!
57
+ p.tags.length
58
+ end
59
+ end
@@ -0,0 +1,36 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ Expectations do
4
+ expect Tagging do
5
+ Tag.new.taggings.proxy_reflection.klass
6
+ end
7
+
8
+ expect Tag.new(:name => "duplicate").not.to.be.valid? do
9
+ Tag.create!(:name => "duplicate")
10
+ end
11
+
12
+ expect Tag.new(:name => "not dup").to.be.valid? do
13
+ Tag.create!(:name => "not dup", :kind => "something")
14
+ end
15
+
16
+ expect Tag.new.not.to.be.valid?
17
+ expect String do
18
+ t = Tag.new
19
+ t.valid?
20
+ t.errors[:name]
21
+ end
22
+
23
+ expect Tag.create!(:name => "iamawesome", :kind => "awesomestuff") do
24
+ Tag.find_or_initialize_with_name_like_and_kind("iamawesome", "awesomestuff")
25
+ end
26
+
27
+ expect true do
28
+ Tag.create!(:name => "iamawesome", :kind => "stuff")
29
+ Tag.find_or_initialize_with_name_like_and_kind("iamawesome", "otherstuff").new_record?
30
+ end
31
+
32
+ expect Tag.create!(:kind => "language", :name => "French") do
33
+ Tag.of_kind("language").first
34
+ end
35
+
36
+ end
@@ -0,0 +1,8 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ Expectations do
4
+ expect Tag do
5
+ t = Tagging.new :tag => Tag.new(:name => 'some_tag')
6
+ t.tag
7
+ end
8
+ end
@@ -0,0 +1,37 @@
1
+ require 'rubygems'
2
+ require 'activerecord'
3
+ require File.dirname(__FILE__)+'/../lib/is_msfte_taggable'
4
+ require 'expectations'
5
+ require 'logger'
6
+
7
+ ActiveRecord::Base.configurations = {'sqlite3' => {:adapter => 'sqlite3', :database => ':memory:'}}
8
+ ActiveRecord::Base.establish_connection('sqlite3')
9
+
10
+ ActiveRecord::Base.logger = Logger.new(STDERR)
11
+ ActiveRecord::Base.logger.level = Logger::WARN
12
+
13
+ ActiveRecord::Schema.define(:version => 0) do
14
+ create_table :users do |t|
15
+ t.string :name, :default => ''
16
+ end
17
+
18
+ create_table :posts do |t|
19
+ t.string :title, :default => ''
20
+ end
21
+
22
+ create_table :tags do |t|
23
+ t.string :name, :default => ''
24
+ t.string :kind, :default => ''
25
+ end
26
+
27
+ create_table :taggings do |t|
28
+ t.integer :tag_id
29
+
30
+ t.string :taggable_type, :default => ''
31
+ t.integer :taggable_id
32
+ end
33
+ end
34
+
35
+ class Post < ActiveRecord::Base
36
+ is_msfte_taggable :tags, :languages, :unique
37
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gnugeek-is_msfte_taggable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Brian Knox
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-14 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Extends ActiveRecord models to have tagging with full text search capability
17
+ email: gnutse@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - CHANGELOG
25
+ files:
26
+ - init.rb
27
+ - README.rdoc
28
+ - CHANGELOG
29
+ - lib/is_msfte_taggable.rb
30
+ - lib/tagging.rb
31
+ - lib/tag.rb
32
+ - generators/is_msfte_taggable/is_msfte_taggable_generator.rb
33
+ - generators/is_msfte_taggable/USAGE
34
+ - generators/is_msfte_taggable/templates/autocomplete.js.erb
35
+ - generators/is_msfte_taggable/templates/migration.rb
36
+ - generators/is_msfte_taggable/templates/taggable_controller.rb
37
+ - generators/is_msfte_taggable/templates/taggable_helper.rb
38
+ has_rdoc: true
39
+ homepage: http://github.com/gnugeek/is_msfte_taggable/
40
+ post_install_message:
41
+ rdoc_options:
42
+ - --main
43
+ - README.rdoc
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project:
61
+ rubygems_version: 1.2.0
62
+ signing_key:
63
+ specification_version: 2
64
+ summary: Tagging with full text search for MS SQL Server
65
+ test_files:
66
+ - test/is_msfte_taggable_test.rb
67
+ - test/tagging_test.rb
68
+ - test/tag_test.rb
69
+ - test/test_helper.rb