tanker 0.5.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ gem 'jeweler'
4
4
  gem 'will_paginate', '>= 2.3.15'
5
5
 
6
6
  group :test do
7
- gem 'rspec', '>= 2.0.0.beta.22'
7
+ gem 'rspec', '>= 2.5.0'
8
8
  end
9
9
 
10
10
 
data/README.rdoc CHANGED
@@ -1,7 +1,5 @@
1
1
  = Tanker - IndexTank integration to your favorite orm
2
2
 
3
- *Note:* This gem is still quite alpha but please feel free to send comments
4
-
5
3
  IndexTank is a great search indexing service, this gem tries to make
6
4
  any orm keep in sync with indextank with ease. This gem has a simple
7
5
  but powerful approach to integrating IndexTank to any orm. The gem
@@ -39,6 +37,7 @@ in the IndexTank Dashboard
39
37
 
40
38
  class Topic < ActiveRecord::Base
41
39
  acts_as_taggable
40
+ has_many :authors
42
41
 
43
42
  # just include the Tanker module
44
43
  include Tanker
@@ -50,7 +49,32 @@ in the IndexTank Dashboard
50
49
  tankit 'my_index' do
51
50
  indexes :title
52
51
  indexes :content
52
+ indexes :id
53
53
  indexes :tag_list #NOTICE this is an array of Tags! Awesome!
54
+
55
+ # you may also dynamically retrieve field data
56
+ indexes :author_names do
57
+ authors.map {|author| author.name }
58
+ end
59
+
60
+ # to pass in a list of variables with your document,
61
+ # supply a hash with the variable integers as keys:
62
+ variables do
63
+ {
64
+ 0 => authors.first.id, # these will be available in your queries
65
+ 1 => id # as doc.var[0] and doc.var[1]
66
+ }
67
+ end
68
+
69
+ # You may defined some server-side functions that can be
70
+ # referenced in your queries. They're always referenced by
71
+ # their integer key:
72
+ functions do
73
+ {
74
+ 1 => '-age',
75
+ 2 => 'doc.var[0] - doc.var[1]'
76
+ }
77
+ end
54
78
  end
55
79
 
56
80
  # define the callbacks to update or delete the index
@@ -78,10 +102,83 @@ Search Topics
78
102
  @topics = Topic.search_tank('blah', :conditions => {:tag_list => 'tag1'}) # Gets 2 results!
79
103
  @topics = Topic.search_tank('blah', :conditions => {:title => 'Awesome', :tag_list => 'tag1'}) # Gets 1 result!
80
104
 
105
+ Search multiple models
106
+
107
+ @results = Tanker.search([Topic, Post], 'blah') # Gets any Topic OR Post results that match!
108
+
109
+ Search with negative conditions
110
+
111
+ @topics = Topic.search_tank('keyword', :conditions => {'tag_list' => 'tag1'}) # Gets 1 result!
112
+ @topics = Topic.search_tank('keyword', :conditions => {'-id' => [5,9]}) # Ignores documents with ids of '5' or '9'
113
+
114
+ Search with query variables
115
+
116
+ @topics = Topic.search_tank('keyword', :variables => {0 => 5.6}) # Makes 5.6 available server-side as q[0]
117
+
81
118
  Paginate Results
82
119
 
83
120
  <% will_paginate(@topics) %>
84
121
 
122
+ == Geospatial Search Example
123
+
124
+ IndexTank and the Tanker gem support native geographic calculations. All you need is to define two document variables with your latitude and your longitude
125
+
126
+ class Restaurant < ActiveRecord::Base
127
+ include Tanker
128
+
129
+ tankit 'my_index' do
130
+ indexes :name
131
+
132
+ # You may define lat/lng at any variable number you like, as long
133
+ # as you refer to them consistently with the same integers
134
+ variables do
135
+ {
136
+ 0 => lat
137
+ 1 => lng
138
+ }
139
+ end
140
+
141
+ functions do
142
+ {
143
+ # This function is good for sorting your results. It will
144
+ # rank relevant, close results higher and irrelevant, distant results lower
145
+ 1 => "relevance / miles(d[0], d[1], q[0], q[1])",
146
+
147
+ # This function is useful for limiting the results that your query returns.
148
+ # It just calculates the distance of each document in miles, which you can use
149
+ # in your query. Notice that we're using 0 and 1 consistently as 'lat' and 'lng'
150
+ 2 => "miles(d[0], d[1], q[0], q[1])"
151
+ }
152
+ end
153
+ end
154
+ end
155
+
156
+ # When searching, you need to provide a latitude and longitude in the query so that
157
+ # these variables are accessible on the server.
158
+ Restaurant.search("tasty",
159
+ :var0 => 47.689948,
160
+ :var1 => -122.363684,
161
+ # And we'll return all results sorted by distance
162
+ :function => 1)
163
+
164
+ # Or we can use just function 2 to return results in the default order, while
165
+ # limiting the total results to just those within 50 miles from our lat/lng:
166
+ Restaurant.search("tasty",
167
+ :var0 => 47.689948,
168
+ :var1 => -122.363684,
169
+ # the following is a fancy way of saying "return all results where
170
+ the result of function 2 is at least anything and less than 50"
171
+ :filter_functions => {2 => [['*', 50]]})
172
+ # And we can combine these two function calls to return only results within 50 miles
173
+ # and sort them by relevance / distance:
174
+ Restaurant.search("tasty",
175
+ :var0 => 47.689948,
176
+ :var1 => -122.363684,
177
+ :function => 1,
178
+ :filter_functions => {2 => [['*', 50]]})
179
+
180
+
181
+
85
182
  == Reindex your data
