RDR3R: Callbacks (коллбеки — обратные вызовы) в Rails

октября 5, 2011  |  Published in Ruby on Rails, Ruby on Rails 3  |  7 Comments

Callbacks — колбеки (обратные вызовы) — это вызов методов как реакция на какое-нибудь действие объекта. Коллбеки привязываются к определенным методом и могут быть выполнены до или после вызова метода к которому они прикреплены. Например, когда в Rails вы используете ассоциацию с зависимостью, например такую:

Post has_many :comments, :dependent => :destroy

То после действия destroy для объекта — записи запускается соответствующий after_destroy коллбек, который выполняет удаление всех принадлежащищ Post’у комментариев.

В Rails существуют коллбеки модели и колбеки контроллеров, в Sinatra (Rack) существуют коллбеки обработчиков запроса, даже в RSpec существуют коллбеки before и after для подготовки и удаления данных для спецификаций.

В ActiveRecord существует следующий набор предопределенных коллбеков (рядом с колбеком стоит его номер в порядке выполнения):

1 before_validation — выполняется перед валидацией
1 before_validation_on_create — перед валидацией, но только для новой записи. Deprecated, следует использовать before_validation :on => [:create].
2 after_validation — после валидации
2 after_validation_on_create — после валидации, но только для новой записи. Deprecated, следует использовать after_validation :on => [:create].
3 before_save — перед сохранением
4 before_create — перед созданием
5 after_create — после создания
6 after_save — после сохранения

Чем отличаются, скажем before_save и before_create так это тем,что before_save выполняется при каждом сохранении записи, а before_create только при первом, т.е. для новой записи.

Также сущесвуют коллбеки порядок которых нельза так просто указать:

Удаление записи:
before_destroy
after_destroy

Обновление записи:
before_validation_on_updateDeprecated, следует использовать before_validation :on => [:update].
after_validation_on_updateDeprecated, следует использовать after_validation :on => [:update].
before_update
after_update

Существуют также around-коллбеки, которые совмещают в себе before и after коллбеки. Вот они:

around_save
around_create
around_update
around_destroy

По некоторым коллбекам почти невозможно найти документацию. Оправдать это можно тем, что они используются крайне редко, и вообще не рекомендуются к использованию.

Редко используемые коллбеки:

after_initialize — коллбек выполняется при инициализации экземпляра модели. Этот коллбек нерекомендуется использовать так как after_initialize ухудшает производительность.

after_find — коллбек выполняется при поиске при помощи метода #find, также не рекомендуется использовать из-за ухудшения производительности.

after_touch — выполняется после метода touch который выполняет сохранение записи со значением updated_at установленным в текущий момент времени.

after_commit и after_rollback — коллбеки, что выполняются при успешной и при неудачной транзакции.

Лично мне ни разу не приходилось сталкиваться с использованием этих коллбеков, в смысле, не только я их не использовал, но и не видел их использования в некоторых проектах исходники которых я изучах.

Как использовать коллбеки?
Существует 3 способа использование: декларативный, выполнение блока и переопределение метода. Все их я продемонстрирую на примере after_save. Смысла показывать пример для всех коллбеков не вижу, они абсолютно идентичны, разница только в том, когда они выполняются.

Декларативный способ:

class RubyDev < ActiveRecord::Base
  after_save :do_something

  def do_something
    #do something
  end
end

В декларативном способе в метод after_save мы передаем симво, который потом внутри метода преобразуется в процедуру с вызовом внутри метода с соответствующим символу именем. Этот метод должен быть определен в коде вашей модели.

Выполнение блока кода:

class RubyDev < ActiveRecord::Base
  after_save {
    #do something
  }
end

Переопределение метода:

class RubyDev < ActiveRecord::Base
  def after_save
    #do something
  end
end

Способ с переопределением метода считается плохим стилем и вроде даже будет deprecated, что значит, что эта возможность должна ищезнуть.

С коллбеками в моделях вроде все понятно, перейдем к коллбекам в контролерах!
В контроллерах коллбеки называются фильтрами, не знаю почему, но могу предположить, что это потому, что они могут «отфильтровывать» некоторый повторяющийся в контроллерах код. В контроллерах имеется всего три коллбека: before_filter, after_filter и around_filter которые выполняются соответственно до, после и до и после выполнения определенного экшена. Рассмотримпростой пример использования:

class RubyDevController < ApplicationController
  before_filter :my_method

  def index
    #do something here
  end

  def show
    #do something else
  end

  private
  def my_method
      #do something before actions
  end
end

