elasticsearch-model 0.0.1 → 0.1.0.rc1

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