elastic_ar_sync 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bd13df6ed68262c7b4c23444f09d48a3530ad97e5df6f682f505ec32f9304de1
4
+ data.tar.gz: 3b4e0cb0b9c5f0226eaa85aab9d4c68b10a4a1e57c1a435f877a1dbcaa61b257
5
+ SHA512:
6
+ metadata.gz: 219487c1c412620a86beabe73e1ef4db107a17d427d69e775ef301e20fe98a24cec1f9af23ee98611a777ba6b05604551ac25fb4e06fbe43b5878e6b020b0be0
7
+ data.tar.gz: bc3b782c8f7b8626383b3dee9ec539ad4d7d00d62702c6edfa91eeeab9be91edfe25b3e693fa602b859b85ed52ad746d1d037e8d6d2847119be4770b80ef3436
@@ -0,0 +1,20 @@
1
+ Copyright 2020 KitakatsuTed
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ # ElasticArSync
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'elastic_ar_sync'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install elastic_ar_sync
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,27 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'ElasticArSync'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ require 'bundler/gem_tasks'
18
+
19
+ require 'rake/testtask'
20
+
21
+ Rake::TestTask.new(:test) do |t|
22
+ t.libs << 'test'
23
+ t.pattern = 'test/**/*_test.rb'
24
+ t.verbose = false
25
+ end
26
+
27
+ task default: :test
@@ -0,0 +1,15 @@
1
+ require "elastic_ar_sync/railtie"
2
+
3
+ module ElasticArSync
4
+ module Elastic
5
+ module Services
6
+ autoload :DocumentIndexer, 'elastic_ar_sync/elastic/services/document_indexer'
7
+ autoload :IndexHandler, 'elastic_ar_sync/elastic/services/index_handler'
8
+ end
9
+ module Worker
10
+ autoload :IndexImportWorker, 'elastic_ar_sync/elastic/worker/index_import_worker'
11
+ autoload :IndexWorker, 'elastic_ar_sync/elastic/worker/index_worker'
12
+ end
13
+ autoload :Syncable, 'elastic_ar_sync/elastic/syncable'
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ class ElasticArSync::Elastic::Services::DocumentIndexer
2
+ def index_document(klass, operation, record_id)
3
+ case operation.to_s
4
+ when /index/
5
+ record = Object.const_get(klass).find(record_id)
6
+
7
+ Elasticsearch::Model.client.index(
8
+ index: record.__elasticsearch__.index_name,
9
+ type: record.__elasticsearch__.document_type,
10
+ id: record.id,
11
+ body: record.__elasticsearch__.as_indexed_json)
12
+ when /delete/
13
+ Elasticsearch::Model.client.delete(index: Object.const_get(klass).__elasticsearch__.index_name,
14
+ type: Object.const_get(klass).__elasticsearch__.document_type,
15
+ id: record_id)
16
+ else
17
+ raise ArgumentError, "Unknown operation '#{operation.to_s}'"
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,40 @@
1
+ class ElasticArSync::Elastic::Services::IndexHandler
2
+ def initialize(klass)
3
+ @klass = klass
4
+ end
5
+
6
+ # インデックスを作成 デフォルトは クラス名の小文字_環境名
7
+ def create_index(new_index_name)
8
+ @klass.__elasticsearch__.client.indices.create index: new_index_name, body: { settings: @klass.settings.to_hash, mappings: @klass.mapping.to_hash }
9
+ end
10
+
11
+ def delete_index(target_index)
12
+ raise 'can not delete because this index is using now' if target_index == target_alias
13
+
14
+ @klass.__elasticsearch__.client.indices.delete index: target_index rescue false
15
+ end
16
+
17
+ # DBの内容をESのインデックスに同期する
18
+ # レコード量が多いと時間がかかるのでこれだけは非同期実行
19
+ def import_all_record(target_index ,batch_size = 100)
20
+ @klass.__elasticsearch__.import(index: target_index, batch_size: batch_size)
21
+ end
22
+
23
+ # ダウンタイムなしでインデックスを切り替える
24
+ # https://techlife.cookpad.com/entry/2015/09/25/170000
25
+ def switch_alias(alias_name:, new_index_name:)
26
+ raise 'this is already assigned' if new_index_name == target_alias
27
+
28
+ actions = [{ add: { index: new_index_name, alias: alias_name } }]
29
+ old_indexes = @klass.get_aliases.keys
30
+ old_indexes.each { |old_index| actions << { remove: { index: old_index, alias: alias_name } } }
31
+
32
+ @klass.__elasticsearch__.client.indices.update_aliases(body: { actions: actions })
33
+ end
34
+
35
+ private
36
+
37
+ def target_alias
38
+ @klass.get_aliases.keys.select { |index| @klass.get_aliases[index]["aliases"].present? }.first
39
+ end
40
+ end
@@ -0,0 +1,106 @@
1
+ module ElasticArSync
2
+ module Elastic
3
+ module Syncable
4
+ extend ActiveSupport::Concern
5
+ include ElasticArSync::Elastic::Worker
6
+ include ElasticArSync::Elastic::Services
7
+
8
+ included do
9
+ include Elasticsearch::Model
10
+
11
+ index_name "#{self.to_s.downcase.pluralize}_#{Rails.env}"
12
+
13
+ # after_commitでRDBを操作した時にESのインデックスも同期させる
14
+ # アプリのサーバーに負荷をかけ無いように非同期で実行させる
15
+ after_commit on: [:create] do
16
+ document_sync_create(self.class, id)
17
+ end
18
+
19
+ after_commit on: [:update] do
20
+ document_sync_update(self.class, id)
21
+ end
22
+
23
+ after_commit on: [:destroy] do
24
+ document_sync_delete(self.class, id)
25
+ end
26
+
27
+ def document_sync_create(klass, record_id)
28
+ ElasticArSync::Elastic::Worker::IndexWorker.perform_async(klass, :index, record_id)
29
+ end
30
+
31
+ def document_sync_update(klass, record_id)
32
+ ElasticArSync::Elastic::Worker::IndexWorker.perform_async(klass, :index, record_id)
33
+ end
34
+
35
+ def document_sync_delete(klass, record_id)
36
+ ElasticArSync::Elastic::Worker::IndexWorker.perform_async(klass, :delete, record_id)
37
+ end
38
+
39
+ def as_indexed_json(_option = {})
40
+ attributes.symbolize_keys.select { |key, _| self.class.mapping_list_keys.include?(key) }
41
+ end
42
+ end
43
+
44
+ module ClassMethods
45
+ def index_setup
46
+ response = create_index
47
+ switch_alias(new_index_name: response["index"])
48
+ import_all_record(target_index: response["index"])
49
+ end
50
+ # インデックスを作成 デフォルトは クラス名の小文字_環境名
51
+ def create_index
52
+ ElasticArSync::Elastic::Services::IndexHandler.new(self).create_index("#{index_name}_#{Time.zone.now.strftime('%Y%m%d%H%M')}")
53
+ end
54
+
55
+ def delete_index(target_index)
56
+ ElasticArSync::Elastic::Services::IndexHandler.new(self).delete_index(target_index)
57
+ end
58
+
59
+ # DBの内容をESのインデックスに同期する
60
+ def import_all_record(target_index: ,batch_size: 100)
61
+ ElasticArSync::Elastic::Worker::IndexImportWorker.perform_async(self, target_index, batch_size)
62
+ end
63
+
64
+ # ダウンタイムなしでインデックスを切り替える
65
+ # https://techlife.cookpad.com/entry/2015/09/25/170000
66
+ def switch_alias(new_index_name:)
67
+ ElasticArSync::Elastic::Services::IndexHandler.new(self).switch_alias(alias_name: index_name, new_index_name: new_index_name)
68
+ end
69
+
70
+ def index_config(dynamic: 'false', number_of_shards: 1, attr_mappings: default_index_mapping)
71
+ settings index: { number_of_shards: number_of_shards } do
72
+ # ES6からStringが使えないのでtextかkeywordにする。
73
+ mappings dynamic: dynamic do
74
+ attr_mappings.each do |key, value|
75
+ indexes key, type: value
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ def default_index_mapping
82
+ mapping = {}
83
+ attribute_types.each do |attribute, active_model_type|
84
+ type = active_model_type.type
85
+ type = :text if (active_model_type.type.to_sym == :string) || (type == :integer && defined_enums.symbolize_keys.keys.include?(attribute.to_sym))
86
+ type = :date if active_model_type.type == :datetime
87
+ mapping[attribute.to_sym] = type
88
+ end
89
+ mapping
90
+ end
91
+
92
+ def mapping_list_keys
93
+ mappings.to_hash[:_doc][:properties].keys
94
+ end
95
+
96
+ def get_aliases
97
+ begin
98
+ __elasticsearch__.client.indices.get_alias(index: '')
99
+ rescue Elasticsearch::Transport::Transport::Errors::NotFound
100
+ raise Elasticsearch::Transport::Transport::Errors::NotFound, "インデックスがありません alias_name: #{alias_name}"
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,16 @@
1
+ class ElasticArSync::Elastic::Worker::IndexImportWorker
2
+ include Sidekiq::Worker
3
+ sidekiq_options queue: :elasticsearch, retry: false
4
+
5
+ def perform(klass, target_index, batch_size)
6
+ Rails.logger.debug "[elastic IndexImportWorker] start import #{target_index}"
7
+
8
+ begin
9
+ ElasticArSync::Elastic::Services::IndexHandler.new(Object.const_get(klass)).import_all_record(target_index, batch_size)
10
+ rescue => e
11
+ Rails.logger.debug "[elastic IndexImportWorker] error occur #{target_index} \n #{e.message}"
12
+ end
13
+
14
+ Rails.logger.debug "[elastic IndexImportWorker] finish import #{target_index}"
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ class ElasticArSync::Elastic::Worker::IndexWorker
2
+ include Sidekiq::Worker
3
+ sidekiq_options queue: :elasticsearch, retry: false
4
+
5
+ def perform(klass, operation, record_id)
6
+ Rails.logger.debug "[elasticsearch IndexWorker] operation: #{operation} #{klass} ID: #{record_id}"
7
+ ElasticArSync::Elastic::Services::DocumentIndexer.new.index_document(klass, operation, record_id)
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ module ElasticArSync
2
+ class Railtie < ::Rails::Railtie
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module ElasticArSync
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :elastic_ar_sync do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: elastic_ar_sync
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - KitakatsuTed
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-07-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 5.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 5.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: This gem has useful methods to sync rdb data with es data.
42
+ email:
43
+ - travy300637@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - MIT-LICENSE
49
+ - README.md
50
+ - Rakefile
51
+ - lib/elastic_ar_sync.rb
52
+ - lib/elastic_ar_sync/elastic/services/document_indexer.rb
53
+ - lib/elastic_ar_sync/elastic/services/index_handler.rb
54
+ - lib/elastic_ar_sync/elastic/syncable.rb
55
+ - lib/elastic_ar_sync/elastic/worker/index_import_worker.rb
56
+ - lib/elastic_ar_sync/elastic/worker/index_worker.rb
57
+ - lib/elastic_ar_sync/railtie.rb
58
+ - lib/elastic_ar_sync/version.rb
59
+ - lib/tasks/elastic_ar_sync_tasks.rake
60
+ homepage: https://github.com/KitakatsuTed/elastic_ar_sync
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.7.6.2
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: easy setup elasticsearch for ActiveRecord
84
+ test_files: []