Concerns в Rails 4

21/04/2013

Все заметили что в Rails 4 создается директория concerns в моделях и контроллерах? Я заметил, но не придал этому особого значения. Когда появилось свободное время – я таки решил посмотреть что же это такое. Concern'ы позволяют DRY'ить ваши модели и контроллеры. Этот функционал введен уже давно и в четвертой версии рельс директории создаются по умолчанию.

Задача

Пока искал информацию о концернах, вспомнил что мне надо реализовать функционал «безопасного» удаления элементов модели, чтобы запись не удалялась в базе данных, а просто переключался триггер «deleted» или устанавливалась дата удаления. На этом примере я и покажу как юзать concerns.

Немного кода

Чтобы не напрягать знающих кучей букв в этой блогозаписи (если такие вообще читают меня :В), я сразу опубликую исходный код:

# app/models/concerns/archivable.rb
module Archivable
  extend ActiveSupport::Concern

  included do
    scope :published, -> { where(deleted_at: nil) }
  end

  def delete
    self.touch :deleted_at
  end

  def revive
    self.update_attribute :deleted_at, nil
  end
end

# app/models/blip.rb
class Blip < ActiveRecord::Base
  include Archivable

  # ...
end

Вот так легко мы можем расширять наши модели. Для использования этого надо добавить поле deleted_at в нужную модель. А теперь немного поиграемся:

console> Blip.all

Blip Load (0.2ms)  SELECT "blips".* FROM "blips"
+----+-------------+---------+-------+-------------+--------------+------------+
| id | text        | user_id | group | created_at  | updated_at   | deleted_at |
+----+-------------+---------+-------+-------------+--------------+------------+
| 2  | second blip | 18      | 2     | 2013-04-... | 2013-04-2... |            |
| 3  | third blip  | 18      | 2     | 2013-04-... | 2013-04-2... |            |
| 4  | it's boo... | 18      | 2     | 2013-04-... | 2013-04-2... |            |
| 5  | lalki       | 18      | 2     | 2013-04-... | 2013-04-2... |            |
| 1  | whoa? It... | 18      | 1     | 2013-04-... | 2013-04-2... |            |
+----+-------------+---------+-------+-------------+--------------+------------+
5 rows in set


console> martyr = Blip.all.sample

Blip Load (0.3ms)  SELECT "blips".* FROM "blips"
+----+-------+---------+-------+----------------+-----------------+------------+
| id | text  | user_id | group | created_at     | updated_at      | deleted_at |
+----+-------+---------+-------+----------------+-----------------+------------+
| 5  | lalki | 18      | 2     | 2013-04-21 ... | 2013-04-21 1... |            |
+----+-------+---------+-------+----------------+-----------------+------------+
1 row in set


console> martyr.delete

(0.1ms)  BEGIN
(0.5ms)  UPDATE "blips" SET "deleted_at" = $1, "updated_at" = $2 WHERE "blips"."id" = 5  [["deleted_at", Sun, 21 Apr 2013 17:41:49 MSK +04:00], ["updated_at", Sun, 21 Apr 2013 17:41:49 MSK +04:00]]
(2.1ms)  COMMIT


console> Blip.all

Blip Load (0.3ms)  SELECT "blips".* FROM "blips"
+----+-------------+---------+-------+-------------+-------------+-------------+
| id | text        | user_id | group | created_at  | updated_at  | deleted_at  |
+----+-------------+---------+-------+-------------+-------------+-------------+
| 2  | second blip | 18      | 2     | 2013-04-... | 2013-04-... |             |
| 3  | third blip  | 18      | 2     | 2013-04-... | 2013-04-... |             |
| 4  | it's boo... | 18      | 2     | 2013-04-... | 2013-04-... |             |
| 1  | whoa? It... | 18      | 1     | 2013-04-... | 2013-04-... |             |
| 5  | lalki       | 18      | 2     | 2013-04-... | 2013-04-... | 2013-04-... |
+----+-------------+---------+-------+-------------+-------------+-------------+
5 rows in set


console> Blip.published

Blip Load (0.3ms)  SELECT "blips".* FROM "blips" WHERE "blips"."deleted_at" IS NULL
+----+-------------+---------+-------+-------------+--------------+------------+
| id | text        | user_id | group | created_at  | updated_at   | deleted_at |
+----+-------------+---------+-------+-------------+--------------+------------+
| 2  | second blip | 18      | 2     | 2013-04-... | 2013-04-2... |            |
| 3  | third blip  | 18      | 2     | 2013-04-... | 2013-04-2... |            |
| 4  | it's boo... | 18      | 2     | 2013-04-... | 2013-04-2... |            |
| 1  | whoa? It... | 18      | 1     | 2013-04-... | 2013-04-2... |            |
+----+-------------+---------+-------+-------------+--------------+------------+
4 rows in set


console> martyr.revive

(0.1ms)  BEGIN
(0.3ms)  UPDATE "blips" SET "deleted_at" = $1, "updated_at" = $2 WHERE "blips"."id" = 5  [["deleted_at", nil], ["updated_at", Sun, 21 Apr 2013 17:42:04 MSK +04:00]]
(1.6ms)  COMMIT


console> Blip.all

Blip Load (0.3ms)  SELECT "blips".* FROM "blips"
+----+-------------+---------+-------+-------------+--------------+------------+
| id | text        | user_id | group | created_at  | updated_at   | deleted_at |
+----+-------------+---------+-------+-------------+--------------+------------+
| 2  | second blip | 18      | 2     | 2013-04-... | 2013-04-2... |            |
| 3  | third blip  | 18      | 2     | 2013-04-... | 2013-04-2... |            |
| 4  | it's boo... | 18      | 2     | 2013-04-... | 2013-04-2... |            |
| 1  | whoa? It... | 18      | 1     | 2013-04-... | 2013-04-2... |            |
| 5  | lalki       | 18      | 2     | 2013-04-... | 2013-04-2... |            |
+----+-------------+---------+-------+-------------+--------------+------------+
5 rows in set


console>

Все работает, все красиво. Теперь немного о возможностях концернов, т.к. в нашем примере я использовал не все. Вот его структура:

module Archivable
  extend ActiveSupport::Concern

  included do
    # relations, callbacks, validations, scopes and others...
  end

  # instance methods

  module ClassMethods
    # class methods
  end
end

Код в блоке included выполняется при создании объекта модели, там можно хранить их свойства. Instance методы, очевидно, определяются для объекта модели, и class методы определяются для класса, их можно использовать в свойствах или как я в примере, для получения не архивированных записей.

Немного литературы: