gnugeek-is_msfte_taggable 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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