четверг, 25 мая 2017 г.

Couldn't find file 'jquery' with type 'application/javascript'

Данная ошибка возникла в Rails 5 проекте. Связана она с тем, что не удается найти файл jquery. Устранить эту ошибку можно по-разному, все зависит от того, куда именно вы хотите поместить зависимость от jquery.

Добавить зависимость от jquery в проект

1) В этом случае для устранения нужно добавить в Gemfile проекта:
gem 'jquery-rails'

2) Согласно инструкции по подключению гема jquery-rails, нужно еще добавить следующее в /app/assets/javascripts/application.js
//= require jquery
//= require jquery_ujs

3) В корневой папке проекта сделать bundle install.

Добавить зависимость от jquery в engine или гем

Если к проекту подключается ваш собственный engine или гем, то зависимость от jquery можно определить в нем.
1) Укажите зависимость в корневом файле с расширением .gemspec:
Gem::Specification.new do |s|
  # ...
  s.add_dependency 'jquery-rails'
end

2) Важно! Теперь подключите 'jquery-rails' в файле YOUR-GEM/lib/YOUR-GEM/engine.rb:
# Здесь вручную подключаем гем, указанный в зависимостях
require 'jquery-rails'

module YourGem # <-- тут название вашего гема
  class Engine < ::Rails::Engine
    # ...
  end
end

3) В корневой папке проекта сделать bundle install.

среда, 3 июня 2015 г.

Пример рефакторинга ruby-метода

Однажды в нашем проекте на Ruby on Rails в одном классе мне попался метод:
def self_payer
  if @paid_type == PaymentContact::TYPE_PERSONAL
    @self_payer ||= Payer.self_payer.is_developer.first || Payer.find_by_title!(SELF_PAYER)
  else
    if @business_type.title == Payer::BUSINESS_TYPE_ENTREPRENEUR
      @self_payer ||= Payer.self_payer.is_developer.first
    else
      @self_payer ||= Payer.find_by_title!(SELF_PAYER)
    end
  end
end

Здесь видно повторение Payer.self_payer.is_developer.first, которое мы вынесем в приватный метод first_developer.

Выражение @paid_type == PaymentContact::TYPE_PERSONAL использует переменную экземпляра класса @paid_type, а также выглядит громоздким, поэтому вынесем его в метод personal_payment? .

Выражение @business_type.title == Payer::BUSINESS_TYPE_ENTREPRENEUR тоже использует переменную экземпляра класса и также выглядит громоздко, выносим его в метод entrepreneur?.

Подумаем, пообщаемся с коллегами и выясним, что Payer.self_payer.is_developer.first всегда будет возвращать какой-то результат, поэтому уберем в первом if выражение: || Payer.find_by_title!(SELF_PAYER) .

Вот, что у нас получится в результате рефакторинга ruby метода:
def self_payer
  if personal_payment?
    @self_payer ||= first_developer
  else
    if entrepreneur?
      @self_payer ||= first_developer
    else
      @self_payer ||= Payer.find_by_title!(SELF_PAYER)
    end
  end
end

Поговорим с коллегами, чтобы понять суть этого метода. Оказывается, что он должен возвращать первого разработчика (first_developer), если платеж делает физ. лицо ИЛИ индивидуальный предприниматель. Исходя из этого, оставляем один if:
def self_payer
  if personal_payment? || entrepreneur?
    @self_payer ||= first_developer
  else
    @self_payer ||= Payer.find_by_title!(SELF_PAYER)
  end
end

В этом получившемся ruby методе переменная @self_payer повторяется. Поискав по классу, где этот метод расположен, мы выясняем, что эта переменная больше нигде не используется. Поэтому метод упрощается до такого вида:
def self_payer
  (personal_payment? || entrepreneur?) ? first_developer : Payer.find_by_title!(SELF_PAYER)
end

Таким образом, путем рефакторинга мы 11 строк исходного метода сократили до 3 путем пересмотра логики работы и вынесения излишне подробных действий в отдельные методы. Теперь метод self_payer читается легко: "Если платеж осуществляет ФЛ или ИП, то в качестве собственного юр. лица нужно вернуть первую попавшуюся компанию-разработчик (first_developer). В противном случае нужно найти плательщика по названию, сохраненному в константе класса (SELF_PAYER)".

среда, 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, которая может сломать шаблон. Теперь пользователь вместо того, чтобы созерцать ошибку, сможет увидеть часть нужной ему информации.

воскресенье, 29 марта 2015 г.

Примеры упрощения кода

