load_balanced_tire 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/.gitignore +14 -0
  2. data/.travis.yml +29 -0
  3. data/Gemfile +4 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.markdown +760 -0
  6. data/Rakefile +78 -0
  7. data/examples/rails-application-template.rb +249 -0
  8. data/examples/tire-dsl.rb +876 -0
  9. data/lib/tire.rb +55 -0
  10. data/lib/tire/alias.rb +296 -0
  11. data/lib/tire/configuration.rb +30 -0
  12. data/lib/tire/dsl.rb +43 -0
  13. data/lib/tire/http/client.rb +62 -0
  14. data/lib/tire/http/clients/curb.rb +61 -0
  15. data/lib/tire/http/clients/faraday.rb +71 -0
  16. data/lib/tire/http/response.rb +27 -0
  17. data/lib/tire/index.rb +361 -0
  18. data/lib/tire/logger.rb +60 -0
  19. data/lib/tire/model/callbacks.rb +40 -0
  20. data/lib/tire/model/import.rb +26 -0
  21. data/lib/tire/model/indexing.rb +128 -0
  22. data/lib/tire/model/naming.rb +100 -0
  23. data/lib/tire/model/percolate.rb +99 -0
  24. data/lib/tire/model/persistence.rb +71 -0
  25. data/lib/tire/model/persistence/attributes.rb +143 -0
  26. data/lib/tire/model/persistence/finders.rb +66 -0
  27. data/lib/tire/model/persistence/storage.rb +69 -0
  28. data/lib/tire/model/search.rb +307 -0
  29. data/lib/tire/results/collection.rb +114 -0
  30. data/lib/tire/results/item.rb +86 -0
  31. data/lib/tire/results/pagination.rb +54 -0
  32. data/lib/tire/rubyext/hash.rb +8 -0
  33. data/lib/tire/rubyext/ruby_1_8.rb +7 -0
  34. data/lib/tire/rubyext/symbol.rb +11 -0
  35. data/lib/tire/search.rb +188 -0
  36. data/lib/tire/search/facet.rb +74 -0
  37. data/lib/tire/search/filter.rb +28 -0
  38. data/lib/tire/search/highlight.rb +37 -0
  39. data/lib/tire/search/query.rb +186 -0
  40. data/lib/tire/search/scan.rb +114 -0
  41. data/lib/tire/search/script_field.rb +23 -0
  42. data/lib/tire/search/sort.rb +25 -0
  43. data/lib/tire/tasks.rb +135 -0
  44. data/lib/tire/utils.rb +17 -0
  45. data/lib/tire/version.rb +22 -0
  46. data/test/fixtures/articles/1.json +1 -0
  47. data/test/fixtures/articles/2.json +1 -0
  48. data/test/fixtures/articles/3.json +1 -0
  49. data/test/fixtures/articles/4.json +1 -0
  50. data/test/fixtures/articles/5.json +1 -0
  51. data/test/integration/active_model_indexing_test.rb +51 -0
  52. data/test/integration/active_model_searchable_test.rb +114 -0
  53. data/test/integration/active_record_searchable_test.rb +446 -0
  54. data/test/integration/boolean_queries_test.rb +43 -0
  55. data/test/integration/count_test.rb +34 -0
  56. data/test/integration/custom_score_queries_test.rb +88 -0
  57. data/test/integration/dis_max_queries_test.rb +68 -0
  58. data/test/integration/dsl_search_test.rb +22 -0
  59. data/test/integration/explanation_test.rb +44 -0
  60. data/test/integration/facets_test.rb +259 -0
  61. data/test/integration/filtered_queries_test.rb +66 -0
  62. data/test/integration/filters_test.rb +63 -0
  63. data/test/integration/fuzzy_queries_test.rb +20 -0
  64. data/test/integration/highlight_test.rb +64 -0
  65. data/test/integration/index_aliases_test.rb +122 -0
  66. data/test/integration/index_mapping_test.rb +43 -0
  67. data/test/integration/index_store_test.rb +96 -0
  68. data/test/integration/index_update_document_test.rb +111 -0
  69. data/test/integration/mongoid_searchable_test.rb +309 -0
  70. data/test/integration/percolator_test.rb +111 -0
  71. data/test/integration/persistent_model_test.rb +130 -0
  72. data/test/integration/prefix_query_test.rb +43 -0
  73. data/test/integration/query_return_version_test.rb +70 -0
  74. data/test/integration/query_string_test.rb +52 -0
  75. data/test/integration/range_queries_test.rb +36 -0
  76. data/test/integration/reindex_test.rb +46 -0
  77. data/test/integration/results_test.rb +39 -0
  78. data/test/integration/scan_test.rb +56 -0
  79. data/test/integration/script_fields_test.rb +38 -0
  80. data/test/integration/sort_test.rb +36 -0
  81. data/test/integration/text_query_test.rb +39 -0
  82. data/test/models/active_model_article.rb +31 -0
  83. data/test/models/active_model_article_with_callbacks.rb +49 -0
  84. data/test/models/active_model_article_with_custom_document_type.rb +7 -0
  85. data/test/models/active_model_article_with_custom_index_name.rb +7 -0
  86. data/test/models/active_record_models.rb +122 -0
  87. data/test/models/article.rb +15 -0
  88. data/test/models/mongoid_models.rb +97 -0
  89. data/test/models/persistent_article.rb +11 -0
  90. data/test/models/persistent_article_in_namespace.rb +12 -0
  91. data/test/models/persistent_article_with_casting.rb +28 -0
  92. data/test/models/persistent_article_with_defaults.rb +11 -0
  93. data/test/models/persistent_articles_with_custom_index_name.rb +10 -0
  94. data/test/models/supermodel_article.rb +17 -0
  95. data/test/models/validated_model.rb +11 -0
  96. data/test/test_helper.rb +93 -0
  97. data/test/unit/active_model_lint_test.rb +17 -0
  98. data/test/unit/configuration_test.rb +74 -0
  99. data/test/unit/http_client_test.rb +76 -0
  100. data/test/unit/http_response_test.rb +49 -0
  101. data/test/unit/index_alias_test.rb +275 -0
  102. data/test/unit/index_test.rb +894 -0
  103. data/test/unit/logger_test.rb +125 -0
  104. data/test/unit/model_callbacks_test.rb +116 -0
  105. data/test/unit/model_import_test.rb +71 -0
  106. data/test/unit/model_persistence_test.rb +528 -0
  107. data/test/unit/model_search_test.rb +913 -0
  108. data/test/unit/results_collection_test.rb +281 -0
  109. data/test/unit/results_item_test.rb +162 -0
  110. data/test/unit/rubyext_test.rb +66 -0
  111. data/test/unit/search_facet_test.rb +153 -0
  112. data/test/unit/search_filter_test.rb +42 -0
  113. data/test/unit/search_highlight_test.rb +46 -0
  114. data/test/unit/search_query_test.rb +301 -0
  115. data/test/unit/search_scan_test.rb +113 -0
  116. data/test/unit/search_script_field_test.rb +26 -0
  117. data/test/unit/search_sort_test.rb +50 -0
  118. data/test/unit/search_test.rb +499 -0
  119. data/test/unit/tire_test.rb +126 -0
  120. data/tire.gemspec +90 -0
  121. metadata +549 -0
