texticle 2.0.2 → 2.0.3

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.
@@ -1,3 +1,22 @@
1
+ == 2.0.3
2
+
3
+ * 1 new feature
4
+
5
+ * Allow searching through relations. Model.join(:relation).search(:relation => {:column => "query"})
6
+ works, and reduces the need for multi-model tables. Huge thanks to Ben Hamill for the pull request.
7
+ * Allow searching through all model columns irrespective of the column's type; we cast all columns to text
8
+ in the search query. Performance may degrade when searching through anything but a string column.
9
+
10
+ * 2 bugfixes
11
+
12
+ * Fix exceptions when adding Texticle to a table-less model.
13
+ * Column names in a search query are now scoped to the current table.
14
+
15
+ * 1 dev improvement
16
+
17
+ * Running `rake` from the project root will setup the test environment by creating a test database
18
+ and running the necessary migrations. `rake` can also be used to run all the project tests.
19
+
1
20
  == 2.0.2
2
21
 
3
22
  * 1 bugfix
@@ -25,7 +44,7 @@
25
44
 
26
45
  require 'texticle/searchable'
27
46
  class Game
28
- include Searchable(:title)
47
+ extend Searchable(:title)
29
48
  end
30
49
 
31
50
  This also allows Texticle use in Rails without having #search available to all models:
@@ -38,6 +38,19 @@ Your models now have access to the search method:
38
38
  Game.search_by_title_and_system('Final Fantasy', 'PS2')
