Предотвращение повторного запуска Rake-скрипта 25 февраля 10

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

В начале задачи – проверяем, запущена ли она уже, и если нет, создаем PID-файл.

pid_filename = "#{RAILS_ROOT}/tmp/#{task.name.gsub ':', '.'}.pid" # или любой другой
 
if File.exists?(pid_filename) && system("kill -0 #{File.read(pid_filename).to_i}")
  fail "Task already running"
else
  File.open(pid_filename,'w') {|f| f.write(Process.pid) }
end

В конце задачи – удаляем PID-файл:

File.unlink(pid_filename) rescue nil

Кеширование страниц средствами Ruby on Rails 25 февраля 10

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

Рельсы умеют такое делать «из коробки», практически одной строчкой:

class SomeController < ApplicationController
  caches_page :index
 
  def index
  end
end

Все! Кеш уже работает. Правда, почему-то рельсы не предусматривают две вещи. Кеш не работает с GET-параметрами – раз. Хранится прямо в public – два.

GET-параметры

Тут все просто: хочешь, чтобы для разного набора параметров кешировались разные страницы – помещай параметры в путь. Кстати, к таким ссылкам и Google лучше относится.

Для меня это в первую очередь касалось постраничной разбивки. Если ты используешь will_paginate, то тебе достаточно вынести URL с номером страницы в таблицу роутов:

map.resources :items
map.paged_items '/items/page/:page', :controller => 'items', :action => 'index'

Перемещаем кеш в более удобное место (например, в public/cache)

Путь к кешу указывается в конфиге:

# config/environments/production.rb
config.action_controller.page_cache_directory = RAILS_ROOT+"/public/cache/"

Осталось научить Apache забирать кеш из нужного каталога. Решается это довольно простыми реврайтами, не считая того, что отдавать нужно только файлы, которые точно лежат в кеше – иначе Rails станет получать неправильные пути и перестанет работать.

В общем, нужен вот такой .htaccess:

# public/.htaccess
RewriteEngine On
 
# убираем слеш в конце пути
RewriteCond %{REQUEST_URI} ^([^.]+)/$
RewriteRule ^[^.]+/$ /%1 [QSA,L]
 
# перенаправляем в кеш, только если там есть файл
RewriteCond %{THE_REQUEST} ^(GET|HEAD)
RewriteCond %{DOCUMENT_ROOT}/cache/%1 -f
RewriteRule ^.+$ /cache/%1 [QSA,L]
 
# то же для страниц без расширения (к ним Rails приписывает .html)
RewriteCond %{THE_REQUEST} ^(GET|HEAD)
RewriteCond %{REQUEST_URI} ^([^.]+)$
RewriteCond %{DOCUMENT_ROOT}/cache/%1.html -f
RewriteRule ^[^.]+$ /cache/%1.html [QSA,L]
 
# то же для пустого пути
RewriteCond %{THE_REQUEST} ^(GET|HEAD)
RewriteCond %{DOCUMENT_ROOT}/cache/index.html -f
RewriteRule ^$ /cache/index.html [QSA,L]

