tanker 0.5.6 → 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.
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)