elasticsearch_record 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +74 -0
  6. data/README.md +216 -0
  7. data/Rakefile +8 -0
  8. data/docs/CHANGELOG.md +44 -0
  9. data/docs/CODE_OF_CONDUCT.md +84 -0
  10. data/docs/LICENSE.txt +21 -0
  11. data/lib/active_record/connection_adapters/elasticsearch/column.rb +32 -0
  12. data/lib/active_record/connection_adapters/elasticsearch/database_statements.rb +149 -0
  13. data/lib/active_record/connection_adapters/elasticsearch/quoting.rb +38 -0
  14. data/lib/active_record/connection_adapters/elasticsearch/schema_statements.rb +134 -0
  15. data/lib/active_record/connection_adapters/elasticsearch/type/format_string.rb +28 -0
  16. data/lib/active_record/connection_adapters/elasticsearch/type/multicast_value.rb +52 -0
  17. data/lib/active_record/connection_adapters/elasticsearch/type/object.rb +44 -0
  18. data/lib/active_record/connection_adapters/elasticsearch/type/range.rb +42 -0
  19. data/lib/active_record/connection_adapters/elasticsearch/type.rb +16 -0
  20. data/lib/active_record/connection_adapters/elasticsearch_adapter.rb +197 -0
  21. data/lib/arel/collectors/elasticsearch_query.rb +112 -0
  22. data/lib/arel/nodes/select_agg.rb +22 -0
  23. data/lib/arel/nodes/select_configure.rb +9 -0
  24. data/lib/arel/nodes/select_kind.rb +9 -0
  25. data/lib/arel/nodes/select_query.rb +20 -0
  26. data/lib/arel/visitors/elasticsearch.rb +589 -0
  27. data/lib/elasticsearch_record/base.rb +14 -0
  28. data/lib/elasticsearch_record/core.rb +59 -0
  29. data/lib/elasticsearch_record/extensions/relation.rb +15 -0
  30. data/lib/elasticsearch_record/gem_version.rb +17 -0
  31. data/lib/elasticsearch_record/instrumentation/controller_runtime.rb +39 -0
  32. data/lib/elasticsearch_record/instrumentation/log_subscriber.rb +70 -0
  33. data/lib/elasticsearch_record/instrumentation/railtie.rb +16 -0
  34. data/lib/elasticsearch_record/instrumentation.rb +17 -0
  35. data/lib/elasticsearch_record/model_schema.rb +43 -0
  36. data/lib/elasticsearch_record/patches/active_record/relation_merger_patch.rb +85 -0
  37. data/lib/elasticsearch_record/patches/arel/select_core_patch.rb +64 -0
  38. data/lib/elasticsearch_record/patches/arel/select_manager_patch.rb +91 -0
  39. data/lib/elasticsearch_record/patches/arel/select_statement_patch.rb +41 -0
  40. data/lib/elasticsearch_record/patches/arel/update_manager_patch.rb +46 -0
  41. data/lib/elasticsearch_record/patches/arel/update_statement_patch.rb +60 -0
  42. data/lib/elasticsearch_record/persistence.rb +80 -0
  43. data/lib/elasticsearch_record/query.rb +129 -0
  44. data/lib/elasticsearch_record/querying.rb +90 -0
  45. data/lib/elasticsearch_record/relation/calculation_methods.rb +155 -0
  46. data/lib/elasticsearch_record/relation/core_methods.rb +64 -0
  47. data/lib/elasticsearch_record/relation/query_clause.rb +43 -0
  48. data/lib/elasticsearch_record/relation/query_clause_tree.rb +94 -0
  49. data/lib/elasticsearch_record/relation/query_methods.rb +276 -0
  50. data/lib/elasticsearch_record/relation/result_methods.rb +222 -0
  51. data/lib/elasticsearch_record/relation/value_methods.rb +54 -0
  52. data/lib/elasticsearch_record/result.rb +236 -0
  53. data/lib/elasticsearch_record/statement_cache.rb +87 -0
  54. data/lib/elasticsearch_record/version.rb +10 -0
  55. data/lib/elasticsearch_record.rb +60 -0
  56. data/sig/elasticsearch_record.rbs +4 -0
  57. metadata +175 -0
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticsearchRecord
4
+ class Result
5
+ include Enumerable
6
+
7
+ # creates an empty response
8
+ # @return [ElasticsearchRecord::Result (frozen)]
9
+ def self.empty
10
+ new(nil).freeze
11
+ end
12
+
13
+ attr_reader :response, :columns, :column_types
14
+
15
+ # initializes a new result object
16
+ # @param [Elasticsearch::API::Response, Object, nil] response
17
+ # @param [Array] columns
18
+ # @param [Hash] column_types
19
+ def initialize(response, columns = [], column_types = {})
20
+ # contains either the response or creates a empty hash (if nil)
21
+ @response = response.presence || {}
22
+
23
+ # used to build computed_results
24
+ @columns = columns
25
+
26
+ # used to cast values
27
+ @column_types = column_types
28
+ end
29
+
30
+ # returns the response duration time
31
+ # @return [Integer]
32
+ def took
33
+ response['took']
34
+ end
35
+
36
+ # returns the response total value.
37
+ # either chops the +total+ value directly from response, from hits or aggregations.
38
+ # @return [Integer]
39
+ def total
40
+ # chop total only
41
+ @total ||= _chop_total
42
+ end
43
+
44
+ # returns the response RAW hits hash.
45
+ # PLEASE NOTE: Does not return the nested hits (+response['hits']['hits']+) array!
46
+ # @return [ActiveSupport::HashWithIndifferentAccess, Hash]
47
+ def hits
48
+ response.key?('hits') ? response['hits'].with_indifferent_access : {}
49
+ end
50
+
51
+ # Returns the RAW values from the hits - aka. +rows+.
52
+ # PLEASE NOTE: The array will only contain the RAW data from each +_source+ (meta info like '_score' is not included)
53
+ # The +rows+ alias use used by the ActiveRecord ConnectionAdapters and must not be removed!
54
+ # @return [Array]
55
+ def results
56
+ return [] unless response['hits']
57
+
58
+ response['hits']['hits'].map { |result| result['_source'] }
59
+ end
60
+
61
+ alias_method :rows, :results
62
+
63
+ # returns the response RAW aggregations hash.
64
+ # @return [ActiveSupport::HashWithIndifferentAccess, Hash]
65
+ def aggregations
66
+ response.key?('aggregations') ? response['aggregations'].with_indifferent_access : {}
67
+ end
68
+
69
+ # Returns true if this result set includes the column named +name+.
70
+ # used by ActiveRecord
71
+ def includes_column?(name)
72
+ @columns&.include?(name)
73
+ end
74
+
75
+ # Returns the number of elements in the response array.
76
+ # Either uses the +hits+ length or the +responses+ length _(msearch)_.
77
+ # @return [Integer]
78
+ def length
79
+ if response.key?('hits')
80
+ response['hits']['hits'].length
81
+ elsif response.key?('responses')
82
+ # used by +msearch+
83
+ response['responses'].length
84
+ else
85
+ 0
86
+ end
87
+ end
88
+
89
+ # Calls the given block once for each element in row collection, passing
90
+ # row as parameter.
91
+ #
92
+ # Returns an +Enumerator+ if no block is given.
93
+ def each(&block)
94
+ if block_given?
95
+ computed_results.each(&block)
96
+ else
97
+ computed_results.to_enum { @computed_results.size }
98
+ end
99
+ end
100
+
101
+ # Returns true if there are no records, otherwise false.
102
+ def empty?
103
+ length == 0
104
+ end
105
+
106
+ # Returns an array of hashes representing each row record.
107
+ def to_ary
108
+ computed_results
109
+ end
110
+
111
+ alias :to_a :to_ary
112
+
113
+ def [](idx)
114
+ computed_results[idx]
115
+ end
116
+
117
+ # Returns the last record from the rows collection.
118
+ def last(n = nil)
119
+ n ? computed_results.last(n) : computed_results.last
120
+ end
121
+
122
+ # used by ActiveRecord
123
+ def result # :nodoc:
124
+ self
125
+ end
126
+
127
+ # used by ActiveRecord
128
+ def cancel # :nodoc:
129
+ self
130
+ end
131
+
132
+ # used by ActiveRecord
133
+ def cast_values(type_overrides = {})
134
+ # :nodoc:
135
+ if columns.one?
136
+ # Separated to avoid allocating an array per row
137
+ key = columns.first
138
+
139
+ type = if type_overrides.is_a?(Array)
140
+ type_overrides.first
141
+ else
142
+ column_type(columns.first, type_overrides)
143
+ end
144
+
145
+ computed_results.map do |result|
146
+ type.deserialize(result[key])
147
+ end
148
+ else
149
+ types = if type_overrides.is_a?(Array)
150
+ type_overrides
151
+ else
152
+ columns.map { |name| column_type(name, type_overrides) }
153
+ end
154
+
155
+ size = types.size
156
+
157
+ computed_results.map do |result|
158
+ Array.new(size) { |i|
159
+ key = columns[i]
160
+ types[i].deserialize(result[key])
161
+ }
162
+ end
163
+ end
164
+ end
165
+
166
+ private
167
+
168
+ # used by ActiveRecord
169
+ def column_type(name, type_overrides = {})
170
+ type_overrides.fetch(name, Type.default_value)
171
+ end
172
+
173
+ # chops total value from response
174
+ # @return [Integer]
175
+ def _chop_total
176
+ return self.response['total'] if self.response.key?('total')
177
+ return self.response['hits']['total']['value'] if self.response.key?('hits')
178
+ return self.response['aggregations'].count if self.response.key?('aggregations')
179
+
180
+ 0
181
+ end
182
+
183
+ # used for +msearch+ results
184
+ # @return [Array]
185
+ def _results_for_responses
186
+ response['responses'].map { |response| self.class.new(response, self.columns, self.column_types) }
187
+ end
188
+
189
+ # used for +search+ results
190
+ # @return [Array]
191
+ def _results_for_hits
192
+ # PLEASE NOTE: the 'hits' response has multiple nodes: BASE nodes & the +_source+ node.
193
+ # The real data is within the source node, but we also want the BASE nodes for possible score & type check
194
+ base_fields = ActiveRecord::ConnectionAdapters::ElasticsearchAdapter.base_structure_keys
195
+
196
+ # check for provided columns
197
+ if @columns.present?
198
+ # We freeze the strings to prevent them getting duped when
199
+ # used as keys in ActiveRecord::Base's @attributes hash.
200
+ # ALSO IMPORTANT: remove base_fields from possible provided columns
201
+ columns = @columns ? (@columns - base_fields).map(&:-@) : []
202
+
203
+ # this is the hashed result array
204
+ response['hits']['hits'].map { |doc|
205
+ result = doc.slice(*base_fields)
206
+ columns.each do |column|
207
+ result[column] = doc['_source'][column]
208
+ end
209
+
210
+ result
211
+ }
212
+ else
213
+ # if we don't have any columns we just resolve the _source data as it is
214
+ # this might end up in unknown (but mapped) attributes (if they are stored as nil in ES)
215
+
216
+ # this is the hashed result array
217
+ response['hits']['hits'].map { |doc|
218
+ doc.slice(*base_fields).merge(doc['_source'])
219
+ }
220
+ end
221
+ end
222
+
223
+ # builds computed results (used to build ActiveRecord models)
224
+ # @return [Array]
225
+ def computed_results
226
+ @computed_results ||= if response.key?('hits')
227
+ _results_for_hits
228
+ elsif response.key?('responses')
229
+ # used by +msearch+
230
+ _results_for_responses
231
+ else
232
+ []
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticsearchRecord
4
+ class StatementCache < ActiveRecord::StatementCache
5
+
6
+ class PartialQuery < ActiveRecord::StatementCache::PartialQuery # :nodoc:
7
+ def initialize(values)
8
+ @values = values
9
+ # no need to create indexes
10
+ end
11
+
12
+ def sql_for(binds, connection)
13
+ # dup original array
14
+ claims = @values.dup
15
+
16
+ # substitute binds
17
+ claims.each do |claim|
18
+ # action, args = claim
19
+ claim[1] = deep_substitute_binds(claim[1], binds, connection)
20
+ end
21
+
22
+ # build a new query collector
23
+ collector = ::Arel::Collectors::ElasticsearchQuery.new
24
+
25
+ claims.each do |claim|
26
+ collector << claim
27
+ end
28
+
29
+ collector
30
+ end
31
+
32
+ private
33
+
34
+ def deep_substitute_binds(thing, binds, connection)
35
+ if thing.is_a?(ActiveRecord::StatementCache::Substitute)
36
+ value = binds.shift
37
+ if ActiveModel::Attribute === value
38
+ value = value.value_for_database
39
+ end
40
+ connection.quote(value)
41
+ elsif thing.is_a?(Hash)
42
+ thing.transform_values { |val|
43
+ deep_substitute_binds(val, binds, connection)
44
+ }
45
+ elsif thing.is_a?(Array)
46
+ thing.map { |val|
47
+ deep_substitute_binds(val, binds, connection)
48
+ }
49
+ else
50
+ thing
51
+ end
52
+ end
53
+ end
54
+
55
+ class PartialQueryCollector < ActiveRecord::StatementCache::PartialQueryCollector # :nodoc:
56
+ def add_bind(obj)
57
+ super
58
+
59
+ # only add binds, no parts - so we need to remove the previously set part
60
+ @parts.pop
61
+
62
+ self
63
+ end
64
+
65
+ def add_binds(binds, proc_for_binds = nil)
66
+ super
67
+
68
+ # only add binds, no parts - so we need to remove the previously set part
69
+ if binds.size == 1
70
+ @parts.pop
71
+ else
72
+ @parts.pop((binds.size * 2) - 1)
73
+ end
74
+
75
+ self
76
+ end
77
+ end
78
+
79
+ def self.partial_query(values)
80
+ PartialQuery.new(values)
81
+ end
82
+
83
+ def self.partial_query_collector
84
+ PartialQueryCollector.new
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'gem_version'
4
+
5
+ module ElasticsearchRecord
6
+ # Returns the version of the currently loaded Gem as a <tt>Gem::Version</tt>
7
+ def self.version
8
+ gem_version
9
+ end
10
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # VERSION
4
+ require_relative 'elasticsearch_record/version'
5
+
6
+ require 'active_record'
7
+
8
+ # new arel
9
+ require 'arel/collectors/elasticsearch_query'
10
+ require 'arel/nodes/select_agg'
11
+ require 'arel/nodes/select_configure'
12
+ require 'arel/nodes/select_kind'
13
+ require 'arel/nodes/select_query'
14
+ require 'arel/visitors/elasticsearch'
15
+
16
+ # new adapter
17
+ require 'active_record/connection_adapters/elasticsearch_adapter'
18
+
19
+ module ElasticsearchRecord
20
+ extend ActiveSupport::Autoload
21
+
22
+ eager_autoload do
23
+ autoload :Base
24
+ autoload :Core
25
+ autoload :ModelSchema
26
+ autoload :Persistence
27
+ autoload :Querying
28
+ autoload :Query
29
+ autoload :Result
30
+ autoload :StatementCache
31
+ end
32
+
33
+ module Extensions
34
+ extend ActiveSupport::Autoload
35
+
36
+ autoload :Relation
37
+ end
38
+
39
+ module Relation
40
+ extend ActiveSupport::Autoload
41
+
42
+ autoload :CalculationMethods
43
+ autoload :CoreMethods
44
+ autoload :QueryClause
45
+ autoload :QueryClauseTree
46
+ autoload :QueryMethods
47
+ autoload :ResultMethods
48
+ autoload :ValueMethods
49
+ end
50
+ end
51
+
52
+ ActiveSupport.on_load(:active_record) do
53
+ # load patches
54
+ require 'elasticsearch_record/patches/active_record/relation_merger_patch'
55
+ require 'elasticsearch_record/patches/arel/select_core_patch'
56
+ require 'elasticsearch_record/patches/arel/select_manager_patch'
57
+ require 'elasticsearch_record/patches/arel/select_statement_patch'
58
+ require 'elasticsearch_record/patches/arel/update_manager_patch'
59
+ require 'elasticsearch_record/patches/arel/update_statement_patch'
60
+ end
@@ -0,0 +1,4 @@
1
+ module ElasticsearchRecord
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,175 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: elasticsearch_record
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Tobias Gonsior
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-10-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 7.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 7.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: elasticsearch
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '8.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '8.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.21'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.21'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
83
+ description: 'ElasticsearchRecord is a ActiveRecord-fork and tries to provide the
84
+ same functionality for Elasticsearch.
85
+
86
+ '
87
+ email:
88
+ - info@ruby-smart.org
89
+ executables: []
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - ".rspec"
94
+ - ".ruby-version"
95
+ - Gemfile
96
+ - Gemfile.lock
97
+ - README.md
98
+ - Rakefile
99
+ - docs/CHANGELOG.md
100
+ - docs/CODE_OF_CONDUCT.md
101
+ - docs/LICENSE.txt
102
+ - lib/active_record/connection_adapters/elasticsearch/column.rb
103
+ - lib/active_record/connection_adapters/elasticsearch/database_statements.rb
104
+ - lib/active_record/connection_adapters/elasticsearch/quoting.rb
105
+ - lib/active_record/connection_adapters/elasticsearch/schema_statements.rb
106
+ - lib/active_record/connection_adapters/elasticsearch/type.rb
107
+ - lib/active_record/connection_adapters/elasticsearch/type/format_string.rb
108
+ - lib/active_record/connection_adapters/elasticsearch/type/multicast_value.rb
109
+ - lib/active_record/connection_adapters/elasticsearch/type/object.rb
110
+ - lib/active_record/connection_adapters/elasticsearch/type/range.rb
111
+ - lib/active_record/connection_adapters/elasticsearch_adapter.rb
112
+ - lib/arel/collectors/elasticsearch_query.rb
113
+ - lib/arel/nodes/select_agg.rb
114
+ - lib/arel/nodes/select_configure.rb
115
+ - lib/arel/nodes/select_kind.rb
116
+ - lib/arel/nodes/select_query.rb
117
+ - lib/arel/visitors/elasticsearch.rb
118
+ - lib/elasticsearch_record.rb
119
+ - lib/elasticsearch_record/base.rb
120
+ - lib/elasticsearch_record/core.rb
121
+ - lib/elasticsearch_record/extensions/relation.rb
122
+ - lib/elasticsearch_record/gem_version.rb
123
+ - lib/elasticsearch_record/instrumentation.rb
124
+ - lib/elasticsearch_record/instrumentation/controller_runtime.rb
125
+ - lib/elasticsearch_record/instrumentation/log_subscriber.rb
126
+ - lib/elasticsearch_record/instrumentation/railtie.rb
127
+ - lib/elasticsearch_record/model_schema.rb
128
+ - lib/elasticsearch_record/patches/active_record/relation_merger_patch.rb
129
+ - lib/elasticsearch_record/patches/arel/select_core_patch.rb
130
+ - lib/elasticsearch_record/patches/arel/select_manager_patch.rb
131
+ - lib/elasticsearch_record/patches/arel/select_statement_patch.rb
132
+ - lib/elasticsearch_record/patches/arel/update_manager_patch.rb
133
+ - lib/elasticsearch_record/patches/arel/update_statement_patch.rb
134
+ - lib/elasticsearch_record/persistence.rb
135
+ - lib/elasticsearch_record/query.rb
136
+ - lib/elasticsearch_record/querying.rb
137
+ - lib/elasticsearch_record/relation/calculation_methods.rb
138
+ - lib/elasticsearch_record/relation/core_methods.rb
139
+ - lib/elasticsearch_record/relation/query_clause.rb
140
+ - lib/elasticsearch_record/relation/query_clause_tree.rb
141
+ - lib/elasticsearch_record/relation/query_methods.rb
142
+ - lib/elasticsearch_record/relation/result_methods.rb
143
+ - lib/elasticsearch_record/relation/value_methods.rb
144
+ - lib/elasticsearch_record/result.rb
145
+ - lib/elasticsearch_record/statement_cache.rb
146
+ - lib/elasticsearch_record/version.rb
147
+ - sig/elasticsearch_record.rbs
148
+ homepage: https://github.com/ruby-smart/elasticsearch_record
149
+ licenses:
150
+ - MIT
151
+ metadata:
152
+ allowed_push_host: https://rubygems.org
153
+ homepage_uri: https://github.com/ruby-smart/elasticsearch_record
154
+ source_code_uri: https://github.com/ruby-smart/elasticsearch_record
155
+ changelog_uri: https://github.com/ruby-smart/elasticsearch_record/blob/main/docs/CHANGELOG.md
156
+ post_install_message:
157
+ rdoc_options: []
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: 2.7.0
165
+ required_rubygems_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - ">="
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ requirements: []
171
+ rubygems_version: 3.3.7
172
+ signing_key:
173
+ specification_version: 4
174
+ summary: ActiveRecord functionality for Elasticsearch indexes & documents.
175
+ test_files: []