redisearch-rails 0.2.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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