ActiveRecord Hacks

от автора

Сегодня я поделюсь своим набором не всегда очевидных функций и возможностей Active Record, с которыми я столкнулся в процессе разработки Ruby on Rails приложений или нашел в чужих блогах.

Обход валидации при использовании update_attributes

Стандартный метод update_attributes не имеет ключа, позволяющему обойти валидацию, поэтому приходится прибегать к assign_attributes с последующим save

@user = User.find(params[:id]) @user.assign_attributes(:name, "") @user.save(validate: false) 

Разумеется – лучше не прибегать к этому способу очень часто 🙂

Разделение на 2 непересекающихся коллекции

Иногда возникает задача разделения выборки объектов на 2 непересекающиеся коллекции. Сделать это можно с помощью такого использования scope.

Article < ActiveRecord::Base   scope :unchecked, where(:checked => false)    #or this, apologies for somewhat unefficient, but you already seem to have several queries   scope :unchecked2, lambda { |checked| where(["id not in (?)", checked.pluck(:id)]) }  end 

Ну и соответственно доступ к обеим коллекциям можн ополучить с помощью

Article.unchecked Article.unchecked2(@unchecked_articles) 

pluck

В предыдущем примере я использовал метод pluck. Наверняка каждый из вас использовал что-то типа

Article.all.select(:title).map(&:title) 

или даже

Article.all.map(&:title)

Так вот – pluck позволяет сделать это проще

Article.all.pluck(:title)

Доступ к базовому классу

В процессе работы над одним проектом я столкнулся с большой вложенностью классов моделей и необходимостью добраться до корневого класса. Классы выглядели примерно так:

class Art < ActiveRecord::Base end  class Picture < Art end  class PlainPicture < Picture end

Для того, чтобы добраться из PlainPicture до Art можно использовать метод becomes

@plain_pictures = PlainPicture.all  @plain_pictures.map { |i| if i.class < Art then i.becomes(Art) else i end }.each do |pp|   #do something with Art end

first_or_create и first_or_initialize

Еще один замечательный метод – first_or_create. Из названия ясно что он делает, а мы давайте посмотрим как его можно использовать

Art.where(name: "Black square").first_or_create

Также мы его можем использовать в блочной конструкции

Art.where(name: "Black square").first_or_create do |art|   art.author = "Malevich" end    

А если вы не хотите сохранять – можно использовать first_or_initialize например таким образом

@art = Art.where(name: "Black square").first_or_initialize

scoped и none

Обратите внимание на еще 2 замечательных метода – scoped и none. Как они работают – покажу на примере, при этом хочу отметить, что надо разделять их поведение в rails3 и rails4, так как оно различается.

def filter(filter_name)   case filter_name   when :all     scoped   when :published     where(:published => true)   when :unpublished     where(:published => false)   else     none   end end

Как поведет себя метод в случае передачи в него :published и :unpublished я надеюсь вам понятно, различия в версиях rails тут нет.

Использование scoped в нашем примере в случае rails3 позволяет создать анонимный скоп, который может использоваться для сложных составных запросов. Если попытаться его применить в rails4, то можно увидеть сообщение, что метод стал deprecated и вместо него предлагается использовать Model.all. В случае же rails3 Model.all возвращает не ожидаемый нами ActiveRecord::Relation, а Array.

Ситуация с none похожа на scoped с точностью до наоборот 🙂 Этот метод возвращает пустой ActiveRecord::Relation, но работает он только в rails4. Нужен он в том случае, если нужно вернуть нулевые результаты, Для использования в rails3 есть такой workaround:

scope :none, where(:id => nil).where("id IS NOT ?", nil)

или даже такой (например в initializer)

class ActiveRecord::Base  def self.none    where(arel_table[:id].eq(nil).and(arel_table[:id].not_eq(nil)))  end end

find_each

Метод find_each очень удобен для того, чтобы обработать большое количество записей из базы данных. Можно было бы конечно сделать выборку типа

Article.where(published: true).each do |article|   #do something end

Но в таком случае нам придется хранить в памяти всю выборку целиком, что в случае большого объема данных очень нерентабельно. В этом случае правильнее будет использовать такой подход

Article.where(published: true).find_each do |article|   #do something end

который небольшими выборками (по 1000 объектов за раз по умолчанию) обрабатывает данные.

to_sql и explain

Два метода, которые помогут вам разобраться как работает ваш запрос.

Art.joins(:user).to_sql

вернет вам sql-запрос, который приложение составит для завпроса в базу данных, а

Art.joins(:user).explain

покажет техническую информацию по запросу – примерное количество времени, объем выборки и другие данные.

scoping

Этот метод позволяет сделать выборку внутри выборки, например

Article.where(published: true).scoping do   Article.first end

осуществит запрос типа

SELECT * FROM articles WHERE published = true LIMIT 1

merge

Еще один интересный метод, который позволяет пересечь несколько выборок. Например

class Account < ActiveRecord::Base   # ...    # Returns all the accounts that have unread messages.   def self.with_unread_messages     joins(:messages).merge( Message.unread )   end end

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

ссылка на оригинал статьи http://habrahabr.ru/post/181145/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *