Практически во всех языках программирования одну и ту же задачу можно решить несколькими способами. Однако какие-то из них лучше, какие-то хуже. Для некоторых требуется написать 10 строк кода, для других можно обойтись и одной.
Совершенствование кода и его оптимизация порой отнимает больше времени, чем заняло написание первой версии. Вы часто встречали новый для вас код или интересную реализацию, и говорили себе: «Оказывается, это можно сделать стандартными средствами, а я изобретал велосипед»? Лично я — да. Поэтому в этой статье я собрал свою коллекцию «велосипедов», и рассказал, как от них можно избавиться.
Простой пример — допустим, у вас имеется массив объектов класса User. У них есть свойство activated, которое выставляется на 1, если юзер активировал свою учетную запись. Вам нужно проверить, все ли юзеры из массива активированы. Не берем во внимание ActiveRecord (там можно и по-другому сделать), моя цель — показать работу с массивами.
Способ первый, самый примитивный:
Первое, что я тут сделал — вместо цикла стал использовать метод массива each:
Весь наш код можно записать всего лишь одной строкой:
Кроме этого, есть еще несколько малоизвестных, но полезных методов:
Сount — самый функциональный. Можно задать параметры в скобках:
Size или length (для массивов они идентичны) — самый быстрый способ, потому что единственное его назначение — просто подсчитать количество всех элементов.
Еще один «велосипед», с которым я встречался — это своя реализация named scope. Например, у вас есть таблица Cars. Вам нужно часто обращаться к базе, и вытаскивать машины определенного цвета. Можно написать в модели свой метод:
На помощь приходит очень удобный и простой в использовании named_scope.
Использовать его практически также — Car.red. Но кроме этого, можно использовать его вместе с методом find, например:
Теперь Car.red.with_users создаст сами-знаете-какой запрос. Удобно? Несомненно.
Еще несколько интересных аналогов есть у самой обычной ссылки link_to.
Первая это link_to_if, которая выведет ссылку, только если она совпадает с условием. Если не совпадает — ссылки не будет, вместо нее останется лишь текст.
Например, у нас есть ссылка «Поднять карму». Если юзер уже голосовал, она не должна быть доступна. Некоторые делают для этой цели отдельный стиль, и отображают ссылку как обычный текст. Но можно написать:
Довольно часто можно встретить замену ссылки текстом, если браузер находится на текущей странице. Делается это с помощью link_to_unless_current:
Еще одна проблема, с которой я когда-то столкнулся — это валидация и ошибки. Приложение мое было на русском языке (конечно же), и по умолчанию ошибки выдавались в таком формате: «Title не может быть пустым».
В первую очередь нужно установить гем Russian, я очень надеюсь что вы про него уже знаете. Это позволит локализовать большинство стандартных сообщений.
Затем в папке config/locales надо открыть (или создать, если нет) файл ru.yml. Вот пример из моего файла:
Поле full_messages используется для полных сообщений об ошибках. Вот как они прописываются в модели:
Если вам надо в экшене отдавать данные и аяксом, и «по-старинке», то проверку на него можно сделать с помощью request.xhr?, например:
Для отладки приложений я использую логгер и гем ruby-debug. Логгер — это простой и быстрый способ узнать «какого хрена он nil вместо users возвращает». Делается он просто:
Если надо посмотреть или изменить состояние переменных, то можно воспользоваться дебаггером. Для этого помещаем запись debugger, например:
Сначала переходим к irb, а затем смотрим наши переменные. Кроме просмотра, их можно поменять и уже потом пускать их в приложение.
По дебаггеру можно написать целую книгу, тут я привел примеры лишь базового использования. Для тех, кто хочет «всё и сразу» — читаем ссылки абзацем ниже.
Тут я приведу несколько полезных ссылок, которые помогли мне, и, надеюсь, будут полезны и вам.
Совершенствование кода и его оптимизация порой отнимает больше времени, чем заняло написание первой версии. Вы часто встречали новый для вас код или интересную реализацию, и говорили себе: «Оказывается, это можно сделать стандартными средствами, а я изобретал велосипед»? Лично я — да. Поэтому в этой статье я собрал свою коллекцию «велосипедов», и рассказал, как от них можно избавиться.
Методы массивов
Простой пример — допустим, у вас имеется массив объектов класса User. У них есть свойство activated, которое выставляется на 1, если юзер активировал свою учетную запись. Вам нужно проверить, все ли юзеры из массива активированы. Не берем во внимание ActiveRecord (там можно и по-другому сделать), моя цель — показать работу с массивами.
Способ первый, самый примитивный:
- @users = User.find(:all)
- activated_users = 0
- foreach user in @users
- activated_users += 1 if user.activated == 1
- end
-
- # если количество совпадает, значит все юзеры активированы
- activated_users == @users.size
Первое, что я тут сделал — вместо цикла стал использовать метод массива each:
- @users = User.find(:all)
- activated_users = 0
-
- # лично мне такой способ записи нравится больше,
- # чем стандартный для многих языков цикл foreach
- @users.each do |user|
- activated_users += 1 if user.activated == 1
- end
-
- # или же в одну строку:
- @users.each{ |user| activated_users += 1 if user.activated == 1}
Но даже эта запись меня в последствии не устроила. Я нашел более рациональный вариант:
- # Метод select позволяет вытащить элементы массива по заданному условию
- # И потом мы сравниваем размеры обоих массивов
- @users = User.find(:all)
- @users.select{ |user| user.activated == 1 }.size == @users.size
Однако все хорошее уже придумано за нас, достаточно лишь прочитать документацию. У массивов есть замечательный метод all?, который проверяет, все ли элементы удовлетворяют условию. К слову, еще есть метод any?, возвращающий true если хотя бы один элемент соответствует условию.Весь наш код можно записать всего лишь одной строкой:
- @users.all?{ |user| user.activated == 1 }
- # или даже так:
- @users.all?(&:activated) # при условии что activated принимает значение true/false, т.к. 0 в Ruby считается true
Кроме этого, есть еще несколько малоизвестных, но полезных методов:
- # Взять первые 5 элементов
- @array.take(5)
-
- # Выбрать случайный элемент
- @array.choice
-
- # Разбросать элементы в случайном порядке
- @array.shuffle!
-
- # Взять элемент массива по его номеру
- # Причем этот метод работает быстрее, чем @array[1]
- @array.at(1)
Каким способом лучше всего подсчитать количество элементов в массиве?
Сount — самый функциональный. Можно задать параметры в скобках:
- array = [1,1,2,2,3,4,5]
- array.count # => 7
- array.count(1) # подсчитает количество элементов "1" => 2
- array.count{ |p| p > 2 } # подсчитает элементы по условию => 3
Size или length (для массивов они идентичны) — самый быстрый способ, потому что единственное его назначение — просто подсчитать количество всех элементов.
Частоиспользуемые запросы ActiveRecord
Еще один «велосипед», с которым я встречался — это своя реализация named scope. Например, у вас есть таблица Cars. Вам нужно часто обращаться к базе, и вытаскивать машины определенного цвета. Можно написать в модели свой метод:
- class Car < ActiveRecord::Base
- def self.only_red
- self.find(:all, :conditions => "color = 'red'")
- end
- end
Он просто ищет все машины красного цвета. Обращаться к нему просто — Car.only_red. Но самый главный недостаток — если вам захочется все это еще и отсортировать (или добавить что-то типа: limit => 5), то придется или дорабатывать метод в модели, или использовать стандартный find.На помощь приходит очень удобный и простой в использовании named_scope.
- class Car < ActiveRecord::Base
- named_scope :red, :conditions => 'color = "red"'
- end
Использовать его практически также — Car.red. Но кроме этого, можно использовать его вместе с методом find, например:
- Car.red.find(:all, :limit => 10, :order => "id DESC")
А еще их можно комбинировать. Сначала добавим новый scope который позволит включить в запрос пользователей, которым принадлежат машины:
- named_scope :with_users, :include => :users
Теперь Car.red.with_users создаст сами-знаете-какой запрос. Удобно? Несомненно.
Link_to и его друзья
Еще несколько интересных аналогов есть у самой обычной ссылки link_to.
Первая это link_to_if, которая выведет ссылку, только если она совпадает с условием. Если не совпадает — ссылки не будет, вместо нее останется лишь текст.
Например, у нас есть ссылка «Поднять карму». Если юзер уже голосовал, она не должна быть доступна. Некоторые делают для этой цели отдельный стиль, и отображают ссылку как обычный текст. Но можно написать:
- link_to_if (user.voted_for_karma? == false), "Поднять карму", :action => "add_karma"
Что тут можно поменять? Конечно же, (user.voted_for_karma? == false) смотрится непонятно, ведь можно просто убрать сравнение с false, используя link_to_unless:
- link_to_unless user.voted_for_karma?, "Поднять карму", :action => "add_karma"
Можно сделать еще круче — сообщим что юзер уже голосовал.
- # link_name тут (не)используется как название оригинальной ссылки
- link_to_unless(user.voted_for_karma?, "Поднять карму", :action => "add_karma") { |link_name| "Вы уже проголосовали" }
А еще можно выдавать другую ссылку, если юзер не залогинился, например перенаправлять на регистрацию:
- link_to_if(user.logged_in?, "Создать тему", :action => "add_post") do |link_name|
- # если незалогиненный юзер тыкнет по ссылке, то попадет на страницу логина
- link_to(link_name, :controller => "users", :action => "login")
- end
Довольно часто можно встретить замену ссылки текстом, если браузер находится на текущей странице. Делается это с помощью link_to_unless_current:
- <!-- index.html.erb -->
- <ul id="navbar">
- <li><%= link_to_unless_current("Home", { :action => "index" }) %></li>
- <li><%= link_to_unless_current("About Us", { :action => "about" }) %></li>
- </ul>
- <!-- если мы перейдем на страницу About Us, то в html коде увидим следующее: -->
- <ul id="navbar">
- <li><a href="/controller/index">Home</a></li>
- <li>About Us</li>
- </ul>
Русификация валидации
Еще одна проблема, с которой я когда-то столкнулся — это валидация и ошибки. Приложение мое было на русском языке (конечно же), и по умолчанию ошибки выдавались в таком формате: «Title не может быть пустым».
В первую очередь нужно установить гем Russian, я очень надеюсь что вы про него уже знаете. Это позволит локализовать большинство стандартных сообщений.
Затем в папке config/locales надо открыть (или создать, если нет) файл ru.yml. Вот пример из моего файла:
- ru:
- activerecord:
- errors:
- full_messages:
- rate:
- exists: "Нельзя оценить одного и того же пользователя дважды"
- number_limit: "Оценка может принмать значения от -2 до 2"
- attributes:
- news:
- title: "Заголовок"
- body: "Текст"
- article: "Статья"
Как вы видите, чтобы локализовать поле title в таблице news, достаточно добавить вышеприведенную запись в файл локализации. Поле full_messages используется для полных сообщений об ошибках. Вот как они прописываются в модели:
- class Rate < ActiveRecord::Base
- validates_uniqueness_of :rate_owner_id, :message => "rate.exists"
- validates_inclusion_of :value, :in => [-2, -1, 1, 2], :message => "rate.number_limit"
- end
Параметр message берет файл локализации, идет по адресу activerecord.errors.full_messages, затем по указанному в модели, и возвращает локализованную строку. Все достаточно просто. Если не указывать message, то на выводе будет стандартная фраза об ошибке, но в моем случае я заменил ее своей.Методы для контроллера
Если вам надо в экшене отдавать данные и аяксом, и «по-старинке», то проверку на него можно сделать с помощью request.xhr?, например:
- def search
- @users = User.find(:all, :conditions => "login LIKE '#{params[:q]}' ", :limit => 30)
- render :json => @users if request.xhr?
- end
Еще один полезный метод — это verify. Судя по названию, он служит для удостоверения, что на контроллер пришел правильный запрос. В основном это служит для защиты, и для некоторых методов я настоятельно рекомендую его использовать.
- class UsersController < ApplicationController
- # Проверяем метод create - он должен принимать только post запросы и указанные параметры.
- # Если что-то не так, то перенаправляем юзера в начало, и добавляем флеш-сообщение об ошибке.
- verify :only => :create, :method => :post, :params => [:login, :email, :name], :redirect_to => :index, :add_flash => {:error => 'Неверный запрос. У вас точно все в порядке?'}
- end
К слову, не стоит расписывать ошибку как можно подробнее — основная валидация должна проходить в модели. Тут же мы проверяем параметры, и ругаемся если что-то не так.Отладка
Для отладки приложений я использую логгер и гем ruby-debug. Логгер — это простой и быстрый способ узнать «какого хрена он nil вместо users возвращает». Делается он просто:
- # inspect я вызываю для удобного отображения объектов в консоли
- logger.debug "users = #{users.inspect}"
К слову, можно использовать не только logger.debug, но и logger.info, logger.warn или даже logger.fatal. В собственных классах можно писать RAILS_DEFAULT_LOGGER.debug, потому что на фразу logger.debug он откликаться не будет.Если надо посмотреть или изменить состояние переменных, то можно воспользоваться дебаггером. Для этого помещаем запись debugger, например:
- class UsersController < ApplicationController
-
- def index
- @user = User.find_by_id(params[:id])
- role = @user.role
- debugger
- end
- end
После этого нам нужно или запустить сервер с параметром --debugger, или дописать
- require 'ruby-debug'
в environment.rb. После этого обновляем страницу index в браузере -, но она остановится и не будет загружена. Самое время заглянуть в консоль, там нас ждет дебаггер.
- (rdb:1) irb
- irb(#<UsersController:0x1039edd20>):001:0> @user
- => #<User id: 1, login: "Vizakenjack">
- irb(#<UsersController:0x1039edd20>):001:0> @user.login = 'Admin'
- => "Admin">
Сначала переходим к irb, а затем смотрим наши переменные. Кроме просмотра, их можно поменять и уже потом пускать их в приложение.
По дебаггеру можно написать целую книгу, тут я привел примеры лишь базового использования. Для тех, кто хочет «всё и сразу» — читаем ссылки абзацем ниже.
Заключение
Тут я приведу несколько полезных ссылок, которые помогли мне, и, надеюсь, будут полезны и вам.
- Гайды по рельсам. На английском. Очень полезный сборник статей.
- Ruby Toolbox — сборник плагинов, гемов и расширений. На все случаи жизни.
- Rails API и Ruby API. Документация языка и фреймворка. Хоть аналогов и немало, мне приглянулся этот сайт.
- Викиучебник по Ruby, на русском. Читается очень легко. Для интереса можно зайти в раздел «Сети» — там даже есть описание трояна!
- Rubular — сайт для создания и тестирования регулярных выражений,