load_balanced_tire 0.1

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.
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