Упрощать код очень важно, т.к. это увеличивает его читаемость. Одну и ту же задачу можно сделать одной, так и десятью строчками кода. Чем код компактнее, тем его легче поддерживать. Разумеется, не нужно доходить до фанатизма, пытаясь уместить все в одну строчку, т.к. это может привести к ухудшению его читаемости.

  1. lots.map { |lot| lot.start_date }.compact.min
    
    # Упростили
    lots.map(&:start_date).compact.min
    
  2. if age.present?
      I18n.t('firm.regular')
    else
      I18n.t('firm.new')
    end
    
    # Упростили:
    I18n.t("firm.#{age.present? ? :regular : :new}")
    
  3. if company.type_id == nil || company.type_id == CompanyType.online.id
      return false
    end
    
    # Упростили
    return false if [nil, CompanyType.online.id].include? company.type_id
    
  4. if companies.select { |company| company.store_type == company.online_type }.empty?
      return false
    end
    
    # Упростили
    return false if companies.none? { |company| company.store_type == company.online_type }
    

пятница, 27 марта 2015 г.

Rails: undefined method `to_datetime' for nil:NilClass

В Rails может возникнуть ошибка undefined method `to_datetime' for nil:NilClass при выполнении следующего кода:
filtered_products = products.select do |product|
  product.start_date && product.end_date &&
  (product.start_date <= start_date_ago && start_date_ago <= product.end_date) ||
  (start_date_ago <= product.start_date && product.start_date < first_start_date)
end

Здесь берутся товары и отбираются те из них, что попадают в нужный диапазон дат.
product.start_date - дата начала периода публикации товара
product.end_date - дата конца периода публикации товара
В начале мы проверяем, что у товара есть эти параметры, а уже после этого сравниваем их с датами first_start_date и start_date_ago, которые определены где-то выше по коду. Это позволяет нам избежать ошибки:
NoMethodError: undefined method `<' for nil:NilClass.

Почему же в приведенном выше коде возникает ошибка undefined method `to_datetime' for nil:NilClass ?

Это связано с тем, что мы по невнимательности нарушили расстановку скобок. Сначала нам нужно убедиться в наличии параметров product.start_date и product.end_date, а уже потом выполнять остальные сравнения:
filtered_products = products.select do |product|
  product.start_date && product.end_date &&
  ((product.start_date <= start_date_ago && start_date_ago <= product.end_date) ||
  (start_date_ago <= product.start_date && product.start_date < first_start_date))
end




вторник, 10 марта 2015 г.

ArgumentError: comparison of String with Date failed

Что не так со следующим кодом?
# здесь custom_lot_attributes - колонка типа 'hstore' в таблице lots
start_date = lot.custom_lot_attributes['start_date']
if start_date && start_date < Date.today
  # ...
end

При выполнии этого участка кода возникает ошибка:
ArgumentError: comparison of String with Date failed
Это связано с тем, что мы пытаемся сравнить дату со строкой. Из hstore мы получаем строку, поэтому в переменной start_date содержится дата в виде строки, к примеру "2015-03-10".
Для устранения ошибки нужно преобразовать строку в дату. Для этого в Rails есть метод to_date.
Теперь ошибки возникать не будет:

start_date = lot.custom_lot_attributes['start_date'].to_date
if start_date && start_date < Date.today
  # ...
end

А сможете ли вы найти ошибку в этом коде?
start_date = lot.custom_lot_attributes['start_date'].to_date
if start_date && start_date < Date.today
  lot.custom_lot_attributes['start_date'] = Date.today
  lot.custom_lot_attributes['end_date'] = 
    lot.custom_lot_attributes['end_date'] +
    (Date.today - lot.custom_lot_attributes['start_date'])
end

Данный код будет вызывать ошибку:
TypeError: can't convert Rational into String
Причина аналогична предыдущей, мы забыли преобразовать строковое значение, полученное из hstore, в дату.
Вот как должно быть:
start_date = lot.custom_lot_attributes['start_date'].to_date
if start_date && start_date < Date.today
  lot.custom_lot_attributes['start_date'] = Date.today
  lot.custom_lot_attributes['end_date'] = 
    lot.custom_lot_attributes['end_date'].to_date +
    (Date.today - lot.custom_lot_attributes['start_date'].to_date)
end


вторник, 10 февраля 2015 г.

NoMethodError: undefined method `[]' for nil:NilClass

Данная ошибка может возникать в Ruby при попытке обратиться к несуществующему свойству объекта, который почему-то стал nil.
Рассмотрим пример, запустив консоль Rials (rails c):
hash = {a: 1, b: {c: 2}}
=> {:a=>1, :b=>{:c=>2}}

# Получаем элемент с индексом :a
hash[:a]
=> 1

# Получаем элемент с индексом :b
hash[:b]
=> {:c=>2}

# Получаем элемент с индексом :b, а для него - с индексом :c
hash[:b][:c]
=> 2

# Пытаемся получить эл-нт с индексом :zzz, а для него - с индексом :c
hash[:zzz][:c]
NoMethodError: undefined method `[]' for nil:NilClass

Как видно, при попытке получить элемент с индексом :zzz мы получаем nil, и уже для nil мы пытаемся найти элемент с индексом :c, что приводит к ошибке, ведь у nil нет метода [].

Решение:

hash[:zzz].try(:[], :c)

Обратите внимание, что этот способ работает только в Rails. В чистом Ruby он не поможет, там можно использовать трюк с if:
hash[:zzz][:c] if hash[:zzz]
 

пятница, 30 января 2015 г.

Шаблонизатор haml вместо erb

В веб-фреймворке Ruby on Rails есть отличная возможность подключить новый шаблонизатор haml вместо имеющегося стандартного erb-шаблонизатора. Для этого достаточно в Gemfile проекта добавить строчку:  
gem "haml"

Так выглядит erb-шаблон для отображения товаров - index.html.erb:
<h1>Listing products</h1>
<% if notice %>
  <p id="notice"><%= notice %></p>
<% end %>

<table class="products">
<% @products.each do |product| %>
  <tr class="<%= cycle('list_line_odd', 'list_line_even') %>">
    <td>
      <%= image_tag(product.image_url, class: 'list_image') %>
    </td>
    <td class="list_description">
      <dl>
        <dt><%= product.title %></dt>
        <dd><%= truncate(strip_tags(product.description),
               length: 80) %></dd>
      </dl>
    </td>
    <td class="list_actions">
      <%= link_to 'Показать', product %><br/>
      <%= link_to 'Ред.', edit_product_path(product) %><br/>
      <%= link_to 'Удалить', product, method: :delete,
                  data: { confirm: I18n.t('app.delete_confirm') } %>
    </td>
  </tr>
<% end %>
</table>
<br />
<%= link_to 'New product', new_product_path %>

А вот тот же шаблон с использованием haml - index.html.haml:
%h1 Listing products
- if notice
  %p#notice= notice
%table.products
  - @products.each do |product|
    %tr{class: "#{cycle('list_line_odd', 'list_line_even')}"}
      %td
        %p= image_tag(product.image_url, class: 'list_image')
      %td.list_description
        %dl>
          %dt= product.title
          %dd= truncate(strip_tags(product.description), length: 80)
      %td.list_actions
        = link_to 'Показать', product
        %br/
        = link_to 'Ред.', edit_product_path(product)
        %br/
        = link_to 'Удалить', product, method: :delete, data: { confirm: I18n.t('app.delete_confirm') }
%p= link_to 'New product', new_product_path

Если вы используете в работе Ruby on Rails, использование шаблонизатора haml позволит вам сократить количество кода в ваших шаблонах и сделает их более читаемыми. Поначалу может показаться, что нет смысл учить чью-то придумку, ведь есть старый добрый html. Однако в больших проектах кода много и его читаемость можно улучшить применив этот шаблонизатор. Кроме того, правила haml-шаблонизатора весьма просты, освоить их можно минут за 20, проделав самостоятельно примеры, приведенные на официальном сайте шаблонизатора haml.

Также есть аналогичный шаблонизатор - slim, синтаксис которого еще лучше. Однако, у нас в проекте используется haml, поскольку "так исторически сложилось".

понедельник, 1 декабря 2014 г.

Определить первый родительский класс

В языке программирования Ruby каждый объект имеет как минимум один родительский класс. Однако большинство объектов имеют в цепочке наследования несколько классов. Каждый дочерний класс может переопределить метод, определенный в родительском.
Требуется написать метод
def location_in_hierarchy(object, method)
...
end
который будет принимать объект и метод и возвращать класс, в котором данный метод был впервые определен.

Сериализация и десериализация объекта класса в Ruby

Объект в языке Ruby можно представить в виде строки. Также можно преобразовать строку обратно в объект. Для этого необходимо подключить библиотеку yaml.
# encoding: utf-8
require "yaml"

class Car
  def initialize(model, speed)
    @model = model
    @speed = speed
  end

  # Десереализуем в объект
  def self.deserialize(yaml_string)
    YAML::load(yaml_string)
  end
  
  # Объект сериализуем
  def serialize
    YAML::dump(self)
  end

  def to_s
    "Car: [model = #{@model}, speed = #{@speed}]"
  end
end

audi = Car.new('Audi', 400)
yaml = audi.serialize
puts "yaml выглядит так:"
p yaml
puts "Yaml можно обратно преобразовать в объект\n: #{Car.deserialize(yaml)}"
puts

bmw = Car.new('BMW', 600)
mazda = Car.new('Mazda', 120)
cars = [bmw, mazda]

puts "Мы можем даже сериализовать массивы: #{cars}"
Программа выведет:
yaml выглядит так:
"--- !ruby/object:Car\nmodel: Audi\nspeed: 400\n"
Yaml можно обратно преобразовать в объект
: Car: [model = Audi, speed = 400]

Мы можем даже сериализовать массивы: [Car: [model = BMW, speed = 600], Car: [model = Mazda, speed = 120]]

пятница, 21 ноября 2014 г.

Ruby on Rails показывает пустую страницу после деплоя на locum.ru

