Ruby On Rails - best practices

Прошел где-то месяц, после того как я начал изучать Ruby On Rails. Так как Livestreet потихоньку движется в сторону framework'a (чему я сильно рад), хочу поделиться с разработчиками livestreet чего же такого там есть, чего сильно упрощает жизнь при разработке.

Вообще, ruby on rails позиционируется как фреймворк, который максимально упрощает разработку и в то же время структурирует систему.
В то же самое время, livestreet сильно подходит для высоконагруженных проектов (больше чем rails).

Convention over configuration


Переводится как согласование над конфигурацией. Основная идея — сэкономить время программиста и задать некие стандарты разработки.

Например, у нас есть модель user. Рельсы будут использовать табличку users для нее автоматически (множественное число опредлеяется в движке). Если название таблички отличается, то мы можем написать table_name => :users_table, тем самым задав свое название. То же самое с роутами, шаблонами и еще много чем.

Что это нам дает? Структурируемость кода, экономию времени.

Active Record


Призван чтобы так же облегчить жизнь программиста и структурировать код.

Сама реализация достаточно обширна. Опишу некоторые плюсы, которые успел подчерпнуть.
Каждая модель по-умолчанию относится к одной табличке (user -> users). Все связи этой модели с другими прописывается в ней же.
Например код

has_many :comments

в классе модели user «привязывает» модель comment к модели user.
И у модели user появляется метод comments, который возвращает список комментариев. (используется так: user.comments).

Далее мы можем привязать с другой стороны: В классе модели comments пишем

belongs_to :user


Теперь у нас есть метод comment.user, который возвращает пользователя. Тут действует принцип COC (о котором я писал выше). По-умолчанию в табличке comments берется поле user_id, но его можно перезадать. Автоматом прописываются связи.

Вообще, к моделям добавляется много дефолтных методов (типа delete, find_by_id итд), среди которых очень часто много скрыто под COC

Генераторы



Существуют спец. скрипты, которые генерируют код автоматически. Например, можно сгенерить контроллер с моделью (создаются автоматом файлы с кодом)

Так же есть такое понятие как scaffolding (строительные леса). Это генератор (скрипт), который генерирует модель, контроллер с базовыми eventами(как в LS) и представлениями с формами (CRUD):
* список элементов
* просмотр одного элемента
* редактирование
* удаление

Потом конечно же всё 100 раз заменяется, но для быстрого внедрения сущности самое оно.

Миграции БД


При разработке если нам нужно добавить новые поля/табличку, или удалить чего-нибудь, с помощью генератора, мы создаем т.н. миграцию. Это файл, в котором пишутся изменения «туда» и «сюда»
Напр:

  def self.up
     add_column :collages, :status, :string
  end

  def self.down
     remove_column :collages, :status
  end


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

Кстати, rails не привязан к mysql. Помогают отвязаться миграции и activerecord, где всё скрыто. Так локально можно разрабатывать на sqlLite, а продакш на oracle.
Хотя это накладывает некоторые ограничения. Например, нет возможности добавлять связи к табличкам.

Думал, что напишу больше, однако времени не хватило. Так что будет вторая часть:)

Параллельно хотел бы разместить небольшую объяву:
Ищу программиста под livestreet, который поможет мне с одним проектом. Проект — сайт про детей, бюджет у проекта не большой — 8 т.р.
Требуется модификация ЛС и модуля «галерея». Это может быть человек, которому просто интересно программировать под ЛС, пусть он даже и не обладает некоторыми серьезными знаниями.
Я могу его подучить и расписать что и как надо программировать:)

По всем вопросам жду в личку.

49 комментариев

avatar
ROR это классно! Поддержу автора в этой идеи.

Теперь выскажу свое мнение по предложениям:

1. Convention over configuration — вопрос косвенно поднимался в комментариях к топику в о нововведениях. Сейчас система слишком динамично меняется, чтобы говорить о каких-то глобальных Convention, а локально это уже внедряется — например, routing rewrite.

2. Active Record — это только один из паттернов связки реляционнки и логики приложения. В LS используется data mapper, дело стиля, какие выгоды по вашему получит приложения от перехода на AR?

3. Генераторы. Мне тоже раньше эта функциональность импонировала, но долгий опыт работы с фреймворками меня научил — НАСЛЕДОВАНИЕ КЛАССОВ = ЛУЧШИЙ КОДОГЕНЕРАТОР. Главное правильно смонтировать каркас, и не это касается не только CRUD.

Да, стандартный CRUD можно сделать на ROR за минуту-две. Но у меня под ZF есть каркас приложения, на основе которого я реализую CRUD минут за 5. При том, что этот вариант мне нужно будет гораздо меньше «допиливать», и он более функционален.
avatar
1. А почему нельзя задавать сразу же конвеншены даже на данном этапе? Наоборот, комьюнити только начинает расти, переучивать людей сильно не придется…
2. data mapper, насколько я знаю, это вынесение запросов в отдельный класс, который позволяет абстрагироваться от базы данных всё приложение.
В rails Active Record же существенно более сложный механизм (хотя, можно взять только основную идею). По поводу выгод это нужно либо писать отдельный топик, либо почитать мануалы. Я наверное напишу сам…
3. Наследование классов это другое, нежели генерация. Генерировать можно много чего:)

Не стану спорить на тему последнего абзаца, потому что не знаю чего там. Да и не надо, главное подумать нужно ли это ЛС и, если да, то как лучше реализовывать.
avatar
2. data mapper, насколько я знаю, это вынесение запросов в отдельный класс, который позволяет абстрагироваться от базы данных всё приложение.


Нет. Суть дата мепинга объясню на конкретном примере сущности «User». У вас есть две стороны барикады:

1) приложение, для которого User это «что-то» («нечто»), имеющее определенные характеристики (имя, логин, пароль, ...), определенные права, совершающее определенные действия, и меняющееся при совершении этих действий;

2) база данных, для которой пользователь — это только строка в таблице и несколько смежных ячеек. И все.

Паттерн Data Mapper подразумевает, что для наведения порядка вы — а) имеете класса UserEntity, инкапсулирующий в себе характеристики и действия объекта приложения; б) UserMapper — который может превратить объект UserEntity в строку данных в реляционной базе данных, и наоборот, читая строку(и) базы вернуть вам нормальный UserEntity.

То, что SQL выносятся в отдельный файл, это следствие, а не причина.
avatar
Ну вообще, то что я написал ничуть не противоречит. Получается, это одно из свойств паттерна.

Да и вообще, тема топика не в Data Mappere, а в том как сделать ЛС лучше)
avatar
том как сделать ЛС лучше)

В этом-то мой вопрос: как AR сделает LS лучше? :)

Проблема работы уровня Models в LS в том, что нет общей платформы — каждый mapper пишется так или иначе «с нуля». Равно как и каждый новый Entity. По хорошему здесь нужно писать хороший DB-layer со своим объектом row (строка таблицы) и rowset (набор строк) — а в дальнейшем оперировать наследованными от него объектами.

А там дальше начинается та область, где граница между Data Mapping & Active Records становиться очень тонка :)
avatar
Оставьте oDb->select[Row] — дешево и сердито.
avatar
да зачем всё усложнять? mysql_query()
avatar
не утрируйте, просто механизм который сейчас реализован в движке на мой взгляд вполне годен.
avatar
от «вполне годен» до «удобен» и «оптимален» далекая дорога. Хотя второго крайне сложно достигнуть. Чем-то всегда приходится жертвовать…
avatar
Ну решать вам, но мне кажется что если идти по дороге легковесного фреймворка, то наворачивать кучу абстракций для БД не стоит.
avatar
Да решать даже не мне:)

Я просто показал как бывает и не пропагандирую тот или иной подход. Спорить на эту тему глупо, нужно просто подумать как в данном случае лучше.
avatar
Легковесность-легковесностью, а программисты всегда дороже железа.
avatar
ну я не только про железо говорил, но и про понятность структуры кода, мне например было довольно просто разобратся с ls когда я его впервые увидел, чего не могу сказать про зенд…
avatar
На самом деле, то что есть сейчас мало чем отличается от mysql_query. DbSimple просто выполняет «черную» работу, но не добавляет никакую новую логику в работу приложения.

В этом есть серьезные неудобства, для того чтобы это понять достаточно написать пару модулей… Но это не смертельно, посмотрите на WordPress — живет процветает с 10 тысяч стронних плагинов, а работает до сих пор на $db->query();
avatar
посмотрите на WordPress — живет процветает с 10 тысяч стронних плагинов, а работает до сих пор на $db->query();
тут стоит как раз задуматься о потенциальной аудитории девелоперов клепающих плагины, она гораздо больше тех кто проникся активрекордами и прочими шаманствами
avatar
Макс, думаешь $db->query() причина большой аудитории девелоперов?
Мне кажется всё-таки следствие, потому что ВП один из первых блоговых движков. Тем более с системой плагинов.
avatar
я не про популярность ВП, а про потенциальную аудиторию
avatar
Макс, штук 5 скринкастов «как создать плагин под ЛС» и начальное освоение закончено=) Обещаю, написать один под новую версию:)

Другое дело, если твой движок используют потому что под него УДОБНО разрабатывать сайты.

Кстати, вот примеры скринкастов фич rails: railscasts.com)
avatar
для того чтобы это понять достаточно написать пару модулей
написал пару модулей, мне как раз было удобней генерировать один сложный sql и вынимать все что мне надо, чем воевать с абстракциями. У меня просто был как опыт написания подобного, другому человеку было сложно разобратся в этом, sql тут более «универсален». Ладно заканчиваю словогенерацией заниматься…
avatar
Ладно заканчиваю словогенерацией заниматься…

Сказал сам себе тоже самое 5 миинут назад :) На вкус и цвет все фломастеры разные…
avatar
Наследование классов это другое, нежели генерация.

Вы уверены?
Генерирование — это создание кода исходя из а) ранее созданных форматов, б) переданных параметров кастомизации.
В наследовании мы получаем тоже самое — мы переносим ранее созданный формат (экземпляр) логики, кастомизируя его.

Вот если бы кодогенератор мог создавать логику кода, которая изначально не заложена в него… тогда это был бы уже говорили об искусственном интеллекте, пишущем программы по нашей просьбе. Но человечеству до этого еще далеко.
avatar
За исключением того что наследование как не крути является более гибким инструментом (с точки зрения кастомизации конечно) чем кг.
А чем плохо/старо/долго писать все последовательно расширяя классы? Разве это не удобнее/нагляднее чем генерация?
avatar
Да и к тому же кода меньше будет.
avatar
Я об этом и говорю :) Спор то и начался с моих слов
НАСЛЕДОВАНИЕ КЛАССОВ = ЛУЧШИЙ КОДОГЕНЕРАТОР

avatar
1. Вы можете наследовать sql и tpl?
При генерации может создаться много ненужного кода, который можно просто удалить.
avatar
Вы можете наследовать sql и tpl?


Да, могу. На примере SQL. Пишется класс, к примеру, SQL, объект которого может «собирать» по кускам ваш запрос,
$select=new SQL();
$select->select(array('login','pass'));
$select->from('users');
$select->where('id=',5);
$select->build(); // тут выдается SQL


Далее делаете в SQL функцию init(), которая загружает стандартные (базовые) составляющие. Потом наследуете этот класс, перегружаете init(), добавляя новые составляющие (только сначала вызвав parent::init()).

Вот вам и наследование SQL. Tpl — все тоже самое.
Лично я постоянно пользуюсь наследование вида и функционала Web Form, реализованное через Zend_Form.
avatar
Признаюсь, не имел дела с такого вида наследованием. Со стороны звучит страшновато, хотя если есть и пользуются, значит кому-то это нужно:)

Конечно же не идет речь о хелперах, когда мы создаем форму в шаблоне, вызывая метод, чтобы потом можно было легко менять структуру, изменяя методы.
avatar
Опять же хочу сказать что основная цель у меня не доказать что я прав и всё знаю, а просто показать что есть и как это работает… Чтобы можно было подумать.
avatar
Конвеншоны, конечно, должны быть. И тут соглашусь с Алексеем, что, с одной стороны, они постепенно складываются, но с другой — система слишком молодая и слошком динамично развивающаяся, чтобы говорить об уже устоявшихся соглашениях. Но общий ход развития вполне предсказуем и логичен.

Про Active Record сколько не читал, до моего глубинного сознания не доходит пока гениальность и актуальность этого подхода. Возможно, опыт традиционного программирования давлеет. Единственный, но весьма несущественный нюанс — я тоже таблицы, как правило, во множественном числе называю. :)

А вот генераторы — тема интересная. В этом направлении стоит подумать. Может, удастся избежать использования eval()?
avatar
1. Ответил выше

2. Для этого достаточно написать сайтик с 10 сущностями и потом вспомнить сколько времени понадобилось. А после этого отдать его своему другу и спросить, насколько всё понятно.

