This is really hard to do a google search about because I have no idea if it's a Ruby thing or a Rails thing and google does not do a good job searching for "on"
In a file that looks like so
# app/models/concerns/searchable.rb
module Searchable
extend ActiveSupport::Concern
included do
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
# Every time our entry is created, updated, or deleted, we update the index accordingly.
after_commit on: %i[create update] do
__elasticsearch__.index_document
end
after_commit on: %i[destroy] do
__elasticsearch__.delete_document
end
# We serialize our model's attributes to JSON, including only the title and category fields.
def as_indexed_json(_options = {})
as_json(only: %i[title category])
end
# Here we define the index configuration
settings settings_attributes do
# We apply mappings to the title and category fields.
mappings dynamic: false do
# for the title we use our own autocomplete analyzer that we defined below in the settings_attributes method.
indexes :title, type: :text, analyzer: :autocomplete
# the category must be of the keyword type since we're only going to use it to filter articles.
indexes :category, type: :keyword
end
end
def self.search(query, filters)
# lambda function adds conditions to the search definition.
set_filters = lambda do |context_type, filter|
@search_definition[:query][:bool][context_type] |= [filter]
end
@search_definition = {
# we indicate that there should be no more than 5 documents to return.
size: 5,
# we define an empty query with the ability to dynamically change the definition
# Query DSL https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html
query: {
bool: {
must: [],
should: [],
filter: []
}
}
}
# match all documents if the query is blank.
if query.blank?
set_filters.call(:must, match_all: {})
else
set_filters.call(
:must,
match: {
title: {
query: query,
# fuzziness means you can make one typo and still match your document.
fuzziness: 1
}
}
)
end
# the system will return only those documents that pass this filter
set_filters.call(:filter, term: { category: filters[:category] }) if filters[:category].present?
# and finally we pass the search query to Elasticsearch.
__elasticsearch__.search(@search_definition)
end
end
class_methods do
def settings_attributes
{
index: {
analysis: {
analyzer: {
# we define a custom analyzer with the name autocomplete.
autocomplete: {
# type should be custom for custom analyzers.
type: :custom,
# we use a standard tokenizer.
tokenizer: :standard,
# we apply two token filters.
# autocomplete filter is a custom filter that we defined above.
# and lowercase is a built-in filter.
filter: %i[lowercase autocomplete]
}
},
filter: {
# we define a custom token filter with the name autocomplete.
# Autocomplete filter is of edge_ngram type. The edge_ngram tokenizer divides the text into smaller parts (grams).
# For example, the word “ruby” will be split into [“ru”, “rub”, “ruby”].
# edge_ngram is useful when we need to implement autocomplete functionality. However, the so-called "completion suggester" - is another way to integrate the necessary options.
autocomplete: {
type: :edge_ngram,
min_gram: 2,
max_gram: 25
}
}
}
}
}
end
end
end
I am not sure what after_commit on: %i[create update] do
is supposed to mean.
I managed to find this information https://apidock.com/rails/ActiveRecord/Transactions/ClassMethods/after_commit
which gives me an idea of how to use this sytax.
But I'm still not sure how this syntax "on:" is created. It doesn't seem like a Ruby thing. It seems like a Rails shorthand for something but what exactly is it?
On a separate note, is there any source that lists all the shorthands that Rails provides? It's such a pain to figure out if something is a Rails shorthand or if it's a Ruby syntax.
CodePudding user response:
Let's dissect the various parts of the syntax in
after_commit on: %i[create update] do
# ...
end
At first, about the array at the end. It uses Ruby's %-syntax to create an object, in this case an array of Symbols. %i[create update]
is thus equivalent to [:create, :update]
There are various options to use this %-syntax to create various objects, e.g. %[foo]
is equivalent to "foo"
and %w[foo bar]
is equivalent to ["foo", "bar"]
. The delimiter characters are arbitrary here. Instead of [
and ]
, you can also use {
and }
, or even something like %i|foo bar|
or %i.foo bar`. See the syntax documentation for details.
Second, the on:
. Here, you are passing the keyword argument on
to the after_commit
method and pass the array of Symbols to it. Keyword arguments are kind-of similar to regular positional arguments you can pass to methods, you just pass the argument values along with the names, similar to a Hash.
Historically, Ruby supported passing a Hash as the last argument to a method and allowed omitting the braces there (so that you could use after_commit(on: [:create, :update])
rather than after_commit({:on => [:create, :update]})
. Ruby's keyword arguments (which were first introduced in Ruby 2.0) build upon this syntax and refined the semantics a bit along the way. For the most part, it still works the same as when passing a Hash with Symbol keys to a method though. Note that different to e.g. Python. positional arguments and keyword arguments can not be arbitrarily mixed.
You can learn more about keyword arguments (and regular positional arguments) at the respective documentation.
The method call is thus equivalent to:
after_commit(on: [:create, :update]) do
# ...
end
The on ... end
part is a block which is passed to the after_commit
method, another Ruby syntax. This allows to pass a block of code to a method which can do something with this, similar to how you can pass anonymous functions around in Javascript. Blocks are used extensively in Ruby so it's important to learn about them.
Finally, the after_commit
method and its associated behavior is defined by Rails. It is used to register callbacks which are run on certain events (in this case, to run some code after the database transaction successfully was committed in which the current ActiveRecord model was created or updated).
This is described in documentation about ActiveRecord callbacks.
CodePudding user response:
after_commit
is triggered on create
, update
, destroy
If you want to run the callback only on specific actions you can make use of :on
Example:
after_commit :calculate_total, on: [:create, :update]
private
def calculate_total
update(total: subtotal tax)
end
Given the above snippet, it makes sense to calculate the total only on create
and update
but not on destroy
Read more here - https://apidock.com/rails/ActiveRecord/Transactions/ClassMethods/after_commit
CodePudding user response:
With "on:" you can specify the ActiveRecord actions which will trigger this callback method.
Callbacks can also be registered to only fire on certain life cycle events
class User < ApplicationRecord
before_validation :normalize_name, on: :create
# :on takes an array as well
after_validation :set_location, on: [ :create, :update ]
private
def normalize_name
self.name = name.downcase.titleize
end
def set_location
self.location = LocationService.query(self)
end
end
You can check out more from here
CodePudding user response:
Try the following code in the console if you can:
def fn_args(*args, &block)
puts 'args: ' args.inspect
puts 'block: ' block.inspect
block.call
end
fn_args on: %i[create update] do
puts 'Heya from passed block'
end
I get:
args: [{:on=>[:create, :update]}]
block: #<Proc:0x0000020bc91c6a98@(irb):21>
Heya from passed block
=> nil
What does this mean?
fn_args
is created with the same function signature as after_commit
, we can see (I think) that on: %i[create update]
is getting treated as a generic hash as part of the function arguments. It's not a special language feature (Ruby or Rails), though the interpretation of on: %i[create update]
as a hash (or part of a hash) is a special feature of Ruby and the way it handles function arguments.
We could rewrite our call to:
fn_args({ on: %i[create update] }) do
puts 'Heya from passed block'
end
and it should work the same way.