Если говорить о деплое Ruby on Rails на хостинг locum.ru, то это отображение пустой страницы при переходе по url проекта может быть вызвана рядом причин.

1. Попробуйте перезапустить мастер-процесс unicorn проекта, зайдя в админку locum и нажав на кнопку "Перезапустить проект" в настройках проекта.

2. Если по-прежнему по обращению по url проекта отображается пустая страница, то проверьте файл: config/secrets.yml:
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

Вы определяли на сервере locum значение этой переменной?

3. Определим переменную SECRET_KEY_BASE на сервере locum. Для этого зайдем на сервер и добавим ее в ~/.bashrc:
    $ ssh hosting_kenny@fluorine.locum.ru
    $ vim ~/.bashrc

В редакторе vim откроется указанный файл. Добавьте в конец строку:
# Содержимое ОБЯЗАТЕЛЬНО заключите в ДВОЙНЫЕ квычки
export SECRET_KEY_BASE="AAAAB ...I5t9YYbX"

Содержимое этой переменной можно сгерерировать у себя на ПК. Для этого нужно перейти в корневую папку своего проекта и запустить команду:
    $ rake secret
f400fdd1ad5fb822012c3a09a8b5f7871f9e35105198b5d69
d7623167934b810c011397ae921800e234405bddbf05bbc8
1f5f8730f06a5a443c4bb784b499705

4. Проверим, видит ли Ruby установленную переменную. Откроем новое окно терминала и зайдем на удаленный сервер locum. Запустим интерактивную консоль ruby (irb):
    $ ssh hosting_kenny@fluorine.locum.r
    $ irb
    irb(main):002:0> ENV['SECRET_KEY_BASE']

Вы должны увидеть значение, которое вы установили этой переменной.
Через админку хостинга locum перезапустите проект.
После этого данная переменная станет доступна в окружении сервера, и вы не будете видеть пустой страницы, когда перейдете по url своего проекта.

Если данный способ вам не помог, просто откройте файл config/secrests.yml и определите secret_key_base для среды production непосредственно:

production:
  secret_key_base: edb5d003ae05f ..... da17d5bc

четверг, 20 ноября 2014 г.

Урок по созданию git-репозитория на хостинге locum

На хостинге locum.ru можно создать приватный git-репозиторий для своих проектов. В этом уроке мы пошагово покажем, как это сделать.

Предупреждение:

1.Подключимся по ssh на удаленный сервер locum:
    $ ssh hosting_kenny@fluorine.locum.ru

Здесь hosting_kenny - наше имя пользователя на сервере, fluorine.locum.ru - адрес сервера.

2. Перейдем в папку git, расположенную в домашней директории на удаленном сервере (если этой папки нет, создайте ее):
    $ cd ~/git

3. В папке ~/git создадим папку blog (совпадает с названием нашего проекта в админке) и инициируем git-репозиторий нашего проекта:
    $ mkdir ~/git/blog.git
    $ cd ~/git/blog.git
    $ git init --bare

Данной командой мы создаем "голый" репозиторий (без рабочего каталога). По соглашению папки "голых" репозиториев оканчиваются на .git. С этого момента у нас для проекта blog есть удаленный репозиторий.

4. На своем ПК перейдем в папку своего проекта, инициируем там локальный репозиторий и добавим ссылку на созданный удаленный репозиторий.
    $ ~/my_projects/blog
    $ git init
    $ git remote add origin ssh://hosting_kenny@fluorine.locum.ru/home/hosting_kenny/git/blog.git

/home/hosting_kenny/git/blog.git - это абсолютный путь к нашему репозиторию на хостинге. Последней командой мы добавили в наш локальный репозиторий удаленный и назвали его origin.

5. У себя на ПК сделаем начальный коммит и отправим все изменения в наш удаленный git-репозиторий на сервере locum:
    $ git add .
    $ git commit -m "Initial commit"
    $ git push -u origin master

   
Флаг -u (--set-upstream) позволяет установить соответствие между нашим репозиторием и удаленным. Это впоследствии позволит нам использовать короткие команды: git push.

Создание соединения с удаленным сервером по ssh

Мы рассмотрим создание ssh-соединения с удаленным ПК в ОС Linux (Ubuntu 14.04 х64).

1. Проверим, установлен ли у нас в системе ssh. Откроем окно терминала (рекомендуем использовать Terminator) и введем команду:
    $ ssh
