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.
- 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
|