texticle 1.0.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,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+
5
+ # Autotest.add_hook :initialize do |at|
6
+ # at.extra_files << "../some/external/dependency.rb"
7
+ #
8
+ # at.libs << ":../some/external"
9
+ #
10
+ # at.add_exception 'vendor'
11
+ #
12
+ # at.add_mapping(/dependency.rb/) do |f, _|
13
+ # at.files_matching(/test_.*rb$/)
14
+ # end
15
+ #
16
+ # %w(TestA TestB).each do |klass|
17
+ # at.extra_class_map[klass] = "test/test_misc.rb"
18
+ # end
19
+ # end
20
+
21
+ # Autotest.add_hook :run_command do |at|
22
+ # system "rake build"
23
+ # end
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2009-04-14
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
@@ -0,0 +1,12 @@
1
+ .autotest
2
+ CHANGELOG.rdoc
3
+ Manifest.txt
4
+ README.rdoc
5
+ Rakefile
6
+ lib/texticle.rb
7
+ lib/texticle/full_text_index.rb
8
+ lib/texticle/tasks.rb
9
+ rails/init.rb
10
+ test/helper.rb
11
+ test/test_full_text_index.rb
12
+ test/test_texticle.rb
@@ -0,0 +1,78 @@
1
+ = texticle
2
+
3
+ * http://texticle.rubyforge.org/
4
+
5
+ == DESCRIPTION:
6
+
7
+ Texticle exposes full text search capabilities from PostgreSQL, and allows
8
+ you to declare full text indexes. Texticle will extend ActiveRecord with
9
+ named_scope methods making searching easy and fun!
10
+
11
+ == FEATURES/PROBLEMS:
12
+
13
+ * Only works with PostgreSQL
14
+
15
+ == SYNOPSIS:
16
+
17
+ ###
18
+ # In environment.rb
19
+
20
+ config.gem 'texticle'
21
+
22
+ ###
23
+ # Declare your index in your model
24
+
25
+ class Product < ActiveRecord::Base
26
+ index do
27
+ name
28
+ description
29
+ end
30
+ end
31
+
32
+ ###
33
+ # Use the search method
34
+
35
+ Product.search('hello world')
36
+
37
+ ###
38
+ # Full text searches can be sped up by creating indexes. To create the
39
+ # indexes, add these lines to your Rakefile:
40
+ require 'rubygems'
41
+ require 'texticle/tasks'
42
+
43
+ # Then run:
44
+
45
+ $ rake texticle:create_indexes
46
+
47
+ == REQUIREMENTS:
48
+
49
+ * Texticle may be used outside rails, but works best with rails.
50
+
51
+ == INSTALL:
52
+
53
+ * sudo gem install texticle
54
+
55
+ == LICENSE:
56
+
57
+ (The MIT License)
58
+
59
+ Copyright (c) 2009 Aaron Patterson
60
+
61
+ Permission is hereby granted, free of charge, to any person obtaining
62
+ a copy of this software and associated documentation files (the
63
+ 'Software'), to deal in the Software without restriction, including
64
+ without limitation the rights to use, copy, modify, merge, publish,
65
+ distribute, sublicense, and/or sell copies of the Software, and to
66
+ permit persons to whom the Software is furnished to do so, subject to
67
+ the following conditions:
68
+
69
+ The above copyright notice and this permission notice shall be
70
+ included in all copies or substantial portions of the Software.
71
+
72
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
73
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
74
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
75
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
76
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
77
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
78
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,16 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ $: << "lib"
7
+ require 'texticle'
8
+
9
+ Hoe.new('texticle', Texticle::VERSION) do |p|
10
+ p.developer('Aaron Patterson', 'aaronp@rubyforge.org')
11
+ p.readme_file = 'README.rdoc'
12
+ p.history_file = 'CHANGELOG.rdoc'
13
+ p.extra_rdoc_files = FileList['*.rdoc']
14
+ end
15
+
16
+ # vim: syntax=Ruby
@@ -0,0 +1,76 @@
1
+ require 'texticle/full_text_index'
2
+
3
+ ####
4
+ # Texticle exposes full text search capabilities from PostgreSQL, and allows
5
+ # you to declare full text indexes. Texticle will extend ActiveRecord with
6
+ # named_scope methods making searching easy and fun!
7
+ #
8
+ # Texticle.index is automatically added to ActiveRecord::Base.
9
+ #
10
+ # To declare an index on a model, just use the index method:
11
+ #
12
+ # class Product < ActiveRecord::Base
13
+ # index do
14
+ # name
15
+ # description
16
+ # end
17
+ # end
18
+ #
19
+ # This will allow you to do full text search on the name and description
20
+ # columns for the Product model. It defines a named_scope method called
21
+ # "search", so you can take advantage of the search like this:
22
+ #
23
+ # Product.search('foo bar')
24
+ #
25
+ # Indexes may also be named. For example:
26
+ #
27
+ # class Product < ActiveRecord::Base
28
+ # index 'author' do
29
+ # name
30
+ # author
31
+ # end
32
+ # end
33
+ #
34
+ # A named index will add a named_scope with the index name prefixed by
35
+ # "search". In order to take advantage of the "author" index, just call:
36
+ #
37
+ # Product.search_author('foo bar')
38
+ #
39
+ # Finally, column names can be ranked. The ranks are A, B, C, and D. This
40
+ # lets us declare that matches in the "name" column are more important
41
+ # than matches in the "description" column:
42
+ #
43
+ # class Product < ActiveRecord::Base
44
+ # index do
45
+ # name 'A'
46
+ # description 'B'
47
+ # end
48
+ # end
49
+ module Texticle
50
+ # The version of Texticle you are using.
51
+ VERSION = '1.0.0'
52
+
53
+ # A list of full text indexes
54
+ attr_accessor :full_text_indexes
55
+
56
+ ###
57
+ # Create an index with +name+ using +dictionary+
58
+ def index name = nil, dictionary = 'english', &block
59
+ search_name = ['search', name].compact.join('_')
60
+
61
+ class_eval(<<-eoruby)
62
+ named_scope :#{search_name}, lambda { |term|
63
+ {
64
+ :select => "\#{table_name}.*, ts_rank_cd((\#{full_text_indexes.first.to_s}),
65
+ plainto_tsquery(\#{connection.quote(term)\})) as rank",
66
+ :conditions =>
67
+ ["\#{full_text_indexes.first.to_s} @@ plainto_tsquery(?)", term],
68
+ :order => 'rank DESC'
69
+ }
70
+ }
71
+ eoruby
72
+ index_name = [table_name, name, 'fts_idx'].compact.join('_')
73
+ (self.full_text_indexes ||= []) <<
74
+ FullTextIndex.new(index_name, dictionary, self, &block)
75
+ end
76
+ end
@@ -0,0 +1,48 @@
1
+ module Texticle
2
+ class FullTextIndex # :nodoc:
3
+ attr_accessor :index_columns
4
+
5
+ def initialize name, dictionary, model_class, &block
6
+ @name = name
7
+ @dictionary = dictionary
8
+ @model_class = model_class
9
+ @index_columns = {}
10
+ @string = nil
11
+ instance_eval(&block)
12
+ end
13
+
14
+ def create
15
+ @model_class.connection.execute(<<-eosql)
16
+ CREATE index #{@name}
17
+ ON #{@model_class.table_name}
18
+ USING gin((#{to_s}))
19
+ eosql
20
+ end
21
+
22
+ def destroy
23
+ @model_class.connection.execute(<<-eosql)
24
+ DROP index IF EXISTS #{@name}
25
+ eosql
26
+ end
27
+
28
+ def to_s
29
+ return @string if @string
30
+ vectors = []
31
+ @index_columns.sort_by { |k,v| k }.each do |weight, columns|
32
+ c = columns.map { |x| "coalesce(#{@model_class.table_name}.#{x}, '')" }
33
+ if weight == 'none'
34
+ vectors << "to_tsvector('#{@dictionary}', #{c.join(" || ' ' || ")})"
35
+ else
36
+ vectors <<
37
+ "setweight(to_tsvector('#{@dictionary}', #{c.join(" || ' ' || ")}), '#{weight}')"
38
+ end
39
+ end
40
+ @string = vectors.join(" || ' ' || ")
41
+ end
42
+
43
+ def method_missing name, *args
44
+ weight = args.shift || 'none'
45
+ (index_columns[weight] ||= []) << name.to_s
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ require 'rake'
2
+ require 'texticle'
3
+
4
+ namespace :texticle do
5
+ desc "Create full text indexes"
6
+ task :create_indexes => ['texticle:destroy_indexes'] do
7
+ Dir[File.join(RAILS_ROOT, 'app', 'models', '*.rb')].each do |f|
8
+ klass = File.basename(f, '.rb').classify.constantize
9
+ if klass.respond_to?(:full_text_indexes)
10
+ (klass.full_text_indexes || []).each do |fti|
11
+ fti.create
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ desc "Destroy full text indexes"
18
+ task :destroy_indexes => [:environment] do
19
+ Dir[File.join(RAILS_ROOT, 'app', 'models', '*.rb')].each do |f|
20
+ klass = File.basename(f, '.rb').classify.constantize
21
+ if klass.respond_to?(:full_text_indexes)
22
+ (klass.full_text_indexes || []).each do |fti|
23
+ fti.destroy
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ require 'texticle'
2
+
3
+ ActiveRecord::Base.extend(Texticle)
@@ -0,0 +1,43 @@
1
+ require "test/unit"
2
+ require "texticle"
3
+
4
+ class TexticleTestCase < Test::Unit::TestCase
5
+ unless RUBY_VERSION >= '1.9'
6
+ undef :default_test
7
+ end
8
+
9
+ def setup
10
+ warn "#{name}" if ENV['TESTOPTS'] == '-v'
11
+ end
12
+
13
+ def fake_model
14
+ Class.new do
15
+ @connected = false
16
+ @executed = []
17
+ @named_scopes = []
18
+
19
+ class << self
20
+ attr_accessor :connected, :executed, :named_scopes
21
+
22
+ def connection
23
+ @connected = true
24
+ self
25
+ end
26
+
27
+ def execute sql
28
+ @executed << sql
29
+ end
30
+
31
+ def table_name; 'fake_model'; end
32
+
33
+ def named_scope *args
34
+ @named_scopes << args
35
+ end
36
+
37
+ def quote thing
38
+ "'#{thing}'"
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,74 @@
1
+ require 'helper'
2
+
3
+ class TestFullTextIndex < TexticleTestCase
4
+ def setup
5
+ super
6
+ @fm = fake_model
7
+ end
8
+
9
+ def test_initialize
10
+ fti = Texticle::FullTextIndex.new('ft_index', 'english', @fm) do
11
+ name
12
+ value 'A'
13
+ end
14
+ assert_equal 'name', fti.index_columns['none'].first
15
+ assert_equal 'value', fti.index_columns['A'].first
16
+ end
17
+
18
+ def test_destroy
19
+ fti = Texticle::FullTextIndex.new('ft_index', 'english', @fm) do
20
+ name
21
+ value 'A'
22
+ end
23
+ fti.destroy
24
+ assert @fm.connected
25
+ assert_equal 1, @fm.executed.length
26
+ executed = @fm.executed.first
27
+ assert_match "DROP index IF EXISTS #{fti.instance_variable_get(:@name)}", executed
28
+ end
29
+
30
+ def test_create
31
+ fti = Texticle::FullTextIndex.new('ft_index', 'english', @fm) do
32
+ name
33
+ value 'A'
34
+ end
35
+ fti.create
36
+ assert @fm.connected
37
+ assert_equal 1, @fm.executed.length
38
+ executed = @fm.executed.first
39
+ assert_match fti.to_s, executed
40
+ assert_match "CREATE index #{fti.instance_variable_get(:@name)}", executed
41
+ assert_match "ON #{@fm.table_name}", executed
42
+ end
43
+
44
+ def test_to_s_no_weight
45
+ fti = Texticle::FullTextIndex.new('ft_index', 'english', @fm) do
46
+ name
47
+ end
48
+ assert_equal "to_tsvector('english', coalesce(#{@fm.table_name}.name, ''))", fti.to_s
49
+ end
50
+
51
+ def test_to_s_A_weight
52
+ fti = Texticle::FullTextIndex.new('ft_index', 'english', @fm) do
53
+ name 'A'
54
+ end
55
+ assert_equal "setweight(to_tsvector('english', coalesce(#{@fm.table_name}.name, '')), 'A')", fti.to_s
56
+ end
57
+
58
+ def test_to_s_multi_weight
59
+ fti = Texticle::FullTextIndex.new('ft_index', 'english', @fm) do
60
+ name 'A'
61
+ value 'A'
62
+ description 'B'
63
+ end
64
+ assert_equal "setweight(to_tsvector('english', coalesce(#{@fm.table_name}.name, '') || ' ' || coalesce(#{@fm.table_name}.value, '')), 'A') || ' ' || setweight(to_tsvector('english', coalesce(#{@fm.table_name}.description, '')), 'B')", fti.to_s
65
+ end
66
+
67
+ def test_mixed_weight
68
+ fti = Texticle::FullTextIndex.new('ft_index', 'english', @fm) do
69
+ name
70
+ value 'A'
71
+ end
72
+ assert_equal "setweight(to_tsvector('english', coalesce(#{@fm.table_name}.value, '')), 'A') || ' ' || to_tsvector('english', coalesce(#{@fm.table_name}.name, ''))", fti.to_s
73
+ end
74
+ end
@@ -0,0 +1,47 @@
1
+ require 'helper'
2
+
3
+ class TestTexticle < TexticleTestCase
4
+ def test_index_method
5
+ x = fake_model
6
+ x.class_eval do
7
+ extend Texticle
8
+ index do
9
+ name
10
+ end
11
+ end
12
+ assert_equal 1, x.full_text_indexes.length
13
+ assert_equal 1, x.named_scopes.length
14
+
15
+ x.full_text_indexes.first.create
16
+ assert_match "#{x.table_name}_fts_idx", x.executed.first
17
+ assert_equal :search, x.named_scopes.first.first
18
+ end
19
+
20
+ def test_named_index
21
+ x = fake_model
22
+ x.class_eval do
23
+ extend Texticle
24
+ index('awesome') do
25
+ name
26
+ end
27
+ end
28
+ assert_equal 1, x.full_text_indexes.length
29
+ assert_equal 1, x.named_scopes.length
30
+
31
+ x.full_text_indexes.first.create
32
+ assert_match "#{x.table_name}_awesome_fts_idx", x.executed.first
33
+ assert_equal :search_awesome, x.named_scopes.first.first
34
+ end
35
+
36
+ def test_named_scope_select
37
+ x = fake_model
38
+ x.class_eval do
39
+ extend Texticle
40
+ index('awesome') do
41
+ name
42
+ end
43
+ end
44
+ ns = x.named_scopes.first[1].call('foo')
45
+ assert_match(/^#{x.table_name}\.\*/, ns[:select])
46
+ end
47
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: texticle
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Aaron Patterson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-15 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.12.1
24
+ version:
25
+ description: |-
26
+ Texticle exposes full text search capabilities from PostgreSQL, and allows
27
+ you to declare full text indexes. Texticle will extend ActiveRecord with
28
+ named_scope methods making searching easy and fun!
29
+ email:
30
+ - aaronp@rubyforge.org
31
+ executables: []
32
+
33
+ extensions: []
34
+
35
+ extra_rdoc_files:
36
+ - Manifest.txt
37
+ - CHANGELOG.rdoc
38
+ - README.rdoc
39
+ files:
40
+ - .autotest
41
+ - CHANGELOG.rdoc
42
+ - Manifest.txt
43
+ - README.rdoc
44
+ - Rakefile
45
+ - lib/texticle.rb
46
+ - lib/texticle/full_text_index.rb
47
+ - lib/texticle/tasks.rb
48
+ - rails/init.rb
49
+ - test/helper.rb
50
+ - test/test_full_text_index.rb
51
+ - test/test_texticle.rb
52
+ has_rdoc: true
53
+ homepage: http://texticle.rubyforge.org/
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options:
58
+ - --main
59
+ - README.rdoc
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ requirements: []
75
+
76
+ rubyforge_project: texticle
77
+ rubygems_version: 1.3.2
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: Texticle exposes full text search capabilities from PostgreSQL, and allows you to declare full text indexes
81
+ test_files:
82
+ - test/test_full_text_index.rb
83
+ - test/test_texticle.rb