86
183
 
87
184
  If you are using rails 3 there are of couple of rake tasks included in your project
@@ -93,11 +190,28 @@ the indexes in the Index Tank interface this task creates them for you provided
93
190
  you have enough indexes in your account.
94
191
 
95
192
  rake tanker:reindex
193
+
194
+ This task pushes any functions you've defined in your tankit() blocks to indextank.com
195
+
196
+ rake tanker:functions
96
197
 
97
198
  This task re-indexes all the your models that have the Tanker module included.
98
199
  Usually you will only need a single Index Tank index but if you want to separate
99
200
  your indexes please use different index names in the tankit method call in your models
100
201
 
202
+ To reindex just one class rather than all the classes in your application just call:
203
+
204
+ Model.tanker_reindex
205
+
206
+ And if you'd like to specify a custom number of records to PUT with each call to indextank.com you may specify it as the first argument:
207
+
208
+ Model.tanker_reindex(:batch_size => 1000) # defaults to batch size of 200
209
+
210
+ Or you can restrict indexing to a subset of records. This is particularly helpful for massive datasets where only a small portion of the records is useful.
211
+
212
+ Model.tanker_reindex(:batch_size => 1000, :scope => :not_deleted) # will call Model.not_deleted.all internally
213
+
214
+
101
215
  == TODO
102
216
  * Documentation
103
217
  * More fancy stuff (Suggestions?)
data/Rakefile CHANGED
@@ -9,9 +9,9 @@ begin
9
9
  gem.description = %Q{IndexTank is a great search indexing service, this gem tries to make any orm keep in sync with indextank with ease}
10
10
  gem.email = "kidpollo@gmail.com"
11
11
  gem.homepage = "http://github.com/kidpollo/tanker"
12
- gem.authors = ["@kidpollo"]
13
- gem.add_development_dependency "rspec", ">= 2.0.0.beta.22"
14
- gem.add_dependency 'will_paginate', '>= 2.3.15'
12
+ gem.authors = ["@kidpollo", "Jack Danger Canty"]
13
+ #gem.add_development_dependency "rspec", ">= 2.0.0.beta.22"
14
+ #gem.add_dependency 'will_paginate', '>= 2.3.15'
15
15
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
16
  end
17
17
  Jeweler::GemcutterTasks.new
@@ -21,10 +21,11 @@ end
21
21
 
22
22
  require 'rake/testtask'
23
23
  Rake::TestTask.new(:test) do |test|
24
- test.libs << 'lib' << 'test'
25
- test.pattern = 'test/**/test_*.rb'
24
+ test.libs << 'lib' << 'spec'
25
+ test.pattern = 'spec/*_spec.rb'
26
26
  test.verbose = true
27
27
  end
28
+ task :spec => :test
28
29
 
29
30
  begin
30
31
  require 'rcov/rcovtask'
@@ -39,7 +40,7 @@ rescue LoadError
39
40
  end
40
41
  end
41
42
 
42
- task :test => :check_dependencies
43
+ #task :test => :check_dependencies
43
44
 
44
45
  task :default => :test
45
46
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.6
1
+ 1.0.0
@@ -139,6 +139,11 @@ module IndexTank
139
139
  return r
140
140
  end
141
141
 
142
+ def add_documents(data_array)
143
+ code, r = PUT "/docs", data_array
144
+ return r
145
+ end
146
+
142
147
  def update_variables(docid, variables, options={})
143
148
  options.merge!( :docid => docid, :variables => variables )
144
149
  code, r = PUT "/docs/variables", options
@@ -2,11 +2,46 @@ namespace :tanker do
2
2
 
3
3
  desc "Reindex all models"
4
4
  task :reindex => :environment do
5
- puts "reinexing all models"
5
+ puts "reindexing all models"
6
6
  load_models
7
7
  Tanker::Utilities.reindex_all_models
8
8
  end
9
9
 