Должно вывестись примерно это:   
    usage: ssh [-1246AaCfgKkMNnqsTtVvXxYy] [-b bind_address] [-c ciphe...

Если возникла ошибка, вам нужно установить ssh:
    $ sudo apt-get install ssh

2. Попытаемся установить соединение с удаленным компьютером:
    $ ssh hosting_kenny@fluorine.locum.ru

Здесь hosting_kenny - имя пользователя на сервере, а fluorine.locum.ru - адрес сервера. Укажите свои имя пользователя и адрес сервера.
Скорее всего, удаленный сервер потребует от нас пароль. И так каждый раз, пока мы не настроим ssh-соединение при помощи ключей.

3. Проверим, есть ли у нас на ПК сгенерированные приватный/публичный ключи:
ls -al ~/.ssh
Эта команда выводит файлы папки .ssh домашней директории нашего пользователя ОС. К примеру, это могут быть такие файлы:
  • authorized_keys - перечень публичных ключей пользователей, которым разрешено подключаться к нашему ПК по ssh.
  • id_rsa - приватная часть ключа (как правило, защищается от посторонних посредством пароля)
  • id_rsa.pub - публичная часть ключа

Если файлов id_rsa.pub и id_rsa у вас на ПК нет, то сгенерируем их:
    $ ssh-keygen -t rsa -C "your_email@example.com"

4. Теперь, когда у нас есть файл id_rsa.pub, поместим его содержимое на удаленный сервер в директорию ~/.ssh/authorized_keys. Выполним у себя на ПК команду:
    $ cat ~/.ssh/id_rsa.pub | ssh hosting_kenny@fluorine.locum.ru 'cat >> ~/.ssh/authorized_keys'
Эта команда добавляет ваш публичный ключ к списку авторизованных ключей удаленного сервера. При этом у нас потребуют ввести пароль.

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

Кстати, сгенерированный публичный ключ id_rsa.pub можно также добавить к вашему аккаунту на github.com, чтобы свободно осуществлять соединение с ним.

Простой проект на Ruby on Rails

Создадим на своем локальном компьютере простой проект, который потом мы разместим на хостинге locum посредством Capistrano.

1. Убедимся, что у нас на локальном ПК используется версия Ruby 2.1.5:
    $ rvm use 2.1.5
    $ ruby -v
    ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-linux]

2. Проверим, какая версия rails используется:
    $ rails -v
    Rails 4.1.8
Если возникла ошибка, то вам нужно установить Ruby on Rails для выбранной в rvm версии ruby:
    $ gem install rails

3. Создадим заготовку Rails-проекта и запустим сервер разработки:
    $ rails new blog
    $ cd blog
    $ rails s
Перейдите в браузере по адресу: http://127.0.0.1:3000/ Там вы должны будете увидеть приветствие Welcome aboard.

4. Остановим сервер разработки и сгенерируем контроллер posts:
    $ rails g controller posts index
При этом будет создан ряд файлов.

5. Сгенерируем модель Post:
    $ rails g model Post title:string{100} text:text
Будут созданы:
  • модель app/models/post.rb
  • файл миграции db/migrate/20141120101447_create_posts.rb
6. На основе созданного файла миграций создадим для модели Post в базе данных таблицу. Для этого запустим миграцию:
    $ rake db:migrate

7. Для активации CRUD для модели Post отредактируем файл config/routes.rb:
Rails.application.routes.draw do
  root 'posts#index'
  resources :posts
end

8. Запустим сервер разработки и перейдем на страницу отображения статей:
    $ rails s
Перейдем по адресу: http://127.0.0.1:3000/.
Здесь должен отображаться список названий статей. Но у нас пока нет ни одной статьи, поэтому создадим форму для их добавления.

9. Форма добавления должна располагаться по адресу: http://localhost:3000/posts/new
При переходе мы получим ошибку: The action 'new' could not be found for PostsController.
Для ее устранения добавим экшен new в файл app/controllers/posts_controller.rb:
class PostsController < ApplicationController
  def index
  end

  def new
    @post = Post.new
  end
end

10. Если еще раз попробуем перейти по адресу из п.9, то получим ошибку об отсутствии шаблона: Missing template posts/new, application/new with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :jbuilder]}. Searched in: * "/home/yesnik/learn/rails_projects/blog/app/views"
Для ее устранения создадим файл app/views/posts/new.html.erb:
<h3>Добавить новый пост</h3>

<%= form_for :post, url: posts_path do |f| %>
  <p>
    <%= f.label :title, "Заголовок" %><br>
    <%= f.text_field :title %>
  </p>
  <p>
    <%= f.label :text, "Содержание" %><br>
    <%= f.text_area :text %>
  </p>
  <p>
    <%= f.submit "Создать" %>
  </p>
<% end %>

После этого при переходе по адресу из п.9 мы увидим форму добавления поста.

11. Если попытаться создать пост, мы получим ошибку:
The action 'create' could not be found for PostsController
Для ее устранения добавим экшен create в файл app/controllers/posts_controller.rb.
class PostsController < ApplicationController
  def index
  end

  def new
    @post = Post.new
  end

  def create
   @post = Post.new(post_params)
   @post.save
   # После сохранения делаем редирект на новую статью
   redirect_to @post
  end

  def show
   @post = Post.find(params[:id])
  end

  private
  def post_params
   params.require(:post).permit(:title, :text)
  end
end

