elasticsearch_record 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +74 -0
- data/README.md +216 -0
- data/Rakefile +8 -0
- data/docs/CHANGELOG.md +44 -0
- data/docs/CODE_OF_CONDUCT.md +84 -0
- data/docs/LICENSE.txt +21 -0
- data/lib/active_record/connection_adapters/elasticsearch/column.rb +32 -0
- data/lib/active_record/connection_adapters/elasticsearch/database_statements.rb +149 -0
- data/lib/active_record/connection_adapters/elasticsearch/quoting.rb +38 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_statements.rb +134 -0
- data/lib/active_record/connection_adapters/elasticsearch/type/format_string.rb +28 -0
- data/lib/active_record/connection_adapters/elasticsearch/type/multicast_value.rb +52 -0
- data/lib/active_record/connection_adapters/elasticsearch/type/object.rb +44 -0
- data/lib/active_record/connection_adapters/elasticsearch/type/range.rb +42 -0
- data/lib/active_record/connection_adapters/elasticsearch/type.rb +16 -0
- data/lib/active_record/connection_adapters/elasticsearch_adapter.rb +197 -0
- data/lib/arel/collectors/elasticsearch_query.rb +112 -0
- data/lib/arel/nodes/select_agg.rb +22 -0
- data/lib/arel/nodes/select_configure.rb +9 -0
- data/lib/arel/nodes/select_kind.rb +9 -0
- data/lib/arel/nodes/select_query.rb +20 -0
- data/lib/arel/visitors/elasticsearch.rb +589 -0
- data/lib/elasticsearch_record/base.rb +14 -0
- data/lib/elasticsearch_record/core.rb +59 -0
- data/lib/elasticsearch_record/extensions/relation.rb +15 -0
- data/lib/elasticsearch_record/gem_version.rb +17 -0
- data/lib/elasticsearch_record/instrumentation/controller_runtime.rb +39 -0
- data/lib/elasticsearch_record/instrumentation/log_subscriber.rb +70 -0
- data/lib/elasticsearch_record/instrumentation/railtie.rb +16 -0
- data/lib/elasticsearch_record/instrumentation.rb +17 -0
- data/lib/elasticsearch_record/model_schema.rb +43 -0
- data/lib/elasticsearch_record/patches/active_record/relation_merger_patch.rb +85 -0
- data/lib/elasticsearch_record/patches/arel/select_core_patch.rb +64 -0
- data/lib/elasticsearch_record/patches/arel/select_manager_patch.rb +91 -0
- data/lib/elasticsearch_record/patches/arel/select_statement_patch.rb +41 -0
- data/lib/elasticsearch_record/patches/arel/update_manager_patch.rb +46 -0
- data/lib/elasticsearch_record/patches/arel/update_statement_patch.rb +60 -0
- data/lib/elasticsearch_record/persistence.rb +80 -0
- data/lib/elasticsearch_record/query.rb +129 -0
- data/lib/elasticsearch_record/querying.rb +90 -0
- data/lib/elasticsearch_record/relation/calculation_methods.rb +155 -0
- data/lib/elasticsearch_record/relation/core_methods.rb +64 -0
- data/lib/elasticsearch_record/relation/query_clause.rb +43 -0
- data/lib/elasticsearch_record/relation/query_clause_tree.rb +94 -0
- data/lib/elasticsearch_record/relation/query_methods.rb +276 -0
- data/lib/elasticsearch_record/relation/result_methods.rb +222 -0
- data/lib/elasticsearch_record/relation/value_methods.rb +54 -0
- data/lib/elasticsearch_record/result.rb +236 -0
- data/lib/elasticsearch_record/statement_cache.rb +87 -0
- data/lib/elasticsearch_record/version.rb +10 -0
- data/lib/elasticsearch_record.rb +60 -0
- data/sig/elasticsearch_record.rbs +4 -0
- 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
|