elasticsearch-model 0.0.1 → 0.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/.gitignore +3 -0
  2. data/LICENSE.txt +1 -1
  3. data/README.md +669 -8
  4. data/Rakefile +52 -0
  5. data/elasticsearch-model.gemspec +48 -17
  6. data/examples/activerecord_article.rb +77 -0
  7. data/examples/activerecord_associations.rb +153 -0
  8. data/examples/couchbase_article.rb +66 -0
  9. data/examples/datamapper_article.rb +71 -0
  10. data/examples/mongoid_article.rb +68 -0
  11. data/examples/ohm_article.rb +70 -0
  12. data/examples/riak_article.rb +52 -0
  13. data/gemfiles/3.gemfile +11 -0
  14. data/gemfiles/4.gemfile +11 -0
  15. data/lib/elasticsearch/model.rb +151 -1
  16. data/lib/elasticsearch/model/adapter.rb +145 -0
  17. data/lib/elasticsearch/model/adapters/active_record.rb +97 -0
  18. data/lib/elasticsearch/model/adapters/default.rb +44 -0
  19. data/lib/elasticsearch/model/adapters/mongoid.rb +90 -0
  20. data/lib/elasticsearch/model/callbacks.rb +35 -0
  21. data/lib/elasticsearch/model/client.rb +61 -0
  22. data/lib/elasticsearch/model/importing.rb +94 -0
  23. data/lib/elasticsearch/model/indexing.rb +332 -0
  24. data/lib/elasticsearch/model/naming.rb +101 -0
  25. data/lib/elasticsearch/model/proxy.rb +127 -0
  26. data/lib/elasticsearch/model/response.rb +70 -0
  27. data/lib/elasticsearch/model/response/base.rb +44 -0
  28. data/lib/elasticsearch/model/response/pagination.rb +96 -0
  29. data/lib/elasticsearch/model/response/records.rb +71 -0
  30. data/lib/elasticsearch/model/response/result.rb +50 -0
  31. data/lib/elasticsearch/model/response/results.rb +32 -0
  32. data/lib/elasticsearch/model/searching.rb +107 -0
  33. data/lib/elasticsearch/model/serializing.rb +35 -0
  34. data/lib/elasticsearch/model/support/forwardable.rb +44 -0
  35. data/lib/elasticsearch/model/version.rb +1 -1
  36. data/test/integration/active_record_associations_parent_child.rb +138 -0
  37. data/test/integration/active_record_associations_test.rb +306 -0
  38. data/test/integration/active_record_basic_test.rb +139 -0
  39. data/test/integration/active_record_import_test.rb +74 -0
  40. data/test/integration/active_record_namespaced_model_test.rb +49 -0
  41. data/test/integration/active_record_pagination_test.rb +109 -0
  42. data/test/integration/mongoid_basic_test.rb +178 -0
  43. data/test/test_helper.rb +57 -0
  44. data/test/unit/adapter_active_record_test.rb +93 -0
  45. data/test/unit/adapter_default_test.rb +31 -0
  46. data/test/unit/adapter_mongoid_test.rb +87 -0
  47. data/test/unit/adapter_test.rb +69 -0
  48. data/test/unit/callbacks_test.rb +30 -0
  49. data/test/unit/client_test.rb +27 -0
  50. data/test/unit/importing_test.rb +97 -0
  51. data/test/unit/indexing_test.rb +364 -0
  52. data/test/unit/module_test.rb +46 -0
  53. data/test/unit/naming_test.rb +76 -0
  54. data/test/unit/proxy_test.rb +88 -0
  55. data/test/unit/response_base_test.rb +40 -0
  56. data/test/unit/response_pagination_test.rb +159 -0
  57. data/test/unit/response_records_test.rb +87 -0
  58. data/test/unit/response_result_test.rb +52 -0
  59. data/test/unit/response_results_test.rb +31 -0
  60. data/test/unit/response_test.rb +57 -0
  61. data/test/unit/searching_search_request_test.rb +73 -0
  62. data/test/unit/searching_test.rb +39 -0
  63. data/test/unit/serializing_test.rb +17 -0
  64. metadata +418 -11