12. Однако добавив экшен и отправив форму, мы обнаружим еще одну ошибку: The action 'show' could not be found for PostsController. Для ее устранения нам понадобится в файл контроллера добавить метод show, а также создать шаблон для показа поста app/views/posts/show.html.erb:
<p><%= link_to "К списку постов", posts_path %></p>
<h3><%= @post.title %></h3>
<p><%= @post.text %></p>

13. Теперь после добавления поста происходит редирект на страницу его просмотра (show). Нажав ссылку "К списку постов", мы переходим на страницу, где должны показываться названия всех постов. Чтобы они отобразились, отредактируем экшен index в файле контроллера app/controllers/posts_controller.rb:
class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
  # ...
end

Также отредактируем шаблон app/views/posts/index.html.erb:
<h3>Список постов</h3>
<ul>
<% @posts.each do |post| %>
  <li>
   <%= link_to post.title, post_path(post) %> 
   <small>от <%= post.created_at.strftime "%d.%m.%Y в %H:%M" %></small>
   <%= link_to "ред.", edit_post_path(post) %>
   <%= link_to "[X]", post_path(post), method: :delete, 
    data: {confirm: "Точно удалить пост?"} %>
  </li>
<% end %>
</ul>
<%= link_to "Добавить пост", new_post_path %>

14. Если вы теперь перейдете по ссылке http://localhost:3000/posts , то увидите ссылки ред. и [Х] рядом с названием каждой статьи. Для того, чтобы они заработали, добавим экшены edit и destroy в контроллер app/controllers/posts_controller.rb:
class PostsController < ApplicationController

  def edit
    @post = Post.find(params[:id])
  end

  def destroy
    @post = Post.find(params[:id])
    @post.destroy
    # После удаления поста редирект на список постов
    redirect_to posts_path
  end
  # ...
end

Теперь при нажатии на [X] произойдет удаление поста.

15. Чтобы заработало редактирование содержимого поста, добавим шаблон app/views/posts/edit.html.erb:
<h3>Редактирование поста</h3>
<%= form_for @post do |f| %>
 <p>
  <%= f.label :title, "Заголовок" %><br>
  <%= f.text_field :title %>
 </p>
 <p>
  <%= f.label :text, "Содержание" %><br>
  <%= f.text_area :text %>
 </p>
 <p>
  <%= f.submit "Сохранить" %>
 </p>
<% end %>

Т.к. posts мы сделали ресурсом в config/routes.rb, то Rails сам определит нужный экшен.

16. Если отредактировать статью и сохранить, возникнет ошибка: The action 'update' could not be found for PostsController. Для ее устранения добавим экшен update в контроллер app/controllers/posts_controller.rb:
class PostsController < ApplicationController

  def update
    @post = Post.find(params[:id])
      if @post.update(post_params)
        redirect_to @post
      else
        render 'edit'
    end
  end
  # ...
end

Поздравляем! Теперь у вас есть приложение на Ruby on Rails, в котором можно добавлять, редактировать и удалять посты.






Настройка Ruby on Rails на хостинге Locum

Уже давно пользуюсь хостингом locum.ru для своих проектов. И вот возникла необходимость разместить там проект на Ruby on Rails. Сделать это было нелегко, поэтому выкладываю инструкцию по деплою Rails приложений на этом хостинге.
Предупреждение: Чтобы легко разрабатывать на Ruby on Rails, используйте на своем ПК ОС Linux. Мы используем Ubuntu 14.04 x64.

Мы будем деплоить простое Ruby on Rails приложение, созданное по уроку. Вы можете деплоить свой проект. Главное, чтобы он был работоспособен у вас на ПК, когда вы запускаете сервер разработки командой rails s.

1. Создадим в админке хостинга locum.ru проект. В админке хостинга: Услуги > Проекты > кнопка "Создать проект".
Тип проекта: Ruby on Rails. Имя: blog. Создать базу данных: PostgreSQL.
После создания проекта в его настройках вы увидите сервер, к которому он привязан. В нашем случае это - fluorine. В настройках проекта также выберем версию интерпретатора Ruby: 2.1.5.
Нужно подождать некоторое время (у нас это заняло 5-10 мин.), чтобы адрес, к которому проект привязан, стал доступен: http://blog.kenny.lclients.ru/ , где kenny - имя пользователя на хостинге locum.
Перейдя по адресу проекта, вы увидите страницу стандартного приветствия: Welcome aboard

2. Для деплоя будем применять capistrano, поэтому нужно обязательно поместить проект в GIT-репозиторий. Поскольку хостинг locum позволяет создать приватный GIT-репозиторий, то мы этим воспользуемся. О том, как это сделать, можно узнать в уроке по созданию git-репозитория на хостинге locum.

3. Проверим окружение нашего локального ПК.
3.1. Проверим версию ruby и rails:
    $ ruby -v
    ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-linux]
    $ rails -v
    Rails 4.1.8