10
+ desc "Update IndexTank functions"
11
+ task :functions => :environment do
12
+ puts "reindexing all IndexTank functions"
13
+ load_models
14
+ indexes = {}
15
+ Tanker::Utilities.get_model_classes.each do |model|
16
+ model.tanker_config.functions.each do |idx, definition|
17
+ indexes[model.tanker_config.index_name] ||= {}
18
+ indexes[model.tanker_config.index_name][idx] = definition
19
+ end
20
+ end
21
+ if indexes.blank?
22
+ puts <<-HELP
23
+ No IndexTank functions defined.
24
+ Define your server-side functions inside your model's tankit block like so:
25
+ tankit 'myindex' do
26
+ functions do
27
+ {
28
+ 1 => "-age",
29
+ 2 => "relevance / miles(d[0], d[1], q[0], q[1])"
30
+ }
31
+ end
32
+ end
33
+ HELP
34
+ else
35
+ indexes.each do |index_name, functions|
36
+ index = Tanker.api.get_index(index_name)
37
+ functions.each do |idx, definition|
38
+ index.add_function(idx, definition)
39
+ puts "Index #{index_name.inspect} function: #{idx} => #{definition.inspect}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+
10
45
  desc "Clear all Index Tank indexes"
11
46
  task :clear_indexes => :environment do
12
47
  puts "clearing all indexes"
@@ -6,7 +6,7 @@ module Tanker
6
6
  end
7
7
 
8
8
  def get_available_indexes
9
- get_model_classes.map{|model| model.index_name}.uniq.compact
9
+ get_model_classes.map{|model| model.tanker_config.index_name}.uniq.compact
10
10
  end
11
11
 
12
12
  def clear_all_indexes
@@ -32,10 +32,7 @@ module Tanker
32
32
 
33
33
  def reindex_all_models
34
34
  get_model_classes.each do |klass|
35
- puts "Indexing #{klass.to_s} model"
36
- klass.all.each do |model_instance|
37
- model_instance.update_tank_indexes
38
- end
35
+ klass.tanker_reindex
39
36
  end
40
37
  end
41
38
  end
data/lib/tanker.rb CHANGED
@@ -1,8 +1,12 @@
1
- require "rubygems"
2
- require "bundler"
3
1
 
4
- Bundler.setup :default
2
+ begin
3
+ require "rubygems"
4
+ require "bundler"
5
5
 
6
+ Bundler.setup :default
7
+ rescue => e
8
+ puts "Tanker: #{e.message}"
9
+ end
6
10
  require 'indextank_client'
7
11
  require 'tanker/configuration'
8
12
  require 'tanker/utilities'
@@ -10,7 +14,10 @@ require 'will_paginate/collection'
10
14
 
11
15
 
12
16
  if defined? Rails
13
- require 'tanker/railtie'
17
+ begin
18
+ require 'tanker/railtie'
19
+ rescue LoadError
20
+ end
14
21
  end
15
22
 
16
23
  module Tanker
@@ -33,8 +40,7 @@ module Tanker
33
40
  @included_in << klass
34
41
  @included_in.uniq!
35
42
 
36
- klass.instance_variable_set('@tanker_configuration', configuration)
37
- klass.instance_variable_set('@tanker_indexes', [])
43
+ configuration # raises error if not defined
38
44
  klass.send :include, InstanceMethods
39
45
  klass.extend ClassMethods
40
46
 
@@ -42,62 +48,177 @@ module Tanker
42
48
  define_method(:per_page) { 10 } unless respond_to?(:per_page)
43
49
  end
44
50
  end
51
+
52
+ def batch_update(records)
53
+ return false if records.empty?
54
+ data = records.map do |record|
55
+ options = record.tanker_index_options
56
+ options.merge!( :docid => record.it_doc_id, :fields => record.tanker_index_data )
57
+ options
58
+ end
59
+ records.first.class.tanker_index.add_documents(data)
60
+ end
61
+
62
+ def search(models, query, options = {})
63
+ ids = []
64
+ models = [models].flatten.uniq
65
+ page = (options.delete(:page) || 1).to_i
66
+ per_page = (options.delete(:per_page) || models.first.per_page).to_i
67
+ index = models.first.tanker_index
68
+ query = query.join(' ') if Array === query
69
+
70
+ if (index_names = models.map(&:tanker_config).map(&:index_name).uniq).size > 1
71
+ raise "You can't search across multiple indexes in one call (#{index_names.inspect})"
72
+ end
73
+
74
+
75
+ # move conditions into the query body
76
+ if conditions = options.delete(:conditions)
77
+ conditions.each do |field, value|
78
+ value = [value].flatten.compact
79
+ value.each do |item|
80
+ query += " #{field}:(#{item})"
81
+ end
82
+ end
83
+ end
84
+
85
+ # rephrase filter_functions
86
+ if filter_functions = options.delete(:filter_functions)
87
+ filter_functions.each do |function_number, ranges|
88
+ options[:"filter_function#{function_number}"] = ranges.map{|r|r.join(':')}.join(',')
89
+ end
90
+ end
91
+
92
+ # rephrase filter_docvars
93
+ if filter_docvars = options.delete(:filter_docvars)
94
+ filter_docvars.each do |var_number, ranges|
95
+ options[:"filter_docvar#{var_number}"] = ranges.map{|r|r.join(':')}.join(',')
96
+ end
97
+ end
98
+
99
+ query = "__any:(#{query.to_s}) __type:(#{models.map(&:name).join(' OR ')})"
100
+ options = { :start => per_page * (page - 1), :len => per_page }.merge(options)
101
+ results = index.search(query, options)
102
+
103
+ @entries = WillPaginate::Collection.create(page, per_page) do |pager|
104
+ # inject the result array into the paginated collection:
105
+ pager.replace instantiate_results(results)
106
+
107
+ unless pager.total_entries
108
+ # the pager didn't manage to guess the total count, do it manually
109
+ pager.total_entries = results["matches"]
110
+ end
111
+ end
112
+ end
113
+
114
+ protected
115
+
116
+ def instantiate_results(index_result)
117
+ results = index_result['results']
118
+ return [] if results.empty?
119
+
120
+ id_map = results.inject({}) do |acc, result|
121
+ model, id = result["docid"].split(" ", 2)
122
+ acc[model] ||= []
123
+ acc[model] << id.to_i
124
+ acc
125
+ end
126
+
127
+ if 1 == id_map.size # check for simple case, just one model involved
128
+ klass = constantize(id_map.keys.first)
129
+ # eager-load and return just this model's records
130
+ klass.find(id_map.values.flatten)
131
+ else # complex case, multiple models involved
132
+ id_map.each do |klass, ids|
133
+ # replace the id list with an eager-loaded list of records for this model
134
+ id_map[klass] = constantize(klass).find(ids)
135
+ end
136
+ # return them in order
137
+ results.map do |result|
138
+ model, id = result["docid"].split(" ", 2)
139
+ id_map[model].detect {|record| id.to_i == record.id }
140
+ end
141
+ end
142
+ end
143
+
144
+ def constantize(klass_name)
145
+ Object.const_defined?(klass_name) ?
146
+ Object.const_get(klass_name) :
147
+ Object.const_missing(klass_name)
148
+ end
45
149
  end
