decisiv-is_msfte_taggable 0.0.4

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.
data/CHANGELOG ADDED
@@ -0,0 +1,6 @@
1
+ [master]
2
+
3
+ *
4
+
5
+ [0.0.1]
6
+ * (14 Apr 2009) Gemified
data/README.rdoc ADDED
@@ -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,114 @@
1
+
2
+ require 'is_msfte_taggable/tag'
3
+ require 'is_msfte_taggable/tagging'
4
+
5
+ module IsMsfteTaggable
6
+
7
+ class TagList < Array
8
+ def to_s
9
+ join(', ')
10
+ end
11
+ end
12
+
13
+ module ActiveRecordExtension
14
+
15
+ def is_msfte_taggable(*kinds)
16
+ class_inheritable_accessor :tag_kinds
17
+ self.tag_kinds = kinds.map(&:to_s).map(&:singularize)
18
+ self.tag_kinds << :tag if kinds.empty?
19
+ include IsMsfteTaggable::TaggableMethods
20
+ end
21
+
22
+ end
23
+
24
+ module TaggableMethods
25
+
26
+ def self.included(klass)
27
+ klass.class_eval do
28
+ include IsMsfteTaggable::TaggableMethods::InstanceMethods
29
+
30
+ has_many :taggings, :as => :taggable
31
+ has_many :tags, :through => :taggings
32
+ after_save :save_tags
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 = '#{klass.name}' 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 = '#{klass.name}' 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
+
80
+ protected
81
+
82
+ def tag_list_name_for_kind(kind)
83
+ "@#{kind}_list"
84
+ end
85
+
86
+ def tag_list_instance_variable(kind)
87
+ instance_variable_get(tag_list_name_for_kind(kind))
88
+ end
89
+
90
+ def save_tags
91
+ tag_kinds.each do |tag_kind|
92
+ delete_unused_tags(tag_kind)
93
+ add_new_tags(tag_kind)
94
+ end
95
+
96
+ taggings.each(&:save)
97
+ end
98
+
99
+ def delete_unused_tags(tag_kind)
100
+ tags.of_kind(tag_kind).each { |t| tags.delete(t) unless get_tag_list(tag_kind).include?(t.name) }
101
+ end
102
+
103
+ def add_new_tags(tag_kind)
104
+ tag_names = tags.of_kind(tag_kind).map(&:name)
105
+ get_tag_list(tag_kind).each do |tag_name|
106
+ tags << Tag.find_or_initialize_with_name_like_and_kind(tag_name, tag_kind) unless tag_names.include?(tag_name)
107
+ end
108
+ end
109
+
110
+ end
111
+ end
112
+ end
113
+
114
+ ActiveRecord::Base.send :extend, IsMsfteTaggable::ActiveRecordExtension
@@ -0,0 +1,29 @@
1
+ module IsMsfteTaggable
2
+ class Tag < ActiveRecord::Base
3
+
4
+ set_table_name :tags
5
+
6
+ has_many :taggings, :class_name => 'IsMsfteTaggable::Tagging', :foreign_key => 'tag_id'
7
+
8
+ validates_presence_of :name
9
+ validates_uniqueness_of :name, :scope => :kind
10
+
11
+ named_scope :with_name_like_and_kind, lambda { |name, kind| { :conditions => ["name like ? AND kind = ?", name, kind] } }
12
+ named_scope :of_kind, lambda { |kind| { :conditions => {:kind => kind} } }
13
+ named_scope :unique_by_name_for_kind, lambda { |kind| { :conditions => {:kind => kind}, :group => 'id,name,kind,created_at,updated_at' } }
14
+
15
+ class << self
16
+
17
+ def find_or_initialize_with_name_like_and_kind(name, kind)
18
+ with_name_like_and_kind(name, kind).first || new(:name => name, :kind => kind)
19
+ end
20
+
21
+ def unique_tag_list_by_kind(kind)
22
+ unique_by_name_for_kind('available_service').map(&:name)
23
+ end
24
+
25
+ end
26
+
27
+
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ module IsMsfteTaggable
2
+ class Tagging < ActiveRecord::Base
3
+
4
+ set_table_name :taggings
5
+
6
+ belongs_to :tag, :class_name => 'IsMsfteTaggable::Tag', :foreign_key => 'tag_id'
7
+
8
+ end
9
+ 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
data/test/tag_test.rb ADDED
@@ -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,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: decisiv-is_msfte_taggable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Ken Collins
8
+ - Brian Knox
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-09-10 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Extends ActiveRecord models to have tagging with full text search capability
18
+ email: gnutse@gmail.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - README.rdoc
25
+ - CHANGELOG
26
+ files:
27
+ - init.rb
28
+ - README.rdoc
29
+ - CHANGELOG
30
+ - lib/is_msfte_taggable.rb
31
+ - lib/is_msfte_taggable/tagging.rb
32
+ - lib/is_msfte_taggable/tag.rb
33
+ - generators/is_msfte_taggable/is_msfte_taggable_generator.rb
34
+ - generators/is_msfte_taggable/USAGE
35
+ - generators/is_msfte_taggable/templates/autocomplete.js.erb
36
+ - generators/is_msfte_taggable/templates/migration.rb
37
+ - generators/is_msfte_taggable/templates/taggable_controller.rb
38
+ - generators/is_msfte_taggable/templates/taggable_helper.rb
39
+ has_rdoc: true
40
+ homepage: http://github.com/decisiv/is_msfte_taggable/
41
+ licenses:
42
+ post_install_message:
43
+ rdoc_options:
44
+ - --main
45
+ - README.rdoc
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.3.5
64
+ signing_key:
65
+ specification_version: 2
66
+ summary: Tagging with full text search for MS SQL Server
67
+ test_files:
68
+ - test/is_msfte_taggable_test.rb
69
+ - test/tagging_test.rb
70
+ - test/tag_test.rb
71
+ - test/test_helper.rb