Примечание: На вашем ПК можно установить несколько версий ruby. Это можно сделать при помощи rvm manager. Установите ту версию, которая будет использоваться на сервере. В нашем случае, это ruby 2.1.5.

3.2. Проверим, установлен ли у нас capistrano:
    $ cap -V
    Capistrano Version: 3.2.1 (Rake Version: 10.3.2)
Если нет, установим:
    $ sudo apt-get install capistrano

Примечание: После установки капистрано при попытке выполнить cap -V может возникнуть ошибка: cannot load such file -- capistrano/cli. Для ее устранения попробуйте удалить гем capistrano, сделать bundle update и снова установить гем capistrano:
    $ gem uninstall capistrano
    $ bundle update
    $ gem install capistrano

4. Для корректной работы capistrano с нашим локальным проектом, добавим следующие строки в Gemfile:
gem 'capistrano'
gem 'capistrano-rails'
gem 'capistrano-bundler'
gem 'capistrano-rvm'
gem 'unicorn'

Мы изменили Gemfile, поэтому нужно дать команду на установку новых гемов:
    $ bundle install

5. На нашем ПК даем capistrano команду на генерацию нужных файлов:
    $ cap install
Будут выполнены следующие действия:

mkdir -p config/deploy
create config/deploy.rb
create config/deploy/staging.rb
create config/deploy/production.rb
mkdir -p lib/capistrano/tasks



6. Теперь нужно корректно настроить файл config/deploy.rb. Но прежде чем это сделать, давайте получим значение переменной $PATH на сервере locum.
    $ ssh hosting_kenny@fluorine.locum.ru
    $ rvm use ruby-2.1.5
    $ echo $PATH
Хостинг locum использует rvm. В настройках проекта в админке мы задали, что будем использовать версию 2.1.5. Поэтому здесь мы перешли на использование этой версии ruby и получили значение переменной $PATH для данной версии. Полученное значение нужно поместить в параметр path метода set :default_env в файле config/deploy.rb:
# config valid only for Capistrano 3.1
lock '3.2.1'

application = 'blog'
login = 'kenny'
$user = 'hosting_' + login
$server = 'fluorine.locum.ru'
rvm_ruby_string = '2.1.5'
deploy_to = "/home/#{ $user }/projects/#{ application }"
unicorn_conf = "/etc/unicorn/#{ application }.#{ login }.rb"
unicorn_pid = "/var/run/unicorn/#{ $user }/#{ application }.#{ login }.pid"
unicorn_start_cmd = "(cd #{ deploy_to }/current; rvm use #{ rvm_ruby_string } do bundle exec unicorn_rails -Dc #{ unicorn_conf })"

set :application, application
set :repo_url, "ssh://#{ $user }@#{ $server }/home/#{ $user }/git/#{ application }.git"

# Default branch is :master
# ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }.call

# Default deploy_to directory is /var/www/my_app
set :deploy_to, deploy_to

# Default value for :scm is :git
# set :scm, :git

# Default value for :format is :pretty
# set :format, :pretty

# Default value for :log_level is :debug
# set :log_level, :debug

# Default value for :pty is false
set :pty, true

# Default value for :linked_files is []
# set :linked_files, %w{config/database.yml}

# Default value for linked_dirs is []
# set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}

# Default value for default_env is {}
set :default_env, { 
  # Выполните на удаленном сервере 
  # $ echo $PATH  и результат поместите сюда:
  path: '/usr/local/rvm/gems/ruby-2.1.5/bin:/usr/local/rvm/gems/ruby-2.1.5@global/bin:/usr/local/rvm/rubies/ruby-2.1.5/bin:/usr/local/rvm/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:/usr/X11R6/bin:/home/hosting_kenny/.gem/ruby/1.8/bin:/var/lib/gems/1.8/bin:/home/hosting_kenny/local/lib/python2.7/site-packages:/home/hosting_kenny/local/lib:/home/hosting_kenny/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games'
}

# Default value for keep_releases is 5
# set :keep_releases, 5

namespace :deploy do
  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      # Your restart mechanism here, for example:
      # execute :touch, release_path.join('tmp/restart.txt')
    end
  end

  after :publishing, :restart

  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
      # Here we can do anything such as:
      # within release_path do
      #   execute :rake, 'cache:clear'
      # end
    end
  end
end

7. Расскомментируем следующие строки в Capfile нашего проекта blog и убедимся, что он включает следующие команды:
require 'capistrano/deploy'
require 'capistrano/rvm'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'

8. Пока capistrano не знает, как делать деплой на продакшен. Исправим это, отредактировав файл config/deploy/production.rb:
connect_to = "#{$user}@#{$server}"

role :app, [connect_to]
role :web, [connect_to]
role :db,  [connect_to]

Все остальные строки в этом файле следует закомментировать.

9. Попробуем дать команду на деплой приложения в продакшен. Для этого у себя на локальном ПК выполните команду, находясь в папке проекта:
    $ cap production deploy

