redisearch-rails 0.2.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 389ea395f1fe1ef5130083e382f435fa4515d1ab3f9952f4bc660eed47838d40
4
- data.tar.gz: cb56f462ab039f9b93eb7c9310cbd50a3a9678a1b2eb1d2c4205caec3066754c
3
+ metadata.gz: f416956f2ec55228c53f37ddaadc2f0a5200caa7ccf6399c1f991217ed2242f3
4
+ data.tar.gz: 01624db3f4e81c614550dc998e0214bfaddb61a2b301e052b05612de2e9c12db
5
5
  SHA512:
6
- metadata.gz: 9f534dafaba92007233233420f17b7230f521df2d7a82b785b6cc2c35d03201d37c38ecbb4913aa90500aedc690cd9fd8840919261234c70626f9202f9dd83b1
7
- data.tar.gz: 650c151013343ab35936a565b9ad657f54fc978ee64ed77ddec04fa04751d2b513318e8c25f4327756779305996526b768e2b6bc4f6fc717d16224b9607f5227
6
+ metadata.gz: 452964535c36fda982b1c7dc2299df33bacaf9e58bdc93969ed5e8ebf787c5fca1bd7f6e461bf09f4006fbbc47b2e7535770b8a62b969f92b55df91d8eeaee0b
7
+ data.tar.gz: 1e2f6985c7b5bec11b1a8b5c223925c6e66995f91ec21cf558ce409de9fbeb936f30f7ced8d78605199c57efc95d26cc8b291d101e26a5340e32265400ebda11
data/.travis.yml CHANGED
@@ -1,7 +1,28 @@
1
1
  ---
2
- sudo: false
2
+ os: linux
3
+
3
4
  language: ruby
4
5
  cache: bundler
6
+
5
7
  rvm:
