elasticsearch-model 5.1.0 → 6.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -3
  3. data/Gemfile +5 -0
  4. data/README.md +18 -11
  5. data/Rakefile +27 -36
  6. data/elasticsearch-model.gemspec +6 -6
  7. data/examples/activerecord_custom_analyzer.rb +135 -0
  8. data/examples/activerecord_mapping_completion.rb +3 -18
  9. data/examples/datamapper_article.rb +11 -1
  10. data/gemfiles/3.0.gemfile +6 -1
  11. data/gemfiles/4.0.gemfile +8 -1
  12. data/gemfiles/5.0.gemfile +7 -1
  13. data/gemfiles/6.0.gemfile +18 -0
  14. data/lib/elasticsearch/model/adapters/active_record.rb +8 -24
  15. data/lib/elasticsearch/model/adapters/mongoid.rb +10 -3
  16. data/lib/elasticsearch/model/importing.rb +1 -1
  17. data/lib/elasticsearch/model/indexing.rb +6 -4
  18. data/lib/elasticsearch/model/naming.rb +9 -2
  19. data/lib/elasticsearch/model/response/aggregations.rb +1 -1
  20. data/lib/elasticsearch/model/response/base.rb +3 -2
  21. data/lib/elasticsearch/model/response/pagination/kaminari.rb +109 -0
  22. data/lib/elasticsearch/model/response/pagination/will_paginate.rb +95 -0
  23. data/lib/elasticsearch/model/response/pagination.rb +2 -192
  24. data/lib/elasticsearch/model/response/result.rb +1 -1
  25. data/lib/elasticsearch/model/response/suggestions.rb +1 -1
  26. data/lib/elasticsearch/model/response.rb +11 -10
  27. data/lib/elasticsearch/model/version.rb +1 -1
  28. data/lib/elasticsearch/model.rb +15 -8
  29. data/spec/elasticsearch/model/adapter_spec.rb +119 -0
  30. data/spec/elasticsearch/model/adapters/active_record/associations_spec.rb +334 -0
  31. data/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +340 -0
  32. data/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb +18 -0
  33. data/spec/elasticsearch/model/adapters/active_record/import_spec.rb +187 -0
  34. data/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb +110 -0
  35. data/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb +38 -0
  36. data/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb +315 -0
  37. data/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb +75 -0
  38. data/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb +61 -0
  39. data/spec/elasticsearch/model/adapters/active_record_spec.rb +207 -0
  40. data/spec/elasticsearch/model/adapters/default_spec.rb +41 -0
  41. data/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb +267 -0
  42. data/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb +66 -0
  43. data/spec/elasticsearch/model/adapters/mongoid_spec.rb +235 -0
  44. data/spec/elasticsearch/model/adapters/multiple_spec.rb +125 -0
  45. data/spec/elasticsearch/model/callbacks_spec.rb +33 -0
  46. data/spec/elasticsearch/model/client_spec.rb +66 -0
  47. data/spec/elasticsearch/model/hash_wrapper_spec.rb +12 -0
  48. data/spec/elasticsearch/model/importing_spec.rb +214 -0
  49. data/spec/elasticsearch/model/indexing_spec.rb +918 -0
  50. data/spec/elasticsearch/model/module_spec.rb +101 -0
  51. data/spec/elasticsearch/model/multimodel_spec.rb +55 -0
  52. data/spec/elasticsearch/model/naming_inheritance_spec.rb +184 -0
  53. data/spec/elasticsearch/model/naming_spec.rb +186 -0
  54. data/spec/elasticsearch/model/proxy_spec.rb +107 -0
  55. data/spec/elasticsearch/model/response/aggregations_spec.rb +66 -0
  56. data/spec/elasticsearch/model/response/base_spec.rb +90 -0
  57. data/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +410 -0
  58. data/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb +262 -0
  59. data/spec/elasticsearch/model/response/records_spec.rb +118 -0
  60. data/spec/elasticsearch/model/response/response_spec.rb +131 -0
  61. data/spec/elasticsearch/model/response/result_spec.rb +122 -0
  62. data/spec/elasticsearch/model/response/results_spec.rb +56 -0
  63. data/spec/elasticsearch/model/searching_search_request_spec.rb +112 -0
  64. data/spec/elasticsearch/model/searching_spec.rb +49 -0
  65. data/spec/elasticsearch/model/serializing_spec.rb +22 -0
  66. data/spec/spec_helper.rb +161 -0
  67. data/spec/support/app/answer.rb +33 -0
  68. data/spec/support/app/article.rb +22 -0
  69. data/spec/support/app/article_for_pagination.rb +12 -0
  70. data/spec/support/app/article_with_custom_serialization.rb +13 -0
  71. data/spec/support/app/article_with_dynamic_index_name.rb +15 -0
  72. data/spec/support/app/author.rb +9 -0
  73. data/spec/support/app/authorship.rb +4 -0
  74. data/spec/support/app/category.rb +3 -0
  75. data/spec/support/app/comment.rb +3 -0
  76. data/spec/support/app/episode.rb +11 -0
  77. data/spec/support/app/image.rb +19 -0
  78. data/spec/support/app/import_article.rb +12 -0
  79. data/spec/support/app/mongoid_article.rb +21 -0
  80. data/spec/support/app/namespaced_book.rb +10 -0
  81. data/spec/support/app/parent_and_child_searchable.rb +24 -0
  82. data/spec/support/app/post.rb +14 -0
  83. data/spec/support/app/question.rb +27 -0
  84. data/spec/support/app/searchable.rb +48 -0
  85. data/spec/support/app/series.rb +11 -0
  86. data/spec/support/app.rb +21 -0
  87. data/spec/support/model.json +1 -0
  88. data/{test → spec}/support/model.yml +0 -0
  89. metadata +134 -89
  90. data/test/integration/active_record_associations_parent_child_test.rb +0 -147
  91. data/test/integration/active_record_associations_test.rb +0 -339
  92. data/test/integration/active_record_basic_test.rb +0 -251
  93. data/test/integration/active_record_custom_serialization_test.rb +0 -67
  94. data/test/integration/active_record_import_test.rb +0 -115
  95. data/test/integration/active_record_namespaced_model_test.rb +0 -54
  96. data/test/integration/active_record_pagination_test.rb +0 -149
  97. data/test/integration/dynamic_index_name_test.rb +0 -52
  98. data/test/integration/mongoid_basic_test.rb +0 -176
  99. data/test/integration/multiple_models_test.rb +0 -176
  100. data/test/support/model.json +0 -1
  101. data/test/test_helper.rb +0 -92
  102. data/test/unit/adapter_active_record_test.rb +0 -157
  103. data/test/unit/adapter_default_test.rb +0 -41
  104. data/test/unit/adapter_mongoid_test.rb +0 -104
  105. data/test/unit/adapter_multiple_test.rb +0 -106
  106. data/test/unit/adapter_test.rb +0 -69
  107. data/test/unit/callbacks_test.rb +0 -31
  108. data/test/unit/client_test.rb +0 -27
  109. data/test/unit/hash_wrapper_test.rb +0 -13
  110. data/test/unit/importing_test.rb +0 -203
  111. data/test/unit/indexing_test.rb +0 -687
  112. data/test/unit/module_test.rb +0 -68
  113. data/test/unit/multimodel_test.rb +0 -38
  114. data/test/unit/naming_inheritance_test.rb +0 -94
  115. data/test/unit/naming_test.rb +0 -103
  116. data/test/unit/proxy_test.rb +0 -98
  117. data/test/unit/response_aggregations_test.rb +0 -46
  118. data/test/unit/response_base_test.rb +0 -40
  119. data/test/unit/response_pagination_kaminari_test.rb +0 -433
  120. data/test/unit/response_pagination_will_paginate_test.rb +0 -398
  121. data/test/unit/response_records_test.rb +0 -91
  122. data/test/unit/response_result_test.rb +0 -90
  123. data/test/unit/response_results_test.rb +0 -31
  124. data/test/unit/response_test.rb +0 -104
  125. data/test/unit/searching_search_request_test.rb +0 -78
  126. data/test/unit/searching_test.rb +0 -41
  127. data/test/unit/serializing_test.rb +0 -17
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ describe Elasticsearch::Model::Serializing do
4
+
5
+ before(:all) do
6
+ class ::DummySearchingModel
7
+ extend Elasticsearch::Model::Searching::ClassMethods
8
+ def self.index_name; 'foo'; end
9
+ def self.document_type; 'bar'; end
10
+ end
11
+ end
12
+
13
+ after(:all) do
14
+ remove_classes(DummySearchingModel)
15
+ end
16
+
17
+ before do
18
+ allow(DummySearchingModel).to receive(:client).and_return(client)
19
+ end
20
+
21
+ let(:client) do
22
+ double('client')
23
+ end
24
+
25
+ describe '#initialize' do
26
+
27
+ context 'when the search definition is a simple query' do
28
+
29
+ before do
30
+ expect(client).to receive(:search).with(index: 'foo', type: 'bar', q: 'foo').and_return({})
31
+ end
32
+
33
+ let(:search) do
34
+ Elasticsearch::Model::Searching::SearchRequest.new(DummySearchingModel, 'foo')
35
+ end
36
+
37
+ it 'passes the query to the client' do
38
+ expect(search.execute!).to eq({})
39
+ end
40
+ end
41
+
42
+ context 'when the search definition is a hash' do
43
+
44
+ before do
45
+ expect(client).to receive(:search).with(index: 'foo', type: 'bar', body: { foo: 'bar' }).and_return({})
46
+ end
47
+
48
+ let(:search) do
49
+ Elasticsearch::Model::Searching::SearchRequest.new(DummySearchingModel, foo: 'bar')
50
+ end
51
+
52
+ it 'passes the hash to the client' do
53
+ expect(search.execute!).to eq({})
54
+ end
55
+ end
56
+
57
+ context 'when the search definition is a json string' do
58
+
59
+ before do
60
+ expect(client).to receive(:search).with(index: 'foo', type: 'bar', body: '{"foo":"bar"}').and_return({})
61
+ end
62
+
63
+ let(:search) do
64
+ Elasticsearch::Model::Searching::SearchRequest.new(DummySearchingModel, '{"foo":"bar"}')
65
+ end
66
+
67
+ it 'passes the json string to the client' do
68
+ expect(search.execute!).to eq({})
69
+ end
70
+ end
71
+
72
+ context 'when the search definition is a custom object' do
73
+
74
+ before(:all) do
75
+ class MySpecialQueryBuilder
76
+ def to_hash; {foo: 'bar'}; end
77
+ end
78
+ end
79
+
80
+ after(:all) do
81
+ Object.send(:remove_const, :MySpecialQueryBuilder) if defined?(MySpecialQueryBuilder)
82
+ end
83
+
84
+ before do
85
+ expect(client).to receive(:search).with(index: 'foo', type: 'bar', body: {foo: 'bar'}).and_return({})
86
+ end
87
+
88
+ let(:search) do
89
+ Elasticsearch::Model::Searching::SearchRequest.new(DummySearchingModel, MySpecialQueryBuilder.new)
90
+ end
91
+
92
+ it 'passes the query builder to the client and calls #to_hash on it' do
93
+ expect(search.execute!).to eq({})
94
+ end
95
+ end
96
+
97
+ context 'when extra options are specified' do
98
+
99
+ before do
100
+ expect(client).to receive(:search).with(index: 'foo', type: 'bar', q: 'foo', size: 15).and_return({})
101
+ end
102
+
103
+ let(:search) do
104
+ Elasticsearch::Model::Searching::SearchRequest.new(DummySearchingModel, 'foo', size: 15)
105
+ end
106
+
107
+ it 'passes the extra options to the client as part of the request' do
108
+ expect(search.execute!).to eq({})
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe Elasticsearch::Model::Searching::ClassMethods do
4
+
5
+ before(:all) do
6
+ class ::DummySearchingModel
7
+ extend Elasticsearch::Model::Searching::ClassMethods
8
+
9
+ def self.index_name; 'foo'; end
10
+ def self.document_type; 'bar'; end
11
+ end
12
+ end
13
+
14
+ after(:all) do
15
+ remove_classes(DummySearchingModel)
16
+ end
17
+
18
+ it 'has the search method' do
19
+ expect(DummySearchingModel).to respond_to(:search)
20
+ end
21
+
22
+ describe '#search' do
23
+
24
+ let(:response) do
25
+ double('search', execute!: { 'hits' => {'hits' => [ {'_id' => 2 }, {'_id' => 1 } ]} })
26
+ end
27
+
28
+ before do
29
+ expect(Elasticsearch::Model::Searching::SearchRequest).to receive(:new).with(DummySearchingModel, 'foo', { default_operator: 'AND' }).and_return(response)
30
+ end
31
+
32
+ it 'creates a search object' do
33
+ expect(DummySearchingModel.search('foo', default_operator: 'AND')).to be_a(Elasticsearch::Model::Response::Response)
34
+ end
35
+ end
36
+
37
+ describe 'lazy execution' do
38
+
39
+ let(:response) do
40
+ double('search').tap do |r|
41
+ expect(r).to receive(:execute!).never
42
+ end
43
+ end
44
+
45
+ it 'does not execute the search until the results are accessed' do
46
+ DummySearchingModel.search('foo')
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Elasticsearch::Model::Serializing do
4
+
5
+ before(:all) do
6
+ class DummyClass
7
+ include Elasticsearch::Model::Serializing::InstanceMethods
8
+
9
+ def as_json(options={})
10
+ 'HASH'
11
+ end
12
+ end
13
+ end
14
+
15
+ after(:all) do
16
+ remove_classes(DummyClass)
17
+ end
18
+
19
+ it 'delegates to #as_json by default' do
20
+ expect(DummyClass.new.as_indexed_json).to eq('HASH')
21
+ end
22
+ end
@@ -0,0 +1,161 @@
1
+ require 'pry-nav'
2
+ require 'kaminari'
3
+ require 'kaminari/version'
4
+ require 'will_paginate'
5
+ require 'will_paginate/collection'
6
+ require 'elasticsearch/model'
7
+ require 'hashie/version'
8
+ require 'active_model'
9
+ require 'mongoid'
10
+ require 'yaml'
11
+ require 'active_record'
12
+
13
+ unless defined?(ELASTICSEARCH_URL)
14
+ ELASTICSEARCH_URL = ENV['ELASTICSEARCH_URL'] || "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9200)}"
15
+ end
16
+
17
+ RSpec.configure do |config|
18
+ config.formatter = 'documentation'
19
+ config.color = true
20
+
21
+ config.before(:suite) do
22
+ require 'ansi'
23
+ tracer = ::Logger.new(STDERR)
24
+ tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" }
25
+ Elasticsearch::Model.client = Elasticsearch::Client.new host: ELASTICSEARCH_URL,
26
+ tracer: (ENV['QUIET'] ? nil : tracer)
27
+
28
+ unless ActiveRecord::Base.connected?
29
+ ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" )
30
+ end
31
+ require 'support/app'
32
+
33
+ if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5'
34
+ ::ActiveRecord::Base.raise_in_transactional_callbacks = true
35
+ end
36
+ end
37
+
38
+ config.after(:all) do
39
+ drop_all_tables!
40
+ delete_all_indices!
41
+ end
42
+ end
43
+
44
+ # Is the ActiveRecord version at least 4.0?
45
+ #
46
+ # @return [ true, false ] Whether the ActiveRecord version is at least 4.0.
47
+ #
48
+ # @since 6.0.1
49
+ def active_record_at_least_4?
50
+ defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4
51
+ end
52
+
53
+ # Delete all documents from the indices of the provided list of models.
54
+ #
55
+ # @param [ Array<ActiveRecord::Base> ] models The list of models.
56
+ #
57
+ # @return [ true ]
58
+ #
59
+ # @since 6.0.1
60
+ def clear_indices(*models)
61
+ models.each do |model|
62
+ begin; Elasticsearch::Model.client.delete_by_query(index: model.index_name, q: '*'); rescue; end
63
+ end and true
64
+ end
65
+
66
+ # Delete all documents from the tables of the provided list of models.
67
+ #
68
+ # @param [ Array<ActiveRecord::Base> ] models The list of models.
69
+ #
70
+ # @return [ true ]
71
+ #
72
+ # @since 6.0.1
73
+ def clear_tables(*models)
74
+ begin; models.map(&:delete_all); rescue; end and true
75
+ end
76
+
77
+ # Drop all tables of models registered as subclasses of ActiveRecord::Base.
78
+ #
79
+ # @return [ true ]
80
+ #
81
+ # @since 6.0.1
82
+ def drop_all_tables!
83
+ ActiveRecord::Base.descendants.each do |model|
84
+ begin
85
+ ActiveRecord::Schema.define do
86
+ drop_table model
87
+ end if model.table_exists?
88
+ rescue
89
+ end
90
+ end and true
91
+ end
92
+
93
+ # Drop all indices of models registered as subclasses of ActiveRecord::Base.
94
+ #
95
+ # @return [ true ]
96
+ #
97
+ # @since 6.0.1
98
+ def delete_all_indices!
99
+ client = Elasticsearch::Model.client
100
+ ActiveRecord::Base.descendants.each do |model|
101
+ begin
102
+ client.indices.delete(index: model.index_name) if model.__elasticsearch__.index_exists?
103
+ rescue
104
+ end
105
+ end and true
106
+ end
107
+
108
+ # Remove all classes.
109
+ #
110
+ # @param [ Array<Class> ] classes The list of classes to remove.
111
+ #
112
+ # @return [ true ]
113
+ #
114
+ # @since 6.0.1
115
+ def remove_classes(*classes)
116
+ classes.each do |_class|
117
+ Object.send(:remove_const, _class.name.to_sym) if defined?(_class)
118
+ end and true
119
+ end
120
+
121
+ # Determine whether the tests with Mongoid should be run.
122
+ # Depends on whether MongoDB is running on the default host and port, `localhost:27017`.
123
+ #
124
+ # @return [ true, false ]
125
+ #
126
+ # @since 6.0.1
127
+ def test_mongoid?
128
+ $mongoid_available ||= begin
129
+ require 'mongoid'
130
+ if defined?(Mongo) # older versions of Mongoid use the driver, Moped
131
+ client = Mongo::Client.new(['localhost:27017'])
132
+ Timeout.timeout(1) do
133
+ client.database.command(ping: 1) && true
134
+ end
135
+ end and true
136
+ rescue Timeout::Error, LoadError, Mongo::Error => e
137
+ client.close
138
+ $stderr.puts("MongoDB not installed or running: #{e}")
139
+ end
140
+ end
141
+
142
+ # Connect Mongoid and set up its Logger if Mongoid tests should be run.
143
+ #
144
+ # @since 6.0.1
145
+ def connect_mongoid(source)
146
+ if test_mongoid?
147
+ $stderr.puts "Mongoid #{Mongoid::VERSION}", '-'*80
148
+
149
+ if !ENV['QUIET'] == 'true'
150
+ logger = ::Logger.new($stderr)
151
+ logger.formatter = lambda { |s, d, p, m| " #{m.ansi(:faint, :cyan)}\n" }
152
+ logger.level = ::Logger::DEBUG
153
+ Mongoid.logger = logger
154
+ Mongo::Logger.logger = logger
155
+ else
156
+ Mongo::Logger.logger.level = ::Logger::WARN
157
+ end
158
+
159
+ Mongoid.connect_to(source)
160
+ end
161
+ end
@@ -0,0 +1,33 @@
1
+ class Answer < ActiveRecord::Base
2
+ include Elasticsearch::Model
3
+
4
+ belongs_to :question
5
+
6
+ JOIN_TYPE = 'answer'.freeze
7
+
8
+ index_name 'questions_and_answers'.freeze
9
+ document_type 'doc'.freeze
10
+
11
+ before_create :randomize_id
12
+
13
+ def randomize_id
14
+ begin
15
+ self.id = SecureRandom.random_number(1_000_000)
16
+ end while Answer.where(id: self.id).exists?
17
+ end
18
+
19
+ mapping do
20
+ indexes :text
21
+ indexes :author
22
+ end
23
+
24
+ def as_indexed_json(options={})
25
+ # This line is necessary for differences between ActiveModel::Serializers::JSON#as_json versions
26
+ json = as_json(options)[JOIN_TYPE] || as_json(options)
27
+ json.merge(join_field: { name: JOIN_TYPE, parent: question_id })
28
+ end
29
+
30
+ after_commit lambda { __elasticsearch__.index_document(routing: (question_id || 1)) }, on: :create
31
+ after_commit lambda { __elasticsearch__.update_document(routing: (question_id || 1)) }, on: :update
32
+ after_commit lambda {__elasticsearch__.delete_document(routing: (question_id || 1)) }, on: :destroy
33
+ end
@@ -0,0 +1,22 @@
1
+ class ::Article < ActiveRecord::Base
2
+ include Elasticsearch::Model
3
+ include Elasticsearch::Model::Callbacks
4
+
5
+ document_type 'article'
6
+
7
+ settings index: {number_of_shards: 1, number_of_replicas: 0} do
8
+ mapping do
9
+ indexes :title, type: 'text', analyzer: 'snowball'
10
+ indexes :body, type: 'text'
11
+ indexes :clicks, type: 'integer'
12
+ indexes :created_at, type: 'date'
13
+ end
14
+ end
15
+
16
+ def as_indexed_json(options = {})
17
+ attributes
18
+ .symbolize_keys
19
+ .slice(:title, :body, :clicks, :created_at)
20
+ .merge(suggest_title: title)
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ class ::ArticleForPagination < ActiveRecord::Base
2
+ include Elasticsearch::Model
3
+
4
+ scope :published, -> { where(published: true) }
5
+
6
+ settings index: { number_of_shards: 1, number_of_replicas: 0 } do
7
+ mapping do
8
+ indexes :title, type: 'text', analyzer: 'snowball'
9
+ indexes :created_at, type: 'date'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ class ::ArticleWithCustomSerialization < ActiveRecord::Base
2
+ include Elasticsearch::Model
3
+ include Elasticsearch::Model::Callbacks
4
+
5
+ mapping do
6
+ indexes :title
7
+ end
8
+
9
+ def as_indexed_json(options={})
10
+ # as_json(options.merge root: false).slice('title')
11
+ { title: self.title }
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ class ::ArticleWithDynamicIndexName < ActiveRecord::Base
2
+ include Elasticsearch::Model
3
+ include Elasticsearch::Model::Callbacks
4
+
5
+ def self.counter=(value)
6
+ @counter = 0
7
+ end
8
+
9
+ def self.counter
10
+ (@counter ||= 0) && @counter += 1
11
+ end
12
+
13
+ mapping { indexes :title }
14
+ index_name { "articles-#{counter}" }
15
+ end
@@ -0,0 +1,9 @@
1
+ class Author < ActiveRecord::Base
2
+ has_many :authorships
3
+
4
+ after_update { self.authorships.each(&:touch) }
5
+
6
+ def full_name
7
+ [first_name, last_name].compact.join(' ')
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ class Authorship < ActiveRecord::Base
2
+ belongs_to :author
3
+ belongs_to :post, touch: true
4
+ end
@@ -0,0 +1,3 @@
1
+ class Category < ActiveRecord::Base
2
+ has_and_belongs_to_many :posts
3
+ end
@@ -0,0 +1,3 @@
1
+ class Comment < ActiveRecord::Base
2
+ belongs_to :post, touch: true
3
+ end
@@ -0,0 +1,11 @@
1
+ class Episode < ActiveRecord::Base
2
+ include Elasticsearch::Model
3
+ include Elasticsearch::Model::Callbacks
4
+
5
+ settings index: {number_of_shards: 1, number_of_replicas: 0} do
6
+ mapping do
7
+ indexes :name, type: 'text', analyzer: 'snowball'
8
+ indexes :created_at, type: 'date'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ class Image
2
+ include Mongoid::Document
3
+ include Elasticsearch::Model
4
+ include Elasticsearch::Model::Callbacks
5
+
6
+ field :name, type: String
7
+ attr_accessible :name if respond_to? :attr_accessible
8
+
9
+ settings index: {number_of_shards: 1, number_of_replicas: 0} do
10
+ mapping do
11
+ indexes :name, type: 'text', analyzer: 'snowball'
12
+ indexes :created_at, type: 'date'
13
+ end
14
+ end
15
+
16
+ def as_indexed_json(options={})
17
+ as_json(except: [:_id])
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ class ImportArticle < ActiveRecord::Base
2
+ include Elasticsearch::Model
3
+
4
+ scope :popular, -> { where('views >= 5') }
5
+
6
+ mapping do
7
+ indexes :title, type: 'text'
8
+ indexes :views, type: 'integer'
9
+ indexes :numeric, type: 'integer'
10
+ indexes :created_at, type: 'date'
11
+ end
12
+ end
@@ -0,0 +1,21 @@
1
+ class ::MongoidArticle
2
+ include Mongoid::Document
3
+ include Elasticsearch::Model
4
+ include Elasticsearch::Model::Callbacks
5
+
6
+ field :id, type: String
7
+ field :title, type: String
8
+ field :views, type: Integer
9
+ attr_accessible :title if respond_to? :attr_accessible
10
+
11
+ settings index: { number_of_shards: 1, number_of_replicas: 0 } do
12
+ mapping do
13
+ indexes :title, type: 'text', analyzer: 'snowball'
14
+ indexes :created_at, type: 'date'
15
+ end
16
+ end
17
+
18
+ def as_indexed_json(options={})
19
+ as_json(except: [:id, :_id])
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ module MyNamespace
2
+ class Book < ActiveRecord::Base
3
+ include Elasticsearch::Model
4
+ include Elasticsearch::Model::Callbacks
5
+
6
+ document_type 'book'
7
+
8
+ mapping { indexes :title }
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ module ParentChildSearchable
2
+ INDEX_NAME = 'questions_and_answers'.freeze
3
+ JOIN = 'join'.freeze
4
+
5
+ def create_index!(options={})
6
+ client = Question.__elasticsearch__.client
7
+ client.indices.delete index: INDEX_NAME rescue nil if options[:force]
8
+
9
+ settings = Question.settings.to_hash.merge Answer.settings.to_hash
10
+ mapping_properties = { join_field: { type: JOIN,
11
+ relations: { Question::JOIN_TYPE => Answer::JOIN_TYPE } } }
12
+
13
+ merged_properties = mapping_properties.merge(Question.mappings.to_hash[:doc][:properties]).merge(
14
+ Answer.mappings.to_hash[:doc][:properties])
15
+ mappings = { doc: { properties: merged_properties }}
16
+
17
+ client.indices.create index: INDEX_NAME,
18
+ body: {
19
+ settings: settings.to_hash,
20
+ mappings: mappings }
21
+ end
22
+
23
+ extend self
24
+ end
@@ -0,0 +1,14 @@
1
+ class Post < ActiveRecord::Base
2
+ include Searchable
3
+
4
+ has_and_belongs_to_many :categories, after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ],
5
+ after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ]
6
+ has_many :authorships
7
+ has_many :authors, through: :authorships,
8
+ after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ],
9
+ after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ]
10
+ has_many :comments, after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ],
11
+ after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ]
12
+
13
+ after_touch() { __elasticsearch__.index_document }
14
+ end
@@ -0,0 +1,27 @@
1
+ class Question < ActiveRecord::Base
2
+ include Elasticsearch::Model
3
+
4
+ has_many :answers, dependent: :destroy
5
+
6
+ JOIN_TYPE = 'question'.freeze
7
+ JOIN_METADATA = { join_field: JOIN_TYPE}.freeze
8
+
9
+ index_name 'questions_and_answers'.freeze
10
+ document_type 'doc'.freeze
11
+
12
+ mapping do
13
+ indexes :title
14
+ indexes :text
15
+ indexes :author
16
+ end
17
+
18
+ def as_indexed_json(options={})
19
+ # This line is necessary for differences between ActiveModel::Serializers::JSON#as_json versions
20
+ json = as_json(options)[JOIN_TYPE] || as_json(options)
21
+ json.merge(JOIN_METADATA)
22
+ end
23
+
24
+ after_commit lambda { __elasticsearch__.index_document }, on: :create
25
+ after_commit lambda { __elasticsearch__.update_document }, on: :update
26
+ after_commit lambda { __elasticsearch__.delete_document }, on: :destroy
27
+ end
@@ -0,0 +1,48 @@
1
+ module Searchable
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ include Elasticsearch::Model
6
+ include Elasticsearch::Model::Callbacks
7
+
8
+ # Set up the mapping
9
+ #
10
+ settings index: { number_of_shards: 1, number_of_replicas: 0 } do
11
+ mapping do
12
+ indexes :title, analyzer: 'snowball'
13
+ indexes :created_at, type: 'date'
14
+
15
+ indexes :authors do
16
+ indexes :first_name
17
+ indexes :last_name
18
+ indexes :full_name, type: 'text' do
19
+ indexes :raw, type: 'keyword'
20
+ end
21
+ end
22
+
23
+ indexes :categories, type: 'keyword'
24
+
25
+ indexes :comments, type: 'nested' do
26
+ indexes :text
27
+ indexes :author
28
+ end
29
+ end
30
+ end
31
+
32
+ # Customize the JSON serialization for Elasticsearch
33
+ #
34
+ def as_indexed_json(options={})
35
+ {
36
+ title: title,
37
+ text: text,
38
+ categories: categories.map(&:title),
39
+ authors: authors.as_json(methods: [:full_name], only: [:full_name, :first_name, :last_name]),
40
+ comments: comments.as_json(only: [:text, :author])
41
+ }
42
+ end
43
+
44
+ # Update document in the index after touch
45
+ #
46
+ after_touch() { __elasticsearch__.index_document }
47
+ end
48
+ end
@@ -0,0 +1,11 @@
1
+ class Series < ActiveRecord::Base
2
+ include Elasticsearch::Model
3
+ include Elasticsearch::Model::Callbacks
4
+
5
+ settings index: {number_of_shards: 1, number_of_replicas: 0} do
6
+ mapping do
7
+ indexes :name, type: 'text', analyzer: 'snowball'
8
+ indexes :created_at, type: 'date'
9
+ end
10
+ end
11
+ end