3. Ага:)
avatar
Интересно, а как кодогенерация может помочь избежать использования eval()?
avatar
Я думаю, речь идет об отдельном классе генератора (в рельсах это вот: api.rubyonrails.org/classes/Rails/Generator/Base.html) который позволяет писать скрипты генерации проще.
avatar
Честно — не знаю, потому что серьезно не анализировал и глубоко не копал. Но логика такая: сейчас при вызове неизвестного (для текущего класса) метода на лету конструируется выражение в текстовом виде и скармливается интерпретатору. И такое происходит каждый раз. Если будем тысячу раз вызывать один и тот же метод, то тысячу раз он будет конструироваться заново. Вполне допускаю, что можно построить схему так, чтоб для таких случаев генерился некий код, который будет просто инклудится.

Подчеркну — это лишь предположение. Но было б классно, если б такое можно было реализовать.
avatar
а это уже немного другое:)

Я имел в виду генератор — скрипт, который генерит код. А откуда будет грузиться код это уже дело не генератора…
avatar
Так и я про код. А код может быть либо одной строкой с вызовом функции/метода, либо целым классом, где все незивестные методы определяются, как надо. Но это уже детали :)
avatar
Да, а по поводу программиста — советую обратиться к Лоре (http://livestreet.ru/blog/1012.html), не встречал ни одного нарекания со стороны тех, кто с ней работал.
avatar
единственное что на мой взгляд спорно в Convention over configuration, это то что модель может юзать несколько табличек, на счет роутов и шаблонов тут более понятно, в случае чего можно перекрыть. Генераторы тема интересная, но на мой взгляд ls идет в сторону легкого фреймворка (чему я кстати очень рад) и поэтому мне кажется тут генераторам не место, сила ООП спасет нас. И к тому же (не знаю почему) у меня всегда была нелюбовь к генераторам…
avatar
единственное что на мой взгляд спорно в Convention over configuration, это то что модель может юзать несколько табличек
Да, забыл сказать, что мне тоже как-то непонятно это положение. Сам я RoR не юзал, но такой вот постулат — одна модель => одна таблица — меня лично сильно смущает.
avatar
Модели достаточно гибко расширяются и разделяются. Например, если есть пользователь(user) и у него есть адрес(address)

Можно всё это хранить в одной бд, но модель address будет расширять модель user и будет:

  new_address = конструктор нового адреса
  user.address = new_address

Хотя хранится всё в табличке users.

А в каких случаях может быть по-другому?
avatar
я RoR не использовал, только смотрел краями, а как там дело обстоит со сложными sql запросами, если идет жесткая привязка к табличке? Например мне надо вынуть юзера, количество исписанных им блогов, и сколько его друзей комментили в этом блоге?)
avatar
Даже если мы выбираем данные из одной таблицы, то JOIN все равно никто не отменял :)
avatar
Там всё достаточно гибко.

Например, есть метод find_by_sql («select * from users»)…

Так же можно написать так:
user.find_all (:select => «select users.*, count (blogs.*)», :joins => :blogs, :group => «user_id»}

На счет друзей тут уже сложнее вечером написать на «языке» rails.

В любом случае всегда доступен find_by_sql

Более того, необходимые счетчики одной строчкой добавляются к табличкам (напр. кол-во комментариев пользователей).
avatar
find_by_sql

вопрос снят
avatar
А если наоборот и модель использует несколько табличек, то можно их логически отделить так же… для пользователя

  has_one :address # табличка users соединяется с табличкой addresses по user_id

И мы используем user.address
avatar
Этот паттерн работы с базой называется Data Table Gateway. Суть в том что таблица из базы отображается в нашем приложении объектом некоего класса. Мы учим этот класс доставать записи, находить, вставлять и обновлять и дальше работаем уже не с ТАБЛИЦЕЙ, а с ОБЪЕКТОМ нашего класса. Связи в базе (refference) переносятся на связи между объектами (reference map).

Т.е. класс UserModel работает с таблицей users, BlogModel — с таблицей blogs. Если на уровне данных есть связь между пользователями и блогами, то работаем не с этой связью, а со связью UserModel <-> BlogModel.

Чаще всего используется в совокупности с Data Row Gateway. Очень неудобно, когда сущность «размазана» в базе по таблицам или поддается динамической кастомизации.
avatar
да, и разбавьте текст катом, пожалуйста.
avatar
Спасибо, забыл)
avatar
Ruby on Rails — это ваш первый язык?
Посоветуйте с чего начать изучать его? И сколько примерно необходимо для этого времени?
avatar
В то же самое время, livestreet сильно подходит для высоконагруженных проектов (больше чем rails).
???
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.