46
150
 
47
151
  # these are the class methods added when Tanker is included
152
+ # They're kept to a minimum to prevent namespace pollution
48
153
  module ClassMethods
49
154
 
50
- attr_reader :tanker_indexes, :index_name
155
+ attr_accessor :tanker_config
51
156
 
52
157
  def tankit(name, &block)
53
158
  if block_given?
54
- @index_name = name
55
- self.instance_exec(&block)
159
+ self.tanker_config = ModelConfig.new(name, block)
56
160
  else
57
161
  raise(NoBlockGiven, 'Please provide a block')
58
162
  end
59
163
  end
60
164
 
61
- def indexes(field)
62
- @tanker_indexes << field
165
+ def search_tank(query, options = {})
166
+ Tanker.search([self], query, options)
63
167
  end
64
168
 
65
- def index
66
- @index ||= Tanker.api.get_index(self.index_name)
169
+ def tanker_index
170
+ tanker_config.index
67
171
  end
68
172
 
69
- def search_tank(query, options = {})
70
- ids = []
71
- page = options.delete(:page) || 1
72
- per_page = options.delete(:per_page) || self.per_page
173
+ def tanker_reindex(options = {})
174
+ puts "Indexing #{self} model"
73
175
 
74
- # transform fields in query
75
- if options.has_key? :conditions
76
- options[:conditions].each do |field,value|
77
- query += " #{field}:(#{value})"
78
- end
79
- end
176
+ batches = []
177
+ options[:batch_size] ||= 200
178
+ records = options[:scope] ? send(options[:scope]).all : all
179
+ record_size = records.size
80
180
 
81
- query = "__any:(#{query.to_s}) __type:#{self.name}"
82
- options = { :start => per_page * (page - 1), :len => per_page }.merge(options)
83
- results = index.search(query, options)
181
+ records.each_with_index do |model_instance, idx|
182
+ batch_num = idx / options[:batch_size]
183
+ (batches[batch_num] ||= []) << model_instance
184
+ end
84
185
 
85
- ids = unless results["results"].empty?
86
- results["results"].map{|res| res["docid"].split(" ", 2)[1]}
87
- else
88
- []
186
+ timer = Time.now
187
+ batches.each_with_index do |batch, idx|
188
+ Tanker.batch_update(batch)
189
+ puts "Indexed #{batch.size} records #{(idx * options[:batch_size]) + batch.size}/#{record_size}"
89
190
  end
191
+ puts "Indexed #{record_size} #{self} records in #{Time.now - timer} seconds"
192
+ end
193
+ end
90
194
 
91
- @entries = WillPaginate::Collection.create(page, per_page) do |pager|
92
- result = self.find(ids)
93
- # inject the result array into the paginated collection:
94
- pager.replace(result)
195
+ class ModelConfig
196
+ attr_reader :index_name
95
197
 
96
- unless pager.total_entries
97
- # the pager didn't manage to guess the total count, do it manually
98
- pager.total_entries = results["matches"]
99
- end
100
- end
198
+ def initialize(index_name, block)
199
+ @index_name = index_name
200
+ @indexes = []
201
+ @functions = {}
202
+ instance_exec &block
203
+ end
204
+
205
+ def indexes(field = nil, &block)
206
+ @indexes << [field, block] if field
207
+ @indexes
208
+ end
209
+
210
+ def variables(&block)
211
+ @variables = block if block
212
+ @variables
213
+ end
214
+
215
+ def functions(&block)
216
+ @functions = block.call if block
217
+ @functions
218
+ end
219
+
220
+ def index
221
+ @index ||= Tanker.api.get_index(index_name)
101
222
  end