@@ -0,0 +1,25 @@
1
+ module Tire
2
+ module Search
3
+
4
+ class Sort
5
+ def initialize(&block)
6
+ @value = []
7
+ block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
8
+ end
9
+
10
+ def by(name, direction=nil)
11
+ @value << ( direction ? { name => direction } : name )
12
+ self
13
+ end
14
+
15
+ def to_ary
16
+ @value
17
+ end
18
+
19
+ def to_json
20
+ @value.to_json
21
+ end
22
+ end
23
+
24
+ end
25
+ end
data/lib/tire/tasks.rb ADDED
@@ -0,0 +1,135 @@
1
+ require 'rake'
2
+ require 'benchmark'
3
+
4
+ namespace :tire do
5
+
6
+ full_comment = <<-DESC.gsub(/ /, '')
7
+ Import data from your model using paginate: rake environment tire:import CLASS='MyModel'.
8
+
9
+ Pass params for the `paginate` method:
10
+ $ rake environment tire:import CLASS='Article' PARAMS='{:page => 1}'
11
+
12
+ Force rebuilding the index (delete and create):
13
+ $ rake environment tire:import CLASS='Article' PARAMS='{:page => 1}' FORCE=1
14
+
15
+ Set target index name:
16
+ $ rake environment tire:import CLASS='Article' INDEX='articles-new'
17
+ DESC
18
+ desc full_comment
19
+ task :import do |t|
20
+
21
+ def elapsed_to_human(elapsed)
22
+ hour = 60*60
23
+ day = hour*24
24
+
25
+ case elapsed
26
+ when 0..59
27
+ "#{sprintf("%1.5f", elapsed)} seconds"
28
+ when 60..hour-1
29
+ "#{elapsed/60} minutes and #{elapsed % 60} seconds"
30
+ when hour..day
31
+ "#{elapsed/hour} hours and #{elapsed % hour} minutes"
32
+ else
33
+ "#{elapsed/hour} hours"
34
+ end
35
+ end
36
+
37
+ if ENV['CLASS'].to_s == ''
38
+ puts '='*90, 'USAGE', '='*90, full_comment, ""
39
+ exit(1)
40
+ end
41
+
42
+ klass = eval(ENV['CLASS'].to_s)
43
+ params = eval(ENV['PARAMS'].to_s) || {}
44
+
45
+ params.update :method => 'paginate'
46
+
47
+ index = Tire::Index.new( ENV['INDEX'] || klass.tire.index.name )
48
+
49
+ if ENV['FORCE']
50
+ puts "[IMPORT] Deleting index '#{index.name}'"
51
+ index.delete
52
+ end
53
+
54
+ unless index.exists?
55
+ mapping = defined?(Yajl) ? Yajl::Encoder.encode(klass.tire.mapping_to_hash, :pretty => true) :
56
+ MultiJson.encode(klass.tire.mapping_to_hash)
57
+ puts "[IMPORT] Creating index '#{index.name}' with mapping:", mapping
58
+ index.create :mappings => klass.tire.mapping_to_hash, :settings => klass.tire.settings
59
+ end
60
+
61
+ STDOUT.sync = true
62
+ puts "[IMPORT] Starting import for the '#{ENV['CLASS']}' class"
63
+ tty_cols = 80
64
+ total = klass.all.count rescue nil
65
+ offset = (total.to_s.size*2)+8
66
+ done = 0
67
+
68
+ STDOUT.puts '-'*tty_cols
69
+ elapsed = Benchmark.realtime do
70
+
71
+ # Add Kaminari-powered "paginate" method
72
+ #
73
+ if defined?(Kaminari) && klass.respond_to?(:page)
74
+ klass.instance_eval do
75
+ def paginate(options = {})
76
+ page(options[:page]).per(options[:per_page]).to_a
77
+ end
78
+ end
79
+ end unless klass.respond_to?(:paginate)
80
+
81
+ # Import the documents
82
+ #
83
+ index.import(klass, params) do |documents|
84
+
85
+ if total
86
+ done += documents.to_a.size
87
+ # I CAN HAZ PROGREZ BAR LIEK HOMEBRU!
88
+ percent = ( (done.to_f / total) * 100 ).to_i
89
+ glyphs = ( percent * ( (tty_cols-offset).to_f/100 ) ).to_i
90
+ STDOUT.print( "#" * glyphs )
91
+ STDOUT.print( "\r"*tty_cols+"#{done}/#{total} | \e[1m#{percent}%\e[0m " )
92
+ end
93
+
94
+ # Don't forget to return the documents collection back!
95
+ documents
96
+ end
97
+ end
98
+
99
+ puts "", '='*80, "Import finished in #{elapsed_to_human(elapsed)}"
100
+ end
101
+
102
+ namespace :index do
103
+
104
+ full_comment = <<-DESC.gsub(/ /, '')
105
+ Delete indices passed in the INDEX environment variable; separate multiple indices by comma.
106
+
107
+ Pass name of a single index to drop in the INDEX environmnet variable:
108
+ $ rake environment tire:index:drop INDEX=articles
109
+
110
+ Pass names of multiple indices to drop in the INDEX or INDICES environmnet variable:
111
+ $ rake environment tire:index:drop INDICES=articles-2011-01,articles-2011-02
112
+
113
+ DESC
114
+ desc full_comment
115
+ task :drop do
116
+ index_names = (ENV['INDEX'] || ENV['INDICES']).to_s.split(/,\s*/)
117
+
118
+ if index_names.empty?
119
+ puts '='*90, 'USAGE', '='*90, full_comment, ""
120
+ exit(1)
121
+ end
122
+
123
+ index_names.each do |name|
124
+ index = Tire::Index.new(name)
125
+ print "* Deleting index \e[1m#{index.name}\e[0m... "
126
+ puts index.delete ? "\e[32mOK\e[0m" : "\e[31mFAILED\e[0m | #{index.response.body}"
127
+ end
128
+
129
+ puts ""
130
+
131
+ end
132
+
133
+ end
134
+
135
+ end
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
@@ -0,0 +1,22 @@
1
+ module Tire
2
+ VERSION = "0.4.2"
3
+
4
+ CHANGELOG =<<-END
5
+ IMPORTANT CHANGES LATELY:
6
+
7
+ Version 0.4.1
8
+ -------------
9
+ * Added a Index#settings method to retrieve index settings as a Hash
10
+ * Added support for the "scan" search in the Ruby API
11
+ * Added support for reindexing the index documents into new index
12
+ * Added basic support for index aliases
13
+ * Changed, that Index#bulk_store runs against an index endpoint, not against `/_bulk`
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
21
+ END
22
+ end
@@ -0,0 +1 @@
1
+ {"title" : "One", "tags" : ["ruby"], "published_on" : "2011-01-01", "words" : 125, "draft" : true}
@@ -0,0 +1 @@
1
+ {"title" : "Two", "tags" : ["ruby", "python"], "published_on" : "2011-01-02", "words" : 250}
@@ -0,0 +1 @@
1
+ {"title" : "Three", "tags" : ["java"], "published_on" : "2011-01-02", "words" : 375}
@@ -0,0 +1 @@
1
+ {"title" : "Four", "tags" : ["erlang"], "published_on" : "2011-01-03", "words" : 250}
@@ -0,0 +1 @@
1
+ {"title" : "Five", "tags" : ["javascript", "java"], "published_on" : "2011-01-04", "words" : 125}
@@ -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