@@ -0,0 +1,139 @@
1
+ require 'test_helper'
2
+
3
+ puts "ActiveRecord #{ActiveRecord::VERSION::STRING}", '-'*80
4
+
5
+ module Elasticsearch
6
+ module Model
7
+ class ActiveRecordBasicIntegrationTest < Elasticsearch::Test::IntegrationTestCase
8
+
9
+ class ::Article < ActiveRecord::Base
10
+ include Elasticsearch::Model
11
+ include Elasticsearch::Model::Callbacks
12
+
13
+ settings index: { number_of_shards: 1, number_of_replicas: 0 } do
14
+ mapping do
15
+ indexes :title, type: 'string', analyzer: 'snowball'
16
+ indexes :created_at, type: 'date'
17
+ end
18
+ end
19
+ end
20
+
21
+ context "ActiveRecord basic integration" do
22
+ setup do
23
+ ActiveRecord::Schema.define(:version => 1) do
24
+ create_table :articles do |t|
25
+ t.string :title
26
+ t.datetime :created_at, :default => 'NOW()'
27
+ end
28
+ end
29
+
30
+ Article.delete_all
31
+ Article.__elasticsearch__.create_index! force: true
32
+
33
+ ::Article.create! title: 'Test'
34
+ ::Article.create! title: 'Testing Coding'
35
+ ::Article.create! title: 'Coding'
36
+
37
+ Article.__elasticsearch__.refresh_index!
38
+ end
39
+
40
+ should "index and find a document" do
41
+ response = Article.search('title:test')
42
+
43
+ assert response.any?, "Response should not be empty: #{response.to_a.inspect}"
44
+
45
+ assert_equal 2, response.results.size
46
+ assert_equal 2, response.records.size
47
+
48
+ assert_instance_of Elasticsearch::Model::Response::Result, response.results.first
49
+ assert_instance_of Article, response.records.first
50
+
51
+ assert_equal 'Test', response.results.first.title
52
+ assert_equal 'Test', response.records.first.title
53
+ end
54
+
55
+ should "iterate over results" do
56
+ response = Article.search('title:test')
57
+
58
+ assert_equal ['1', '2'], response.results.map(&:_id)
59
+ assert_equal [1, 2], response.records.map(&:id)
60
+ end
61
+
62
+ should "access results from records" do
63
+ response = Article.search('title:test')
64
+
65
+ response.records.each_with_hit do |r, h|
66
+ assert_not_nil h._score
67
+ assert_not_nil h._source.title
68
+ end
69
+ end
70
+
71
+ should "remove document from index on destroy" do
72
+ article = Article.first
73
+
74
+ article.destroy
75
+ assert_equal 2, Article.count
76
+
77
+ Article.__elasticsearch__.refresh_index!
78
+
79
+ response = Article.search 'title:test'
80
+
81
+ assert_equal 1, response.results.size
82
+ assert_equal 1, response.records.size
83
+ end
84
+
85
+ should "index updates to the document" do
86
+ article = Article.first
87
+
88
+ article.title = 'Writing'
89
+ article.save
90
+
91
+ Article.__elasticsearch__.refresh_index!
92
+
93
+ response = Article.search 'title:write'
94
+
95
+ assert_equal 1, response.results.size
96
+ assert_equal 1, response.records.size
97
+ end
98
+
99
+ should "return results for a DSL search" do
100
+ response = Article.search query: { match: { title: { query: 'test' } } }
101
+
102
+ assert_equal 2, response.results.size
103
+ assert_equal 2, response.records.size
104
+ end
105
+
106
+ should "return a paged collection" do
107
+ response = Article.search query: { match: { title: { query: 'test' } } },
108
+ size: 2,
109
+ from: 1
110
+
111
+ assert_equal 1, response.results.size
112
+ assert_equal 1, response.records.size
113
+
114
+ assert_equal 'Testing Coding', response.results.first.title
115
+ assert_equal 'Testing Coding', response.records.first.title
116
+ end
117
+
118
+ should "allow chaining SQL commands on response.records" do
119
+ response = Article.search query: { match: { title: { query: 'test' } } }
120
+
121
+ assert_equal 2, response.records.size
122
+ assert_equal 1, response.records.where(title: 'Test').size
123
+ assert_equal 'Test', response.records.where(title: 'Test').first.title
124
+ end
125
+
126
+ should "allow ordering response.records in SQL" do
127
+ response = Article.search query: { match: { title: { query: 'test' } } }
128
+
129
+ if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4
130
+ assert_equal 'Testing Coding', response.records.order(title: :desc).first.title
131
+ else
132
+ assert_equal 'Testing Coding', response.records.order('title DESC').first.title
133
+ end
134
+ end
135
+ end
136
+
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,74 @@
1
+ require 'test_helper'
2
+
3
+ module Elasticsearch
4
+ module Model
5
+ class ActiveRecordImportIntegrationTest < Elasticsearch::Test::IntegrationTestCase
6
+
7
+ class ::ImportArticle < ActiveRecord::Base
8
+ include Elasticsearch::Model
9
+
10
+ mapping do
11
+ indexes :title, type: 'string'
12
+ indexes :views, type: 'integer'
13
+ indexes :created_at, type: 'date'
14
+ end
15
+ end
16
+
17
+ context "ActiveRecord importing" do
18
+ setup do
19
+ ActiveRecord::Schema.define(:version => 1) do
20
+ create_table :import_articles do |t|
21
+ t.string :title
22
+ t.string :views # For the sake of invalid data sent to Elasticsearch
23
+ t.datetime :created_at, :default => 'NOW()'
24
+ end
25
+ end
26
+
27
+ ImportArticle.delete_all
28
+ ImportArticle.__elasticsearch__.create_index! force: true
29
+
30
+ 100.times { |i| ImportArticle.create! title: "Test #{i}" }
31
+ end
32
+
33
+ should "import all the documents" do
34
+ assert_equal 100, ImportArticle.count
35
+
36
+ ImportArticle.__elasticsearch__.refresh_index!
37
+ assert_equal 0, ImportArticle.search('*').results.total
38
+
39
+ batches = 0
40
+ errors = ImportArticle.import(batch_size: 10) do |response|
41
+ batches += 1
42
+ end
43
+
44
+ assert_equal 0, errors
45
+ assert_equal 10, batches
46
+
47
+ ImportArticle.__elasticsearch__.refresh_index!
48
+ assert_equal 100, ImportArticle.search('*').results.total
49
+ end
50
+
51
+ should "report and not store/index invalid documents" do
52
+ ImportArticle.create! title: "Test INVALID", views: "INVALID"
53
+
54
+ assert_equal 101, ImportArticle.count
55
+
56
+ ImportArticle.__elasticsearch__.refresh_index!
57
+ assert_equal 0, ImportArticle.search('*').results.total
58
+
59
+ batches = 0
60
+ errors = ImportArticle.__elasticsearch__.import(batch_size: 10) do |response|
61
+ batches += 1
62
+ end
63
+
64
+ assert_equal 1, errors
65
+ assert_equal 11, batches
66
+
67
+ ImportArticle.__elasticsearch__.refresh_index!
68
+ assert_equal 100, ImportArticle.search('*').results.total
69
+ end
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,49 @@
1
+ require 'test_helper'
2
+
3
+ module Elasticsearch
4
+ module Model
5
+ class ActiveRecordNamespacedModelIntegrationTest < Elasticsearch::Test::IntegrationTestCase
6
+
7
+ module ::MyNamespace
8
+ class Article < ActiveRecord::Base
9
+ include Elasticsearch::Model
10
+ include Elasticsearch::Model::Callbacks
11
+
12
+ mapping { indexes :title }
13
+ end
14
+ end
15
+
16
+ context "Namespaced ActiveRecord model integration" do
17
+ setup do
18
+ ActiveRecord::Schema.define(:version => 1) do
19
+ create_table :articles do |t|
20
+ t.string :title
21
+ end
22
+ end
23
+
24
+ MyNamespace::Article.delete_all
25
+ MyNamespace::Article.__elasticsearch__.create_index! force: true
26
+
27
+ MyNamespace::Article.create! title: 'Test'
28
+
29
+ MyNamespace::Article.__elasticsearch__.refresh_index!
30
+ end
31
+
32
+ should "have proper index name and document type" do
33
+ assert_equal "my_namespace-articles", MyNamespace::Article.index_name
34
+ assert_equal "article", MyNamespace::Article.document_type
35
+ end
36
+
37
+ should "save document into index on save and find it" do
38
+ response = MyNamespace::Article.search 'title:test'
39
+
40
+ assert response.any?, "No results returned: #{response.inspect}"
41
+ assert_equal 1, response.size
42
+
43
+ assert_equal 'Test', response.results.first.title
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,109 @@
1
+ require 'test_helper'
2
+
3
+ module Elasticsearch
4
+ module Model
5
+ class ActiveRecordPaginationTest < Elasticsearch::Test::IntegrationTestCase
6
+
7
+ class ::Article < ActiveRecord::Base
8
+ include Elasticsearch::Model
9
+
10
+ settings index: { number_of_shards: 1, number_of_replicas: 0 } do
11
+ mapping do
12
+ indexes :title, type: 'string', analyzer: 'snowball'
13
+ indexes :created_at, type: 'date'
14
+ end
15
+ end
16
+ end
17
+
18
+ Kaminari::Hooks.init
19
+
20
+ context "ActiveRecord pagination" do
21
+ setup do
22
+ ActiveRecord::Schema.define(:version => 1) do
23
+ create_table :articles do |t|
24
+ t.string :title
25
+ t.datetime :created_at, :default => 'NOW()'
26
+ end
27
+ end
28
+
29
+ Article.delete_all
30
+ Article.__elasticsearch__.create_index! force: true
31
+
32
+ 68.times do |i| ::Article.create! title: "Test #{i}" end
33
+
34
+ Article.import
35
+ Article.__elasticsearch__.refresh_index!
36
+ end
37
+
38
+ should "be on the first page by default" do
39
+ records = Article.search('title:test').page(1).records
40
+
41
+ assert_equal 25, records.size
42
+ assert_equal 1, records.current_page
43
+ assert_equal nil, records.prev_page
44
+ assert_equal 2, records.next_page
45
+ assert_equal 3, records.total_pages
46
+
47
+ assert records.first_page?, "Should be the first page"
48
+ assert ! records.last_page?, "Should NOT be the last page"
49
+ assert ! records.out_of_range?, "Should NOT be out of range"
50
+ end
51
+
52
+ should "load next page" do
53
+ records = Article.search('title:test').page(2).records
54
+
55
+ assert_equal 25, records.size
56
+ assert_equal 2, records.current_page
57
+ assert_equal 1, records.prev_page
58
+ assert_equal 3, records.next_page
59
+ assert_equal 3, records.total_pages
60
+
61
+ assert ! records.first_page?, "Should NOT be the first page"
62
+ assert ! records.last_page?, "Should NOT be the last page"
63
+ assert ! records.out_of_range?, "Should NOT be out of range"
64
+ end
65
+
66
+ should "load last page" do
67
+ records = Article.search('title:test').page(3).records
68
+
69
+ assert_equal 18, records.size
70
+ assert_equal 3, records.current_page
71
+ assert_equal 2, records.prev_page
72
+ assert_equal nil, records.next_page
73
+ assert_equal 3, records.total_pages
74
+
75
+ assert ! records.first_page?, "Should NOT be the first page"
76
+ assert records.last_page?, "Should be the last page"
77
+ assert ! records.out_of_range?, "Should NOT be out of range"
78
+ end
79
+
80
+ should "not load invalid page" do
81
+ records = Article.search('title:test').page(6).records
82
+
83
+ assert_equal 0, records.size
84
+ assert_equal 6, records.current_page
85
+ assert_equal 5, records.prev_page
86
+ assert_equal nil, records.next_page
87
+ assert_equal 3, records.total_pages
88
+
89
+ assert ! records.first_page?, "Should NOT be the first page"
90
+ assert records.last_page?, "Should be the last page"
91
+ assert records.out_of_range?, "Should be out of range"
92
+ end
93
+
94
+ context "with specific model settings" do
95
+ teardown do
96
+ Article.instance_variable_set(:@_default_per_page, nil)
97
+ end
98
+ end
99
+
100
+ should "respect paginates_per" do
101
+ Article.paginates_per 50
102
+
103
+ assert_equal 50, Article.search('*').page(1).records.size
104
+ end
105
+ end
106
+
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,178 @@
1
+ require 'test_helper'
2
+
3
+ begin
4
+ require "mongoid"
5
+ session = Moped::Connection.new("localhost", 27017, 0.5)
6
+ session.connect
7
+ ENV["MONGODB_AVAILABLE"] = 'yes'
8
+ rescue LoadError, Moped::Errors::ConnectionFailure => e
9
+ end
10
+
11
+ if ENV["MONGODB_AVAILABLE"]
12
+ puts "Mongoid #{Mongoid::VERSION}", '-'*80
13
+
14
+ logger = ::Logger.new(STDERR)
15
+ logger.formatter = lambda { |s, d, p, m| " #{m.ansi(:faint, :cyan)}\n" }
16
+ logger.level = ::Logger::DEBUG
17
+
18
+ Mongoid.logger = logger unless ENV['QUIET']
19
+ Moped.logger = logger unless ENV['QUIET']
20
+
21
+ Mongoid.connect_to 'mongoid_articles'
22
+
23
+ module Elasticsearch
24
+ module Model
25
+ class MongoidBasicIntegrationTest < Elasticsearch::Test::IntegrationTestCase
26
+
27
+ class ::MongoidArticle
28
+ include Mongoid::Document
29
+ include Elasticsearch::Model
30
+ include Elasticsearch::Model::Callbacks
31
+
32
+ field :id, type: String
33
+ field :title, type: String
34
+ attr_accessible :title if respond_to? :attr_accessible
35
+
36
+ settings index: { number_of_shards: 1, number_of_replicas: 0 } do
37
+ mapping do
38
+ indexes :title, type: 'string', analyzer: 'snowball'
39
+ indexes :created_at, type: 'date'
40
+ end
41
+ end
42
+
43
+ def as_indexed_json(options={})
44
+ as_json(except: [:id, :_id])
45
+ end
46
+ end
47
+
48
+ context "Mongoid integration" do
49
+ setup do
50
+ Elasticsearch::Model::Adapter.register \
51
+ Elasticsearch::Model::Adapter::Mongoid,
52
+ lambda { |klass| !!defined?(::Mongoid::Document) && klass.ancestors.include?(::Mongoid::Document) }
53
+
54
+ MongoidArticle.__elasticsearch__.create_index! force: true
55
+
56
+ MongoidArticle.delete_all
57
+
58
+ MongoidArticle.create! title: 'Test'
59
+ MongoidArticle.create! title: 'Testing Coding'
60
+ MongoidArticle.create! title: 'Coding'
61
+
62
+ MongoidArticle.__elasticsearch__.refresh_index!
63
+ end
64
+
65
+ should "index and find a document" do
66
+ response = MongoidArticle.search('title:test')
67
+
68
+ assert response.any?
69
+
70
+ assert_equal 2, response.results.size
71
+ assert_equal 2, response.records.size
72
+
73
+ assert_instance_of Elasticsearch::Model::Response::Result, response.results.first
74
+ assert_instance_of MongoidArticle, response.records.first
75
+
76
+ assert_equal 'Test', response.results.first.title
77
+ assert_equal 'Test', response.records.first.title
78
+ end
79
+
80
+ should "iterate over results" do
81
+ response = MongoidArticle.search('title:test')
82
+
83
+ assert_equal ['Test', 'Testing Coding'], response.results.map(&:title)
84
+ assert_equal ['Test', 'Testing Coding'], response.records.map(&:title)
85
+ end
86
+
87
+ should "access results from records" do
88
+ response = MongoidArticle.search('title:test')
89
+
90
+ response.records.each_with_hit do |r, h|
91
+ assert_not_nil h._score
92
+ assert_not_nil h._source.title
93
+ end
94
+ end
95
+
96
+ should "remove document from index on destroy" do
97
+ article = MongoidArticle.first
98
+
99
+ article.destroy
100
+ assert_equal 2, MongoidArticle.count
101
+
102
+ MongoidArticle.__elasticsearch__.refresh_index!
103
+
104
+ response = MongoidArticle.search 'title:test'
105
+
106
+ assert_equal 1, response.results.size
107
+ assert_equal 1, response.records.size
108
+ end
109
+
110
+ should "index updates to the document" do
111
+ article = MongoidArticle.first
112
+
113
+ article.title = 'Writing'
114
+ article.save
115
+
116
+ MongoidArticle.__elasticsearch__.refresh_index!
117
+
118
+ response = MongoidArticle.search 'title:write'
119
+
120
+ assert_equal 1, response.results.size
121
+ assert_equal 1, response.records.size
122
+ end
123
+
124
+ should "return results for a DSL search" do
125
+ response = MongoidArticle.search query: { match: { title: { query: 'test' } } }
126
+
127
+ assert_equal 2, response.results.size
128
+ assert_equal 2, response.records.size
129
+ end
130
+
131
+ should "return a paged collection" do
132
+ response = MongoidArticle.search query: { match: { title: { query: 'test' } } },
133
+ size: 2,
134
+ from: 1
135
+
136
+ assert_equal 1, response.results.size
137
+ assert_equal 1, response.records.size
138
+
139
+ assert_equal 'Testing Coding', response.results.first.title
140
+ assert_equal 'Testing Coding', response.records.first.title
141
+ end
142
+
143
+
144
+ context "importing" do
145
+ setup do
146
+ MongoidArticle.delete_all
147
+ 97.times { |i| MongoidArticle.create! title: "Test #{i}" }
148
+ MongoidArticle.__elasticsearch__.create_index! force: true
149
+ end
150
+
151
+ should "import all the documents" do
152
+ assert_equal 97, MongoidArticle.count
153
+
154
+ MongoidArticle.__elasticsearch__.refresh_index!
155
+ assert_equal 0, MongoidArticle.search('*').results.total
156
+
157
+ batches = 0
158
+ errors = MongoidArticle.import(batch_size: 10) do |response|
159
+ batches += 1
160
+ end
161
+
162
+ assert_equal 0, errors
163
+ assert_equal 10, batches
164
+
165
+ MongoidArticle.__elasticsearch__.refresh_index!
166
+ assert_equal 97, MongoidArticle.search('*').results.total
167
+
168
+ response = MongoidArticle.search('test')
169
+ assert response.results.any?, "Search has not returned results: #{response.to_a}"
170
+ end
171
+ end
172
+ end
173
+
174
+ end
175
+ end
176
+ end
177
+
178
+ end