elasticsearch-model 5.1.0 → 6.1.0

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 (125) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -0
  3. data/README.md +18 -11
  4. data/Rakefile +27 -36
  5. data/elasticsearch-model.gemspec +5 -5
  6. data/examples/activerecord_custom_analyzer.rb +135 -0
  7. data/examples/activerecord_mapping_completion.rb +3 -18
  8. data/examples/datamapper_article.rb +11 -1
  9. data/gemfiles/3.0.gemfile +6 -1
  10. data/gemfiles/4.0.gemfile +7 -1
  11. data/gemfiles/5.0.gemfile +7 -1
  12. data/lib/elasticsearch/model/adapters/active_record.rb +7 -23
  13. data/lib/elasticsearch/model/adapters/mongoid.rb +10 -3
  14. data/lib/elasticsearch/model/importing.rb +1 -1
  15. data/lib/elasticsearch/model/indexing.rb +6 -4
  16. data/lib/elasticsearch/model/naming.rb +9 -2
  17. data/lib/elasticsearch/model/response/aggregations.rb +1 -1
  18. data/lib/elasticsearch/model/response/base.rb +3 -2
  19. data/lib/elasticsearch/model/response/pagination/kaminari.rb +109 -0
  20. data/lib/elasticsearch/model/response/pagination/will_paginate.rb +95 -0
  21. data/lib/elasticsearch/model/response/pagination.rb +2 -192
  22. data/lib/elasticsearch/model/response/result.rb +1 -1
  23. data/lib/elasticsearch/model/response/suggestions.rb +1 -1
  24. data/lib/elasticsearch/model/response.rb +11 -10
  25. data/lib/elasticsearch/model/version.rb +1 -1
  26. data/lib/elasticsearch/model.rb +15 -8
  27. data/spec/elasticsearch/model/adapter_spec.rb +119 -0
  28. data/spec/elasticsearch/model/adapters/active_record/associations_spec.rb +334 -0
  29. data/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +340 -0
  30. data/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb +18 -0
  31. data/spec/elasticsearch/model/adapters/active_record/import_spec.rb +187 -0
  32. data/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb +110 -0
  33. data/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb +38 -0
  34. data/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb +315 -0
  35. data/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb +75 -0
  36. data/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb +61 -0
  37. data/spec/elasticsearch/model/adapters/active_record_spec.rb +207 -0
  38. data/spec/elasticsearch/model/adapters/default_spec.rb +41 -0
  39. data/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb +267 -0
  40. data/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb +66 -0
  41. data/spec/elasticsearch/model/adapters/mongoid_spec.rb +235 -0
  42. data/spec/elasticsearch/model/adapters/multiple_spec.rb +125 -0
  43. data/spec/elasticsearch/model/callbacks_spec.rb +33 -0
  44. data/spec/elasticsearch/model/client_spec.rb +66 -0
  45. data/spec/elasticsearch/model/hash_wrapper_spec.rb +12 -0
  46. data/spec/elasticsearch/model/importing_spec.rb +214 -0
  47. data/spec/elasticsearch/model/indexing_spec.rb +918 -0
  48. data/spec/elasticsearch/model/module_spec.rb +101 -0
  49. data/spec/elasticsearch/model/multimodel_spec.rb +55 -0
  50. data/spec/elasticsearch/model/naming_inheritance_spec.rb +184 -0
  51. data/spec/elasticsearch/model/naming_spec.rb +186 -0
  52. data/spec/elasticsearch/model/proxy_spec.rb +107 -0
  53. data/spec/elasticsearch/model/response/aggregations_spec.rb +66 -0
  54. data/spec/elasticsearch/model/response/base_spec.rb +90 -0
  55. data/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +410 -0
  56. data/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb +262 -0
  57. data/spec/elasticsearch/model/response/records_spec.rb +118 -0
  58. data/spec/elasticsearch/model/response/response_spec.rb +131 -0
  59. data/spec/elasticsearch/model/response/result_spec.rb +122 -0
  60. data/spec/elasticsearch/model/response/results_spec.rb +56 -0
  61. data/spec/elasticsearch/model/searching_search_request_spec.rb +112 -0
  62. data/spec/elasticsearch/model/searching_spec.rb +49 -0
  63. data/spec/elasticsearch/model/serializing_spec.rb +22 -0
  64. data/spec/spec_helper.rb +161 -0
  65. data/spec/support/app/answer.rb +33 -0
  66. data/spec/support/app/article.rb +22 -0
  67. data/spec/support/app/article_for_pagination.rb +12 -0
  68. data/spec/support/app/article_with_custom_serialization.rb +13 -0
  69. data/spec/support/app/article_with_dynamic_index_name.rb +15 -0
  70. data/spec/support/app/author.rb +9 -0
  71. data/spec/support/app/authorship.rb +4 -0
  72. data/spec/support/app/category.rb +3 -0
  73. data/spec/support/app/comment.rb +3 -0
  74. data/spec/support/app/episode.rb +11 -0
  75. data/spec/support/app/image.rb +19 -0
  76. data/spec/support/app/import_article.rb +12 -0
  77. data/spec/support/app/mongoid_article.rb +21 -0
  78. data/spec/support/app/namespaced_book.rb +10 -0
  79. data/spec/support/app/parent_and_child_searchable.rb +24 -0
  80. data/spec/support/app/post.rb +14 -0
  81. data/spec/support/app/question.rb +27 -0
  82. data/spec/support/app/searchable.rb +48 -0
  83. data/spec/support/app/series.rb +11 -0
  84. data/spec/support/app.rb +21 -0
  85. data/spec/support/model.json +1 -0
  86. metadata +134 -90
  87. data/test/integration/active_record_associations_parent_child_test.rb +0 -147
  88. data/test/integration/active_record_associations_test.rb +0 -339
  89. data/test/integration/active_record_basic_test.rb +0 -251
  90. data/test/integration/active_record_custom_serialization_test.rb +0 -67
  91. data/test/integration/active_record_import_test.rb +0 -115
  92. data/test/integration/active_record_namespaced_model_test.rb +0 -54
  93. data/test/integration/active_record_pagination_test.rb +0 -149
  94. data/test/integration/dynamic_index_name_test.rb +0 -52
  95. data/test/integration/mongoid_basic_test.rb +0 -176
  96. data/test/integration/multiple_models_test.rb +0 -176
  97. data/test/support/model.json +0 -1
  98. data/test/test_helper.rb +0 -92
  99. data/test/unit/adapter_active_record_test.rb +0 -157
  100. data/test/unit/adapter_default_test.rb +0 -41
  101. data/test/unit/adapter_mongoid_test.rb +0 -104
  102. data/test/unit/adapter_multiple_test.rb +0 -106
  103. data/test/unit/adapter_test.rb +0 -69
  104. data/test/unit/callbacks_test.rb +0 -31
  105. data/test/unit/client_test.rb +0 -27
  106. data/test/unit/hash_wrapper_test.rb +0 -13
  107. data/test/unit/importing_test.rb +0 -203
  108. data/test/unit/indexing_test.rb +0 -687
  109. data/test/unit/module_test.rb +0 -68
  110. data/test/unit/multimodel_test.rb +0 -38
  111. data/test/unit/naming_inheritance_test.rb +0 -94
  112. data/test/unit/naming_test.rb +0 -103
  113. data/test/unit/proxy_test.rb +0 -98
  114. data/test/unit/response_aggregations_test.rb +0 -46
  115. data/test/unit/response_base_test.rb +0 -40
  116. data/test/unit/response_pagination_kaminari_test.rb +0 -433
  117. data/test/unit/response_pagination_will_paginate_test.rb +0 -398
  118. data/test/unit/response_records_test.rb +0 -91
  119. data/test/unit/response_result_test.rb +0 -90
  120. data/test/unit/response_results_test.rb +0 -31
  121. data/test/unit/response_test.rb +0 -104
  122. data/test/unit/searching_search_request_test.rb +0 -78
  123. data/test/unit/searching_test.rb +0 -41
  124. data/test/unit/serializing_test.rb +0 -17
  125. /data/{test → spec}/support/model.yml +0 -0
@@ -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