102
223
 
103
224
  end
@@ -105,28 +226,58 @@ module Tanker
105
226
  # these are the instance methods included
106
227
  module InstanceMethods
107
228
 
229
+ def tanker_config
230
+ self.class.tanker_config || raise(NotConfigured, "Please configure Tanker for #{self.class.inspect} with the 'tankit' block")
231
+ end
232
+
108
233
  def tanker_indexes
109
- self.class.tanker_indexes
234
+ tanker_config.indexes
235
+ end
236
+
237
+ def tanker_variables
238
+ tanker_config.variables
110
239
  end
111
240
 
112
241
  # update a create instance from index tank
113
242
  def update_tank_indexes
243
+ tanker_config.index.add_document(
244
+ it_doc_id, tanker_index_data, tanker_index_options
245
+ )
246
+ end
247
+
248
+ # delete instance from index tank
249
+ def delete_tank_indexes
250
+ tanker_config.index.delete_document(it_doc_id)
251
+ end
252
+
253
+ def tanker_index_data
114
254
  data = {}
115
255
 
116
- tanker_indexes.each do |field|
117
- val = self.instance_eval(field.to_s)
118
- data[field.to_s] = val.to_s unless val.nil?
256
+ # attempt to autodetect timestamp
257
+ if respond_to?(:created_at)
258
+ data[:timestamp] = created_at.to_i
119
259
  end
120
260
 
121
- data[:__any] = data.values.join " . "
261
+ tanker_indexes.each do |field, block|
262
+ val = block ? instance_exec(&block) : send(field)
263
+ val = val.join(' ') if Array === val
264
+ data[field.to_sym] = val.to_s unless val.nil?
265
+ end
266
+
267
+ data[:__any] = data.values.sort_by{|v| v.to_s}.join " . "
122
268
  data[:__type] = self.class.name
123
269
 
124
- self.class.index.add_document(it_doc_id, data)
270
+ data
125
271
  end
126
272
 
127
- # delete instance from index tank
128
- def delete_tank_indexes
129
- self.class.index.delete_document(it_doc_id)
273
+ def tanker_index_options
274
+ options = {}
275
+
276
+ if tanker_variables
277
+ options[:variables] = instance_exec(&tanker_variables)
278
+ end
279
+
280
+ options
130
281
  end
131
282
 
132
283
  # create a unique index based on the model name and unique id
@@ -134,4 +285,4 @@ module Tanker
134
285
  self.class.name + ' ' + self.id.to_s
135
286
  end
136
287
  end
137
- end
288
+ end
data/spec/spec_helper.rb CHANGED
@@ -16,28 +16,45 @@ class Dummy
16
16
 
17
17
  end
18
18
 
19
+ $frozen_moment = Time.now
20
+
19
21
  class Person
22
+
23
+ attr_accessor :name, :last_name
24
+
25
+ def initialize(attrs = {})
26
+ attrs.each {|k,v| self.send "#{k}=", v }
27
+ self.name ||= 'paco'
28
+ self.last_name ||= 'viramontes'
29
+ end
30
+
20
31
  include Tanker
21
32
 
22
33
  tankit 'people' do
23
34
  indexes :name
24
35
  indexes :last_name
36
+ variables do
37
+ {0 => 1.0,
38
+ 1 => 20.0,
39
+ 2 => 300.0}
40
+ end
25
41
  end
26
42
 
27
- def id
28
- 1
43
+ def created_at
44
+ $frozen_moment
29
45
  end
30
46
 
31
- def name
32
- 'paco'
33
- end
34
-
35
- def last_name
36
- 'viramontes'
47
+ def id
48
+ 1
37
49
  end
38
50
  end
39
51
 
40
52
  class Dog
53
+ attr_accessor :name, :id
54
+ def initialize(attrs = {})
55
+ attrs.each {|k,v| self.send "#{k}=", v }
56
+ end
57
+
41
58
  include Tanker
42
59
 
43
60
  tankit 'animals' do
@@ -47,6 +64,11 @@ class Dog
47
64
  end
48
65
 
49
66
  class Cat
67
+ attr_accessor :name, :id
68
+ def initialize(attrs = {})
69
+ attrs.each {|k,v| self.send "#{k}=", v }
70
+ end
71
+
50
72
  include Tanker
51
73
 
52
74
  tankit 'animals' do
data/spec/tanker_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
2
 
3
3
  describe Tanker do
4
4
 
@@ -33,7 +33,20 @@ describe Tanker do
33
33
  end
34
34
 
35
35
  dummy_instance = Dummy.new
