elasticsearch_record 1.0.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 (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