В примере выше мы использовали фильтр before_filter в который через символ передали имя метода, который необходимо выполнить соответственно перед выполнением каждого экшена. Заходит пользователь на …/rubydev/ или на …/rubydev/1, перед любым его запросом к контроллеру будет выполнен метод before_filter.

Для ограничения того, до, после и «вокруг» каких экшенов будет выполняться фильтр, мы можем использовать два параметра:

after_filter :some_method, :except => :index
after_filter :some_method, :only => :index

Параметр :except указывает на то, для каких экшенов следует игнорировать текущий коллбек, а параметр :only наоборот, указывает для каких экшенов необходимо применить текущий коллбек.

:except — Все кроме
:only — Только

В коде контроллера, как и в коде модели вы можете использовать сколько угодно коллбеков:

class RubyDevController < ApplicationController
  before_filter :first
  before_filter :second, :except => :index
  before_filter :third, :only => [:index, :show]
  after_filter :fourth
  around_filter :fifth, :except => :show
  #...
end

Очень часто фильтры в контроллерах используются для выDRYивания кода контроллера:

class PostsController < ApplicationController
  before_filter(:only => [:show, :edit, :update, :destroy]) do
    @post = Post.params[:id]
  end
  #...
end

В примере выше мы делаем так, что нам больше не нужно в определенных контроллерах делать выборку из БД записи для показа, редактирования и удаления так как эта выборка для всех трех случаев вынесена в before_filter. Более явно демонстрирующий выгоды от фильтров пример  использования before_filter — это использования его для проверки того, зарегистрирован ли пользователь, проверки IP-адреса пользователя, прав доступа и т.д.

С тем, как использовать коллбеки в контроллерах и моделях мы разобрались — это достаточно простая и скучная тема. Действительно интересной темой будет то, что Rails предоставляет средства для создания собственных коллбеков! Сейчас мы научимся создавать собственные коллбеки!

В Rails код отвечающий за работу коллбеков находится в классе ActiveSupport::Callbacks для того, чтобы реализовать собственные колбеки, вам необходимо подключить этот класс в ваш код (для ActiveRecord и ActionController он уже подключен).

Пример объявления коллбеков:

require 'active_support/callbacks'

class CallbacksExample
  include ActiveSupport::Callbacks

  define_callbacks :hello

  set_callback :hello, :after, :bye, :bye

  def hello
    run_callbacks :hello do
      puts 'Hello!'
    end
  end

  def bye
    puts 'Bye!'
  end
end

CallbacksExample.new.hello

#Hello!
#Bye!
#Bye!

Если метод определен в какой-то библиотеке и вы не хотите лезть в код библиотеки и исправлять метод (что безусловно правильно), то можете воспользоваться таким простым приемом:

def method_from_some_lib
  run_callbacks :lib_for_some_method { super }
end

Нашли в статьи ошибки и неточности, или просто хотите узнать о чем-то более подробно? — Пишите об этом в комментариях.

Лучшая благодарность автору — ваши комментарии!

Tags: , , ,

Responses

  1. Игорь Александров says:

    октября 5, 2011 at 13:23 (#)

    before_validation_on_create
    after_validation_on_create
    

    Эти методы уже объвлены deprecated. Вместо них следует использовать методы before_validation и after_validation с параметрами.

    before_validation, :method_name, :on => [:create, :update]
    after_validation, :method_name, :on => [:create, :update]
    
  2. admin says:

    октября 7, 2011 at 07:56 (#)

    Игорь Александров, спасибо за замечание, все исправил.

  3. marat says:

    марта 26, 2012 at 12:17 (#)

    а как предотвратить запись в БД через callback? Грубо говоря не выполнились какие нибудь условия

  4. admin says:

    марта 27, 2012 at 16:57 (#)

    marat, для этого существует валидация свойств. Например можно проверять передано ли какое-либо свойство, соответствует ли оно регулярному выражению, какую длину имеет и т.д. Также можно собственные валидации создавать.

  5. Alex says:

    августа 6, 2012 at 11:49 (#)

    А есть ли статья о After_commit — про транзакционные колбеки?

  6. admin says:

    августа 6, 2012 at 19:10 (#)

    Alex, нет, но напишу в скором времени.

  7. Дмитрий says:

    декабря 11, 2013 at 21:19 (#)

    Здравствуйте.

    Хотя и ерунда, наверное, но все же:

    «… перед любым его запросом к контроллеру будет выполнен метод before_filter» — наверное, опечатка? Должно быть: «… будет выполнен метод my_method»?

    Спасибо за статью.

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.