36
- dummy_instance.tanker_indexes.include?(:field).should == true
36
+ dummy_instance.tanker_config.indexes.any? {|field, block| field == :field }.should == true
37
+ end
38
+
39
+ it 'should allow blocks for indexable field data' do
40
+ Tanker.configuration = {:url => 'http://api.indextank.com'}
41
+ Dummy.send(:include, Tanker)
42
+ Dummy.send(:tankit, 'dummy index') do
43
+ indexes :class_name do
44
+ self.class.name
45
+ end
46
+ end
47
+
48
+ dummy_instance = Dummy.new
49
+ dummy_instance.tanker_config.indexes.any? {|field, block| field == :class_name }.should == true
37
50
  end
38
51
 
39
52
  describe 'tanker instance' do
@@ -42,11 +55,11 @@ describe Tanker do
42
55
  end
43
56
 
44
57
  it 'should create a connexion to index tank' do
45
- Person.index.class.should == IndexTank::IndexClient
58
+ Person.tanker_index.class.should == IndexTank::IndexClient
46
59
  end
47
60
 
48
- it 'should be able to perform a seach query' do
49
- Person.index.should_receive(:search).and_return(
61
+ it 'should be able to perform a seach query directly on the model' do
62
+ Person.tanker_index.should_receive(:search).and_return(
50
63
  {
51
64
  "matches" => 1,
52
65
  "results" => [{
@@ -69,18 +82,129 @@ describe Tanker do
69
82
  collection.current_page.should == 1
70
83
  end
71
84
 
85
+ it 'should be able to use multi-value query phrases' do
86
+ Person.tanker_index.should_receive(:search).with(
87
+ "__any:(hey! location_id:(1) location_id:(2)) __type:(Person)",
88
+ {:start => 0, :len => 10}
89
+ ).and_return({'results' => [], 'matches' => 0})
90
+
91
+ collection = Person.search_tank('hey!', :conditions => {:location_id => [1,2]})
92
+ end
93
+
94
+ it 'should be able to use filter_functions' do
95
+ Person.tanker_index.should_receive(:search).with(
96
+ "__any:(hey!) __type:(Person)",
97
+ { :start => 0,
98
+ :len => 10,
99
+ :filter_function2 => "0:10,20:40"
100
+ }
101
+ ).and_return({'results' => [], 'matches' => 0})
102
+
103
+ collection = Person.search_tank('hey!',
104
+ :filter_functions => {
105
+ 2 => [[0,10], [20,40]]
106
+ })
107
+ end
108
+ it 'should be able to use filter_docvars' do
109
+ Person.tanker_index.should_receive(:search).with(
110
+ "__any:(hey!) __type:(Person)",
111
+ { :start => 0,
112
+ :len => 10,
113
+ :filter_docvar3 => "*:7,80:100"
114
+ }
115
+ ).and_return({'results' => [], 'matches' => 0})
116
+
117
+ collection = Person.search_tank('hey!',
118
+ :filter_docvars => {
119
+ 3 => [['*',7], [80,100]]
120
+ })
121
+ end
122
+
123
+ it 'should be able to perform a seach query over several models' do
124
+ index = Tanker.api.get_index('animals')
125
+ Dog.should_receive(:tanker_index).and_return(index)
126
+ index.should_receive(:search).and_return(
127
+ {
128
+ "matches" => 2,
129
+ "results" => [{
130
+ "docid" => 'Dog 7',
131
+ "name" => 'fido'
132
+ },
133
+ {
134
+ "docid" => 'Cat 9',
135
+ "name" => 'fluffy'
136
+ }],
137
+ "search_time" => 1
138
+ }
139
+ )
140
+
141
+ Dog.should_receive(:find).and_return(
142
+ [Dog.new(:name => 'fido', :id => 7)]
143
+ )
144
+ Cat.should_receive(:find).and_return(
145
+ [Cat.new(:name => 'fluffy', :id => 9)]
146
+ )
147
+
148
+ collection = Tanker.search([Dog, Cat], 'hey!')
149
+ collection.class.should == WillPaginate::Collection
150
+ collection.total_entries.should == 2
151
+ collection.total_pages.should == 1
152
+ collection.per_page.should == 10
153
+ collection.current_page.should == 1
154
+ end
155
+
72
156
  it 'should be able to update the index' do
73
- person = Person.new
157
+ person = Person.new(:name => 'Name', :last_name => 'Last Name')
74
158
 
75
- Person.index.should_receive(:add_document)
159
+ Person.tanker_index.should_receive(:add_document).with(
160
+ Person.new.it_doc_id,
161
+ {
162
+ :__any => "#{$frozen_moment.to_i} . Last Name . Name",
163
+ :__type => 'Person',
164
+ :name => 'Name',
165
+ :last_name => 'Last Name',
166
+ :timestamp => $frozen_moment.to_i
167
+ },
168
+ {
169
+ :variables => {
170
+ 0 => 1.0,
171
+ 1 => 20.0,
172
+ 2 => 300.0
173
+ }
174
+ }
175
+ )
76
176
 
77
177
  person.update_tank_indexes
78
178
  end
79
179
 
80
- it 'should be able to delete de document from the index' do
180
+ it 'should be able to batch update the index' do
181
+ person = Person.new(:name => 'Name', :last_name => 'Last Name')
182
+
183
+ Person.tanker_index.should_receive(:add_documents).with(
184
+ [ {
185
+ :docid => Person.new.it_doc_id,
186
+ :fields => {
187
+ :__any => "#{$frozen_moment.to_i} . Last Name . Name",
188
+ :__type => 'Person',
189
+ :name => 'Name',
190
+ :last_name => 'Last Name',
191
+ :timestamp => $frozen_moment.to_i
192
+ },
193
+ :variables => {
194
+ 0 => 1.0,
195
+ 1 => 20.0,
196
+ 2 => 300.0
197
+ }
198
+ } ]
199
+ )
200
+
201
+ Tanker.batch_update([person])
202
+ end
203
+
204
+ it 'should be able to delete the document from the index' do
81
205
  person = Person.new
82
206
 
83
- Person.index.should_receive(:delete_document)
207
+ Person.tanker_index.should_receive(:delete_document)
84
208
 
85
209
  person.delete_tank_indexes
86
210
  end
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
2
 
3
3
  class Dummy
4
4
  include Tanker
data/tanker.gemspec CHANGED
@@ -1,65 +1,62 @@
1
1
  # Generated by jeweler
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{tanker}
8
- s.version = "0.5.6"
8
+ s.version = "1.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["@kidpollo"]
12
- s.date = %q{2011-02-02}
11
+ s.authors = ["@kidpollo", "Jack Danger Canty"]
12
+ s.date = %q{2011-04-02}
13
13
  s.description = %q{IndexTank is a great search indexing service, this gem tries to make any orm keep in sync with indextank with ease}
14
14
  s.email = %q{kidpollo@gmail.com}
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE",
17
- "README.rdoc"
17
+ "README.rdoc"
18
18
  ]
19
19
  s.files = [
20
20
  ".document",
21
- ".gitignore",
22
- "Gemfile",
23
- "Gemfile.lock",
24
- "LICENSE",
25
- "README.rdoc",
26
- "Rakefile",
27
- "VERSION",
28
- "autotest/discover.rb",
29
- "lib/indextank_client.rb",
30
- "lib/tanker.rb",
31
- "lib/tanker/configuration.rb",
32
- "lib/tanker/railtie.rb",
33
- "lib/tanker/tasks/tanker.rake",
34
- "lib/tanker/utilities.rb",
35
- "spec/spec_helper.rb",
36
- "spec/tanker_spec.rb",
37
- "spec/utilities_spec.rb",
38
- "tanker.gemspec"
21
+ "Gemfile",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "autotest/discover.rb",
27
+ "lib/indextank_client.rb",
28
+ "lib/tanker.rb",
29
+ "lib/tanker/configuration.rb",
30
+ "lib/tanker/railtie.rb",
31
+ "lib/tanker/tasks/tanker.rake",
32
+ "lib/tanker/utilities.rb",
33
+ "spec/spec_helper.rb",
34
+ "spec/tanker_spec.rb",
35
+ "spec/utilities_spec.rb",
36
+ "tanker.gemspec"
39
37
  ]
40
38
  s.homepage = %q{http://github.com/kidpollo/tanker}
41
- s.rdoc_options = ["--charset=UTF-8"]
42
39
  s.require_paths = ["lib"]
43
- s.rubygems_version = %q{1.4.2}
40
+ s.rubygems_version = %q{1.7.1}
44
41
  s.summary = %q{IndexTank integration to your favorite orm}
45
42
  s.test_files = [
46
43
  "spec/spec_helper.rb",
47
- "spec/tanker_spec.rb",
48
- "spec/utilities_spec.rb"
44
+ "spec/tanker_spec.rb",
45
+ "spec/utilities_spec.rb"
49
46
  ]
50
47
 
51
48
  if s.respond_to? :specification_version then
52
49
  s.specification_version = 3
53
50
 
54
51
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
55
- s.add_development_dependency(%q<rspec>, [">= 2.0.0.beta.22"])
52
+ s.add_runtime_dependency(%q<jeweler>, [">= 0"])
56
53
  s.add_runtime_dependency(%q<will_paginate>, [">= 2.3.15"])
57
54
  else
58
- s.add_dependency(%q<rspec>, [">= 2.0.0.beta.22"])
55
+ s.add_dependency(%q<jeweler>, [">= 0"])
59
56
  s.add_dependency(%q<will_paginate>, [">= 2.3.15"])
60
57
  end
61
58
  else
62
- s.add_dependency(%q<rspec>, [">= 2.0.0.beta.22"])
59
+ s.add_dependency(%q<jeweler>, [">= 0"])
63
60
  s.add_dependency(%q<will_paginate>, [">= 2.3.15"])
64
61
  end
65
62
  end
metadata CHANGED
@@ -1,56 +1,39 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tanker
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
5
4
  prerelease:
6
- segments:
7
- - 0
8
- - 5
9
- - 6
10
- version: 0.5.6
5
+ version: 1.0.0
11
6
  platform: ruby
12
7
  authors:
13
8
  - "@kidpollo"
9
+ - Jack Danger Canty
14
10
  autorequire:
15
11
  bindir: bin
16
12
  cert_chain: []
17
13
 
18
- date: 2011-02-02 00:00:00 -08:00
19
- default_executable:
14
+ date: 2011-04-02 00:00:00 Z
20
15
  dependencies:
21
16
  - !ruby/object:Gem::Dependency
22
- name: rspec
23
- prerelease: false
17
+ name: jeweler
24
18
  requirement: &id001 !ruby/object:Gem::Requirement
25
19
  none: false
26
20
  requirements:
27
21
  - - ">="
28
22
  - !ruby/object:Gem::Version
29
- hash: 62196431
30
- segments:
31
- - 2
32
- - 0
33
- - 0
34
- - beta
35
- - 22
36
- version: 2.0.0.beta.22
37
- type: :development
23
+ version: "0"
24
+ type: :runtime
25
+ prerelease: false
38
26
  version_requirements: *id001
39
27
  - !ruby/object:Gem::Dependency
40
28
  name: will_paginate
41
- prerelease: false
42
29
  requirement: &id002 !ruby/object:Gem::Requirement
43
30
  none: false
44
31
  requirements:
45
32
  - - ">="
46
33
  - !ruby/object:Gem::Version
47
- hash: 29
48
- segments:
49
- - 2
50
- - 3
51
- - 15
52
34
  version: 2.3.15
53
35
  type: :runtime
36
+ prerelease: false
54
37
  version_requirements: *id002
55
38
  description: IndexTank is a great search indexing service, this gem tries to make any orm keep in sync with indextank with ease
56
39
  email: kidpollo@gmail.com
@@ -63,9 +46,7 @@ extra_rdoc_files:
63
46
  - README.rdoc
64
47
  files:
65
48
  - .document
66
- - .gitignore
67
49
  - Gemfile
68
- - Gemfile.lock
69
50
  - LICENSE
70
51
  - README.rdoc
71
52
  - Rakefile
@@ -81,13 +62,12 @@ files:
81
62
  - spec/tanker_spec.rb
82
63
  - spec/utilities_spec.rb
83
64
  - tanker.gemspec
84
- has_rdoc: true
85
65
  homepage: http://github.com/kidpollo/tanker
86
66
  licenses: []
87
67
 
88
68
  post_install_message:
89
- rdoc_options:
90
- - --charset=UTF-8
69
+ rdoc_options: []
70
+
91
71
  require_paths:
92
72
  - lib
93
73
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -95,23 +75,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
75
  requirements:
96
76
  - - ">="
97
77
  - !ruby/object:Gem::Version
98
- hash: 3
99
- segments:
100
- - 0
101
78
  version: "0"
102
79
  required_rubygems_version: !ruby/object:Gem::Requirement
103
80
  none: false
104
81
  requirements:
105
82
  - - ">="
106
83
  - !ruby/object:Gem::Version
107
- hash: 3
108
- segments:
109
- - 0
110
84
  version: "0"
111
85
  requirements: []
112
86
 
113
87
  rubyforge_project:
114
- rubygems_version: 1.4.2
88
+ rubygems_version: 1.7.1
115
89
  signing_key:
116
90
  specification_version: 3
117
91
  summary: IndexTank integration to your favorite orm
data/.gitignore DELETED
@@ -1,21 +0,0 @@
1
- ## MAC OS
2
- .DS_Store
3
-
4
- ## TEXTMATE
5
- *.tmproj
6
- tmtags
7
-
8
- ## EMACS
9
- *~
10
- \#*
11
- .\#*
12
-
13
- ## VIM
14
- *.swp
15
-
16
- ## PROJECT::GENERAL
17
- coverage
18
- rdoc
19
- pkg
20
-
21
- ## PROJECT::SPECIFIC
data/Gemfile.lock DELETED
@@ -1,32 +0,0 @@
1
- GEM
2
- remote: http://rubygems.org/
3
- specs:
4
- diff-lcs (1.1.2)
5
- gemcutter (0.6.1)
6
- git (1.2.5)
7
- jeweler (1.4.0)
8
- gemcutter (>= 0.1.0)
9
- git (>= 1.2.5)
10
- rubyforge (>= 2.0.0)
11
- json_pure (1.4.6)
12
- rspec (2.0.0.beta.22)
13
- rspec-core (= 2.0.0.beta.22)
14
- rspec-expectations (= 2.0.0.beta.22)
15
- rspec-mocks (= 2.0.0.beta.22)
16
- rspec-core (2.0.0.beta.22)
17
- rspec-expectations (2.0.0.beta.22)
18
- diff-lcs (>= 1.1.2)
19
- rspec-mocks (2.0.0.beta.22)
20
- rspec-core (= 2.0.0.beta.22)
21
- rspec-expectations (= 2.0.0.beta.22)
22
- rubyforge (2.0.4)
23
- json_pure (>= 1.1.7)
24
- will_paginate (2.3.15)
25
-
26
- PLATFORMS
27
- ruby
28
-
29
- DEPENDENCIES
30
- jeweler
31
- rspec (>= 2.0.0.beta.22)
32
- will_paginate (>= 2.3.15)