is_msfte_searchable 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ *.gem
@@ -0,0 +1,3 @@
1
+ = 3.2.0
2
+
3
+ * Initial release. Works with ActiveRecord 3.2.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec 'is_msfte_searchable.gemspec'
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2012 Decisiv, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,7 @@
1
+ # IsMsfteSearchable
2
+
3
+ IsMsfteSearchable provides a class method on ActiveRecord::Base that
4
+ subclasses may use to flag themselves as having full-text indexes in
5
+ Microsoft SQL Server. It provides utility methods for searching using
6
+ the index, as well as utility methods for building and destroying the
7
+ index.
@@ -0,0 +1,13 @@
1
+ require 'bundler'
2
+ require 'rake/testtask'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ desc 'Test is_msfte_searchable'
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.libs << 'test'
9
+ t.pattern = 'test/**/*_test.rb'
10
+ t.verbose = true
11
+ end
12
+
13
+ task :default => [:test]
@@ -0,0 +1,8 @@
1
+ require 'active_support/concern'
2
+ require 'active_record'
3
+ require 'is_msfte_searchable/active_record_extension'
4
+ require 'is_msfte_searchable/active_record_mixin'
5
+ require 'is_msfte_searchable/arel_mixin'
6
+ require 'is_msfte_searchable/version'
7
+
8
+ ActiveRecord::Base.send :include, IsMsfteSearchable::ActiveRecordExtension
@@ -0,0 +1,21 @@
1
+ module IsMsfteSearchable
2
+ module ActiveRecordExtension
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def is_msfte_searchable(options={})
7
+ options.reverse_merge! :change_tracking => true, :update_index => true
8
+ cattr_accessor :msfte_table_name, :msfte_columns, :msfte_catalog, :msfte_unique_key_column, :msfte_unique_key_index, :msfte_change_tracking, :msfte_update_index
9
+ self.msfte_table_name = options[:table_name] ? options[:table_name].to_s : table_name
10
+ self.msfte_columns = options[:columns] ? options[:columns].map(&:to_s) : column_names
11
+ self.msfte_catalog = options[:catalog] ? options[:catalog].to_s : "#{msfte_table_name}_fti"
12
+ self.msfte_unique_key_column = options[:unique_key_column] ? options[:unique_key_column].to_s : primary_key
13
+ self.msfte_unique_key_index = options[:unique_key_index] ? options[:unique_key_index].to_s : "#{msfte_unique_key_column}_idx"
14
+ self.msfte_change_tracking = options[:change_tracking]
15
+ self.msfte_update_index = options[:update_index]
16
+ include IsMsfteSearchable::ActiveRecordMixin
17
+ include IsMsfteSearchable::ArelMixin
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,45 @@
1
+ module IsMsfteSearchable
2
+ module ActiveRecordMixin
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def msfte_setup
7
+ connection.execute %|sp_fulltext_catalog '#{msfte_catalog}', 'create'|
8
+ connection.execute %|sp_fulltext_table 'dbo.#{msfte_table_name}', 'create', '#{msfte_catalog}', '#{msfte_unique_key_index}'|
9
+ msfte_columns.each { |col| connection.execute(%|sp_fulltext_column '#{msfte_table_name}', '#{col}', 'add'|) }
10
+ connection.execute %|sp_fulltext_table 'dbo.#{msfte_table_name}', 'start_change_tracking'| if msfte_change_tracking
11
+ connection.execute %|sp_fulltext_table 'dbo.#{msfte_table_name}', 'start_background_updateindex'| if msfte_update_index
12
+ end
13
+
14
+ def msfte_teardown
15
+ connection.execute %|sp_fulltext_table 'dbo.#{msfte_table_name}', 'drop'| rescue nil
16
+ connection.execute %|sp_fulltext_catalog '#{msfte_catalog}', 'drop'| rescue nil
17
+ end
18
+
19
+ def msfte_reset!
20
+ msfte_teardown
21
+ yield if block_given?
22
+ msfte_setup
23
+ end
24
+
25
+ def msfte_catalog_rebuild
26
+ connection.execute %|sp_fulltext_catalog '#{msfte_catalog}', 'rebuild'|
27
+ end
28
+
29
+ def msfte_quote(string)
30
+ connection.quote_string(string)
31
+ end
32
+
33
+ def msfte_search_string(query, boolean=nil)
34
+ if boolean
35
+ # sql2k won't treat punctuation as valid search terms, so strip them out until we upgrade to 2k5+
36
+ termed_query = query.gsub(/[^\w\d\s]+/, '').split(/\s+/).map{ |term| %|"#{term} *"| }.join(" #{boolean} ").strip
37
+ else
38
+ termed_query = %|"#{query} *"|
39
+ end
40
+ %|'#{msfte_quote(termed_query)}'|
41
+ end
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,66 @@
1
+ module IsMsfteSearchable
2
+ module ArelMixin
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_eval do
7
+ scope :msfte_with_phrase, lambda { |query|
8
+ return {} if query.blank?
9
+ msfte_contains(msfte_search_string(query))
10
+ }
11
+
12
+ scope :msfte_with_any, lambda { |query|
13
+ return {} if query.blank?
14
+ msfte_contains(msfte_search_string(query, 'OR'))
15
+ }
16
+
17
+ scope :msfte_with_all, lambda { |query|
18
+ return {} if query.blank?
19
+ msfte_contains(msfte_search_string(query, 'AND'))
20
+ }
21
+
22
+ scope :msfte_with_booleans, lambda { |query|
23
+ return {} if query.blank?
24
+ msfte_contains(query, :quote => true)
25
+ }
26
+
27
+ msfte_columns.each do |c|
28
+ scope "msfte_#{c}_with_any".to_sym, lambda { |query|
29
+ return {} if query.blank?
30
+ return msfte_like_bailout(c, query) if Rails.env.test?
31
+ msfte_contains(msfte_search_string(query, 'OR'), :column => c)
32
+ }
33
+
34
+ scope "msfte_#{c}_with_all".to_sym, lambda { |query|
35
+ return {} if query.blank?
36
+ return msfte_like_bailout(c, query) if Rails.env.test?
37
+ msfte_contains(msfte_search_string(query, 'AND'), :column => c)
38
+ }
39
+
40
+ scope "msfte_#{c}_with_booleans".to_sym, lambda { |query|
41
+ return {} if query.blank?
42
+ return msfte_like_bailout(c, query) if Rails.env.test?
43
+ msfte_contains(query, :column => c, :quote => true)
44
+ }
45
+ end
46
+ end
47
+ end
48
+
49
+ module ClassMethods
50
+ private
51
+
52
+ def msfte_like_bailout(column, query)
53
+ { :conditions => "#{table_name}.#{column} LIKE '%#{msfte_quote(query)}%'" }
54
+ end
55
+
56
+ def msfte_contains(query, options = {})
57
+ column = options.fetch(:column, '*')
58
+ quote = options.fetch(:quote, false)
59
+ query_literal = quote ? '?' : query
60
+ condition = "#{table_name}.#{primary_key} IN (SELECT [KEY_TBL].[KEY] FROM CONTAINSTABLE(#{msfte_table_name},#{column},#{query_literal}) AS KEY_TBL)"
61
+ conditions = quote ? [condition, query] : condition
62
+ { :conditions => conditions }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,3 @@
1
+ module IsMsfteSearchable
2
+ VERSION = '3.2.0'
3
+ end
@@ -0,0 +1,191 @@
1
+ require 'helper'
2
+
3
+ describe IsMsfteSearchable::ActiveRecordExtension do
4
+ it "extends ActiveRecord::Base with an is_msfte_searchable class method" do
5
+ ActiveRecord::Base.must_respond_to(:is_msfte_searchable)
6
+ end
7
+
8
+ class FakeActiveRecord
9
+ def self.table_name
10
+ 'people'
11
+ end
12
+
13
+ def self.column_names
14
+ %w(id name)
15
+ end
16
+
17
+ def self.primary_key
18
+ 'id'
19
+ end
20
+
21
+ include IsMsfteSearchable::ActiveRecordExtension
22
+ end
23
+
24
+ describe ".is_msfte_searchable" do
25
+ let(:model) do
26
+ Class.new(FakeActiveRecord)
27
+ end
28
+
29
+ describe "adds msfte_table_name class method" do
30
+ it "that exists" do
31
+ model.is_msfte_searchable
32
+ model.must_respond_to(:msfte_table_name)
33
+ end
34
+
35
+ it "defaults to the model's table_name" do
36
+ model.is_msfte_searchable
37
+ model.msfte_table_name.must_equal 'people'
38
+ end
39
+
40
+ it "is overridden by the :table_name option" do
41
+ model.is_msfte_searchable(:table_name => 'persons')
42
+ model.msfte_table_name.must_equal 'persons'
43
+ end
44
+
45
+ it "and an accessor method" do
46
+ model.is_msfte_searchable
47
+ model.msfte_table_name = 'persons'
48
+ model.msfte_table_name.must_equal 'persons'
49
+ end
50
+ end
51
+
52
+ describe "adds msfte_columns class method" do
53
+ it "that exists" do
54
+ model.is_msfte_searchable
55
+ model.must_respond_to(:msfte_columns)
56
+ end
57
+
58
+ it "defaults to the model's column_names" do
59
+ model.is_msfte_searchable
60
+ model.msfte_columns.must_equal %w(id name)
61
+ end
62
+
63
+ it "is overriden by the :columns option" do
64
+ model.is_msfte_searchable(:columns => [:first_name, :last_name])
65
+ model.msfte_columns.must_equal %w(first_name last_name)
66
+ end
67
+
68
+ it "and an accessor method" do
69
+ model.is_msfte_searchable
70
+ model.msfte_columns = %w(first_name last_name)
71
+ model.msfte_columns.must_equal %w(first_name last_name)
72
+ end
73
+ end
74
+
75
+ describe "adds msfte_catalog class method" do
76
+ it "that exists" do
77
+ model.is_msfte_searchable
78
+ model.must_respond_to(:msfte_catalog)
79
+ end
80
+
81
+ it "defaults to a value derived from the msfte_table_name" do
82
+ model.is_msfte_searchable
83
+ model.msfte_catalog.must_equal "people_fti"
84
+ end
85
+
86
+ it "is overridden by the :catalog option" do
87
+ model.is_msfte_searchable(:catalog => 'persons_fti')
88
+ model.msfte_catalog.must_equal "persons_fti"
89
+ end
90
+
91
+ it "and an accessor method" do
92
+ model.is_msfte_searchable
93
+ model.msfte_catalog = 'persons_fti'
94
+ model.msfte_catalog.must_equal "persons_fti"
95
+ end
96
+ end
97
+
98
+ describe "adds msfte_unique_key_column class method" do
99
+ it "that exists" do
100
+ model.is_msfte_searchable
101
+ model.must_respond_to(:msfte_unique_key_column)
102
+ end
103
+
104
+ it "defaults to the model's primary_key" do
105
+ model.is_msfte_searchable
106
+ model.msfte_unique_key_column.must_equal 'id'
107
+ end
108
+
109
+ it "is overridden by the :unique_key_column option" do
110
+ model.is_msfte_searchable(:unique_key_column => 'pkey')
111
+ model.msfte_unique_key_column.must_equal 'pkey'
112
+ end
113
+
114
+ it "and an accessor method" do
115
+ model.is_msfte_searchable
116
+ model.msfte_unique_key_column = 'pkey'
117
+ model.msfte_unique_key_column.must_equal 'pkey'
118
+ end
119
+ end
120
+
121
+ describe "adds msfte_unique_key_index class method" do
122
+ it "that exists" do
123
+ model.is_msfte_searchable
124
+ model.must_respond_to(:msfte_unique_key_index)
125
+ end
126
+
127
+ it "defaults to a value derived from the msfte_unique_key_column" do
128
+ model.is_msfte_searchable
129
+ model.msfte_unique_key_index.must_equal('id_idx')
130
+ end
131
+
132
+ it "is overridden by the :unique_key_index option" do
133
+ model.is_msfte_searchable(:unique_key_index => 'my_index')
134
+ model.msfte_unique_key_index.must_equal('my_index')
135
+ end
136
+
137
+ it "and an accessor method" do
138
+ model.is_msfte_searchable
139
+ model.msfte_unique_key_index = 'my_index'
140
+ model.msfte_unique_key_index.must_equal('my_index')
141
+ end
142
+ end
143
+
144
+ describe "adds msfte_change_tracking class method" do
145
+ it "that exists" do
146
+ model.is_msfte_searchable
147
+ model.must_respond_to(:msfte_change_tracking)
148
+ end
149
+
150
+ it "defaults to true" do
151
+ model.is_msfte_searchable
152
+ model.msfte_change_tracking.must_equal true
153
+ end
154
+
155
+ it "is overridden by the :change_tracking option" do
156
+ model.is_msfte_searchable(:change_tracking => false)
157
+ model.msfte_change_tracking.must_equal false
158
+ end
159
+
160
+ it "and an accessor method" do
161
+ model.is_msfte_searchable
162
+ model.msfte_change_tracking = false
163
+ model.msfte_change_tracking.must_equal false
164
+ end
165
+ end
166
+
167
+ describe "adds msfte_update_index class method" do
168
+ it "that exists" do
169
+ model.is_msfte_searchable
170
+ model.must_respond_to(:msfte_update_index)
171
+ end
172
+
173
+ it "defaults to true" do
174
+ model.is_msfte_searchable
175
+ model.msfte_update_index.must_equal true
176
+ end
177
+
178
+ it "is overridden by the :update_index option" do
179
+ model.is_msfte_searchable(:update_index => false)
180
+ model.msfte_update_index.must_equal false
181
+ end
182
+
183
+ it "and an accessor method" do
184
+ model.is_msfte_searchable
185
+ model.msfte_update_index = false
186
+ model.msfte_update_index.must_equal false
187
+ end
188
+ end
189
+
190
+ end
191
+ end
@@ -0,0 +1,151 @@
1
+ require 'helper'
2
+
3
+ describe IsMsfteSearchable::ActiveRecordMixin do
4
+ class FakeConnection
5
+ def execute(command)
6
+ end
7
+
8
+ def quote_string(value)
9
+ value
10
+ end
11
+ end
12
+
13
+ class FakeActiveRecord
14
+ def self.table_name
15
+ 'people'
16
+ end
17
+
18
+ def self.column_names
19
+ %w(id name)
20
+ end
21
+
22
+ def self.primary_key
23
+ 'id'
24
+ end
25
+
26
+ def self.connection
27
+ @connection ||= FakeConnection.new
28
+ end
29
+
30
+ def self.scope(name, callable)
31
+ end
32
+
33
+ include IsMsfteSearchable::ActiveRecordExtension
34
+ end
35
+
36
+ let(:model) do
37
+ model = Class.new(FakeActiveRecord)
38
+ model.is_msfte_searchable
39
+ model
40
+ end
41
+
42
+ describe "adds msfte_setup class method" do
43
+ it "that exists" do
44
+ model.must_respond_to(:msfte_setup)
45
+ end
46
+
47
+ it "that executes setup commands on the model's database connection" do
48
+ setup = sequence('setup')
49
+ connection = mock('connection') do
50
+ [
51
+ "sp_fulltext_catalog 'people_fti', 'create'",
52
+ "sp_fulltext_table 'dbo.people', 'create', 'people_fti', 'id_idx'",
53
+ "sp_fulltext_column 'people', 'id', 'add'",
54
+ "sp_fulltext_column 'people', 'name', 'add'",
55
+ "sp_fulltext_table 'dbo.people', 'start_change_tracking'",
56
+ "sp_fulltext_table 'dbo.people', 'start_background_updateindex'"
57
+ ].each do |command|
58
+ expects(:execute).with(command).in_sequence(setup)
59
+ end
60
+ end
61
+ model.stubs(:connection).returns(connection)
62
+ model.msfte_setup
63
+ end
64
+ end
65
+
66
+ describe "adds msfte_teardown class method" do
67
+ it "that exists" do
68
+ model.must_respond_to(:msfte_teardown)
69
+ end
70
+
71
+ it "executes teardown commands on the model's database connection" do
72
+ teardown = sequence(:teardown)
73
+ connection = mock('connection') do
74
+ [
75
+ "sp_fulltext_table 'dbo.people', 'drop'",
76
+ "sp_fulltext_catalog 'people_fti', 'drop'"
77
+ ].each do |command|
78
+ expects(:execute).with(command).in_sequence(teardown)
79
+ end
80
+ end
81
+ model.stubs(:connection).returns(connection)
82
+ model.msfte_teardown
83
+ end
84
+ end
85
+
86
+ describe "adds msfte_reset! class method" do
87
+ it "that exists" do
88
+ model.must_respond_to(:msfte_reset!)
89
+ end
90
+
91
+ it "calls msfte_teardown, then msfte_setup" do
92
+ catalog = states('catalog').starts_as('up')
93
+ model.expects(:msfte_teardown).then(catalog.is('down'))
94
+ model.expects(:msfte_setup).when(catalog.is('down')).then(catalog.is('up'))
95
+ model.msfte_reset!
96
+ end
97
+
98
+ it "yields the given block after msfte_teardown before msfte_setup" do
99
+ catalog = states('catalog').starts_as('up')
100
+ model.expects(:msfte_teardown).then(catalog.is('down'))
101
+ model.expects(:call_the_block!).when(catalog.is('down'))
102
+ model.expects(:msfte_setup).when(catalog.is('down')).then(catalog.is('up'))
103
+ model.msfte_reset! do
104
+ model.call_the_block!
105
+ end
106
+ end
107
+ end
108
+
109
+ describe "adds msfte_catalog_rebuild class method" do
110
+ it "that exists" do
111
+ model.must_respond_to(:msfte_catalog_rebuild)
112
+ end
113
+
114
+ it "executes rebuild commands on the model's database connection" do
115
+ connection = mock('connection') do
116
+ command = "sp_fulltext_catalog 'people_fti', 'rebuild'"
117
+ expects(:execute).with(command)
118
+ end
119
+ model.stubs(:connection).returns(connection)
120
+ model.msfte_catalog_rebuild
121
+ end
122
+ end
123
+
124
+ describe "adds msfte_quote class method" do
125
+ it "that exists" do
126
+ model.must_respond_to(:msfte_quote)
127
+ end
128
+
129
+ it "delegates to the model's database connection" do
130
+ connection = mock('connection') do
131
+ expects('quote_string').with('unquoted').returns('quoted')
132
+ end
133
+ model.stubs(:connection).returns(connection)
134
+ model.msfte_quote('unquoted').must_equal 'quoted'
135
+ end
136
+ end
137
+
138
+ describe "adds msfte_search_string class method" do
139
+ it "that exists" do
140
+ model.must_respond_to(:msfte_search_string)
141
+ end
142
+
143
+ it "adds a wildcard to and quotes the query" do
144
+ model.msfte_search_string('query').must_equal "'\"query *\"'"
145
+ end
146
+
147
+ it "joins the query with the given boolean operator" do
148
+ model.msfte_search_string('term1 term2 ', 'OR').must_equal "'\"term1 *\" OR \"term2 *\"'"
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,183 @@
1
+ require 'helper'
2
+
3
+ class Rails
4
+ class Env
5
+ def self.test?
6
+ false
7
+ end
8
+ end
9
+
10
+ def self.env
11
+ Env
12
+ end
13
+ end
14
+
15
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
16
+
17
+ ActiveRecord::Base.class_eval do
18
+ silence do
19
+ connection.create_table :people, :force => true do |t|
20
+ t.column :name, :string
21
+ end
22
+ end
23
+ end
24
+
25
+ class Person < ActiveRecord::Base
26
+ is_msfte_searchable(:columns => %w(name))
27
+ end
28
+
29
+ # The SQL generated is courtesy of the sqlite adapter, so the whitespace and
30
+ # quoting behavior are specific to it and may be brittle.
31
+ describe IsMsfteSearchable::ArelMixin do
32
+
33
+ describe ".msfte_with_phrase" do
34
+ it "exists" do
35
+ Person.must_respond_to(:msfte_with_phrase)
36
+ end
37
+
38
+ it "returns a scope searching for the query as a phrase" do
39
+ Person.msfte_with_phrase('foo bar').to_sql.must_equal(
40
+ %{SELECT "people".* FROM "people" WHERE (people.id IN (SELECT [KEY_TBL].[KEY] FROM CONTAINSTABLE(people,*,'"foo bar *"') AS KEY_TBL))}
41
+ )
42
+ end
43
+
44
+ it "returns an empty scope when the query is blank" do
45
+ Person.msfte_with_phrase('').to_sql.must_equal(
46
+ %{SELECT "people".* FROM "people" }
47
+ )
48
+ end
49
+ end
50
+
51
+ describe ".msfte_with_any" do
52
+ it "exists" do
53
+ Person.must_respond_to(:msfte_with_any)
54
+ end
55
+
56
+ it "returns a scope searching for any of the query terms" do
57
+ Person.msfte_with_any('foo bar').to_sql.must_equal(
58
+ %{SELECT "people".* FROM "people" WHERE (people.id IN (SELECT [KEY_TBL].[KEY] FROM CONTAINSTABLE(people,*,'"foo *" OR "bar *"') AS KEY_TBL))}
59
+ )
60
+ end
61
+
62
+ it "returns an empty scope when the query is blank" do
63
+ Person.msfte_with_any('').to_sql.must_equal(
64
+ %{SELECT "people".* FROM "people" }
65
+ )
66
+ end
67
+ end
68
+
69
+ describe ".msfte_with_all" do
70
+ it "exists" do
71
+ Person.must_respond_to(:msfte_with_all)
72
+ end
73
+
74
+ it "returns a scope searching for all of the query terms" do
75
+ Person.msfte_with_all('foo bar').to_sql.must_equal(
76
+ %{SELECT "people".* FROM "people" WHERE (people.id IN (SELECT [KEY_TBL].[KEY] FROM CONTAINSTABLE(people,*,'"foo *" AND "bar *"') AS KEY_TBL))}
77
+ )
78
+ end
79
+
80
+ it "returns an empty scope when the query is blank" do
81
+ Person.msfte_with_all('').to_sql.must_equal(
82
+ %{SELECT "people".* FROM "people" }
83
+ )
84
+ end
85
+ end
86
+
87
+ describe ".msfte_with_booleans" do
88
+ it "exists" do
89
+ Person.must_respond_to(:msfte_with_booleans)
90
+ end
91
+
92
+ it "returns a scope searching for the query terms as given?" do
93
+ Person.msfte_with_booleans('foo bar').to_sql.must_equal(
94
+ %{SELECT "people".* FROM "people" WHERE (people.id IN (SELECT [KEY_TBL].[KEY] FROM CONTAINSTABLE(people,*,'foo bar') AS KEY_TBL))}
95
+ )
96
+ end
97
+
98
+ it "returns an empty scope when the query is blank" do
99
+ Person.msfte_with_booleans('').to_sql.must_equal(
100
+ %{SELECT "people".* FROM "people" }
101
+ )
102
+ end
103
+ end
104
+
105
+ describe "column methods" do
106
+
107
+ describe ".msfte_name_with_any" do
108
+ it "exists" do
109
+ Person.must_respond_to(:msfte_name_with_any)
110
+ end
111
+
112
+ it "returns a scope searching for any of the query terms" do
113
+ Person.msfte_name_with_any('foo bar').to_sql.must_equal(
114
+ %{SELECT "people".* FROM "people" WHERE (people.id IN (SELECT [KEY_TBL].[KEY] FROM CONTAINSTABLE(people,name,'"foo *" OR "bar *"') AS KEY_TBL))}
115
+ )
116
+ end
117
+
118
+ it "returns an empty scope when the query is blank" do
119
+ Person.msfte_name_with_any('').to_sql.must_equal(
120
+ %{SELECT "people".* FROM "people" }
121
+ )
122
+ end
123
+
124
+ it "returns a scope using a LIKE query when Rails.env.test?" do
125
+ Rails.env.stubs(:test?).returns(true)
126
+ Person.msfte_name_with_any('foo bar').to_sql.must_equal(
127
+ %{SELECT "people".* FROM "people" WHERE (people.name LIKE '%foo bar%')}
128
+ )
129
+ end
130
+ end
131
+
132
+ describe ".msfte_name_with_all" do
133
+ it "exists" do
134
+ Person.must_respond_to(:msfte_name_with_all)
135
+ end
136
+
137
+ it "returns a scope searching for all of the query terms" do
138
+ Person.msfte_name_with_all('foo bar').to_sql.must_equal(
139
+ %{SELECT "people".* FROM "people" WHERE (people.id IN (SELECT [KEY_TBL].[KEY] FROM CONTAINSTABLE(people,name,'"foo *" AND "bar *"') AS KEY_TBL))}
140
+ )
141
+ end
142
+
143
+ it "returns an empty scope when the query is blank" do
144
+ Person.msfte_name_with_all('').to_sql.must_equal(
145
+ %{SELECT "people".* FROM "people" }
146
+ )
147
+ end
148
+
149
+ it "returns a scope using a LIKE query when Rails.env.test?" do
150
+ Rails.env.stubs(:test?).returns(true)
151
+ Person.msfte_name_with_all('foo bar').to_sql.must_equal(
152
+ %{SELECT "people".* FROM "people" WHERE (people.name LIKE '%foo bar%')}
153
+ )
154
+ end
155
+ end
156
+
157
+ describe ".msfte_name_with_booleans" do
158
+ it "exists" do
159
+ Person.must_respond_to(:msfte_name_with_booleans)
160
+ end
161
+
162
+ it "returns a scope searching for the query terms as given?" do
163
+ Person.msfte_name_with_booleans('foo bar').to_sql.must_equal(
164
+ %{SELECT "people".* FROM "people" WHERE (people.id IN (SELECT [KEY_TBL].[KEY] FROM CONTAINSTABLE(people,name,'foo bar') AS KEY_TBL))}
165
+ )
166
+ end
167
+
168
+ it "returns an empty scope when the query is blank" do
169
+ Person.msfte_name_with_booleans('').to_sql.must_equal(
170
+ %{SELECT "people".* FROM "people" }
171
+ )
172
+ end
173
+
174
+ it "returns a scope using a LIKE query when Rails.env.test?" do
175
+ Rails.env.stubs(:test?).returns(true)
176
+ Person.msfte_name_with_booleans('foo bar').to_sql.must_equal(
177
+ %{SELECT "people".* FROM "people" WHERE (people.name LIKE '%foo bar%')}
178
+ )
179
+ end
180
+ end
181
+ end
182
+
183
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'bundler/setup'
4
+ Bundler.require(:default)
5
+ require 'minitest/autorun'
6
+ require 'mocha'
metadata ADDED
@@ -0,0 +1,179 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: is_msfte_searchable
3
+ version: !ruby/object:Gem::Version
4
+ hash: 15
5
+ prerelease:
6
+ segments:
7
+ - 3
8
+ - 2
9
+ - 0
10
+ version: 3.2.0
11
+ platform: ruby
12
+ authors:
13
+ - Ken Collins
14
+ - Donald Ball
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2012-03-14 00:00:00 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activerecord
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 15
30
+ segments:
31
+ - 3
32
+ - 2
33
+ - 0
34
+ version: 3.2.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: activesupport
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 15
46
+ segments:
47
+ - 3
48
+ - 2
49
+ - 0
50
+ version: 3.2.0
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: rake
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 63
62
+ segments:
63
+ - 0
64
+ - 9
65
+ - 2
66
+ version: 0.9.2
67
+ type: :development
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ hash: 45
78
+ segments:
79
+ - 2
80
+ - 8
81
+ - 1
82
+ version: 2.8.1
83
+ type: :development
84
+ version_requirements: *id004
85
+ - !ruby/object:Gem::Dependency
86
+ name: mocha
87
+ prerelease: false
88
+ requirement: &id005 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ hash: 61
94
+ segments:
95
+ - 0
96
+ - 10
97
+ - 5
98
+ version: 0.10.5
99
+ type: :development
100
+ version_requirements: *id005
101
+ - !ruby/object:Gem::Dependency
102
+ name: sqlite3
103
+ prerelease: false
104
+ requirement: &id006 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ hash: 9
110
+ segments:
111
+ - 1
112
+ - 3
113
+ version: "1.3"
114
+ type: :development
115
+ version_requirements: *id006
116
+ description: ActiveRecord extensions for Microsoft SQL Server full-text index
117
+ email:
118
+ - ken@metaskills.net
119
+ - donald.ball@gmail.com
120
+ executables: []
121
+
122
+ extensions: []
123
+
124
+ extra_rdoc_files: []
125
+
126
+ files:
127
+ - .gitignore
128
+ - CHANGELOG.md
129
+ - Gemfile
130
+ - LICENSE
131
+ - README.md
132
+ - Rakefile
133
+ - lib/is_msfte_searchable.rb
134
+ - lib/is_msfte_searchable/active_record_extension.rb
135
+ - lib/is_msfte_searchable/active_record_mixin.rb
136
+ - lib/is_msfte_searchable/arel_mixin.rb
137
+ - lib/is_msfte_searchable/version.rb
138
+ - test/active_record_extension_test.rb
139
+ - test/active_record_mixin_test.rb
140
+ - test/arel_mixin_test.rb
141
+ - test/helper.rb
142
+ homepage: http://github.com/Decisiv/is_msfte_searchable/
143
+ licenses: []
144
+
145
+ post_install_message:
146
+ rdoc_options:
147
+ - --charset=UTF-8
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ none: false
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ hash: 3
156
+ segments:
157
+ - 0
158
+ version: "0"
159
+ required_rubygems_version: !ruby/object:Gem::Requirement
160
+ none: false
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ hash: 3
165
+ segments:
166
+ - 0
167
+ version: "0"
168
+ requirements: []
169
+
170
+ rubyforge_project:
171
+ rubygems_version: 1.8.17
172
+ signing_key:
173
+ specification_version: 3
174
+ summary: ActiveRecord extensions for Microsoft SQL Server full-text index
175
+ test_files:
176
+ - test/active_record_extension_test.rb
177
+ - test/active_record_mixin_test.rb
178
+ - test/arel_mixin_test.rb
179
+ - test/helper.rb