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,57 @@
1
+ RUBY_1_8 = defined?(RUBY_VERSION) && RUBY_VERSION < '1.9'
2
+
3
+ exit(0) if RUBY_1_8
4
+
5
+ require 'simplecov' and SimpleCov.start { add_filter "/test|test_/" } if ENV["COVERAGE"]
6
+
7
+ # Register `at_exit` handler for integration tests shutdown.
8
+ # MUST be called before requiring `test/unit`.
9
+ at_exit { Elasticsearch::Test::IntegrationTestCase.__run_at_exit_hooks }
10
+
11
+ puts '-'*80
12
+
13
+ require 'test/unit'
14
+ require 'shoulda-context'
15
+ require 'mocha/setup'
16
+ require 'turn' unless ENV["TM_FILEPATH"] || ENV["NOTURN"] || RUBY_1_8
17
+
18
+ require 'ansi'
19
+ require 'oj'
20
+
21
+ require 'active_record'
22
+ require 'active_model'
23
+
24
+ require 'kaminari'
25
+
26
+ require 'elasticsearch/model'
27
+
28
+ require 'elasticsearch/extensions/test/cluster'
29
+ require 'elasticsearch/extensions/test/startup_shutdown'
30
+
31
+ module Elasticsearch
32
+ module Test
33
+ class IntegrationTestCase < ::Test::Unit::TestCase
34
+ extend Elasticsearch::Extensions::Test::StartupShutdown
35
+
36
+ startup { Elasticsearch::Extensions::Test::Cluster.start(nodes: 1) if ENV['SERVER'] and not Elasticsearch::Extensions::Test::Cluster.running? }
37
+ shutdown { Elasticsearch::Extensions::Test::Cluster.stop if ENV['SERVER'] && started? }
38
+ context "IntegrationTest" do; should "noop on Ruby 1.8" do; end; end if RUBY_1_8
39
+
40
+ def setup
41
+ ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" )
42
+ logger = ::Logger.new(STDERR)
43
+ logger.formatter = lambda { |s, d, p, m| "#{m.ansi(:faint, :cyan)}\n" }
44
+ ActiveRecord::Base.logger = logger unless ENV['QUIET']
45
+
46
+ ActiveRecord::LogSubscriber.colorize_logging = false
47
+ ActiveRecord::Migration.verbose = false
48
+
49
+ tracer = ::Logger.new(STDERR)
50
+ tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" }
51
+
52
+ Elasticsearch::Model.client = Elasticsearch::Client.new host: 'localhost:9250',
53
+ tracer: (ENV['QUIET'] ? nil : tracer)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,93 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Model::AdapterActiveRecordTest < Test::Unit::TestCase
4
+ context "Adapter ActiveRecord module: " do
5
+ class ::DummyClassForActiveRecord
6
+ RESPONSE = Struct.new('DummyActiveRecordResponse') do
7
+ def response
8
+ { 'hits' => {'hits' => [ {'_id' => 2}, {'_id' => 1} ]} }
9
+ end
10
+ end.new
11
+
12
+ def response
13
+ RESPONSE
14
+ end
15
+
16
+ def ids
17
+ [2, 1]
18
+ end
19
+ end
20
+
21
+ RESPONSE = { 'hits' => { 'total' => 123, 'max_score' => 456, 'hits' => [] } }
22
+
23
+ setup do
24
+ @records = [ stub(id: 1, inspect: '<Model-1>'), stub(id: 2, inspect: '<Model-2>') ]
25
+ @records.stubs(:load).returns(true)
26
+ end
27
+
28
+ should "have the register condition" do
29
+ assert_not_nil Elasticsearch::Model::Adapter.adapters[Elasticsearch::Model::Adapter::ActiveRecord]
30
+ assert_equal false, Elasticsearch::Model::Adapter.adapters[Elasticsearch::Model::Adapter::ActiveRecord].call(DummyClassForActiveRecord)
31
+ end
32
+
33
+ context "Records" do
34
+ setup do
35
+ DummyClassForActiveRecord.__send__ :include, Elasticsearch::Model::Adapter::ActiveRecord::Records
36
+ end
37
+
38
+ should "have the implementation" do
39
+ assert_instance_of Module, Elasticsearch::Model::Adapter::ActiveRecord::Records
40
+
41
+ instance = DummyClassForActiveRecord.new
42
+ instance.expects(:klass).returns(mock('class', where: @records))
43
+
44
+ assert_equal @records, instance.records
45
+ end
46
+
47
+ should "load the records" do
48
+ instance = DummyClassForActiveRecord.new
49
+ instance.expects(:records).returns(@records)
50
+ instance.load
51
+ end
52
+
53
+ should "reorder the records based on hits order" do
54
+ @records.instance_variable_set(:@records, @records)
55
+
56
+ instance = DummyClassForActiveRecord.new
57
+ instance.expects(:klass).returns(mock('class', where: @records))
58
+
59
+ assert_equal [1, 2], @records. to_a.map(&:id)
60
+ assert_equal [2, 1], instance.records.to_a.map(&:id)
61
+ end
62
+
63
+ should "not reorder records when SQL order is present" do
64
+ @records.instance_variable_set(:@records, @records)
65
+
66
+ instance = DummyClassForActiveRecord.new
67
+ instance.expects(:klass).returns(stub('class', where: @records)).at_least_once
68
+ instance.records.expects(:order).returns(@records)
69
+
70
+ assert_equal [2, 1], instance.records. to_a.map(&:id)
71
+ assert_equal [1, 2], instance.order(:foo).to_a.map(&:id)
72
+ end
73
+ end
74
+
75
+ context "Callbacks" do
76
+ should "register hooks for automatically updating the index" do
77
+ DummyClassForActiveRecord.expects(:after_commit).times(3)
78
+
79
+ Elasticsearch::Model::Adapter::ActiveRecord::Callbacks.included(DummyClassForActiveRecord)
80
+ end
81
+ end
82
+
83
+ context "Importing" do
84
+ should "implement the __find_in_batches method" do
85
+ DummyClassForActiveRecord.expects(:find_in_batches).returns([])
86
+
87
+ DummyClassForActiveRecord.__send__ :extend, Elasticsearch::Model::Adapter::ActiveRecord::Importing
88
+ DummyClassForActiveRecord.__find_in_batches do; end
89
+ end
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Model::AdapterDefaultTest < Test::Unit::TestCase
4
+ context "Adapter default module" do
5
+ class ::DummyClassForDefaultAdapter; end
6
+
7
+ should "have the default Records implementation" do
8
+ assert_instance_of Module, Elasticsearch::Model::Adapter::Default::Records
9
+
10
+ DummyClassForDefaultAdapter.__send__ :include, Elasticsearch::Model::Adapter::Default::Records
11
+
12
+ instance = DummyClassForDefaultAdapter.new
13
+ klass = mock('class', find: [1])
14
+ instance.expects(:klass).returns(klass)
15
+ instance.records
16
+ end
17
+
18
+ should "have the default Callbacks implementation" do
19
+ assert_instance_of Module, Elasticsearch::Model::Adapter::Default::Callbacks
20
+ end
21
+
22
+ should "have the default Importing implementation" do
23
+ DummyClassForDefaultAdapter.__send__ :include, Elasticsearch::Model::Adapter::Default::Importing
24
+
25
+ assert_raise Elasticsearch::Model::NotImplemented do
26
+ DummyClassForDefaultAdapter.new.__find_in_batches
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,87 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Model::AdapterMongoidTest < Test::Unit::TestCase
4
+ context "Adapter Mongoid module: " do
5
+ class ::DummyClassForMongoid
6
+ RESPONSE = Struct.new('DummyMongoidResponse') do
7
+ def response
8
+ { 'hits' => {'hits' => [ {'_id' => 2}, {'_id' => 1} ]} }
9
+ end
10
+ end.new
11
+
12
+ def response
13
+ RESPONSE
14
+ end
15
+
16
+ def ids
17
+ [2, 1]
18
+ end
19
+ end
20
+
21
+ setup do
22
+ @records = [ stub(id: 1, inspect: '<Model-1>'), stub(id: 2, inspect: '<Model-2>') ]
23
+ ::Symbol.any_instance.stubs(:in).returns(@records)
24
+ end
25
+
26
+ should "have the register condition" do
27
+ assert_not_nil Elasticsearch::Model::Adapter.adapters[Elasticsearch::Model::Adapter::Mongoid]
28
+ assert_equal false, Elasticsearch::Model::Adapter.adapters[Elasticsearch::Model::Adapter::Mongoid].call(DummyClassForMongoid)
29
+ end
30
+
31
+ context "Records" do
32
+ setup do
33
+ DummyClassForMongoid.__send__ :include, Elasticsearch::Model::Adapter::Mongoid::Records
34
+ end
35
+
36
+ should "have the implementation" do
37
+ assert_instance_of Module, Elasticsearch::Model::Adapter::Mongoid::Records
38
+
39
+ instance = DummyClassForMongoid.new
40
+ instance.expects(:klass).returns(mock('class', where: @records))
41
+
42
+ assert_equal @records, instance.records
43
+ end
44
+
45
+ should "reorder the records based on hits order" do
46
+ @records.instance_variable_set(:@records, @records)
47
+
48
+ instance = DummyClassForMongoid.new
49
+ instance.expects(:klass).returns(mock('class', where: @records))
50
+
51
+ assert_equal [1, 2], @records. to_a.map(&:id)
52
+ assert_equal [2, 1], instance.records.to_a.map(&:id)
53
+ end
54
+
55
+ should "not reorder records when SQL order is present" do
56
+ @records.instance_variable_set(:@records, @records)
57
+
58
+ instance = DummyClassForMongoid.new
59
+ instance.expects(:klass).returns(stub('class', where: @records)).at_least_once
60
+ instance.records.expects(:asc).returns(@records)
61
+
62
+ assert_equal [2, 1], instance.records.to_a.map(&:id)
63
+ assert_equal [1, 2], instance.asc.to_a.map(&:id)
64
+ end
65
+ end
66
+
67
+ context "Callbacks" do
68
+ should "register hooks for automatically updating the index" do
69
+ DummyClassForMongoid.expects(:after_create)
70
+ DummyClassForMongoid.expects(:after_update)
71
+ DummyClassForMongoid.expects(:after_destroy)
72
+
73
+ Elasticsearch::Model::Adapter::Mongoid::Callbacks.included(DummyClassForMongoid)
74
+ end
75
+ end
76
+
77
+ context "Importing" do
78
+ should "implement the __find_in_batches method" do
79
+ DummyClassForMongoid.expects(:all).returns([])
80
+
81
+ DummyClassForMongoid.__send__ :extend, Elasticsearch::Model::Adapter::Mongoid::Importing
82
+ DummyClassForMongoid.__find_in_batches do; end
83
+ end
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,69 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Model::AdapterTest < Test::Unit::TestCase
4
+ context "Adapter module" do
5
+ class ::DummyAdapterClass; end
6
+ class ::DummyAdapterClassWithAdapter; end
7
+ class ::DummyAdapter
8
+ Records = Module.new
9
+ Callbacks = Module.new
10
+ Importing = Module.new
11
+ end
12
+
13
+ should "return an Adapter instance" do
14
+ assert_instance_of Elasticsearch::Model::Adapter::Adapter,
15
+ Elasticsearch::Model::Adapter.from_class(DummyAdapterClass)
16
+ end
17
+
18
+ should "return a list of adapters" do
19
+ Elasticsearch::Model::Adapter::Adapter.expects(:adapters)
20
+ Elasticsearch::Model::Adapter.adapters
21
+ end
22
+
23
+ should "register an adapter" do
24
+ begin
25
+ Elasticsearch::Model::Adapter::Adapter.expects(:register)
26
+ Elasticsearch::Model::Adapter.register(:foo, lambda { |c| false })
27
+ ensure
28
+ Elasticsearch::Model::Adapter::Adapter.instance_variable_set(:@adapters, {})
29
+ end
30
+ end
31
+ end
32
+
33
+ context "Adapter class" do
34
+ should "register an adapter" do
35
+ begin
36
+ Elasticsearch::Model::Adapter::Adapter.register(:foo, lambda { |c| false })
37
+ assert Elasticsearch::Model::Adapter::Adapter.adapters[:foo]
38
+ ensure
39
+ Elasticsearch::Model::Adapter::Adapter.instance_variable_set(:@adapters, {})
40
+ end
41
+ end
42
+
43
+ should "return the default adapter" do
44
+ adapter = Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClass)
45
+ assert_equal Elasticsearch::Model::Adapter::Default, adapter.adapter
46
+ end
47
+
48
+ should "return a specific adapter" do
49
+ Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter,
50
+ lambda { |c| c == DummyAdapterClassWithAdapter })
51
+
52
+ adapter = Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClassWithAdapter)
53
+ assert_equal DummyAdapter, adapter.adapter
54
+ end
55
+
56
+ should "return the modules" do
57
+ assert_nothing_raised do
58
+ Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter,
59
+ lambda { |c| c == DummyAdapterClassWithAdapter })
60
+
61
+ adapter = Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClassWithAdapter)
62
+
63
+ assert_instance_of Module, adapter.records_mixin
64
+ assert_instance_of Module, adapter.callbacks_mixin
65
+ assert_instance_of Module, adapter.importing_mixin
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,30 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Model::CallbacksTest < Test::Unit::TestCase
4
+ context "Callbacks module" do
5
+ class ::DummyCallbacksModel
6
+ end
7
+
8
+ module DummyCallbacksAdapter
9
+ module CallbacksMixin
10
+ end
11
+
12
+ def callbacks_mixin
13
+ CallbacksMixin
14
+ end; module_function :callbacks_mixin
15
+ end
16
+
17
+ should "include the callbacks mixin from adapter" do
18
+ Elasticsearch::Model::Adapter.expects(:from_class)
19
+ .with(DummyCallbacksModel)
20
+ .returns(DummyCallbacksAdapter)
21
+
22
+ ::DummyCallbacksModel.expects(:__send__).with do |method, parameter|
23
+ assert_equal :include, method
24
+ assert_equal DummyCallbacksAdapter::CallbacksMixin, parameter
25
+ end
26
+
27
+ Elasticsearch::Model::Callbacks.included(DummyCallbacksModel)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Model::ClientTest < Test::Unit::TestCase
4
+ context "Client module" do
5
+ class ::DummyClientModel
6
+ extend Elasticsearch::Model::Client::ClassMethods
7
+ include Elasticsearch::Model::Client::InstanceMethods
8
+ end
9
+
10
+ should "have the default client method" do
11
+ assert_instance_of Elasticsearch::Transport::Client, DummyClientModel.client
12
+ assert_instance_of Elasticsearch::Transport::Client, DummyClientModel.new.client
13
+ end
14
+
15
+ should "set the client for the model" do
16
+ DummyClientModel.client = 'foobar'
17
+ assert_equal 'foobar', DummyClientModel.client
18
+ assert_equal 'foobar', DummyClientModel.new.client
19
+ end
20
+
21
+ should "set the client for a model instance" do
22
+ instance = DummyClientModel.new
23
+ instance.client = 'moobam'
24
+ assert_equal 'moobam', instance.client
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,97 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Model::ImportingTest < Test::Unit::TestCase
4
+ context "Importing module" do
5
+ class ::DummyImportingModel
6
+ end
7
+
8
+ module ::DummyImportingAdapter
9
+ module ImportingMixin
10
+ def __find_in_batches(options={}, &block)
11
+ yield if block_given?
12
+ end
13
+ end
14
+
15
+ def importing_mixin
16
+ ImportingMixin
17
+ end; module_function :importing_mixin
18
+ end
19
+
20
+ should "include methods from the module and adapter" do
21
+ Elasticsearch::Model::Adapter.expects(:from_class)
22
+ .with(DummyImportingModel)
23
+ .returns(DummyImportingAdapter)
24
+
25
+ DummyImportingModel.__send__ :include, Elasticsearch::Model::Importing
26
+
27
+ assert_respond_to DummyImportingModel, :import
28
+ assert_respond_to DummyImportingModel, :__find_in_batches
29
+ end
30
+
31
+ should "call the client when importing" do
32
+ Elasticsearch::Model::Adapter.expects(:from_class)
33
+ .with(DummyImportingModel)
34
+ .returns(DummyImportingAdapter)
35
+
36
+ DummyImportingModel.__send__ :include, Elasticsearch::Model::Importing
37
+
38
+ client = mock('client')
39
+ client.expects(:bulk).returns({'items' => []})
40
+
41
+ DummyImportingModel.expects(:client).returns(client)
42
+ DummyImportingModel.expects(:index_name).returns('foo')
43
+ DummyImportingModel.expects(:document_type).returns('foo')
44
+
45
+ assert_equal 0, DummyImportingModel.import
46
+ end
47
+
48
+ should "return number of errors" do
49
+ Elasticsearch::Model::Adapter.expects(:from_class)
50
+ .with(DummyImportingModel)
51
+ .returns(DummyImportingAdapter)
52
+
53
+ DummyImportingModel.__send__ :include, Elasticsearch::Model::Importing
54
+
55
+ client = mock('client')
56
+ client.expects(:bulk).returns({'items' => [ {'index' => {}}, {'index' => {'error' => 'FAILED'}} ]})
57
+
58
+ DummyImportingModel.stubs(:client).returns(client)
59
+ DummyImportingModel.stubs(:index_name).returns('foo')
60
+ DummyImportingModel.stubs(:document_type).returns('foo')
61
+
62
+ assert_equal 1, DummyImportingModel.import
63
+ end
64
+
65
+ should "yield the response" do
66
+ Elasticsearch::Model::Adapter.expects(:from_class)
67
+ .with(DummyImportingModel)
68
+ .returns(DummyImportingAdapter)
69
+
70
+ DummyImportingModel.__send__ :include, Elasticsearch::Model::Importing
71
+
72
+ client = mock('client')
73
+ client.expects(:bulk).returns({'items' => [ {'index' => {}}, {'index' => {'error' => 'FAILED'}} ]})
74
+
75
+ DummyImportingModel.stubs(:client).returns(client)
76
+ DummyImportingModel.stubs(:index_name).returns('foo')
77
+ DummyImportingModel.stubs(:document_type).returns('foo')
78
+
79
+ DummyImportingModel.import do |response|
80
+ assert_equal 2, response['items'].size
81
+ end
82
+ end
83
+
84
+ should "delete and create the index with the force option" do
85
+ DummyImportingModel.expects(:__find_in_batches).with do |options|
86
+ assert_equal 'bar', options[:foo]
87
+ assert_nil options[:force]
88
+ end
89
+
90
+ DummyImportingModel.expects(:create_index!).with do |options|
91
+ assert_equal true, options[:force]
92
+ end
93
+
94
+ DummyImportingModel.import force: true, foo: 'bar'
95
+ end
96
+ end
97
+ end