среда, 15 апреля 2015 г.

Гем Haml-rails для работы с шаблонизатором haml

По умолчанию в веб-фреймворке Rails используются erb-шаблоны. Однако гораздо удобнее в использовании haml-шаблоны, синтаксис которых лаконичен.
Чтобы в Ruby on Rails включить поддержку haml-шаблонов, нужно в Gemfile добавить гем haml.
После этого ваши шаблоны можно называть так: app/views/products/index.html.haml.

Но что делать, если в нашем проекте уже используются erb-шаблоны? Вручную переписывать их на haml? Конечно, нет! Лучше воспользоваться гемом haml-rails.
Этот гем:
  • имеет команду для конвертации всех erb-шаблонов в haml-шаблоны:
    $ rake haml:erb2haml
    
  • позволяет делать scaffold генерацию с использованием haml-шаблонов.
    Команда
    $ rails g scaffold Order name address:text email pay_type 
    

    сгенерирует шаблоны в формате haml, а не erb, как это происходит по умолчанию.

пятница, 3 апреля 2015 г.

Пример оптимизации миграции в Ruby on Rails

Очень часто при разработке веб-приложения возникает необходимость внести изменения в структуру базы данных: добавить колонку, изменить ее тип, перенести значения из одной колонки в другую и т.п.
Для осуществления этих задач в веб-фреймворке Ruby on Rails существуют миграции.
$ rails g migration add_invoice_number_to_statements invoice_number:string{20}

Эта команда, выполненная в консоли, создаст файл миграции:
db/migrate/20150403113019_add_invoice_number_to_statements.rb
class AddInvoiceNumberToStatements < ActiveRecord::Migration
  def change
    add_column :statements, :invoice_number, :string, :limit => 20
  end
end

Предположим, что нам нужно, в колонку invoice_number еще поместить данные из другой колонки - invoice_id, имеющей тип int. Миграция может выглядеть так:
class AddInvoiceNumberToStatements < ActiveRecord::Migration
  def up
    add_column :statements, :invoice_number, :string, :limit => 20
    
    Statement.find_each do |statement|
      statement.update_column(:invoice_number, statement.invoice_id) if statement.invoice_id
    end
  end

  def down
    remove_column :statements, :invoice_number
  end
end

Вроде мы молодцы, в методе up написали условие if, благодаря которому не будет запроса на обновление поля invoice_number, если в поле invoice_id ничего нет, уменьшает общее число запросов к БД. Кроме того, мы использовали метод find_each, который загружает Statement пачками по 1000 штук, материализует их в объекты и обрабатывает. Это позволит нашей миграции не затратить много оперативной памяти. Какие мы молодцы!
Однако, это не так. Если в таблице statements 50 000 записей, то данная миграция приведет к 50 000 запросам UPDATE.

Иногда полезно миграцию писать на чистом добром SQL:
class AddInvoiceNumberToStatementsAndMigrateData < ActiveRecord::Migration
  def up
    add_column :statements, :invoice_number, :string, :limit => 20

    execute <<-SQL
      COMMENT ON COLUMN statements.invoice_number IS 'Номер счет-фактуры';
      UPDATE statements SET invoice_number = invoice_id::text;
    SQL
  end

  def down
    remove_column :statements, :invoice_number
  end
end

А теперь мы молодцы - миграция отработает молниеносно. А создание комментария к полю является актом творения добра и еще одним поводом для гордости.

четверг, 2 апреля 2015 г.

undefined method `to_datetime' for nil:NilClass

Ошибка undefined method `to_datetime' for nil:NilClass может возникнуть в Ruby on Rails при попытке сравнить две даты, одна из которых nil.
Приведем пример такого кода:
cannot(:revert, Statement) do |statement|
  statement.date <= StatementDeadline.instance.date
end

Чтобы устранить эту ошибку, нужно перед сравнением убедиться в существовании дат:
cannot(:revert, Statement) do |statement|
  statement.date && StatementDeadline.instance.date && (statement.date <= StatementDeadline.instance.date)
end

Безусловно, это увеличивает объем кода, однако позволяет побороть ошибку undefined method `to_datetime' for nil:NilClass, которая может сломать шаблон. Теперь пользователь вместо того, чтобы созерцать ошибку, сможет увидеть часть нужной ему информации.