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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticsearchRecord
4
+ module Extensions
5
+ module Relation
6
+ def self.extended(base)
7
+ base.extend ::ElasticsearchRecord::Relation::CoreMethods
8
+ base.extend ::ElasticsearchRecord::Relation::CalculationMethods
9
+ base.extend ::ElasticsearchRecord::Relation::QueryMethods
10
+ base.extend ::ElasticsearchRecord::Relation::ResultMethods
11
+ base.extend ::ElasticsearchRecord::Relation::ValueMethods
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticsearchRecord
4
+ # Returns the version of the currently loaded module as a <tt>Gem::Version</tt>
5
+ def self.gem_version
6
+ Gem::Version.new VERSION::STRING
7
+ end
8
+
9
+ module VERSION
10
+ MAJOR = 1
11
+ MINOR = 0
12
+ TINY = 0
13
+ PRE = nil
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
+ end
17
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/module/attr_internal'
4
+
5
+ module ElasticsearchRecord
6
+ module Instrumentation
7
+ # Hooks into ActionController to display ElasticsearchRecord runtime
8
+ # @see https://github.com/rails/rails/blob/master/activerecord/lib/active_record/railties/controller_runtime.rb
9
+ #
10
+ module ControllerRuntime
11
+ extend ActiveSupport::Concern
12
+
13
+ protected
14
+
15
+ attr_internal :elasticsearch_record_runtime
16
+
17
+ def cleanup_view_runtime
18
+ elasticsearch_rt_before_render = ElasticsearchRecord::Instrumentation::LogSubscriber.reset_runtime
19
+ runtime = super
20
+ elasticsearch_rt_after_render = ElasticsearchRecord::Instrumentation::LogSubscriber.reset_runtime
21
+ self.elasticsearch_record_runtime = elasticsearch_rt_before_render + elasticsearch_rt_after_render
22
+ runtime - elasticsearch_rt_after_render
23
+ end
24
+
25
+ def append_info_to_payload(payload)
26
+ super
27
+ payload[:elasticsearch_record_runtime] = (elasticsearch_record_runtime || 0) + ElasticsearchRecord::Instrumentation::LogSubscriber.reset_runtime
28
+ end
29
+
30
+ module ClassMethods
31
+ def log_process_action(payload)
32
+ messages, elasticsearch_runtime = super, payload[:elasticsearch_record_runtime]
33
+ messages << ("ElasticsearchRecord: %.1fms" % elasticsearch_runtime.to_f) if elasticsearch_runtime
34
+ messages
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticsearchRecord
4
+ module Instrumentation
5
+ # attach to ElasticsearchRecord related events
6
+ class LogSubscriber < ActiveSupport::LogSubscriber
7
+
8
+ IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
9
+
10
+ def self.runtime=(value)
11
+ Thread.current["elasticsearch_record_runtime"] = value
12
+ end
13
+
14
+ def self.runtime
15
+ Thread.current["elasticsearch_record_runtime"] ||= 0
16
+ end
17
+
18
+ def self.reset_runtime
19
+ rt, self.runtime = runtime, 0
20
+ rt
21
+ end
22
+
23
+ # Intercept `search.elasticsearch` events, and display them in the Rails log
24
+ #
25
+ def query(event)
26
+ self.class.runtime += event.duration
27
+ return unless logger.debug?
28
+
29
+ payload = event.payload
30
+ return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
31
+
32
+ name = if payload[:async]
33
+ "ASYNC #{payload[:name]} (#{payload[:lock_wait].round(1)}ms) (execution time #{event.duration.round(1)}ms)"
34
+ else
35
+ "#{payload[:name]} (#{event.duration.round(1)}ms)"
36
+ end
37
+ name = "CACHE #{name}" if payload[:cached]
38
+
39
+ name = "#{name} (took: #{payload[:arguments][:_qt].round(1)}ms)" if payload[:arguments][:_qt]
40
+
41
+ query = payload[:arguments].except(:index, :_qt).inspect.gsub(/:(\w+)=>/, '\1: ').presence || '-'
42
+
43
+ # final coloring
44
+ name = color(name, CYAN, true)
45
+ query = color(query, gate_color(payload[:gate]), true) if colorize_logging
46
+
47
+ debug " #{name} #{query}"
48
+ end
49
+
50
+ private
51
+
52
+ def gate_color(gate)
53
+ case gate
54
+ when 'core.search', 'core.msearch'
55
+ BLUE
56
+ when 'core.delete', 'core.delete_by_query'
57
+ RED
58
+ when 'core.create'
59
+ GREEN
60
+ when 'core.update', 'core.update_by_query'
61
+ YELLOW
62
+ else
63
+ MAGENTA
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ ElasticsearchRecord::Instrumentation::LogSubscriber.attach_to :elasticsearch_record
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticsearchRecord
4
+ module Instrumentation
5
+ class Railtie < ::Rails::Railtie
6
+ initializer "elasticsearch_record.instrumentation" do |_app|
7
+ require 'elasticsearch_record/instrumentation/log_subscriber'
8
+ require 'elasticsearch_record/instrumentation/controller_runtime'
9
+
10
+ ActiveSupport.on_load(:action_controller) do
11
+ include ElasticsearchRecord::Instrumentation::ControllerRuntime
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticsearchRecord
4
+ module Instrumentation
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :ControllerRuntime
8
+ autoload :LogSubscriber
9
+ autoload :Railtie
10
+ end
11
+ end
12
+
13
+ ActiveSupport.on_load(:active_record) do
14
+ # load Instrumentation
15
+ require 'elasticsearch_record/instrumentation/railtie'
16
+ require 'elasticsearch_record/instrumentation/log_subscriber'
17
+ end
@@ -0,0 +1,43 @@
1
+ module ElasticsearchRecord
2
+ module ModelSchema
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :index_base_name, instance_writer: false, default: nil
7
+ end
8
+
9
+ module ClassMethods
10
+ # Guesses the table name, but does not decorate it with prefix and suffix information.
11
+ def undecorated_table_name(model_name)
12
+ # check the 'index_base_name' first, so +table_name_prefix+ & +table_name_suffix+ can still be used
13
+ index_base_name || super(model_name)
14
+ end
15
+
16
+ # returns an array with columns names, that are not virtual (and not a base structure).
17
+ # so this is a array of real document (+_source+) attributes of the index.
18
+ # @return [Array<String>]
19
+ def source_column_names
20
+ @source_column_names ||= columns.reject(&:virtual).map(&:name) - ActiveRecord::ConnectionAdapters::ElasticsearchAdapter.base_structure_keys
21
+ end
22
+
23
+ # returns an array with columns names, that are searchable (also includes nested )
24
+ def searchable_column_names
25
+ @searchable_column_names ||= columns.reject(&:virtual).reduce([]) { |m, column|
26
+ m << column.name
27
+ m += column.fields if column.fields?
28
+ m
29
+ }
30
+ end
31
+
32
+ # clears schema-related instance variables.
33
+ # @see ActiveRecord::ModelSchema::ClassMethods#reload_schema_from_cache
34
+ def reload_schema_from_cache
35
+ # we also need to clear our custom-defined variables
36
+ @source_column_names = nil
37
+ @searchable_column_names = nil
38
+
39
+ super
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record/relation/merger'
4
+
5
+ module ElasticsearchRecord
6
+ module Patches
7
+ module ActiveRecord
8
+ module RelationMergerPatch
9
+ def self.included(base)
10
+ base.send(:prepend, PrependMethods)
11
+ end
12
+
13
+ module PrependMethods
14
+ def merge
15
+ # check for relation mismatch - prevent mash up different relations
16
+ if elasticsearch_relation? && !other_elasticsearch_relation?
17
+ message = "#{relation.class_name}(##{relation.klass.object_id}) expected, "\
18
+ "got #{other.inspect} which is an instance of #{other.class}(##{other.class.object_id})"
19
+ raise ActiveRecord::AssociationTypeMismatch, message
20
+ end
21
+
22
+ # enable & assign elasticsearch merger
23
+ if other_elasticsearch_relation? && !elasticsearch_relation?
24
+ assign_elasticsearch_relation!
25
+ end
26
+
27
+ super
28
+ end
29
+
30
+ private
31
+
32
+ # return true if this is a elasticsearch enabled relation
33
+ def elasticsearch_relation?
34
+ @elasticsearch_relation ||= relation.respond_to?(:kind_value)
35
+ end
36
+
37
+ # return true if the other relation is a elasticsearch enabled relation
38
+ def other_elasticsearch_relation?
39
+ @other_elasticsearch_relation ||= other.respond_to?(:kind_value)
40
+ end
41
+
42
+ def merge_single_values
43
+ super
44
+
45
+ # only for enabled elasticsearch relation
46
+ if elasticsearch_relation?
47
+ relation.kind_value ||= other.kind_value if other.kind_value.present?
48
+ end
49
+ end
50
+
51
+ def merge_multi_values
52
+ super
53
+
54
+ # only for enabled elasticsearch relation
55
+ if elasticsearch_relation?
56
+ relation.configure_value = relation.configure_value.merge(other.configure_value) if other.configure_value.present?
57
+ end
58
+ end
59
+
60
+ def merge_clauses
61
+ super
62
+
63
+ # only for enabled elasticsearch relation
64
+ if elasticsearch_relation?
65
+ query_clause = relation.query_clause.merge(other.query_clause)
66
+ relation.query_clause = query_clause unless query_clause.empty?
67
+
68
+ aggs_clause = relation.aggs_clause.merge(other.aggs_clause)
69
+ relation.aggs_clause = aggs_clause unless aggs_clause.empty?
70
+ end
71
+ end
72
+
73
+ def assign_elasticsearch_relation!
74
+ # sucks, but there is no other solution yet to NOT mess with
75
+ # ActiveRecord::Delegation::DelegateCache#initialize_relation_delegate_cache
76
+ relation.extend ElasticsearchRecord::Extensions::Relation
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ # include once only!
85
+ ::ActiveRecord::Relation::Merger.include(ElasticsearchRecord::Patches::ActiveRecord::RelationMergerPatch) unless ::ActiveRecord::Relation::Merger.included_modules.include?(ElasticsearchRecord::Patches::ActiveRecord::RelationMergerPatch)
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'arel/nodes/select_core'
4
+
5
+ module ElasticsearchRecord
6
+ module Patches
7
+ module Arel
8
+ module SelectCorePatch
9
+ def self.included(base)
10
+ base.send(:prepend, PrependMethods)
11
+ end
12
+
13
+ module PrependMethods
14
+ def initialize_copy(other)
15
+ super
16
+ @kind = @kind.clone if @kind
17
+ @queries = @queries.clone if @queries
18
+ @aggs = @aggs.clone if @aggs
19
+ end
20
+
21
+ def hash
22
+ [
23
+ super, @kind, @queries, @aggs
24
+ ].hash
25
+ end
26
+
27
+ def eql?(other)
28
+ super &&
29
+ self.kind == other.kind &&
30
+ self.queries == other.queries &&
31
+ self.aggs == other.aggs
32
+ end
33
+ end
34
+
35
+ def kind
36
+ @kind
37
+ end
38
+
39
+ def kind=(value)
40
+ @kind = value
41
+ end
42
+
43
+ def queries
44
+ @queries ||= []
45
+ end
46
+
47
+ def queries=(value)
48
+ @queries = value
49
+ end
50
+
51
+ def aggs
52
+ @aggs ||= []
53
+ end
54
+
55
+ def aggs=(value)
56
+ @aggs = value
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ # include once only!
64
+ ::Arel::Nodes::SelectCore.include(ElasticsearchRecord::Patches::Arel::SelectCorePatch) unless ::Arel::Nodes::SelectCore.included_modules.include?(ElasticsearchRecord::Patches::Arel::SelectCorePatch)
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'arel/select_manager'
4
+ require 'arel/nodes/select_configure'
5
+ require 'arel/nodes/select_kind'
6
+ require 'arel/nodes/select_query'
7
+
8
+ module ElasticsearchRecord
9
+ module Patches
10
+ module Arel
11
+ module SelectManagerPatch
12
+ def self.included(base)
13
+ base.send(:prepend, PrependMethods)
14
+ end
15
+
16
+ module PrependMethods
17
+ def compile_update(*args)
18
+ arel = super
19
+ arel.kind(_kind.expr) if _kind.present?
20
+ arel.query(_query.map(&:expr)) unless _query.empty?
21
+ arel.configure(_configure.expr) if _configure.present?
22
+ arel
23
+ end
24
+
25
+ def compile_delete(*args)
26
+ arel = super
27
+ arel.kind(_kind.expr) if _kind.present?
28
+ arel.query(_query.map(&:expr)) unless _query.empty?
29
+ arel.configure(_configure.expr) if _configure.present?
30
+ arel
31
+ end
32
+ end
33
+
34
+ def kind(value)
35
+ if value
36
+ @ctx.kind = ::Arel::Nodes::SelectKind.new(value)
37
+ else
38
+ @ctx.kind = nil
39
+ end
40
+ end
41
+
42
+ def _kind
43
+ @ctx.kind
44
+ end
45
+
46
+ def configure(value)
47
+ if value
48
+ @ast.configure = ::Arel::Nodes::SelectConfigure.new(value)
49
+ else
50
+ @ast.configure = nil
51
+ end
52
+ end
53
+
54
+ def _configure
55
+ @ast.configure
56
+ end
57
+
58
+ def query(value)
59
+ if value.is_a?(Array)
60
+ value.each { |val|
61
+ @ctx.queries << ::Arel::Nodes::SelectQuery.new(val)
62
+ }
63
+ else
64
+ @ctx.queries << ::Arel::Nodes::SelectQuery.new(value)
65
+ end
66
+ end
67
+
68
+ def _query
69
+ @ctx.queries
70
+ end
71
+
72
+ def aggs(value)
73
+ if value.is_a?(Array)
74
+ value.each { |val|
75
+ @ctx.aggs << ::Arel::Nodes::SelectAgg.new(val)
76
+ }
77
+ else
78
+ @ctx.aggs << ::Arel::Nodes::SelectAgg.new(value)
79
+ end
80
+ end
81
+
82
+ def _aggs
83
+ @ctx.aggs
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ # include once only!
91
+ ::Arel::SelectManager.include(ElasticsearchRecord::Patches::Arel::SelectManagerPatch) unless ::Arel::SelectManager.included_modules.include?(ElasticsearchRecord::Patches::Arel::SelectManagerPatch)
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'arel/nodes/select_statement'
4
+
5
+ module ElasticsearchRecord
6
+ module Patches
7
+ module Arel
8
+ module SelectStatementPatch
9
+ def self.included(base)
10
+ base.send(:prepend, PrependMethods)
11
+ end
12
+
13
+ module PrependMethods
14
+ def initialize_copy(other)
15
+ super
16
+ @configure = @configure&.deep_dup
17
+ end
18
+
19
+ def hash
20
+ [super, @configure].hash
21
+ end
22
+
23
+ def eql?(other)
24
+ super && configure == other.configure
25
+ end
26
+ end
27
+
28
+ def configure
29
+ @configure
30
+ end
31
+
32
+ def configure=(value)
33
+ @configure = value
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ # include once only!
41
+ ::Arel::Nodes::SelectStatement.include(ElasticsearchRecord::Patches::Arel::SelectStatementPatch) unless ::Arel::Nodes::SelectStatement.included_modules.include?(ElasticsearchRecord::Patches::Arel::SelectStatementPatch)
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'arel/update_manager'
4
+ require 'arel/delete_manager'
5
+
6
+ require 'arel/nodes/select_configure'
7
+ require 'arel/nodes/select_kind'
8
+ require 'arel/nodes/select_query'
9
+
10
+ module ElasticsearchRecord
11
+ module Patches
12
+ module Arel
13
+ module UpdateManagerPatch
14
+ def kind(value)
15
+ if value
16
+ @ast.kind = ::Arel::Nodes::SelectKind.new(value)
17
+ else
18
+ @ast.kind = nil
19
+ end
20
+ end
21
+
22
+ def configure(value)
23
+ if value
24
+ @ast.configure = ::Arel::Nodes::SelectConfigure.new(value)
25
+ else
26
+ @ast.configure = nil
27
+ end
28
+ end
29
+
30
+ def query(value)
31
+ if value.is_a?(Array)
32
+ value.each { |val|
33
+ @ast.queries << ::Arel::Nodes::SelectQuery.new(val)
34
+ }
35
+ else
36
+ @ast.queries << ::Arel::Nodes::SelectQuery.new(value)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ # include once only!
45
+ ::Arel::UpdateManager.include(ElasticsearchRecord::Patches::Arel::UpdateManagerPatch) unless ::Arel::UpdateManager.included_modules.include?(ElasticsearchRecord::Patches::Arel::UpdateManagerPatch)
46
+ ::Arel::DeleteManager.include(ElasticsearchRecord::Patches::Arel::UpdateManagerPatch) unless ::Arel::DeleteManager.included_modules.include?(ElasticsearchRecord::Patches::Arel::UpdateManagerPatch)
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'arel/nodes/update_statement'
4
+ require 'arel/nodes/delete_statement'
5
+
6
+ module ElasticsearchRecord
7
+ module Patches
8
+ module Arel
9
+ module UpdateStatementPatch
10
+ def self.included(base)
11
+ base.send(:prepend, PrependMethods)
12
+ end
13
+
14
+ module PrependMethods
15
+ def initialize_copy(other)
16
+ super
17
+ @configure = @configure&.deep_dup
18
+ @queries = @queries&.clone
19
+ end
20
+
21
+ def hash
22
+ [super, @configure, @queries].hash
23
+ end
24
+
25
+ def eql?(other)
26
+ super && kind == other.kind && configure == other.configure && queries == other.queries
27
+ end
28
+ end
29
+
30
+ def kind
31
+ @kind
32
+ end
33
+
34
+ def kind=(value)
35
+ @kind = value
36
+ end
37
+
38
+ def queries
39
+ @queries ||= []
40
+ end
41
+
42
+ def queries=(value)
43
+ @queries = value
44
+ end
45
+
46
+ def configure
47
+ @configure
48
+ end
49
+
50
+ def configure=(value)
51
+ @configure = value
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ # include once only!
59
+ ::Arel::Nodes::UpdateStatement.include(ElasticsearchRecord::Patches::Arel::UpdateStatementPatch) unless ::Arel::Nodes::UpdateStatement.included_modules.include?(ElasticsearchRecord::Patches::Arel::UpdateStatementPatch)
60
+ ::Arel::Nodes::DeleteStatement.include(ElasticsearchRecord::Patches::Arel::UpdateStatementPatch) unless ::Arel::Nodes::DeleteStatement.included_modules.include?(ElasticsearchRecord::Patches::Arel::UpdateStatementPatch)
@@ -0,0 +1,80 @@
1
+ module ElasticsearchRecord
2
+ module Persistence
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ # insert a new record into the Elasticsearch index
8
+ # NOTICE: We don't want to mess up with the Arel-builder - so we send new data directly to the API
9
+ def _insert_record(values)
10
+ # values is not a "key=>values"-Hash, but a +ActiveModel::Attribute+ - so we need to resolve the casted values here
11
+ values = values.transform_values(&:value)
12
+
13
+ # if a primary_key (+_id+) was provided, we need to extract this and allocate this to the arguments
14
+ arguments = if (id = values.delete(self.primary_key)).present?
15
+ { id: id }
16
+ else
17
+ {}
18
+ end
19
+
20
+ # build new query
21
+ query = ElasticsearchRecord::Query.new(
22
+ index: table_name,
23
+ type: ElasticsearchRecord::Query::TYPE_CREATE,
24
+ body: values,
25
+ arguments: arguments,
26
+ refresh: true)
27
+
28
+ # execute query and build a RAW response
29
+ response = connection.exec_query(query, "#{self} Create").response
30
+
31
+ raise RecordNotSaved unless response['result'] == 'created'
32
+
33
+ # return the new id
34
+ response['_id']
35
+ end
36
+
37
+ # updates a persistent entry in the Elasticsearch index
38
+ # NOTICE: We don't want to mess up with the Arel-builder - so we send new data directly to the API
39
+ def _update_record(values, constraints)
40
+ # values is not a "key=>values"-Hash, but a +ActiveModel::Attribute+ - so we need to resolve the casted values here
41
+ values = values.transform_values(&:value)
42
+
43
+ # build new query
44
+ query = ElasticsearchRecord::Query.new(
45
+ index: table_name,
46
+ type: ElasticsearchRecord::Query::TYPE_UPDATE,
47
+ body: { doc: values },
48
+ arguments: { id: constraints['_id'] },
49
+ refresh: true)
50
+
51
+ # execute query and build a RAW response
52
+ response = connection.exec_query(query, "#{self} Update").response
53
+
54
+ raise RecordNotSaved unless response['result'] == 'updated'
55
+
56
+ # return affected rows
57
+ response['_shards']['total']
58
+ end
59
+
60
+ # removes a persistent entry from the Elasticsearch index
61
+ # NOTICE: We don't want to mess up with the Arel-builder - so we send new data directly to the API
62
+ def _delete_record(constraints)
63
+ # build new query
64
+ query = ElasticsearchRecord::Query.new(
65
+ index: table_name,
66
+ type: ElasticsearchRecord::Query::TYPE_DELETE,
67
+ arguments: { id: constraints[self.primary_key] },
68
+ refresh: true)
69
+
70
+ # execute query and build a RAW response
71
+ response = connection.exec_query(query, "#{self} Destroy").response
72
+
73
+ raise RecordNotDestroyed unless response['result'] == 'deleted'
74
+
75
+ # return affected rows
76
+ response['_shards']['total']
77
+ end
78
+ end
79
+ end
80
+ end