39
39
  Game.search_by_title_or_system('Final Fantasy, 'PS3')
40
40
 
41
+ === Creating Indexes for Super Speed
42
+ You can have Postgresql use an index for the full-text search. To declare a full-text index, in a
43
+ migration add code like the following:
44
+
45
+ execute "
46
+ create index on email_logs using gin(to_tsvector('english', subject));
47
+ create index on email_logs using gin(to_tsvector('english', email_address));"
48
+
49
+ In the above example, the table email_logs has two text columns that we search against, subject and email_address.
50
+ You will need to add an index for every text/string column you query against, or else Postgresql will revert to a
51
+ full table scan instead of using the indexes.
52
+
53
+
41
54
  == REQUIREMENTS:
42
55
 
43
56
  * ActiveRecord
data/Rakefile CHANGED
@@ -5,27 +5,58 @@ require 'pg'
5
5
  require 'active_record'
6
6
  require 'benchmark'
7
7
 
8
- require File.expand_path(File.dirname(__FILE__) + '/spec/spec_helper')
8
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/spec')
9
+
10
+ task :default do
11
+ config = File.open(File.expand_path(File.dirname(__FILE__) + '/spec/config.yml')).read
12
+ if config.match /<username>/
13
+ print "Would you like to create and configure the test database? y/n "
14
+ continue = STDIN.getc
15
+ exit 0 unless continue == "Y" || continue == "y"
16
+ sh "createdb texticle"
17
+ File.open(File.expand_path(File.dirname(__FILE__) + '/spec/config.yml'), "w") do |writable_config|
18
+ writable_config << config.sub(/<username>/, `whoami`.chomp)
19
+ end
20
+ Rake::Task["db:migrate"].invoke
21
+ end
22
+ Rake::Task["test"].invoke
23
+ end
24
+
25
+ task :test do
26
+ require 'texticle_spec'
27
+ require 'texticle/searchable_spec'
28
+ end
9
29
 
10
30
  namespace :db do
11
31
  desc 'Run migrations for test database'
12
32
  task :migrate do
33
+ require 'spec_helper'
13
34
  ActiveRecord::Migration.instance_eval do
14
35
  create_table :games do |table|
15
36
  table.string :system
16
37
  table.string :title
38
+ table.text :description
17
39
  end
18
40
  create_table :web_comics do |table|
19
41
  table.string :name
20
42
  table.string :author
43
+ table.text :review
44
+ table.integer :id
45
+ end
46
+ create_table :characters do |table|
47
+ table.string :name
48
+ table.string :description
49
+ table.integer :web_comic_id
21
50
  end
22
51
  end
23
52
  end
24
53
  desc 'Drop tables from test database'
25
54
  task :drop do
55
+ require 'spec_helper'
26
56
  ActiveRecord::Migration.instance_eval do
27
57
  drop_table :games
28
58
  drop_table :web_comics
59
+ drop_table :characters
29
60
  end
30
61
  end
31
62
  end
@@ -1,9 +1,9 @@
1
1
  require 'active_record'
2
2
 
3
3
  module Texticle
4
-
5
4
  def search(query = "", exclusive = true)
6
- language = connection.quote(searchable_language)
5
+ @similarities = []
6
+ @conditions = []
7
7
 
8
8
  unless query.is_a?(Hash)
9
9
  exclusive = false
@@ -12,20 +12,12 @@ module Texticle
12
12
  end
13
13
  end
14
14
 
15
- similarities = []
16
- conditions = []
17
-
18
- query.each do |column, search_term|
19
- column = connection.quote_column_name(column)
20
- search_term = connection.quote normalize(Helper.normalize(search_term))
21
- similarities << "ts_rank(to_tsvector(#{language}, #{quoted_table_name}.#{column}), to_tsquery(#{language}, #{search_term}))"
22
- conditions << "to_tsvector(#{language}, #{column}) @@ to_tsquery(#{language}, #{search_term})"
23
- end
15
+ parse_query_hash(query)
24
16
 
25
17
  rank = connection.quote_column_name('rank' + rand.to_s)
26
18
 
27
- select("#{quoted_table_name + '.*,' if scoped.select_values.empty?} #{similarities.join(" + ")} AS #{rank}").
28
- where(conditions.join(exclusive ? " AND " : " OR ")).
19
+ select("#{quoted_table_name + '.*,' if scoped.select_values.empty?} #{@similarities.join(" + ")} AS #{rank}").
20
+ where(@conditions.join(exclusive ? " AND " : " OR ")).
29
21
  order("#{rank} DESC")
30
22
  end
31
23
 
@@ -45,23 +37,41 @@ module Texticle
45
37
  else
46
38
  super
47
39
  end
40
+ rescue ActiveRecord::StatementInvalid
41
+ super
48
42
  end
49
43
 
50
44
  def respond_to?(method, include_private = false)
51
45
  return super if self == ActiveRecord::Base
52
46
  Helper.dynamic_search_method?(method, self.columns) or super
53
- rescue ActiveRecord::StatementInvalid
47
+ rescue StandardError
54
48
  super
55
49
  end
56
50
 
57
51
  private
58
52
 
53
+ def parse_query_hash(query, table_name = quoted_table_name)
54
+ language = connection.quote(searchable_language)
55
+ table_name = connection.quote_table_name(table_name)
56
+
57
+ query.each do |column_or_table, search_term|
58
+ if search_term.is_a?(Hash)
59
+ parse_query_hash(search_term, column_or_table)
60
+ else
61
+ column = connection.quote_column_name(column_or_table)
62
+ search_term = connection.quote normalize(Helper.normalize(search_term))
63
+ @similarities << "ts_rank(to_tsvector(#{language}, #{table_name}.#{column}::text), to_tsquery(#{language}, #{search_term}::text))"
64
+ @conditions << "to_tsvector(#{language}, #{table_name}.#{column}::text) @@ to_tsquery(#{language}, #{search_term}::text)"
65
+ end
66
+ end
67
+ end
68
+
59
69
  def normalize(query)
60
70
  query
61
71
  end
62
72
 
63
73
  def searchable_columns
64
- columns.select {|column| column.type == :string }.map(&:name)
74
+ columns.select {|column| [:string, :text].include? column.type }.map(&:name)
65
75
  end
66
76
 
67
77
  def searchable_language
@@ -91,7 +101,7 @@ module Texticle
91
101
  end
92
102
 
93
103
  def exclusive_dynamic_search_method?(method, class_columns)
94
- string_columns = class_columns.select {|column| column.type == :string }.map(&:name)
104
+ string_columns = class_columns.map(&:name)
95
105
  columns = exclusive_dynamic_search_columns(method)
96
106
  unless columns.empty?
97
107
  columns.all? {|column| string_columns.include?(column) }
@@ -101,7 +111,7 @@ module Texticle
101
111
  end
102
112
 
103
113
  def inclusive_dynamic_search_method?(method, class_columns)
104
- string_columns = class_columns.select {|column| column.type == :string }.map(&:name)
114
+ string_columns = class_columns.map(&:name)
105
115
  columns = inclusive_dynamic_search_columns(method)
106
116
  unless columns.empty?
107
117
  columns.all? {|column| string_columns.include?(column) }
@@ -116,5 +126,4 @@ module Texticle
116
126
  end
117
127
  end
118
128
  end
119
-
120
129
  end
@@ -5,11 +5,11 @@ def Searchable(*searchable_columns)
5
5
 
6
6
  include Texticle
7
7
 
8
- private
9
-
10
8
  define_method(:searchable_columns) do
11
9
  searchable_columns.map(&:to_s)
12
10
  end
11
+
12
+ private :searchable_columns
13
13
  end
14
14
  end
15
15
 
@@ -1,16 +1,11 @@
1
1
  require 'spec_helper'
2
+ require 'fixtures/webcomic'
2
3
  require 'texticle/searchable'
3
4
 
4
- class WebComic < ActiveRecord::Base
5
- # string :name
6
- # string :author
7
- end
8
-
9
5
  class SearchableTest < Test::Unit::TestCase
10
-
11
6
  context "when extending an ActiveRecord::Base subclass" do
12
7
  setup do
13
- @qcont = WebComic.create :name => "Questionable Content", :author => "Jeff Jaques"
8
+ @qcont = WebComic.create :name => "Questionable Content", :author => "Jeph Jaques"
14
9
  @jhony = WebComic.create :name => "Johnny Wander", :author => "Ananth & Yuko"
15
10
  @ddeeg = WebComic.create :name => "Dominic Deegan", :author => "Mookie"
16
11
  @penny = WebComic.create :name => "Penny Arcade", :author => "Tycho & Gabe"
@@ -18,11 +13,12 @@ class SearchableTest < Test::Unit::TestCase
18
13
 
19
14
  teardown do
20
15
  WebComic.delete_all
16
+ #Object.send(:remove_const, :WebComic) if defined?(WebComic)
21
17
  end
22
18
 
23
- context "with no paramters" do
19
+ context "with no parameters" do
24
20
  setup do
25
- WebComic.extend(Searchable)
21
+ WebComic.extend Searchable
26
22
  end
27
23
 
28
24
  should "search across all columns" do
@@ -33,18 +29,27 @@ class SearchableTest < Test::Unit::TestCase
33
29
 
34
30
  context "with one column as parameter" do
35
31
  setup do
36
- WebComic.extend(Searchable(:name))
32
+ WebComic.extend Searchable(:name)
37
33
  end
38
34
 
39
35
  should "only search across the given column" do
40
36
  assert_equal [@penny], WebComic.search("Penny")
41
37
  assert_empty WebComic.search("Tycho")
42
38
  end
39
+
40
+ should "define :searchable_columns as private" do
41
+ assert_raise(NoMethodError) { WebComic.searchable_columns }
42
+ begin
43
+ WebComic.searchable_columns
44
+ rescue NoMethodError => error
45
+ assert_match error.message, /private method/
46
+ end
47
+ end
43
48
  end
44
49
 
45
50
  context "with two columns as parameters" do
46
51
  setup do
47
- WebComic.extend(Searchable(:name, :author))
52
+ WebComic.extend Searchable(:name, :author)
48
53
  end
49
54
 
50
55
  should "only search across the given column" do
@@ -53,5 +58,4 @@ class SearchableTest < Test::Unit::TestCase
53
58
  end
54
59
  end
55
60
  end
56
-
57
61
  end
@@ -1,25 +1,14 @@
1
1
  # coding: utf-8
2
2
  require 'spec_helper'
3
-
4
- class Game < ActiveRecord::Base
5
- # string :system
6
- # string :title
7
-
8
- def to_s
9
- "#{system}: #{title}"
10
- end
11
- end
12
-
13
- class NotThere < ActiveRecord::Base
14
-
15
- end
3
+ require 'fixtures/webcomic'
4
+ require 'fixtures/character'
5
+ require 'fixtures/game'
16
6
 
17
7
  class TexticleTest < Test::Unit::TestCase
18
-
19
8
  context "after extending ActiveRecord::Base" do
20
- setup do
21
- ActiveRecord::Base.extend(Texticle)
22
- end
9
+ # before(:all)
10
+ ActiveRecord::Base.extend(Texticle)
11
+ class NotThere < ActiveRecord::Base; end
23
12
 
24
13
  should "not break #respond_to?" do
25
14
  assert_nothing_raised do
@@ -35,31 +24,85 @@ class TexticleTest < Test::Unit::TestCase
35
24
  end
36
25
 
37
26
  should "not break #method_missing" do
27
+ assert_raise(NoMethodError) { ActiveRecord::Base.random }
38
28
  begin
39
29
  ActiveRecord::Base.random
40
30
  rescue NoMethodError => error
41
31
  assert_match error.message, /undefined method `random'/
42
32
  end
43
33
  end
34
+
35
+ should "not break #method_missing for table-less classes" do
36
+ assert !NotThere.table_exists?
37
+ assert_raise(NoMethodError) { NotThere.random }
38
+ begin
39
+ NotThere.random
40
+ rescue NoMethodError => error
41
+ assert_match error.message, /undefined method `random'/
42
+ end
43
+ end
44
+
45
+ context "when finding models based on searching a related model" do
46
+ setup do
47
+ @qc = WebComic.create :name => "Questionable Content", :author => "Jeph Jaques"
48
+ @jw = WebComic.create :name => "Johnny Wander", :author => "Ananth & Yuko"
49
+ @pa = WebComic.create :name => "Penny Arcade", :author => "Tycho & Gabe"
50
+
51
+ @gabe = @pa.characters.create :name => 'Gabe', :description => 'the simple one'
52
+ @tycho = @pa.characters.create :name => 'Tycho', :description => 'the wordy one'
53
+ @div = @pa.characters.create :name => 'Div', :description => 'a crude divx player with anger management issues'
54
+
55
+ @martin = @qc.characters.create :name => 'Martin', :description => 'the insecure protagonist'
56
+ @faye = @qc.characters.create :name => 'Faye', :description => 'a sarcastic barrista with anger management issues'
57
+ @pintsize = @qc.characters.create :name => 'Pintsize', :description => 'a crude AnthroPC'
58
+
59
+ @ananth = @jw.characters.create :name => 'Ananth', :description => 'Stubble! What is under that hat?!?'
60
+ @yuko = @jw.characters.create :name => 'Yuko', :description => 'So... small. Carl Sagan haircut.'
61
+ @john = @jw.characters.create :name => 'John', :description => 'Tall. Anger issues?'
62
+ @cricket = @jw.characters.create :name => 'Cricket', :description => 'Chirrup!'
63
+ end
64
+
65
+ teardown do
66
+ WebComic.delete_all
67
+ Character.delete_all
68
+ end
69
+
70
+ should "look in the related model with nested searching syntax" do
71
+ assert_equal [@jw], WebComic.joins(:characters).search(:characters => {:description => 'tall'})
72
+ assert_equal [@pa, @jw, @qc].sort, WebComic.joins(:characters).search(:characters => {:description => 'anger'}).sort
73
+ assert_equal [@pa, @qc].sort, WebComic.joins(:characters).search(:characters => {:description => 'crude'}).sort
74
+ end
75
+ end
44
76
  end
45
77
 
46
78
  context "after extending an ActiveRecord::Base subclass" do
79
+ # before(:all)
80
+ class ::GameFail < Game; end
81
+
47
82
  setup do
48
- Game.extend(Texticle)
49
- @zelda = Game.create :system => "NES", :title => "Legend of Zelda"
50
- @mario = Game.create :system => "NES", :title => "Super Mario Bros."
51
- @sonic = Game.create :system => "Genesis", :title => "Sonic the Hedgehog"
52
- @dkong = Game.create :system => "SNES", :title => "Diddy's Kong Quest"
53
- @megam = Game.create :system => nil, :title => "Mega Man"
54
- @sfnes = Game.create :system => "SNES", :title => "Street Fighter 2"
55
- @sfgen = Game.create :system => "Genesis", :title => "Street Fighter 2"
56
- @takun = Game.create :system => "Saturn", :title => "Magical Tarurūto-kun"
83
+ @zelda = Game.create :system => "NES", :title => "Legend of Zelda", :description => "A Link to the Past."
84
+ @mario = Game.create :system => "NES", :title => "Super Mario Bros.", :description => "The original platformer."
85
+ @sonic = Game.create :system => "Genesis", :title => "Sonic the Hedgehog", :description => "Spiky."
86
+ @dkong = Game.create :system => "SNES", :title => "Diddy's Kong Quest", :description => "Donkey Kong Country 2"
87
+ @megam = Game.create :system => nil, :title => "Mega Man", :description => "Beware Dr. Brain"
88
+ @sfnes = Game.create :system => "SNES", :title => "Street Fighter 2", :description => "Yoga Flame!"
89
+ @sfgen = Game.create :system => "Genesis", :title => "Street Fighter 2", :description => "Yoga Flame!"
90
+ @takun = Game.create :system => "Saturn", :title => "Magical Tarurūto-kun", :description => "カッコイイ!"
57
91
  end
58
92
 
59
93
  teardown do
60
94
  Game.delete_all
61
95
  end
62
96
 
97
+ should "not break respond_to? when connection is unavailable" do
98
+ GameFail.establish_connection({:adapter => :postgresql, :database =>'unavailable', :username=>'bad', :pool=>5, :timeout=>5000}) rescue nil
99
+
100
+ assert_nothing_raised do
101
+ GameFail.respond_to?(:search)
102
+ end
103
+
104
+ end
105
+
63
106
  should "define a #search method" do
64
107
  assert Game.respond_to?(:search)
65
108
  end
@@ -108,6 +151,10 @@ class TexticleTest < Test::Unit::TestCase
108
151
  should "scope consecutively" do
109
152
  assert_equal [@sfgen], Game.search(:system => "Genesis").search(:title => "Street Fighter")
110
153
  end
154
+
155
+ should "cast non-:string columns as text" do
156
+ assert_equal [@mario], Game.search(:id => @mario.id)
157
+ end
111
158
  end
112
159
 
113
160
  context "when using dynamic search methods" do
@@ -116,10 +163,15 @@ class TexticleTest < Test::Unit::TestCase
116
163
  assert_equal [@takun], Game.search_by_system("Saturn")
117
164
  end
118
165
 
119
- should "generate methods for any combination of :string columns" do
166
+ should "generate methods for each :text column" do
167
+ assert_equal [@mario], Game.search_by_description("platform")
168
+ end
169
+
170
+ should "generate methods for any combination of :string and :text columns" do
120
171
  assert_equal [@mario], Game.search_by_title_and_system("Mario", "NES")
121
172
  assert_equal [@sonic], Game.search_by_system_and_title("Genesis", "Sonic")
122
173
  assert_equal [@mario], Game.search_by_title_and_title("Mario", "Mario")
174
+ assert_equal [@megam], Game.search_by_title_and_description("Man", "Brain")
123
175
  end
124
176
 
125
177
  should "generate methods for inclusive searches" do
@@ -130,8 +182,8 @@ class TexticleTest < Test::Unit::TestCase
130
182
  assert_equal [@sfgen], Game.search_by_system("Genesis").search_by_title("Street Fighter")
131
183
  end
132
184
 
133
- should "not generate methods for non-:string columns" do
134
- assert_raise(NoMethodError) { Game.search_by_id }
185
+ should "generate methods for non-:string columns" do
186
+ assert_equal [@mario], Game.search_by_id(@mario.id)
135
187
  end
136
188
 
137
189
  should "work with #respond_to?" do
@@ -140,8 +192,8 @@ class TexticleTest < Test::Unit::TestCase
140
192
  assert Game.respond_to?(:search_by_system_and_title)
141
193
  assert Game.respond_to?(:search_by_system_or_title)
142
194
  assert Game.respond_to?(:search_by_title_and_title_and_title)
195
+ assert Game.respond_to?(:search_by_id)
143
196
 
144
- assert !Game.respond_to?(:search_by_id)
145
197
  assert !Game.respond_to?(:search_by_title_and_title_or_title)
146
198
  end
147
199
 
@@ -160,24 +212,20 @@ class TexticleTest < Test::Unit::TestCase
160
212
  end
161
213
 
162
214
  context "when setting a custom search language" do
215
+ def Game.searchable_language
216
+ 'spanish'
217
+ end
218
+
163
219
  setup do
164
- def Game.searchable_language
165
- 'spanish'
166
- end
167
220
  Game.create :system => "PS3", :title => "Harry Potter & the Deathly Hallows"
168
221
  end
169
222
 
170
223
  teardown do
171
- def Game.searchable_language
172
- 'english'
173
- end
174
224
  Game.delete_all
175
225
  end
176
226
 
177
227
  should "still find results" do
178
228
  assert_not_empty Game.search_by_title("harry")
179
- p
180
229
  end
181
230
  end
182
-
183
231
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: texticle
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 2.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,42 +10,63 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2011-07-10 00:00:00.000000000 -04:00
14
- default_executable:
13
+ date: 2011-08-30 00:00:00.000000000 Z
15
14
  dependencies:
16
15
  - !ruby/object:Gem::Dependency
17
16
  name: pg
18
- requirement: &2156472340 !ruby/object:Gem::Requirement
17
+ requirement: &70337748621180 !ruby/object:Gem::Requirement
19
18
  none: false
20
19
  requirements:
21
- - - ! '>='
20
+ - - ~>
22
21
  - !ruby/object:Gem::Version
23
22
  version: 0.11.0
24
23
  type: :development
25
24
  prerelease: false
26
- version_requirements: *2156472340
25
+ version_requirements: *70337748621180
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: shoulda
29
- requirement: &2156471820 !ruby/object:Gem::Requirement
28
+ requirement: &70337748620400 !ruby/object:Gem::Requirement
30
29
  none: false
31
30
  requirements:
32
- - - ! '>='
31
+ - - ~>
33
32
  - !ruby/object:Gem::Version
34
33
  version: 2.11.3
35
34
  type: :development
36
35
  prerelease: false
37
- version_requirements: *2156471820
36
+ version_requirements: *70337748620400
37
+ - !ruby/object:Gem::Dependency
38
+ name: rake
39
+ requirement: &70337748619540 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 0.8.0
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *70337748619540
48
+ - !ruby/object:Gem::Dependency
49
+ name: ruby-debug19
50
+ requirement: &70337748618760 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: 0.11.6
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *70337748618760
38
59
  - !ruby/object:Gem::Dependency
39
60
  name: activerecord
40
- requirement: &2156471320 !ruby/object:Gem::Requirement
61
+ requirement: &70337748617780 !ruby/object:Gem::Requirement
41
62
  none: false
42
63
  requirements:
43
- - - ! '>='
64
+ - - ~>
44
65
  - !ruby/object:Gem::Version
45
- version: 3.0.0
66
+ version: '3.0'
46
67
  type: :runtime
47
68
  prerelease: false
48
- version_requirements: *2156471320
69
+ version_requirements: *70337748617780
49
70
  description: ! "Texticle exposes full text search capabilities from PostgreSQL, extending\n
50
71
  \ ActiveRecord with scopes making search easy and fun!"
51
72
  email:
@@ -68,7 +89,6 @@ files:
68
89
  - spec/texticle_spec.rb
69
90
  - spec/texticle/searchable_spec.rb
70
91
  - spec/config.yml
71
- has_rdoc: true
72
92
  homepage: http://tenderlove.github.com/texticle
73
93
  licenses: []
74
94
  post_install_message:
@@ -91,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
111
  version: '0'
92
112
  requirements: []
93
113
  rubyforge_project: texticle
94
- rubygems_version: 1.6.2
114
+ rubygems_version: 1.8.10
95
115
  signing_key:
96
116
  specification_version: 3
97
117
  summary: Texticle exposes full text search capabilities from PostgreSQL