Skip to content

Commit

Permalink
Lazy update attributes callback (#7)
Browse files Browse the repository at this point in the history
* chore: use candidate release of esse

* chore: rename index_callbacks to index_callback

* chore: rename esse_index_repos to esse_callbacks

* chore: deprecate renamed methods

* chore: group deprecated methods

* chore: expanding the existing callback to support different callback types

* feat: refactoring indexing callback by moving them to a callback repository

* chore: renaming :index to :indexing

* chore: refactoring specs

* chore: add tests to the esse_callback

* chore: add tests to the update_lazy_attribute_callback callback

* chore: add tests to update_lazy_attribute_callback callback

* chore: install esse from rubygems

* chore: fix rubocop offenses

* chore: fix rubocop offenses
  • Loading branch information
marcosgz authored Jul 10, 2024
1 parent 48cdf41 commit 57474f1
Show file tree
Hide file tree
Showing 28 changed files with 1,151 additions and 499 deletions.
4 changes: 3 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

source 'https://rubygems.org'

gem 'esse', '~> 0.2.4'
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

gem 'esse', '~> 0.3.0'
gem 'sqlite3', '~> 1.7.3'
gem 'activerecord', '~> 5.2'
gem 'esse-rspec', '~> 0.0.6'
Expand Down
10 changes: 5 additions & 5 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
PATH
remote: .
specs:
esse-active_record (0.2.1)
esse-active_record (0.3.0)
activerecord (>= 4.2, < 8)
esse (>= 0.2.3)
esse (>= 0.3.0)

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -39,7 +39,7 @@ GEM
elasticsearch-transport (7.17.10)
faraday (>= 1, < 3)
multi_json
esse (0.2.6)
esse (0.3.0)
multi_json
thor (>= 0.19)
esse-rspec (0.0.6)
Expand Down Expand Up @@ -122,7 +122,7 @@ GEM
standard-performance (1.0.1)
lint_roller (~> 1.0)
rubocop-performance (~> 1.16.0)
thor (1.3.0)
thor (1.3.1)
thread_safe (0.3.6)
tzinfo (1.2.11)
thread_safe (~> 0.1)
Expand All @@ -142,7 +142,7 @@ DEPENDENCIES
awesome_print
dotenv
elasticsearch (~> 7.17, >= 7.17.10)
esse (~> 0.2.4)
esse (~> 0.3.0)
esse-active_record!
esse-rspec (~> 0.0.6)
pry
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ end

### Indexing Callbacks

The `index_callbacks` callback can be used to automaitcally index or delete documents after commit on create/update/destroy events.
The `index_callback` callback can be used to automaitcally index or delete documents after commit on create/update/destroy events.

```ruby
class UsersIndex < Esse::Index
Expand All @@ -173,9 +173,9 @@ class User < ApplicationRecord
# Using a index and repository as argument. Note that the index name is used instead of the
# of the constant name. it's so because index and model depends on each other should result in
# circular dependencies issues.
index_callbacks 'users_index:user'
index_callback 'users_index:user'
# Using a block to direct a different object to be indexed
index_callbacks('organizations') { user.organization } # The `_index` suffix and repo name is optional on the index name
index_callback('organizations') { user.organization } # The `_index` suffix and repo name is optional on the index name
end
```

Expand All @@ -194,7 +194,7 @@ or by some specific list of index or index's repository
```ruby
Esse::ActiveRecord::Hooks.disable!(UsersIndex.repo)
Esse::ActiveRecord::Hooks.enable!(UsersIndex.repo)
Esse::ActiveRecord::Hooks.without_indexing(AccountsIndex UsersIndex.repo, ) do
Esse::ActiveRecord::Hooks.without_indexing(AccountsIndex, UsersIndex.repo) do
10.times { User.create! }
end
```
Expand Down
6 changes: 3 additions & 3 deletions ci/Gemfile.rails-5.2.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
PATH
remote: ..
specs:
esse-active_record (0.2.1)
esse-active_record (0.3.0)
activerecord (>= 4.2, < 8)
esse (>= 0.2.3)
esse (>= 0.3.0)

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -39,7 +39,7 @@ GEM
elasticsearch-transport (7.17.10)
faraday (>= 1, < 3)
multi_json
esse (0.2.6)
esse (0.3.0)
multi_json
thor (>= 0.19)
esse-rspec (0.0.6)
Expand Down
6 changes: 3 additions & 3 deletions ci/Gemfile.rails-6.0.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
PATH
remote: ..
specs:
esse-active_record (0.2.1)
esse-active_record (0.3.0)
activerecord (>= 4.2, < 8)
esse (>= 0.2.3)
esse (>= 0.3.0)

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -37,7 +37,7 @@ GEM
elasticsearch-transport (7.17.10)
faraday (>= 1, < 3)
multi_json
esse (0.2.6)
esse (0.3.0)
multi_json
thor (>= 0.19)
esse-rspec (0.0.6)
Expand Down
6 changes: 3 additions & 3 deletions ci/Gemfile.rails-6.1.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
PATH
remote: ..
specs:
esse-active_record (0.2.1)
esse-active_record (0.3.0)
activerecord (>= 4.2, < 8)
esse (>= 0.2.3)
esse (>= 0.3.0)

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -37,7 +37,7 @@ GEM
elasticsearch-transport (7.17.10)
faraday (>= 1, < 3)
multi_json
esse (0.2.6)
esse (0.3.0)
multi_json
thor (>= 0.19)
esse-rspec (0.0.6)
Expand Down
6 changes: 3 additions & 3 deletions ci/Gemfile.rails-7.0.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
PATH
remote: ..
specs:
esse-active_record (0.2.1)
esse-active_record (0.3.0)
activerecord (>= 4.2, < 8)
esse (>= 0.2.3)
esse (>= 0.3.0)

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -37,7 +37,7 @@ GEM
elasticsearch-transport (7.17.10)
faraday (>= 1, < 3)
multi_json
esse (0.2.6)
esse (0.3.0)
multi_json
thor (>= 0.19)
esse-rspec (0.0.6)
Expand Down
6 changes: 3 additions & 3 deletions ci/Gemfile.rails-7.1.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
PATH
remote: ..
specs:
esse-active_record (0.2.1)
esse-active_record (0.3.0)
activerecord (>= 4.2, < 8)
esse (>= 0.2.3)
esse (>= 0.3.0)

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -47,7 +47,7 @@ GEM
elasticsearch-transport (7.17.10)
faraday (>= 1, < 3)
multi_json
esse (0.2.6)
esse (0.3.0)
multi_json
thor (>= 0.19)
esse-rspec (0.0.6)
Expand Down
2 changes: 1 addition & 1 deletion esse-active_record.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']

spec.add_dependency 'esse', '>= 0.2.3'
spec.add_dependency 'esse', '>= 0.3.0'
spec.add_dependency 'activerecord', '>= 4.2', '< 8'
spec.add_development_dependency 'awesome_print'
spec.add_development_dependency 'dotenv'
Expand Down
1 change: 1 addition & 0 deletions lib/esse/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'esse'
require 'active_record'
require_relative 'active_record/version'
require_relative 'active_record/callbacks'
require_relative 'active_record/model'
require_relative 'active_record/hooks'
require_relative 'active_record/collection'
Expand Down
64 changes: 64 additions & 0 deletions lib/esse/active_record/callbacks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true

module Esse
module ActiveRecord
class Callback
attr_reader :repo, :options, :block_result

def initialize(repo:, block_result: nil, **kwargs)
@repo = repo
@options = kwargs
@block_result = block_result
end

def call(model)
raise NotImplementedError, 'You must implement #call method'
end
end

module Callbacks
class << self
def to_h
@callbacks || {}.freeze
end

def register_callback(identifier, operation, callback_class)
unless callback_class < Esse::ActiveRecord::Callback
raise ArgumentError, 'callback_class must be a subclass of Esse::ActiveRecord::Callback'
end

key = :"#{identifier}_on_#{operation}"

@callbacks = @callbacks ? @callbacks.dup : {}
if @callbacks.key?(key)
raise ArgumentError, "callback #{identifier} for #{operation} operation already registered"
end

@callbacks[key] = callback_class
ensure
@callbacks&.freeze
end

def registered?(identifier, operation)
return false unless @callbacks

@callbacks.key?(:"#{identifier}_on_#{operation}")
end

def fetch!(identifier, operation)
key = :"#{identifier}_on_#{operation}"
if registered?(identifier, operation)
[key, @callbacks[key]]
else
raise ArgumentError, "callback #{identifier} for #{operation} operation not registered"
end
end
end
end
end
end

require_relative 'callbacks/indexing_on_create'
require_relative 'callbacks/indexing_on_update'
require_relative 'callbacks/indexing_on_destroy'
require_relative 'callbacks/update_lazy_attribute'
16 changes: 16 additions & 0 deletions lib/esse/active_record/callbacks/indexing_on_create.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

module Esse::ActiveRecord
module Callbacks
class IndexingOnCreate < Callback
def call(model)
record = block_result || model
document = repo.serialize(record)
repo.index.index(document, **options) if document
true
end
end

register_callback(:indexing, :create, IndexingOnCreate)
end
end
18 changes: 18 additions & 0 deletions lib/esse/active_record/callbacks/indexing_on_destroy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Esse::ActiveRecord
module Callbacks
class IndexingOnDestroy < Callback
def call(model)
record = block_result || model
document = repo.serialize(record)
repo.index.delete(document, **options) if document
true
rescue Esse::Transport::NotFoundError
true
end
end

register_callback(:indexing, :destroy, IndexingOnDestroy)
end
end
34 changes: 34 additions & 0 deletions lib/esse/active_record/callbacks/indexing_on_update.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

module Esse::ActiveRecord
module Callbacks
class IndexingOnUpdate < Callback
def call(model)
record = block_result || model

document = repo.serialize(record)
return true unless document

repo.index.index(document, **options)
return true unless document.routing

prev_record = model.class.new(model.attributes.merge(model.previous_changes.transform_values(&:first))).tap(&:readonly!)
prev_document = repo.serialize(prev_record)

return true unless prev_document
return true if [prev_document.id, prev_document.routing].include?(nil)
return true if prev_document.routing == document.routing
return true if prev_document.id != document.id

begin
repo.index.delete(prev_document, **options)
rescue Esse::Transport::NotFoundError
end

true
end
end

register_callback(:indexing, :update, IndexingOnUpdate)
end
end
27 changes: 27 additions & 0 deletions lib/esse/active_record/callbacks/update_lazy_attribute.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module Esse::ActiveRecord
module Callbacks
class UpdateLazyAttribute < Callback
attr_reader :attribute_name

def initialize(attribute_name:, **kwargs, &block)
@attribute_name = attribute_name
super(**kwargs, &block)
end

def call(model)
related_ids = Array(block_result || model.id)
return true if related_ids.empty?

repo.update_documents_attribute(attribute_name, *related_ids, **options)

true
end
end

register_callback(:update_lazy_attribute, :create, UpdateLazyAttribute)
register_callback(:update_lazy_attribute, :update, UpdateLazyAttribute)
register_callback(:update_lazy_attribute, :destroy, UpdateLazyAttribute)
end
end
4 changes: 2 additions & 2 deletions lib/esse/active_record/hooks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def disable_model!(model_class, *repos)
def ensure_registered_model_class!(model_class)
return if registered_model_class?(model_class)

raise ArgumentError, "Model class #{model_class} is not registered. The model should inherit from Esse::ActiveRecord::Model and have a `index_callbacks' callback defined"
raise ArgumentError, "Model class #{model_class} is not registered. The model should inherit from Esse::ActiveRecord::Model and have a `index_callback' callback defined"
end

# Check if the given model is enabled for indexing. If no repository is specified, all repositories will be checked.
Expand Down Expand Up @@ -147,7 +147,7 @@ def all_repos
# Returns a list of all repositories for the given model
# @return [Array<Symbol>]
def model_repos(model_class)
expand_index_repos(*model_class.esse_index_repos.keys)
expand_index_repos(*model_class.esse_callbacks.keys)
end

# Returns a list of all repositories for the given model
Expand Down
Loading

0 comments on commit 57474f1

Please sign in to comment.