Сегодня вспомнил очередной раз про всё это и решил записать.
В программе: именованные скоупы, колбэки, опции отношений, 'includes' и has_many :through
1. Именованные скоупы
Часто используемые выборки данных, вроде Post.where(published: true).order('created_at desc')
, принято выносить из контроллеров в сами модели. Их называют скоупами.
class Post < ActiveRecord::Base
# ...
scope :recent, where(published: true).order('created_at desc')
end
2. Колбэки
Модели имеют по 2 колбэка (с приставками before и after) для следующих действий:
validation
save
create
update
destroy
В качестве примера использования можно привести что-то вроде after_save :send_welcome_email
. Если before-колбэк возвращает false, действие прерывается.
3. Опции отношений
Для вещей вроде has_many, has_one, belongs_to можно указывать дополнительные опции:
dependent: :destroy # вызовет destroy у всех ассоциированных объектов
foreign_key: :author # меняет внешний ключ
primary_key: :myid # понятно что
validate: true # во время валидации проведёт оную так же и для ассоциированных моделей
# и другие
4. 'includes' в применении к отношениям
Если представить себе такую ситуацию:
def index
@posts = Post.all
...
<% @posts.each do |post| %>
<tr>
<td><%= post.title %></td>
<td><%= post.author.name %></td>
</tr>
<% end %>
То для каждого поста rails будет делать запрос в БД на предмет данных его автора. Это не эффективно и плохо масштабируемо. Чтобы этого не происходило, нужно сделать так:
def index
@posts = Post.includes(:author).all
Тогда запросы к базе будут куда более красивыми и экономными:
SELECT * FROM 'posts'
SELECT * FROM 'users' WHERE 'id' IN (2,7,8)
5. has_many :through
При связи многие-ко-многим для разрешения используется связующая таблица, а rails умеет это понимать и предоставлять доступ к связанным таким образом данным. Для этого в каждой из 2 моделей указывается:
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :role
end
class User < ...
has_many :assignments
has_many :roles, through: assigments
# ...
end
> user = User.last
=> #<User id: 1, ...>
> user.roles << Role.find_by_title("admin")
# SELECT * FROM 'roles' WHERE 'title' = 'admin' LIMIT 1
# INSERT INTO 'assignments' ('role_id', 'user_id') VALUES (2, 1)
> user.roles
# SELECT 'roles'.* FROM 'roles' INNER JOIN 'assignments' ON 'roles'.'id' = 'assignments'.'role_id'
# WHERE 'assignments'.'user_id' = 1