ssickles-tire 0.4.2.7 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/tire.rb +18 -3
- data/lib/tire/alias.rb +11 -35
- data/lib/tire/index.rb +34 -76
- data/lib/tire/model/callbacks.rb +40 -0
- data/lib/tire/model/import.rb +26 -0
- data/lib/tire/model/indexing.rb +128 -0
- data/lib/tire/model/naming.rb +100 -0
- data/lib/tire/model/percolate.rb +99 -0
- data/lib/tire/model/persistence.rb +72 -0
- data/lib/tire/model/persistence/attributes.rb +143 -0
- data/lib/tire/model/persistence/finders.rb +66 -0
- data/lib/tire/model/persistence/storage.rb +71 -0
- data/lib/tire/model/search.rb +305 -0
- data/lib/tire/results/collection.rb +38 -13
- data/lib/tire/results/item.rb +19 -0
- data/lib/tire/rubyext/hash.rb +8 -0
- data/lib/tire/rubyext/ruby_1_8.rb +54 -0
- data/lib/tire/rubyext/symbol.rb +11 -0
- data/lib/tire/search.rb +7 -8
- data/lib/tire/search/scan.rb +8 -8
- data/lib/tire/search/sort.rb +1 -1
- data/lib/tire/utils.rb +17 -0
- data/lib/tire/version.rb +7 -38
- data/test/integration/active_model_indexing_test.rb +51 -0
- data/test/integration/active_model_searchable_test.rb +114 -0
- data/test/integration/active_record_searchable_test.rb +446 -0
- data/test/integration/mongoid_searchable_test.rb +309 -0
- data/test/integration/persistent_model_test.rb +117 -0
- data/test/integration/reindex_test.rb +2 -2
- data/test/integration/scan_test.rb +1 -1
- data/test/models/active_model_article.rb +31 -0
- data/test/models/active_model_article_with_callbacks.rb +49 -0
- data/test/models/active_model_article_with_custom_document_type.rb +7 -0
- data/test/models/active_model_article_with_custom_index_name.rb +7 -0
- data/test/models/active_record_models.rb +122 -0
- data/test/models/mongoid_models.rb +97 -0
- data/test/models/persistent_article.rb +11 -0
- data/test/models/persistent_article_in_namespace.rb +12 -0
- data/test/models/persistent_article_with_casting.rb +28 -0
- data/test/models/persistent_article_with_defaults.rb +11 -0
- data/test/models/persistent_articles_with_custom_index_name.rb +10 -0
- data/test/models/supermodel_article.rb +17 -0
- data/test/models/validated_model.rb +11 -0
- data/test/test_helper.rb +27 -3
- data/test/unit/active_model_lint_test.rb +17 -0
- data/test/unit/index_alias_test.rb +3 -17
- data/test/unit/index_test.rb +30 -18
- data/test/unit/model_callbacks_test.rb +116 -0
- data/test/unit/model_import_test.rb +71 -0
- data/test/unit/model_persistence_test.rb +516 -0
- data/test/unit/model_search_test.rb +899 -0
- data/test/unit/results_collection_test.rb +60 -0
- data/test/unit/results_item_test.rb +37 -0
- data/test/unit/rubyext_test.rb +3 -3
- data/test/unit/search_test.rb +1 -6
- data/test/unit/tire_test.rb +15 -0
- data/tire.gemspec +30 -13
- metadata +153 -41
- data/lib/tire/rubyext/to_json.rb +0 -21
@@ -0,0 +1,54 @@
|
|
1
|
+
# Stolen from ruby core's uri/common.rb, with modifications to support 1.8.x
|
2
|
+
#
|
3
|
+
# https://github.com/ruby/ruby/blob/trunk/lib/uri/common.rb
|
4
|
+
#
|
5
|
+
#
|
6
|
+
|
7
|
+
module URI
|
8
|
+
TBLENCWWWCOMP_ = {} # :nodoc:
|
9
|
+
256.times do |i|
|
10
|
+
TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
|
11
|
+
end
|
12
|
+
TBLENCWWWCOMP_[' '] = '+'
|
13
|
+
TBLENCWWWCOMP_.freeze
|
14
|
+
TBLDECWWWCOMP_ = {} # :nodoc:
|
15
|
+
256.times do |i|
|
16
|
+
h, l = i>>4, i&15
|
17
|
+
TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
|
18
|
+
TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
|
19
|
+
TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
|
20
|
+
TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
|
21
|
+
end
|
22
|
+
TBLDECWWWCOMP_['+'] = ' '
|
23
|
+
TBLDECWWWCOMP_.freeze
|
24
|
+
|
25
|
+
# Encode given +s+ to URL-encoded form data.
|
26
|
+
#
|
27
|
+
# This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP
|
28
|
+
# (ASCII space) to + and converts others to %XX.
|
29
|
+
#
|
30
|
+
# This is an implementation of
|
31
|
+
# http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
|
32
|
+
#
|
33
|
+
# See URI.decode_www_form_component, URI.encode_www_form
|
34
|
+
def self.encode_www_form_component(s)
|
35
|
+
str = s.to_s
|
36
|
+
if RUBY_VERSION < "1.9" && $KCODE =~ /u/i
|
37
|
+
str.gsub(/([^ a-zA-Z0-9_.-]+)/) do
|
38
|
+
'%' + $1.unpack('H2' * Rack::Utils.bytesize($1)).join('%').upcase
|
39
|
+
end.tr(' ', '+')
|
40
|
+
else
|
41
|
+
str.gsub(/[^*\-.0-9A-Z_a-z]/) {|m| TBLENCWWWCOMP_[m]}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Decode given +str+ of URL-encoded form data.
|
46
|
+
#
|
47
|
+
# This decods + to SP.
|
48
|
+
#
|
49
|
+
# See URI.encode_www_form_component, URI.decode_www_form
|
50
|
+
def self.decode_www_form_component(str, enc=nil)
|
51
|
+
raise ArgumentError, "invalid %-encoding (#{str})" unless /\A(?:%[0-9a-fA-F]{2}|[^%])*\z/ =~ str
|
52
|
+
str.gsub(/\+|%[0-9a-fA-F]{2}/) {|m| TBLDECWWWCOMP_[m]}
|
53
|
+
end
|
54
|
+
end
|
data/lib/tire/search.rb
CHANGED
@@ -4,23 +4,18 @@ module Tire
|
|
4
4
|
|
5
5
|
class Search
|
6
6
|
|
7
|
-
attr_reader :indices, :json, :query, :facets, :filters, :options, :explain
|
7
|
+
attr_reader :indices, :json, :query, :facets, :filters, :options, :explain
|
8
8
|
|
9
9
|
def initialize(indices=nil, options={}, &block)
|
10
10
|
@indices = Array(indices)
|
11
|
-
@types = Array(options.delete(:type)).map { |type|
|
11
|
+
@types = Array(options.delete(:type)).map { |type| Utils.escape(type) }
|
12
12
|
@options = options
|
13
|
-
@path = ['/', @indices.join(','), @types.join(','), '_search'].compact.join('/').squeeze('/')
|
14
13
|
|
15
|
-
|
14
|
+
@path = ['/', @indices.join(','), @types.join(','), '_search'].compact.join('/').squeeze('/')
|
16
15
|
|
17
16
|
block.arity < 1 ? instance_eval(&block) : block.call(self) if block_given?
|
18
17
|
end
|
19
18
|
|
20
|
-
def url=( url )
|
21
|
-
@url = url + @path
|
22
|
-
end
|
23
|
-
|
24
19
|
def results
|
25
20
|
@results || (perform; @results)
|
26
21
|
end
|
@@ -29,6 +24,10 @@ module Tire
|
|
29
24
|
@response || (perform; @response)
|
30
25
|
end
|
31
26
|
|
27
|
+
def url
|
28
|
+
Configuration.url + @path
|
29
|
+
end
|
30
|
+
|
32
31
|
def params
|
33
32
|
@options.empty? ? '' : '?' + @options.to_param
|
34
33
|
end
|
data/lib/tire/search/scan.rb
CHANGED
@@ -42,22 +42,22 @@ module Tire
|
|
42
42
|
class Scan
|
43
43
|
include Enumerable
|
44
44
|
|
45
|
-
attr_reader :indices, :options, :search
|
45
|
+
attr_reader :indices, :options, :search
|
46
46
|
|
47
47
|
def initialize(indices=nil, options={}, &block)
|
48
48
|
@indices = Array(indices)
|
49
49
|
@options = options.update(:search_type => 'scan', :scroll => '10m')
|
50
50
|
@seen = 0
|
51
|
-
@url = options.fetch(:url, Configuration.url) + '/_search/scroll'
|
52
51
|
@search = Search.new(@indices, @options, &block)
|
53
52
|
end
|
54
53
|
|
55
|
-
def
|
56
|
-
def
|
57
|
-
def
|
58
|
-
def
|
59
|
-
def
|
60
|
-
def
|
54
|
+
def url; Configuration.url + "/_search/scroll"; end
|
55
|
+
def params; @options.empty? ? '' : '?' + @options.to_param; end
|
56
|
+
def results; @results || (__perform; @results); end
|
57
|
+
def response; @response || (__perform; @response); end
|
58
|
+
def json; @json || (__perform; @json); end
|
59
|
+
def total; @total || (__perform; @total); end
|
60
|
+
def seen; @seen || (__perform; @seen); end
|
61
61
|
|
62
62
|
def scroll_id
|
63
63
|
@scroll_id ||= @search.perform.json['_scroll_id']
|
data/lib/tire/search/sort.rb
CHANGED
data/lib/tire/utils.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Tire
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
def escape(s)
|
7
|
+
URI.encode_www_form_component(s.to_s)
|
8
|
+
end
|
9
|
+
|
10
|
+
def unescape(s)
|
11
|
+
s = s.to_s.respond_to?(:force_encoding) ? s.to_s.force_encoding(Encoding::UTF_8) : s.to_s
|
12
|
+
URI.decode_www_form_component(s)
|
13
|
+
end
|
14
|
+
|
15
|
+
extend self
|
16
|
+
end
|
17
|
+
end
|
data/lib/tire/version.rb
CHANGED
@@ -1,46 +1,9 @@
|
|
1
1
|
module Tire
|
2
|
-
VERSION
|
2
|
+
VERSION = "0.4.3"
|
3
3
|
|
4
4
|
CHANGELOG =<<-END
|
5
5
|
IMPORTANT CHANGES LATELY:
|
6
6
|
|
7
|
-
Version 0.4.2.7
|
8
|
-
---------------
|
9
|
-
* Bumped escape_utils dependency to ~> 0.3.x
|
10
|
-
|
11
|
-
Version 0.4.2.6
|
12
|
-
---------------
|
13
|
-
* Add support for parent / child documents.
|
14
|
-
|
15
|
-
Version 0.4.2.4
|
16
|
-
---------------
|
17
|
-
* Add routing support.
|
18
|
-
|
19
|
-
Version 0.4.2.4
|
20
|
-
---------------
|
21
|
-
* Fixing url routing for index alais commands.
|
22
|
-
|
23
|
-
Version 0.4.2.3
|
24
|
-
---------------
|
25
|
-
* Making the library multi-cluster capable
|
26
|
-
|
27
|
-
Version 0.4.2.2
|
28
|
-
---------------
|
29
|
-
* Tire::Index now removes _id/_type keys from document hashes.
|
30
|
-
|
31
|
-
Version 0.4.2.1
|
32
|
-
---------------
|
33
|
-
* Removed all depedence on ActiveModel
|
34
|
-
* Fixed tests to work with any version of ActiveSupport
|
35
|
-
* Removed wonky 1.9 backports for URL encoding
|
36
|
-
* Fixed tests to work under 1.8.7
|
37
|
-
|
38
|
-
Version 0.4.2
|
39
|
-
-------------
|
40
|
-
* Fixed incorrect handling of PUT requests in the Curb client
|
41
|
-
* Fixed, that blocks passed to `Tire::Index.new` or `Tire.index` losed the scope
|
42
|
-
* Added `Tire::Alias`, interface and DSL to manage aliases as resources
|
43
|
-
|
44
7
|
Version 0.4.1
|
45
8
|
-------------
|
46
9
|
* Added a Index#settings method to retrieve index settings as a Hash
|
@@ -49,5 +12,11 @@ module Tire
|
|
49
12
|
* Added basic support for index aliases
|
50
13
|
* Changed, that Index#bulk_store runs against an index endpoint, not against `/_bulk`
|
51
14
|
* Refactorings, fixes, Ruby 1.8 compatibility
|
15
|
+
|
16
|
+
Version 0.4.2
|
17
|
+
-------------
|
18
|
+
* Fixed incorrect handling of PUT requests in the Curb client
|
19
|
+
* Fixed, that blocks passed to `Tire::Index.new` or `Tire.index` losed the scope
|
20
|
+
* Added `Tire::Alias`, interface and DSL to manage aliases as resources
|
52
21
|
END
|
53
22
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require File.expand_path('../../models/supermodel_article', __FILE__)
|
3
|
+
|
4
|
+
module Tire
|
5
|
+
|
6
|
+
class ActiveModelSearchableIntegrationTest < Test::Unit::TestCase
|
7
|
+
include Test::Integration
|
8
|
+
|
9
|
+
class ::ActiveModelArticleWithCustomAsSerialization < ActiveModelArticleWithCallbacks
|
10
|
+
mapping do
|
11
|
+
indexes :title
|
12
|
+
indexes :content
|
13
|
+
indexes :characters, :as => 'content.length'
|
14
|
+
indexes :readability, :as => proc {
|
15
|
+
content.split(/\W/).reject { |t| t.blank? }.size /
|
16
|
+
content.split(/\./).size
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def setup
|
22
|
+
super
|
23
|
+
ActiveModelArticleWithCustomAsSerialization.index.delete
|
24
|
+
end
|
25
|
+
|
26
|
+
def teardown
|
27
|
+
super
|
28
|
+
ActiveModelArticleWithCustomAsSerialization.index.delete
|
29
|
+
end
|
30
|
+
|
31
|
+
context "ActiveModel serialization" do
|
32
|
+
|
33
|
+
setup do
|
34
|
+
@model = ActiveModelArticleWithCustomAsSerialization.new \
|
35
|
+
:id => 1,
|
36
|
+
:title => 'Test article',
|
37
|
+
:content => 'Lorem Ipsum. Dolor Sit Amet.'
|
38
|
+
@model.update_index
|
39
|
+
@model.index.refresh
|
40
|
+
end
|
41
|
+
|
42
|
+
should "serialize the content length" do
|
43
|
+
m = ActiveModelArticleWithCustomAsSerialization.search('*').first
|
44
|
+
assert_equal 28, m.characters
|
45
|
+
assert_equal 2, m.readability
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require File.expand_path('../../models/supermodel_article', __FILE__)
|
3
|
+
|
4
|
+
module Tire
|
5
|
+
|
6
|
+
class ActiveModelSearchableIntegrationTest < Test::Unit::TestCase
|
7
|
+
include Test::Integration
|
8
|
+
|
9
|
+
def setup
|
10
|
+
super
|
11
|
+
Redis::Persistence.config.redis = Redis.new db: ENV['REDIS_PERSISTENCE_TEST_DATABASE'] || 14
|
12
|
+
Redis::Persistence.config.redis.flushdb
|
13
|
+
@model = SupermodelArticle.new :title => 'Test'
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
super
|
18
|
+
SupermodelArticle.all.each { |a| a.destroy }
|
19
|
+
end
|
20
|
+
|
21
|
+
context "ActiveModel integration" do
|
22
|
+
|
23
|
+
setup do
|
24
|
+
Tire.index('supermodel_articles').delete
|
25
|
+
load File.expand_path('../../models/supermodel_article.rb', __FILE__)
|
26
|
+
end
|
27
|
+
teardown { Tire.index('supermodel_articles').delete }
|
28
|
+
|
29
|
+
should "configure mapping" do
|
30
|
+
assert_equal 'czech', SupermodelArticle.mapping[:title][:analyzer]
|
31
|
+
assert_equal 15, SupermodelArticle.mapping[:title][:boost]
|
32
|
+
|
33
|
+
assert_equal 'czech', SupermodelArticle.index.mapping['supermodel_article']['properties']['title']['analyzer']
|
34
|
+
end
|
35
|
+
|
36
|
+
should "save document into index on save and find it with score" do
|
37
|
+
a = SupermodelArticle.new :title => 'Test'
|
38
|
+
a.save
|
39
|
+
id = a.id
|
40
|
+
|
41
|
+
# Store document of another type in the index
|
42
|
+
Index.new 'supermodel_articles' do
|
43
|
+
store :type => 'other-thing', :title => 'Title for other thing'
|
44
|
+
end
|
45
|
+
|
46
|
+
a.index.refresh
|
47
|
+
|
48
|
+
# The index should contain 2 documents
|
49
|
+
assert_equal 2, Tire.search('supermodel_articles') { query { all } }.results.size
|
50
|
+
|
51
|
+
results = SupermodelArticle.search 'test'
|
52
|
+
|
53
|
+
# The model should find only 1 document
|
54
|
+
assert_equal 1, results.count
|
55
|
+
|
56
|
+
assert_instance_of Results::Item, results.first
|
57
|
+
assert_equal 'Test', results.first.title
|
58
|
+
assert_not_nil results.first._score
|
59
|
+
assert_equal id.to_s, results.first.id.to_s
|
60
|
+
end
|
61
|
+
|
62
|
+
should "remove document from index on destroy" do
|
63
|
+
a = SupermodelArticle.new :title => 'Test'
|
64
|
+
a.save
|
65
|
+
assert_equal 1, SupermodelArticle.all.size
|
66
|
+
|
67
|
+
a.destroy
|
68
|
+
assert_equal 0, SupermodelArticle.all.size
|
69
|
+
|
70
|
+
a.index.refresh
|
71
|
+
results = SupermodelArticle.search 'test'
|
72
|
+
|
73
|
+
assert_equal 0, results.count
|
74
|
+
end
|
75
|
+
|
76
|
+
should "retrieve sorted documents by IDs returned from search" do
|
77
|
+
SupermodelArticle.create :title => 'foo'
|
78
|
+
SupermodelArticle.create :id => 'abc123', :title => 'bar'
|
79
|
+
|
80
|
+
SupermodelArticle.index.refresh
|
81
|
+
results = SupermodelArticle.search 'foo OR bar^100'
|
82
|
+
|
83
|
+
assert_equal 2, results.count
|
84
|
+
|
85
|
+
assert_equal 'bar', results.first.title
|
86
|
+
assert_equal 'abc123', results.first.id
|
87
|
+
end
|
88
|
+
|
89
|
+
context "within Rails" do
|
90
|
+
|
91
|
+
setup do
|
92
|
+
module ::Rails; end
|
93
|
+
end
|
94
|
+
|
95
|
+
should "load the underlying model" do
|
96
|
+
a = SupermodelArticle.new :title => 'Test'
|
97
|
+
a.save
|
98
|
+
a.index.refresh
|
99
|
+
|
100
|
+
results = SupermodelArticle.search 'test'
|
101
|
+
|
102
|
+
assert_instance_of Results::Item, results.first
|
103
|
+
assert_instance_of SupermodelArticle, results.first.load
|
104
|
+
|
105
|
+
assert_equal 'Test', results.first.load.title
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
@@ -0,0 +1,446 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Tire
|
4
|
+
|
5
|
+
class ActiveRecordSearchableIntegrationTest < Test::Unit::TestCase
|
6
|
+
include Test::Integration
|
7
|
+
|
8
|
+
def setup
|
9
|
+
super
|
10
|
+
ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" )
|
11
|
+
|
12
|
+
ActiveRecord::Migration.verbose = false
|
13
|
+
ActiveRecord::Schema.define(:version => 1) do
|
14
|
+
create_table :active_record_articles do |t|
|
15
|
+
t.string :title
|
16
|
+
t.datetime :created_at, :default => 'NOW()'
|
17
|
+
end
|
18
|
+
create_table :active_record_comments do |t|
|
19
|
+
t.string :author
|
20
|
+
t.text :body
|
21
|
+
t.references :article
|
22
|
+
t.timestamps
|
23
|
+
end
|
24
|
+
create_table :active_record_stats do |t|
|
25
|
+
t.integer :pageviews
|
26
|
+
t.string :period
|
27
|
+
t.references :article
|
28
|
+
end
|
29
|
+
create_table :active_record_class_with_tire_methods do |t|
|
30
|
+
t.string :title
|
31
|
+
end
|
32
|
+
create_table :active_record_class_with_dynamic_index_names do |t|
|
33
|
+
t.string :title
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "ActiveRecord integration" do
|
39
|
+
|
40
|
+
setup do
|
41
|
+
ActiveRecordArticle.destroy_all
|
42
|
+
Tire.index('active_record_articles').delete
|
43
|
+
|
44
|
+
load File.expand_path('../../models/active_record_models.rb', __FILE__)
|
45
|
+
end
|
46
|
+
|
47
|
+
teardown do
|
48
|
+
ActiveRecordArticle.destroy_all
|
49
|
+
Tire.index('active_record_articles').delete
|
50
|
+
end
|
51
|
+
|
52
|
+
should "configure mapping" do
|
53
|
+
assert_equal 'snowball', ActiveRecordArticle.mapping[:title][:analyzer]
|
54
|
+
assert_equal 10, ActiveRecordArticle.mapping[:title][:boost]
|
55
|
+
|
56
|
+
assert_equal 'snowball', ActiveRecordArticle.index.mapping['active_record_article']['properties']['title']['analyzer']
|
57
|
+
end
|
58
|
+
|
59
|
+
should "save document into index on save and find it" do
|
60
|
+
a = ActiveRecordArticle.new :title => 'Test'
|
61
|
+
a.save!
|
62
|
+
id = a.id
|
63
|
+
|
64
|
+
a.index.refresh
|
65
|
+
|
66
|
+
results = ActiveRecordArticle.search 'test'
|
67
|
+
|
68
|
+
assert results.any?
|
69
|
+
assert_equal 1, results.count
|
70
|
+
|
71
|
+
assert_instance_of Results::Item, results.first
|
72
|
+
assert_not_nil results.first.id
|
73
|
+
assert_equal id.to_s, results.first.id.to_s
|
74
|
+
assert results.first.persisted?, "Record should be persisted"
|
75
|
+
assert_not_nil results.first._score
|
76
|
+
assert_equal 'Test', results.first.title
|
77
|
+
end
|
78
|
+
|
79
|
+
should "raise exception on invalid query" do
|
80
|
+
ActiveRecordArticle.create! :title => 'Test'
|
81
|
+
|
82
|
+
assert_raise Search::SearchRequestFailed do
|
83
|
+
ActiveRecordArticle.search '[x'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "with eager loading" do
|
88
|
+
setup do
|
89
|
+
ActiveRecordArticle.destroy_all
|
90
|
+
5.times { |n| ActiveRecordArticle.create! :title => "Test #{n+1}" }
|
91
|
+
ActiveRecordArticle.index.refresh
|
92
|
+
end
|
93
|
+
|
94
|
+
should "load records on query search" do
|
95
|
+
results = ActiveRecordArticle.search '"Test 1"', :load => true
|
96
|
+
|
97
|
+
assert results.any?
|
98
|
+
assert_equal ActiveRecordArticle.find(1), results.first
|
99
|
+
end
|
100
|
+
|
101
|
+
should "load records on block search" do
|
102
|
+
results = ActiveRecordArticle.search :load => true do
|
103
|
+
query { string '"Test 1"' }
|
104
|
+
end
|
105
|
+
|
106
|
+
assert_equal ActiveRecordArticle.find(1), results.first
|
107
|
+
end
|
108
|
+
|
109
|
+
should "load records with options on query search" do
|
110
|
+
assert_equal ActiveRecordArticle.find(['1'], :include => 'comments').first,
|
111
|
+
ActiveRecordArticle.search('"Test 1"',
|
112
|
+
:load => { :include => 'comments' }).results.first
|
113
|
+
end
|
114
|
+
|
115
|
+
should "return empty collection for nonmatching query" do
|
116
|
+
assert_nothing_raised do
|
117
|
+
results = ActiveRecordArticle.search :load => true do
|
118
|
+
query { string '"Hic Sunt Leones"' }
|
119
|
+
end
|
120
|
+
assert_equal 0, results.size
|
121
|
+
assert ! results.any?
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
should "remove document from index on destroy" do
|
127
|
+
a = ActiveRecordArticle.new :title => 'Test remove...'
|
128
|
+
a.save!
|
129
|
+
assert_equal 1, ActiveRecordArticle.count
|
130
|
+
|
131
|
+
a.destroy
|
132
|
+
assert_equal 0, ActiveRecordArticle.all.size
|
133
|
+
|
134
|
+
a.index.refresh
|
135
|
+
results = ActiveRecordArticle.search 'test'
|
136
|
+
assert_equal 0, results.count
|
137
|
+
end
|
138
|
+
|
139
|
+
should "return documents with scores" do
|
140
|
+
ActiveRecordArticle.create! :title => 'foo'
|
141
|
+
ActiveRecordArticle.create! :title => 'bar'
|
142
|
+
|
143
|
+
ActiveRecordArticle.index.refresh
|
144
|
+
results = ActiveRecordArticle.search 'foo OR bar^100'
|
145
|
+
assert_equal 2, results.count
|
146
|
+
|
147
|
+
assert_equal 'bar', results.first.title
|
148
|
+
end
|
149
|
+
|
150
|
+
context "with pagination" do
|
151
|
+
setup do
|
152
|
+
1.upto(9) { |number| ActiveRecordArticle.create :title => "Test#{number}" }
|
153
|
+
ActiveRecordArticle.index.refresh
|
154
|
+
end
|
155
|
+
|
156
|
+
context "and parameter searches" do
|
157
|
+
|
158
|
+
should "find first page with five results" do
|
159
|
+
results = ActiveRecordArticle.search 'test*', :sort => 'title', :per_page => 5, :page => 1
|
160
|
+
assert_equal 5, results.size
|
161
|
+
|
162
|
+
# WillPaginate
|
163
|
+
#
|
164
|
+
assert_equal 2, results.total_pages
|
165
|
+
assert_equal 1, results.current_page
|
166
|
+
assert_equal nil, results.previous_page
|
167
|
+
assert_equal 2, results.next_page
|
168
|
+
|
169
|
+
# Kaminari
|
170
|
+
#
|
171
|
+
assert_equal 5, results.limit_value
|
172
|
+
assert_equal 9, results.total_count
|
173
|
+
assert_equal 2, results.num_pages
|
174
|
+
assert_equal 0, results.offset_value
|
175
|
+
|
176
|
+
assert_equal 'Test1', results.first.title
|
177
|
+
end
|
178
|
+
|
179
|
+
should "find next page with five results" do
|
180
|
+
results = ActiveRecordArticle.search 'test*', :sort => 'title', :per_page => 5, :page => 2
|
181
|
+
assert_equal 4, results.size
|
182
|
+
|
183
|
+
assert_equal 2, results.total_pages
|
184
|
+
assert_equal 2, results.current_page
|
185
|
+
assert_equal 1, results.previous_page
|
186
|
+
assert_equal nil, results.next_page
|
187
|
+
|
188
|
+
#kaminari
|
189
|
+
assert_equal 5, results.limit_value
|
190
|
+
assert_equal 9, results.total_count
|
191
|
+
assert_equal 2, results.num_pages
|
192
|
+
assert_equal 5, results.offset_value
|
193
|
+
|
194
|
+
assert_equal 'Test6', results.first.title
|
195
|
+
end
|
196
|
+
|
197
|
+
should "find not find missing page" do
|
198
|
+
results = ActiveRecordArticle.search 'test*', :sort => 'title', :per_page => 5, :page => 3
|
199
|
+
assert_equal 0, results.size
|
200
|
+
|
201
|
+
assert_equal 2, results.total_pages
|
202
|
+
assert_equal 3, results.current_page
|
203
|
+
assert_equal 2, results.previous_page
|
204
|
+
assert_equal nil, results.next_page
|
205
|
+
|
206
|
+
#kaminari
|
207
|
+
assert_equal 5, results.limit_value
|
208
|
+
assert_equal 9, results.total_count
|
209
|
+
assert_equal 2, results.num_pages
|
210
|
+
assert_equal 10, results.offset_value
|
211
|
+
|
212
|
+
assert_nil results.first
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
context "and block searches" do
|
218
|
+
setup { @q = 'test*' }
|
219
|
+
|
220
|
+
should "find first page with five results" do
|
221
|
+
results = ActiveRecordArticle.search do |search|
|
222
|
+
search.query { |query| query.string @q }
|
223
|
+
search.sort { by :title }
|
224
|
+
search.from 0
|
225
|
+
search.size 5
|
226
|
+
end
|
227
|
+
assert_equal 5, results.size
|
228
|
+
|
229
|
+
assert_equal 2, results.total_pages
|
230
|
+
assert_equal 1, results.current_page
|
231
|
+
assert_equal nil, results.previous_page
|
232
|
+
assert_equal 2, results.next_page
|
233
|
+
|
234
|
+
assert_equal 'Test1', results.first.title
|
235
|
+
end
|
236
|
+
|
237
|
+
should "find next page with five results" do
|
238
|
+
results = ActiveRecordArticle.search do |search|
|
239
|
+
search.query { |query| query.string @q }
|
240
|
+
search.sort { by :title }
|
241
|
+
search.from 5
|
242
|
+
search.size 5
|
243
|
+
end
|
244
|
+
assert_equal 4, results.size
|
245
|
+
|
246
|
+
assert_equal 2, results.total_pages
|
247
|
+
assert_equal 2, results.current_page
|
248
|
+
assert_equal 1, results.previous_page
|
249
|
+
assert_equal nil, results.next_page
|
250
|
+
|
251
|
+
assert_equal 'Test6', results.first.title
|
252
|
+
end
|
253
|
+
|
254
|
+
should "not find a missing page" do
|
255
|
+
results = ActiveRecordArticle.search do |search|
|
256
|
+
search.query { |query| query.string @q }
|
257
|
+
search.sort { by :title }
|
258
|
+
search.from 10
|
259
|
+
search.size 5
|
260
|
+
end
|
261
|
+
assert_equal 0, results.size
|
262
|
+
|
263
|
+
assert_equal 2, results.total_pages
|
264
|
+
assert_equal 3, results.current_page
|
265
|
+
assert_equal 2, results.previous_page
|
266
|
+
assert_equal nil, results.next_page
|
267
|
+
|
268
|
+
assert_nil results.first
|
269
|
+
end
|
270
|
+
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
274
|
+
|
275
|
+
context "with proxy" do
|
276
|
+
|
277
|
+
should "allow access to Tire instance methods" do
|
278
|
+
a = ActiveRecordClassWithTireMethods.create :title => 'One'
|
279
|
+
assert_equal "THIS IS MY INDEX!", a.index
|
280
|
+
assert_instance_of Tire::Index, a.tire.index
|
281
|
+
assert a.tire.index.exists?, "Index should exist"
|
282
|
+
end
|
283
|
+
|
284
|
+
should "allow access to Tire class methods" do
|
285
|
+
class ::ActiveRecordClassWithTireMethods < ActiveRecord::Base
|
286
|
+
def self.search(*)
|
287
|
+
"THIS IS MY SEARCH!"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
ActiveRecordClassWithTireMethods.create :title => 'One'
|
292
|
+
ActiveRecordClassWithTireMethods.tire.index.refresh
|
293
|
+
|
294
|
+
assert_equal "THIS IS MY SEARCH!", ActiveRecordClassWithTireMethods.search
|
295
|
+
|
296
|
+
results = ActiveRecordClassWithTireMethods.tire.search 'one'
|
297
|
+
|
298
|
+
assert_equal 'One', results.first.title
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
302
|
+
|
303
|
+
context "with dynamic index name" do
|
304
|
+
setup do
|
305
|
+
@a = ActiveRecordClassWithDynamicIndexName.create! :title => 'Test'
|
306
|
+
@a.index.refresh
|
307
|
+
end
|
308
|
+
|
309
|
+
should "search in proper index" do
|
310
|
+
assert_equal 'dynamic_index', ActiveRecordClassWithDynamicIndexName.index.name
|
311
|
+
assert_equal 'dynamic_index', @a.index.name
|
312
|
+
|
313
|
+
results = ActiveRecordClassWithDynamicIndexName.search 'test'
|
314
|
+
assert_equal 'dynamic_index', results.first._index
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
context "within Rails" do
|
319
|
+
|
320
|
+
setup do
|
321
|
+
module ::Rails; end
|
322
|
+
|
323
|
+
a = ActiveRecordArticle.new :title => 'Test'
|
324
|
+
a.comments.build :author => 'fool', :body => 'Works!'
|
325
|
+
a.stats.build :pageviews => 12, :period => '2011-08'
|
326
|
+
a.save!
|
327
|
+
@id = a.id.to_s
|
328
|
+
|
329
|
+
a.index.refresh
|
330
|
+
@item = ActiveRecordArticle.search('test').first
|
331
|
+
end
|
332
|
+
|
333
|
+
should "have access to indexed properties" do
|
334
|
+
assert_equal 'Test', @item.title
|
335
|
+
assert_equal 'fool', @item.comments.first.author
|
336
|
+
assert_equal 12, @item.stats.first.pageviews
|
337
|
+
end
|
338
|
+
|
339
|
+
should "load the underlying models" do
|
340
|
+
assert_instance_of Results::Item, @item
|
341
|
+
assert_instance_of ActiveRecordArticle, @item.load
|
342
|
+
assert_equal 'Test', @item.load.title
|
343
|
+
|
344
|
+
assert_instance_of Results::Item, @item.comments.first
|
345
|
+
assert_instance_of ActiveRecordComment, @item.comments.first.load
|
346
|
+
assert_equal 'fool', @item.comments.first.load.author
|
347
|
+
end
|
348
|
+
|
349
|
+
should "load the underlying model with options" do
|
350
|
+
ActiveRecordArticle.expects(:find).with(@id, :include => 'comments')
|
351
|
+
@item.load(:include => 'comments')
|
352
|
+
end
|
353
|
+
|
354
|
+
end
|
355
|
+
|
356
|
+
context "with multiple class instances in one index" do
|
357
|
+
setup do
|
358
|
+
ActiveRecord::Schema.define do
|
359
|
+
create_table(:active_record_assets) { |t| t.string :title, :timestamp }
|
360
|
+
create_table(:active_record_model_one) { |t| t.string :title, :timestamp }
|
361
|
+
create_table(:active_record_model_two) { |t| t.string :title, :timestamp }
|
362
|
+
end
|
363
|
+
|
364
|
+
ActiveRecordModelOne.create :title => 'Title One', timestamp: Time.now.to_i
|
365
|
+
ActiveRecordModelTwo.create :title => 'Title Two', timestamp: Time.now.to_i
|
366
|
+
ActiveRecordModelOne.tire.index.refresh
|
367
|
+
ActiveRecordModelTwo.tire.index.refresh
|
368
|
+
|
369
|
+
|
370
|
+
ActiveRecordVideo.create! :title => 'Title One', timestamp: Time.now.to_i
|
371
|
+
ActiveRecordPhoto.create! :title => 'Title Two', timestamp: Time.now.to_i
|
372
|
+
ActiveRecordAsset.tire.index.refresh
|
373
|
+
end
|
374
|
+
|
375
|
+
teardown do
|
376
|
+
ActiveRecordModelOne.destroy_all
|
377
|
+
ActiveRecordModelTwo.destroy_all
|
378
|
+
ActiveRecordModelOne.tire.index.delete
|
379
|
+
ActiveRecordModelTwo.tire.index.delete
|
380
|
+
|
381
|
+
ActiveRecordAsset.destroy_all
|
382
|
+
ActiveRecordAsset.tire.index.delete
|
383
|
+
ActiveRecordModelOne.destroy_all
|
384
|
+
end
|
385
|
+
|
386
|
+
should "eagerly load instances of multiple classes, from multiple indices" do
|
387
|
+
s = Tire.search ['active_record_model_one', 'active_record_model_two'], :load => true do
|
388
|
+
query { string 'title' }
|
389
|
+
sort { by :timestamp }
|
390
|
+
end
|
391
|
+
|
392
|
+
# puts s.results[0].inspect
|
393
|
+
|
394
|
+
assert_equal 2, s.results.length
|
395
|
+
assert_instance_of ActiveRecordModelOne, s.results[0]
|
396
|
+
assert_instance_of ActiveRecordModelTwo, s.results[1]
|
397
|
+
end
|
398
|
+
|
399
|
+
should "eagerly load all STI descendant records" do
|
400
|
+
s = Tire.search('active_record_assets', :load => true) do
|
401
|
+
query { string 'title' }
|
402
|
+
sort { by :timestamp }
|
403
|
+
end
|
404
|
+
|
405
|
+
assert_equal 2, s.results.length
|
406
|
+
assert_instance_of ActiveRecordVideo, s.results[0]
|
407
|
+
assert_instance_of ActiveRecordPhoto, s.results[1]
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
context "with namespaced models" do
|
412
|
+
setup do
|
413
|
+
ActiveRecord::Schema.define { create_table(:active_record_namespace_my_models) { |t| t.string :title, :timestamp } }
|
414
|
+
|
415
|
+
ActiveRecordNamespace::MyModel.create :title => 'Test'
|
416
|
+
ActiveRecordNamespace::MyModel.tire.index.refresh
|
417
|
+
end
|
418
|
+
|
419
|
+
teardown do
|
420
|
+
ActiveRecordNamespace::MyModel.destroy_all
|
421
|
+
ActiveRecordNamespace::MyModel.tire.index.delete
|
422
|
+
end
|
423
|
+
|
424
|
+
should "save document into index on save and find it" do
|
425
|
+
results = ActiveRecordNamespace::MyModel.search 'test'
|
426
|
+
|
427
|
+
assert results.any?, "No results returned: #{results.inspect}"
|
428
|
+
assert_equal 1, results.count
|
429
|
+
|
430
|
+
assert_instance_of Results::Item, results.first
|
431
|
+
end
|
432
|
+
|
433
|
+
should "eagerly load the records from returned hits" do
|
434
|
+
results = ActiveRecordNamespace::MyModel.search 'test', :load => true
|
435
|
+
|
436
|
+
assert results.any?, "No results returned: #{results.inspect}"
|
437
|
+
assert_instance_of ActiveRecordNamespace::MyModel, results.first
|
438
|
+
assert_equal ActiveRecordNamespace::MyModel.find(1), results.first
|
439
|
+
end
|
440
|
+
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
end
|
445
|
+
|
446
|
+
end
|