Теперь можно очищать весь кеш вместе обыкновенным rm -rf public/cache/*.

Почему в Rails нет метода для создания HABTM-таблицы? 15 января 10

В очередной раз набирая те шесть строчек, которыми – по соглашению – нужно описать таблицу для связи «многие ко многим», я подумал: а почему в Rails нет готового метода для этого? Почему каждый раз надо переписывать один и тот же код, попутно вспоминая, как сделать таблицу без ID – ведь используются они только для HABTM?

Подумал и написал:

# config/initializers/create_habtm_table.rb
 
class ActiveRecord::Migration
  # create_habtm_table :posts, :tags
  def self.create_habtm_table(first_model,second_model)
    table_name = [first_model.to_s,second_model.to_s].sort.join('_').to_sym
    create_table table_name, :id => false do |t|
      t.belongs_to first_model.to_s.singularize.to_sym
      t.belongs_to second_model.to_s.singularize.to_sym
    end
    add_index table_name, (first_model.to_s.singularize+'_id').to_sym
    add_index table_name, (second_model.to_s.singularize+'_id').to_sym
  end
 
  # drop_habtm_table :posts, :tags
  def self.drop_habtm_table(first_model, second_model)
    table_name = [first_model.to_s,second_model.to_s].sort.join('_').to_sym
    drop_table table_name
  end
end

Как добавить в конкретный Ruby-объект какое-нибудь свойство 11 января 10

Вообще задача такая:

class Book < ActiveRecord::Base
  has_many :ratings
  has_many :tags, :through => :ratings
end
 
class Rating < ActiveRecord::Base
  belongs_to :book
  belongs_to :tag
  validates_presence_of :score
end

Надо возвращать для каждой книжки не только все ее теги, но и сумму оценок, поставленных для каждого тега.

Ущербные решения

Затык был не в том, как закодить саму выборку суммы, а в каком виде возвращать результат. Первая мысль – массивом хешей типа {:tag => tag, :score => score}. Мало того, что это попахивает PHP, так с такими хешами еще и неудобно работать. Потом я хотел сделать total_score методом Tag:

class Tag < ActiveRecord::Base
  def total_score(book)
    return score_computed_some_way
  end
end
 
@book.tags.first.total_score(@book)

Но это тоже бредовая тавтология, как по мне.

Расширение Ruby-объектов на уровне объекта

Итак. Есть такой метод Object#instance_eval – он позволяет запихнуть в объект практически все, что угодно. (Если бы что-то угодно было чем-то более сложным, чем возвращение объекта, можно было бы вынести его в модуль и подключать через Object#extend)

# scores = {tag_id => score, ...}
def inject_scores_into_tags(tags, scores)
  tags.each do |tag|
    tag.instance_eval do
      @total_score = scores[tag.id]
      def total_score
        @total_score
      end
    end
  end
end

После этого у тегов появляется метод total_score.

Штука для проверки прокси-серверов на доступность и анонимность 27 декабря 09

Вот, давно хотел выложить, но она раньше выглядела еще ужаснее, чем сейчас.

Идея проста: ты даешь ей список прокси-серверов в текстовом файле, она отвечает, какие из них вообще доступны (и отдают правильную страницу), какие анонимные (не открывают твой IP), какие – неанонимные.

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

http://github.com/leonid-shevtsov/proxytools

Обновление файлов на сайте с помощью rsync 24 декабря 09

Продолжаю цикл статей о взаимодествии с архаичными сайтами и их же поддержке.

Пришел к выводу, что удобнее всего производить изменения (крупные, ибо мелкие удобнее в GEdit по gvfs) посредством rsync. То есть, слил код сайта себе на машину, доработал, залил обратно. Когда-то я это делал вручную, по одному файлу, что было сложно и опасно. Тем, кто делает так до сих пор, посвящается статья.

Нужен будет ssh-доступ на искомый сайт.
читать дальше →

Область видимости переменной в Javascript (variable scope) – ликбез 22 декабря 09

Для меня одним из самых проблемных мест Javascript было управление переменными. Излагаю простым русским языком.

Области видимости переменных

Переменные в Javascript бывают глобальными и локальными. Глобальная переменная доступна везде, локальная – только в текущей области видимости.

Технически, глобальные переменные – всего лишь свойства объекта window, поскольку весь код выполняется в его контексте.

<script>
  alert(location); // сообщит window.location
</script>

Из этого следует, что глобальные переменные могут затирать свойства window (я уже молчу о том, что они зло, нарушают инкапсуляцию и все такое).

Объявление переменных

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

function foo() {
  a = 2;
  b = 3;
  return a+b;
}
alert(a); // undefined
a = 'очень важное значение';
alert(a); // очень важное значение
foo();
alert(a); // 2

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

Явно объявлять переменные можно и нужно ключевым словом var.

var a = 2;

Такая строка всегда создает новую локальную переменную. Если объявление происходит вне функций, то она будет глобальной, что вполне логично.

function foo() {
  var a = 2;
  var b = 3;
  return a+b;
}
alert(a); // undefined
var a = 'очень важное значение';
alert(a); // очень важное значение
foo();
alert(a); // очень важное значение

Как объявить глобальную переменную из функции? Как обратиться к глобальной переменной, если есть локальная с таким же именем? Очень просто – нужно обратиться к ней как к свойству window:

function foo() {
  var location = 'location';
  alert(location); // вернет 'location'
  alert(window.location); // вернет window.location
  window.a = 'переменная из функции';
}
alert(a); // undefined
foo();
alert(a); // переменная из функции

Наследование области видимости

Меня всегда смущало то, что в Javascript можно определять функции внутри функций, а использовать их потом где угодно. Ну да, если посмотреть, точно то же самое можно делать в Ruby, и, наверное, во многих других языках тоже.

Переменные при этом передаются очень просто: если на момент определения функции переменная существовала, то она будет существовать и внутри функции. Откуда бы ее не вызывали.

function alertOnTimeout(message, timeout) {
 return setTimeout(function() { 
    // message будет доступен в безымянной функции, переданной таймауту
    alert(message); 
  }, timeout);
}

Передача кода по старинке – строкой, которая прогоняется через eval() – не попадает под это правило, код исполняется в той области видимости, где и определен.

Поскольку объекты в Javascript – это тоже типа функции, то свойство объекта определяется точно так же, как и переменная.

function myObject() {
  var property = 0;
  // Cамо собой, property будет доступно только внутри объекта.
}

А еще в Javascript область видимости переменной ограничивается только функциями, а не блоками типа if (привет, Паскаль). Потому удобнее всего объявлять переменные в начале функции.

this

А что this? А то, что эта переменная автоматически появляется в методах объектов и затирает значение this из предыдущей области видимости. Решение простое – переприсваивать ее значение другой переменной.

$('div.with-links').click(function() {
  var theDiv = this; //сохраняем значение this
  $(this).find('a').click(function() {
    alert($(this).attr('href')); // this - это ссылка
    theDiv.remove(); // а theDiv - это все еще дивак
  });
});

Отдельно замечу, что при оборачивании какой-то поведенческой логики в объект надо помнить, что в создаваемых DOM-событиях значение this самого объекта теряется.

function myObject() {
  var _this = this; // сохраняем ссылку на родительский объект
  var linkRemoved = false;
 
  $('a').click(function() {
    $(this).remove(); // this - это объект ссылки
    _this.linkRemoved = true; // _this - это родительский объект
  });
}

Как открыть терминал из Gvim 10 декабря 09

Во-первых, я добрался до сессий в виме – теперь не приходится переходить в каталог проекта вручную. Пригодился плагин SessionMan.

Во-вторых, раз такое дело, надоело открывать vim и терминал двумя командами. Придумал открывать терминал из вима. Делается это так:

" open new terminal on keystroke
map <F5> <esc><esc>:silent !/usr/bin/x-terminal-emulator<CR>

Кроме того, зачем открывать терминал чтоб набрать там script/console (который у меня сам по себе сокращен до sc)?

" open script/console on keystroke
map <F6> <esc><esc>:silent !/usr/bin/x-terminal-emulator -e script/console<CR>

Само собой, таким образом можно открывать что угодно.

  • silent не просит после выполнения команды нажать ENTER
  • /usr/bin/x-terminal-emulator, в отличие от sh, который в gvim практически не работает, открывает терминал в новом окне.
  • ключ -e выполняет в терминале какую-нибудь команду и закрывается после ее завершения.

Мой единственный совет начинающим программистам 20 ноября 09

Вообще я написал длиннющую статью на эту тему, но потом подумал, что она бесполезна. Нельзя статьей передать опыт.

Если на работу не берут – твоя проблема не в том, что на дворе кризис, рабочих мест мало, что у тебя плохое резюме, что ты мало вращался в каких-то там «кругах», прочитал мало книжек или плохо учился (ничему не учили?) в универе.

Тебе просто нужен практический опыт. Практический. Опыт.

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

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

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

Удачи.

UPD: Мое плохое отношение к опенсорсу основывается на большом количестве людей, которые делают из программирования не за деньги, а ради удовольствия какую-то религию, а из себя, соответственно, мучеников за идею и борцов за свободу.

Избавление gitа от ненужных файлов в глобальном масштабе 16 ноября 09

До недавних времен первым делом после создания git-репозитария я добавлял в .gitignore разнообразные служебные и резервные файлы, которыми IDE и редакторы засоряют проект.

Так вот, сделать это можно раз и навсегда, поскольку git поддерживает глобальный .gitignore.
Технически он наоборот, локальный, поскольку действует только на локальные репозитарии.
Сотоварищей он не спасет. По-моему это вполне законно – инструментарий у каждого свой, поэтому и резервные копии у каждого свои.

Так вот, сначала сообщаем гиту о наличии глобального файла .gitignore:

git config --global core.excludesfile ~/.gitignore

…а потом отправляем в ~/.gitignore список ненавистных файлов. У меня такой:

*.*.sw*
*~

*.log
Thumbs.db

nbproject

Подробнее о gitignore