Может возникнуть ошибка: Gemfile:28: syntax error, unexpected ':', expecting $end gem 'sdoc', '~> 0.4.0', group: :doc, а также такая ошибка: syntax error, unexpected ':', expecting $end gem 'spring',        group: :development
Для устранения, нужно удалить в Gemfile проекта вторые части строк и оставить так:
gem 'sdoc', '~> 0.4.0'
gem 'spring'

Важно: Перед запуском команды cap production deploy ВСЕ ИЗМЕНЕНИЯ в файлах проекта должны быть отправлены на удаленный репозиторий (команда git push), т.к. именно оттуда capistrano берет файлы для деплоя.


Возможные ошибки при деплое Ruby on Rails

1. При попытке перейти по адресу проекта, к примеру, http://blog.kenny.lclients.ru возникает ошибка 502 Bad Gateway.

Скорее всего не запущен процесс сервера unicorn, обслуживающий коннекты к данному адресу.
Зайдите на сервер locum и выполните команду:
    $ ssh hosting_kenny@fluorine.locum.ru
    $ ps ax

  PID TTY      STAT   TIME COMMAND
 1157 pts/1    SNs    0:01 -bash
 3414 ?        SNl    0:02 unicorn_rails master -Dc /etc/unicorn/blog.kenny.rb                                                             
 3442 ?        SNl    0:00 unicorn_rails worker[0] -Dc /etc/unicorn/blog.kenny.rb   
                                                       
 7607 pts/1    RN+    0:00 ps ax
24885 ?        Sl     0:06 unicorn_rails master -Dc /etc/unicorn/railsblog.kenny.rb                                                                   
24956 ?        Sl     0:01 unicorn_rails worker[0] -Dc /etc/unicorn/railsblog.kenny.rb 


Если у вас нет процесса unicorn, который соответствует вашему проекту, то просто нужно его запустить. Для этого нужно перейти в папку проекта и выполнить команду:
    $ cd ~/projects/blog/current
    $ rvm use 2.1.5 do bundle exec unicorn_rails -Dc "/etc/unicorn/blog.kenny.rb"

Здесь blog - название проекта, kenny - логин пользователя на хостинге locum.




вторник, 18 ноября 2014 г.

Почему именно Ruby и Ruby on Rails?

Ruby позволяет достичь краткости, 
а краткость - сестра таланта.

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

Программисту могут быть интересны языки Go, Scala, Haskell. Hо если мало кому нужны специалисты, владеющие этими технологиями, стоит ли их изучать? Если на рынке очень много вакансий PHP-программистов, зачем нам Ruby?

PHP. Начало пути

Мой путь разработки начинался с PHP, поскольку освоить его очень просто, понять принцип его работы - легко. Особенно хорошо делать на нем простые сайты, включающие 20-30 страниц. Просто создаешь на сервере под управлением apache файлик index.php и вставляешь туда html-верстку страницы. В некоторые части верстки вставляешь исполняемый код на php, к примеру, <? echo $title ?>. И все работает! И понятно, откуда что берется и как работает.

Однако в корпоративных сайтах, где много различных разделов и необходим разного рода функционал, такая низкоуровневая простота выливается в огромное количество кода, который сложно понять, особенно новому члену команды. Данная сложность преодолевается php-фреймворками, которые позволяют упростить ряд задач. К примеру, нужно вам очистить данные, получаемые от пользователя. Вы не используете набор встроенных функций php, а вызываете всего лишь один метод фреймворка, который гарантирует, что от пользователя не придет ничего опасного, к примеру, sql-инъекции.

Из фреймворков познакомился с symfony, yii, codeigniter, попробовал в работе cms bitrix и drupal. Поскольку к тому моменту я уже был знаком с django, то объявление моделей в php-фреймворках мне не понравилось. Стала напрягать избыточность кода, необходимого для реализации задачи.

Python. Свет в конце тоннеля

Познакомившись с Python, а в частности с фреймворком Django, я увидел, что для реализации задачи можно использовать меньше кода. Соглашения по кодированию реально упрощают жизнь и позволяют быстро понять, где что находится. На php-фреймворках тоже это возможно, однако там кода больше, больше этого "символьного шума", осложняющего восприятие.
На python работу было найти непросто, поэтому два с половиной года я проработал php-программистом.

Ruby. Надежда на лучшее

Ruby мне очень напомнил Python. Привлекла его простота. Однако синтаксическое определение циклов отпугнуло. Понятие блоков показалось интуитивно неясным. Я стал осваивать этот язык и параллельно изучать Ruby on Rails, чтобы видеть, как может работать Ruby на практике.
Не могу утверждать, что Ruby on Rails для создания сайтов - это класс. Нужно время, чтобы это выяснить.
А пока буду делиться с вами своим опытом в освоении Ruby on Rails и постижении тонкостей Ruby.
Успехов вам в обучении!