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