Подарки

April 25, 2012

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

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

Упасть на первом сломанном спеке

Когда я мечтал пропатчить test/unit, чтобы он падал на первом сломанном тесте. Так как скучно смотреть на множество ошибок, которые часто вызваны одной причиной. В rSpec такая возможность есть в стандартной поставке, ничего патчить не нужно:

rspec --fail-fast
rspec --fail-fast -b # -b(--backtrace) - еще и вывести полный трейс ошибки

Блоки текста с красивыми отступами

  class A
    def do_some
      cmd = <<-CMD
rake db:migrate
rake db:test:prepare
CMD
      system cmd
    end
  end

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

  class A
    def do_some
      cmd = <<-CMD.strip_heredoc
        rake db:migrate
        rake db:test:prepare
      CMD
      system cmd
    end
  end

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

Мне кажется, что я где-то прочитал, что блочные фильтры в контроллере нужно писать так:

class HomeController < ApplicationController
  before_filter do |controller|
    raise "Access Denied" if controller.request.port == 3000
  end
end

Но оказывается параметр controller не нужен. Из-за особенностей реализации фильтров через AS-колбеки activesupport/lib/active_support/callbacks.rb#L337 код вызывается как обычный метод, поэтому можно написать так:

class HomeController < ApplicationController
  before_filter do
    raise "Access Denied" if request.port == 3000
  end
end

Параметр size в image_tag

image_tag('image.jpg', size: '70x25')
    # => <img alt="Image" height="25" src="/assets/image.jpg" width="70" />

Это штришок тоже почему-то ускользнул от меня, я долгое время использовал явные :width и :height (кстати вы всегда 'ширина' и 'высота' пишете правильно с первой попытки? :-))

Использование хэлпер методов вне контроллера

Не всегда удается развести всю логику строго по MVC, поэтому всегда можно подхачить, например вот так:

class User < ActiveRecord::Base
  attr_accessible :first, :second

  delegate :content_tag, :safe_join, to: 'ApplicationController.helpers'
  def full_name
    # Конечно заносить элементы оформления в модель не очень хорошо,
    # я бы даже сказал совсем не хорошо. Но для этого нам и нужны
    # интуиция, опыт и лень, чтобы отличить добро от зла.
    safe_join [content_tag(:b, first), second], ' &mdash; '.html_safe 
  end 
end
%p= @user.full_name  # => <p><b>Alexey</b> &mdash; Vakhov</p>

Вообще я поддерживаю разумные хаки. Бизнес-логику обязательно нужно хачить. Я всегда сначала во вьюхе буду создавать длинную колбаску @products.rejected(&:disabled?).select{|p| p.price > 1000}.reverse. И только когда она станет совсем неприличной, я вынесу ее с длинным, стремным именем в ApplicationHelper и начну мусорить там. Возможно это и нарушает академические стандарты, но зато все скажут спасибо при поддержке. Так как, чтобы поправить вьюху, нужно будет заглянуть всего в один файл.

А если вы увидите что-нибудь типа @products.render_me(self), в контроллере окажется, что @products это декоратор, в декораторе render_me реализован через родительский класс и пачку миксинов. Вообщем после 4-го уровня абстракции кэш вашей памяти переполнится окончательно. К тому же руби все равно не переплюнуть C++ в плане абстракции. Простой код должен выглядеть просто, сложный - сложно.

Обход двух и более массивов одного размера

Глубинный эстетизм кода мне тоже не чужд, поэтому когда мне пришлось написать пару раз что-то похожее на:

array_a = [1, 2, 3]
array_b = [5, 6, 7]

if array_a.size == array_b.size
  array_a.each_with_index do |a, ind|
    b = array_b[ind]
    # do something with a & b
  end
end

несимметричность данного решения меня раздражала. И в рейлс я случайно заметил кусочек, который почему-то не пришел в голову раньше:

if array_a.size == array_b.size
  array_a.size.times do |ind|
    a = array_a[ind]
    b = array_b[ind]
    # do something with a & b
  end
end

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

comments powered by Disqus