6
- - 2.6.1
7
- before_install: gem install bundler -v 1.17.2
8
+ - 2.6.5
9
+ - 2.5.7
10
+ - 2.4.9
11
+
12
+ env:
13
+ - BV=1.17.2
14
+
15
+ before_install:
16
+ - gem install bundler -v $BV
17
+ - docker run -d -p 127.0.0.1:6379:6379 redislabs/redisearch:latest
18
+ - docker ps -a
19
+
20
+ jobs:
21
+ include:
22
+ # Ruby-head (we want to know how we're doing, but not fail the build)
23
+ - rvm: ruby-head
24
+ env: BV=2.1.4
25
+
26
+ allow_failures:
27
+ - rvm: ruby-head
28
+ env: BV=2.1.4
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Redisearch-rails
2
+ [![Build Status](https://travis-ci.org/eventBrain/redisearch-rails.svg?branch=master)](https://travis-ci.org/eventBrain/redisearch-rails)
2
3
 
3
4
  Adds support for easily indexing and search ActiveRecord models using RediSearch module http://redisearch.io/
4
5
 
@@ -0,0 +1,44 @@
1
+ module RediSearch
2
+ class BatchesIndexer
3
+
4
+ attr_reader :klass
5
+
6
+ def initialize(klass)
7
+ @klass = klass
8
+ @index = klass.redisearch_index
9
+ end
10
+
11
+ def reindex(mode: :inline, **options)
12
+ unless [:inline, true, :async].include?(mode)
13
+ raise ArgumentError, "#{mode} its not a valid value for mode"
14
+ end
15
+
16
+ batch_size = klass.redisearch_index_options[:batch_size] || DEFAULT_BATCH_SIZE
17
+
18
+ #this make a select count
19
+ size = klass.redisearch_import.find_in_batches(batch_size: batch_size).size
20
+ case mode
21
+ when :async
22
+ for i in 1..size
23
+ start = (i-1)*batch_size
24
+ finish = (i*batch_size)-1
25
+ RediSearch::ReindexBatchesJob.perform_later(klass.name, start.to_s, finish.to_s, batch_size.to_s)
26
+ end
27
+ else
28
+ reindex_in_batches(batch_size, options)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def reindex_in_batches(batch_size, **options)
35
+ klass.redisearch_import.find_in_batches(batch_size: batch_size) do |records|
36
+ klass.redisearch_index.client.multi do
37
+ records.each do |record|
38
+ record.reindex(mode: :inline, **options.deep_merge(replace: true, partial: true))
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,9 +1,13 @@
1
1
  module RediSearch
2
2
  class Configuration
3
- attr_accessor :redis_config
3
+ attr_accessor :redis_config, :index_prefix, :index_suffix, :queue_name, :model_options
4
4
 
5
5
  def initialize
6
6
  @redis_config = {}
7
+ @index_prefix = nil
8
+ @index_suffix = nil
9
+ @queue_name = :redisearch
10
+ @model_options = {}
7
11
  end
8
12
  end
9
13
  end
@@ -0,0 +1,37 @@
1
+ module RediSearch
2
+ class RecordIndexer
3
+
4
+ attr_reader :record, :index
5
+
6
+ def initialize(record)
7
+ @record = record
8
+ @index = record.class.redisearch_index
9
+ end
10
+
11
+ def reindex(mode: nil, **options)
12
+ unless [:inline, true, nil, :async].include?(mode)
13
+ raise ArgumentError, "#{mode} its not a valid value for mode"
14
+ end
15
+
16
+ mode ||= RediSearch.callbacks_value || record.class.redisearch_index_options[:callbacks] || true
17
+
18
+ case mode
19
+ when :async
20
+ RediSearch::ReindexRecordJob.perform_later(record.class.name, record.id.to_s)
21
+ else
22
+ reindex_record
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def reindex_record
29
+ if record.destroyed? || !record.persisted? || !record.should_index?
30
+ document = index.generate_document(record.redisearch_document_id, {})
31
+ document.del(dd: true)
32
+ else
33
+ record.redisearch_add
34
+ end
35
+ end
36
+ end
37
+ end
@@ -2,21 +2,48 @@ module RediSearch
2
2
  module RediSearchable
3
3
  module ClassMethods
4
4
 
5
- attr_reader :redisearch_index, :redisearch_index_serializer
6
-
7
5
  def redisearch(*args, schema:, **options)
8
- prefix = options[:prefix]
9
- @redisearch_index_serializer = options[:index_serializer]
6
+ options = RediSearch.model_options.merge(options)
7
+
8
+ raise "Only call redisearch once per model" if respond_to?(:redisearch_index)
9
+
10
+ prefix = options[:prefix] || RediSearch.index_prefix
11
+ prefix = prefix.call if prefix.respond_to?(:call)
12
+
13
+ suffix = options[:suffix] || RediSearch.index_suffix
14
+ suffix = suffix.call if suffix.respond_to?(:call)
15
+
16
+ callbacks = options.key?(:callbacks) ? options[:callbacks] : :inline
17
+ unless [:inline, true, false, :async].include?(callbacks)
18
+ raise ArgumentError, "#{callbacks} its not permited value for callbacks"
19
+ end
20
+
21
+ class << self
22
+ attr_reader :redisearch_index, :redisearch_index_serializer, :redisearch_index_options
23
+ end
10
24
 
11
- index_name = [prefix, model_name.plural].compact.join("_")
25
+ index_name = [prefix, model_name.plural, suffix].compact.join("_")
26
+ @redisearch_index_serializer = options[:index_serializer]
12
27
  @redisearch_index = RediSearch.client.generate_index(index_name, schema)
13
28
 
29
+ RediSearch.models << self
30
+
31
+ @redisearch_index_options = options
32
+
14
33
  scope :redisearch_import, -> { all }
15
34
 
35
+ # always add callbacks, even when callbacks is false
36
+ # so Model.callbacks block can be used
37
+ if respond_to?(:after_commit)
38
+ after_commit :reindex, if: -> { RediSearch.callbacks?(default: callbacks) }
39
+ elsif respond_to?(:after_save)
40
+ after_save :reindex, if: -> { RediSearch.callbacks?(default: callbacks) }
41
+ after_destroy :reindex, if: -> { RediSearch.callbacks?(default: callbacks) }
42
+ end
43
+
16
44
  include InstanceMethods
17
45
  extend RediSearchClassMethods
18
46
  end
19
-
20
47
  end
21
48
 
22
49
  module RediSearchClassMethods
@@ -33,21 +60,15 @@ module RediSearch
33
60
  end
34
61
 
35
62
  # Reindex all
36
- def reindex(recreate: false, only: [], **options)
63
+ def reindex(recreate: false, mode: :inline, **options)
37
64
  index = redisearch_index
38
65
 
39
66
  index.drop if recreate
40
67
  index.create unless index.exists?
41
68
 
42
- redisearch_import.find_in_batches do |elements|
43
- redisearch_index.client.multi do
44
- elements.each do |element|
45
- element.redisearch_document.add(options.deep_merge(replace: true, partial: true))
46
- end
47
- end
48
- end
49
- true
69
+ RediSearch::BatchesIndexer.new(self).reindex(mode: mode)
50
70
  end
71
+
51
72
  end
52
73
  end
53
74
  end
@@ -6,6 +6,27 @@ module RediSearch
6
6
  @redisearch_document ||= generate_redisearch_document
7
7
  end
8
8
 
9
+ def redisearch_reindex(**options)
10
+ RediSearch::RecordIndexer.new(self).reindex(options)
11
+ end
12
+ alias_method :reindex, :redisearch_reindex unless method_defined?(:reindex)
13
+
14
+ def should_index?
15
+ true
16
+ end
17
+
18
+ def redisearch_add
19
+ redisearch_document.add(replace: true, partial: true)
20
+ end
21
+
22
+ def redisearch_delete
23
+ redisearch_document.del(dd: true)
24
+ end
25
+
26
+ def redisearch_document_id
27
+ [self.class.redisearch_index.name, self.id].join('_')
28
+ end
29
+
9
30
  private
10
31
 
11
32
  def generate_redisearch_document
@@ -16,10 +37,6 @@ module RediSearch
16
37
  index.generate_document(redisearch_document_id, fields_values)
17
38
  end
18
39
 
19
- def redisearch_document_id
20
- [self.class.redisearch_index.name, self.id].join('_')
21
- end
22
-
23
40
  def redisearch_serializer
24
41
  self.class.redisearch_index_serializer.new(self) if self.class.redisearch_index_serializer
25
42
  end
@@ -0,0 +1,26 @@
1
+ module RediSearch
2
+ class ReindexBatchesJob < ActiveJob::Base
3
+ queue_as { RediSearch.queue_name }
4
+
5
+ def perform(klass, start, finish, batch_size)
6
+ klass = klass.constantize
7
+
8
+ batches_relation = nil
9
+
10
+ if ActiveRecord::VERSION::STRING >= "5.0"
11
+ batches_relation = klass.redisearch_import.find_in_batches(batch_size: batch_size, start: start, finish: finish)
12
+ else
13
+ batches_relation = klass.redisearch_import.where(klass.arel_table[:id].lteq(finish.to_i)).find_in_batches(batch_size: batch_size, start: start)
14
+ end
15
+
16
+ batches_relation.each do |records|
17
+ klass.redisearch_index.client.multi do
18
+ records.each do |record|
19
+ record.reindex(mode: :inline, replace: true)
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ module RediSearch
2
+ class ReindexRecordJob < ActiveJob::Base
3
+ RECORD_NOT_FOUND_CLASSES = [
4
+ "ActiveRecord::RecordNotFound"
5
+ ]
6
+
7
+ queue_as { RediSearch.queue_name }
8
+
9
+ def perform(klass, id)
10
+ model = klass.constantize
11
+
12
+ record =
13
+ begin
14
+ model.redisearch_import.find(id)
15
+ rescue => e
16
+ # check by name rather than rescue directly so we don't need
17
+ # to determine which classes are defined
18
+ raise e unless RECORD_NOT_FOUND_CLASSES.include?(e.class.name)
19
+ nil
20
+ end
21
+
22
+ unless record
23
+ record = model.new
24
+ record.id = id
25
+ end
26
+
27
+ RecordIndexer.new(record).reindex(mode: :inline)
28
+ end
29
+ end
30
+ end
@@ -1,3 +1,3 @@
1
1
  module RediSearch
2
- VERSION = '0.2.0'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -6,8 +6,17 @@ require 'redi_searcher'
6
6
 
7
7
  module RediSearch
8
8
  autoload :RediSearchable, 'redisearch-rails/redisearchable'
9
+ autoload :BatchesIndexer, 'redisearch-rails/batches_indexer'
10
+ autoload :RecordIndexer, 'redisearch-rails/record_indexer'
11
+
12
+ #jobs
13
+ autoload :ReindexRecordJob, 'redisearch-rails/reindex_record_job'
14
+ autoload :ReindexBatchesJob, 'redisearch-rails/reindex_batches_job'
15
+
16
+ DEFAULT_BATCH_SIZE = 1_000
9
17
 
10
18
  class << self
19
+ attr_accessor :models
11
20
  attr_writer :configuration
12
21
 
13
22
  def configuration
@@ -22,7 +31,55 @@ module RediSearch
22
31
  @client ||= RediSearcher::Client.new(configuration.redis_config)
23
32
  end
24
33
 
34
+ def enable_callbacks
35
+ self.callbacks_value = nil
36
+ end
37
+
38
+ def disable_callbacks
39
+ self.callbacks_value = false
40
+ end
41
+
42
+ def callbacks?(default: true)
43
+ if self.callbacks_value.nil?
44
+ default
45
+ else
46
+ self.callbacks_value != false
47
+ end
48
+ end
49
+
50
+ def callbacks_value
51
+ Thread.current[:redisearch_callbacks_enabled]
52
+ end
53
+
54
+ def callbacks_value=(value)
55
+ Thread.current[:redisearch_callbacks_enabled] = value
56
+ end
57
+
58
+ def callbacks(value)
59
+ if block_given?
60
+ previous_value = self.callbacks_value
61
+ begin
62
+ self.callbacks_value = value
63
+ result = yield
64
+ result
65
+ ensure
66
+ self.callbacks_value = previous_value
67
+ end
68
+ else
69
+ self.callbacks_value = value
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def method_missing(m, *args, &block)
76
+ return configuration.send(m) if configuration.respond_to?(m)
77
+ super
78
+ end
25
79
  end
80
+
81
+ @models = []
82
+
26
83
  end
27
84
 
28
85
  ActiveSupport.on_load(:active_record) do
@@ -26,6 +26,7 @@ Gem::Specification.new do |spec|
26
26
 
27
27
  spec.add_dependency 'activerecord', '~> 4.2'
28
28
  spec.add_dependency 'activesupport', '~> 4.2'
29
+ spec.add_dependency 'activejob', '~> 4.2'
29
30
  spec.add_dependency 'redi_searcher', '~> 0.1', '>= 0.1.3'
30
31
 
31
32
  spec.add_development_dependency "bundler", "~> 1.17"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redisearch-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patricio Beckmann
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-02 00:00:00.000000000 Z
11
+ date: 2020-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '4.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activejob
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.2'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: redi_searcher
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -118,10 +132,14 @@ files:
118
132
  - bin/console
119
133
  - bin/setup
120
134
  - lib/redisearch-rails.rb
135
+ - lib/redisearch-rails/batches_indexer.rb
121
136
  - lib/redisearch-rails/configuration.rb
137
+ - lib/redisearch-rails/record_indexer.rb
122
138
  - lib/redisearch-rails/redisearchable.rb
123
139
  - lib/redisearch-rails/redisearchable/class_methods.rb
124
140
  - lib/redisearch-rails/redisearchable/instance_methods.rb
141
+ - lib/redisearch-rails/reindex_batches_job.rb
142
+ - lib/redisearch-rails/reindex_record_job.rb
125
143
  - lib/redisearch-rails/version.rb
126
144
  - redisearch-rails.gemspec
127
145
  homepage: https://github.com/Ticketplus/redisearch-rails