Oct 30

Announcement / NEWS / CHANGELOG.

A nice human-readable :) list of changes since 1.9.2 based on 1.9.3-preview1 is here.

I’ve already switched to 1.9.3 a couple of weeks ago in my development environment, now it’s time to make a switch in production ones.

written by fxposter

Oct 30
brew install readline
brew link readline
CFLAGS='-I/usr/local/include' LDFLAGS='-L/usr/local/lib' rvm install ree

written by fxposter

Aug 27

Капец! :)))

written by fxposter \\ tags:

Aug 22

Уже давным давно мой отец просил меня сделать сайт модельному агентству его жены. Основная проблема заключалась в отсутствии дизайна сайтов и нежелании нанимать дизайнера (у меня с дизайном всё не очень хорошо, если не сказать хуже). В конце-концов я с горем-пополам нафигачил простенький дизайн и за пару дней приделал к нему базовых функционал и панельку для администрирования этого всего дела.

Комментарии по поводу улучшения как дизайна так и функционала категорически приветствуются. :)

Технические подробности: сайт использует Rails 3 RC, jQuery, MongoDB (в качестве ORM используется Mongoid), а для ресайза изображений используется CarrierWave (так как больше ничего поддерживающего Mongoid я не нашел) + RMagick + ImageMagick.

Кстати говоря, для того, чтобы запустить этот сайт мне пришлось обновить операционку на моем Linode-овском VPS, т.к. RMagick требовал более новую версию ImageMagick-а, чем та, которая была в репозиториях Ubuntu 9.04. Кстати, в Linode Library есть очень хороший гайд по обновлению.

written by fxposter \\ tags: , , ,

Jul 11

Некоторые свои посты я дополняю постами-линками на Хабрахабре. Последний подобный пост - “Особенности работы jQuery.live()“. Он так и не попал на главную, но в данном случае это не очень важно, т.к. свой “хабраэффект” я поймал.

До этого у меня случались странности на сервере - он начинал есть все 400% процессора и наглухо зависал - к нему было невозможно подключиться и единственным вариантом восстановления был шатдаун инстанса в админке Linode. Это случалось нечасто и не особо меня парило. Но когда на мой сайт стали постоянно заходить люди с хабра - такое поведение сервера стало постоянным. Он начинал есть весь процессор. После перезагрузки он пару минут держался, а потом опять начинал свой бравое дело. Стало ясно - что-то явно не так в настройках.

После довольно недолгих разбирательств стало ясно, что виноват Apache (у меня стоит Apache + mod_prefork + php + ruby via passenger). Параметры prefork-а:

<IfModule mpm_prefork_module>
StartServers          5
MinSpareServers       5
MaxSpareServers      10
MaxClients          150
MaxRequestsPerChild   0
</IfModule>

Теперь мне стало очевидно всё, кроме одной вещи - почему же все-таки был загружен процессор… А причина зависаний сервера была проста - параметр MaxClients отвечает за то, сколько процессов апач может запустить для обработки пользователей (в prefork-е каждый запрос обрабатывается отдельным процессом, который либо запускается, либо вытаскивается из пула свободных запущенных процессов). Процессы апача ну очень недешевые в плане ресурсов - честно говоря, я до сих пор не знаю, как можно посмотреть, сколько реально кушает процесс апача - по текущей нагрузке на сервак при создании новых процессов апач кушает лишние 10-20мб памяти. 150 процессов * 10 мегабайт… Ну вы поняли. У меня сейчас обычныя VPS-ка на 360Мб. В общем, причина зависаний была в том, что апач создавал кучу процессов и уходит в своп по самое нехочу. А вот вернуться оттуда он уже сам не мог - ему нужна была помощь.

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

ab -c 1 -n 100 http://blog.fxposter.org/

Эмм… Результата я не дождался.

Для тех, кто не работал с ab и не знает, что это такое - Apache Benchmark.

ab -c 1 -n 10 http://blog.fxposter.org/

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

Собственно, стало понятно, как бороть “хабраэффект” - нужно сильно ускорить отдачу странички. Как? Да очень просто - кешированием. Слава богу, что это WordPress и тут есть такие плагины, как wp-cache - прикручивается за несколько минут и пока что отлично работает. Если бы такого плагина не было, то, наверное, у меня бы была сейчас статья о том, что такое Reverse Proxy и как его использовать для кеширования. :) А так - поставил, включил. Результаты:

Заодно решил немного пооптимизировать клиентскую часть - добавить expires и сжатие контента (использовался модуль апача deflate).

Приведу конечные файлы:

# /etc/apache2/mods-available/expires.conf
<IfModule mod_expires.c>

ExpiresActive On

ExpiresByType image/gif "access plus 1 weeks"
ExpiresByType image/png "access plus 1 weeks"
ExpiresByType image/jpeg "access plus 1 weeks"

ExpiresByType text/css "access plus 1 weeks"
ExpiresByType application/javascript "access plus 1 weeks"
</IfModule>
# cat /etc/apache2/mods-available/deflate.conf
<IfModule mod_deflate.c>
 # AddOutputFilterByType DEFLATE text/html text/plain text/xml

 DeflateCompressionLevel 9

 # Insert filter
 SetOutputFilter DEFLATE

 # Netscape 4.x has some problems...
 BrowserMatch ^Mozilla/4 gzip-only-text/html

 # Netscape 4.06-4.08 have some more problems
 BrowserMatch ^Mozilla/4\.0[678] no-gzip

 # MSIE masquerades as Netscape, but it is fine
 # BrowserMatch \bMSIE !no-gzip !gzip-only-text/html

 # NOTE: Due to a bug in mod_setenvif up to Apache 2.0.48
 # the above regex won't work. You can use the following
 # workaround to get the desired effect:
 BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html

 # Don't compress images
 SetEnvIfNoCase Request_URI \
 \.(?:gif|jpe?g|png)$ no-gzip dont-vary

 <IfModule mod_headers.c>
 # Make sure proxies don't deliver the wrong content
 Header append Vary User-Agent env=!dont-vary
 </IfModule>
</IfModule>

Если вас интересует, почему эти файлы выглядят именно так - читайте документацию к модулям апача.

На другой ОС (у меня Ubuntu 9.04) эти файлы, наверняка, должны будут находиться в других местах. У себя я их разместил так, потому что эти конфиги автоматически подключаются вместе с подключением соответствующих модов апача (при использовании команды a2enmod).

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

PS. С точки зрения клиентской оптимизации - css-ки и js-ки лучше бы собрать в один файл, а кучу запросов к изображениям заменить спрайтами. Заниматься первым особо не вижу смысла, т.к. и стилей и скриптов на блоге немного, а изображениями заниматься лень, т.к. все равно собираюсь переезжать на “Twenty Ten“.

written by fxposter \\ tags: , , , , ,

Jul 07

Есть такая фича в Rubygems, как установка гем-ов в директорию пользователя ~/.gem, если gem install запускается без sudo и, соответственно, у пользователя нет разрешения установить что-либо в дефолтную директорию.

В Rubygems до версии 1.3.6 это решалось просто - автоматически. Т.е. если вы вместо sudo gem install пишете gem install - вам выдается warning, но гем успешно устанавливается:

[fxposter@kitty ~]$ gem install haml
WARNING:  Installing to ~/.gem since /usr/lib/ruby/gems/1.8 and
/usr/bin aren't both writable.
Successfully installed haml-3.0.13
1 gem installed
Installing ri documentation for haml-3.0.13...
Installing RDoc documentation for haml-3.0.13...

Вроде бы всё замечательно. Но с версии 1.3.6 это дефолтное поведение изменилось, и при обновлении с помощью gem update --system об этом честно пишется:

–user-install is no longer the default. If you really liked it, see Gem::ConfigFile to learn how to set it by default. (This change was made in 1.3.6)

Но кто реально читают все эти тексты? Да и не все обновляют Rubygems таким образом. В нашем случае произошла смена сервера. Причем на старом сервере стояла старая версия Ruby Enterprise Edition и дефолтные Rubygems 1.3.5. На новом же серваке наши бравые админы опять всё “покомпилили” и вместе с REE у нас там обновился и Rubygems - теперь там стояла версия 1.3.7. И… сначала отвалился деплой, т.к. по умолчанию rake gems:install у нас там не включен. Запустили rake gems:install - не работает. Запустили gem install - не работает. Всё просто замечательно.

Разбирались долго. Сначала думали на админов, которые в рельсах плохо шарят и думали, что они там “понастраивали”. Кстати, понастраивали они действительно знатно - скомпилированный REE почему-то стоит поверх стандартного Ruby 1.8.5 (у нас там CentOS 5, в ближайшем времени переезжаем на Amazon EC2 на Ubuntu). Но это неважно. После долгого разговора с админом и ковыряния серваков я чисто случайно обратил внимание на разные версии Rubygems. Заподозрил неладное. Обновил их на старом серваке на 1.3.7 и… получил сообщение, написанное выше. Отлично. Теперь осталось восстановить поведение старых Rubygems.

В итоге всё оказалось довольно просто. В файл ~/.gemrc нужно дописать следующую строку: "install: --user-install". После чего мой .gemrc стал выглядеть так:

[waysgo@web-waysgo ~]$ cat .gemrc
---
:benchmark: false
:update_sources: true
:sources:
- http://rubygems.org/
- http://gems.github.com/
:bulk_threshold: 1000
:verbose: true
:backtrace: false
install: --user-install

written by fxposter \\ tags: ,

Jun 16

На работе столкнулись с “особенностью” работы jQuery.live(), на которую хотелось бы обратить внимание, потому как, судя по всему, отнюдь не все о ней знают (и в результате чего пишут неработающий код).

Итак, простой пример - навешивание двух событий на один и тот же элемент:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
  <title>jQuery.live() test page</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  <script type="text/javascript">
  jQuery(function() {
    $("a").bind("click", function(e) {
      alert("a");
      return false;
    });
    $("a").bind("click", function(e) {
      alert("b");
      return false;
    });
  });
  </script>
</head>
<body>
  <a href="http://blog.fxposter.org/">Блог FX-а</a>
</body>
</html>

Пример можно посмотреть здесь. В результате клика на ссылку - получаем 2 alert-а, всё хорошо, ожидаемо и предсказуемо.

Переписываем код для работы с jQuery.live(). Для тех, кто в танке - live() вешает событие не на сам элемент, а на document. В результате bubbling-а событие, которое произошло над каким-либо элементом поднимается вверх по DOM-дереву и соответственно вызывает обработчики всех элементов, которые оно встретит. Если вы и этого не знали - то вам не нужно читать мой блог, а пора идти и покупать книгу по JavaScript-у (мне, кстати, тоже давно пора, но всё никак не соберусь). Итак, в конце концов событие доходит до document-а и обработчики вызываются у него. Обработчик, который устанавливает jQuery.live() проверяет - соответствует ли event.target (а именно здесь хранится обьект DOM-дерева, с которым произошло событие) соответствующему селектору (в данном случае - это селектор “a”) и если соответствует - то выполняет обработчик.

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

Далее - зачем нужен “return false” в конце обработчика? Он предотвращает от того, чтобы вызывалось действие по умолчанию (в данном случае - переход по ссылке и событие не поднималось выше). Чаще всего JS-разработчики вообще не думают о bubbling-е и под “return false” понимают только “отмену действия по умолчанию”, ну или они вообще не знают, что именно происходит и пишут “return false”, потому что так работает.

Такое отношение jQuery частенько прощает. Но не в случае с live()-методом. Попробуем запустить следующий пример:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
  <title>jQuery.live() test page</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  <script type="text/javascript">
  jQuery(function() {
    $("a").live("click", function(e) {
      alert("a");
      return false;
    });
    $("a").live("click", function(e) {
      alert("b");
      return false;
    });
  });
  </script>
</head>
<body>
  <a href="http://blog.fxposter.org/">Блог FX-а</a>
</body>
</html>

В результате клика теперь выскакивает только один alert. Пора обратится к документации:

# To stop further handlers from executing after one bound using .live(), the handler must return false. Calling .stopPropagation() will not accomplish this.

Хаха. В данном случае jQuery интерпретирует false “несколько иначе”. :)

Для того, чтобы “пофиксить” подобный баг нужно обратится все к тому же bubbling-у и обработке событий и сделать именно то, что предполагается разработчиком - “отменить действие по умолчанию”. Это делается с помощью метода event.preventDefault() (пример):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
  <title>jQuery.live() test page</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  <script type="text/javascript">
  jQuery(function() {
    $("a").live("click", function(e) {
      alert("a");
      e.preventDefault();
    });
    $("a").live("click", function(e) {
      alert("b");
      e.preventDefault();
    });
  });
  </script>
</head>
<body>
  <a href="http://blog.fxposter.org/">Блог FX-а</a>
</body>
</html>

И самое главное (барабанная дробь!) - при использовании bind() для навешивания обработчиков preventDefault() тоже можно использовать!

Наткнулись мы на эту “фичу”, когда у нас почему-то перестали вызываться некоторые обработчики

Напоследок, замечу еще одно - элемент, на который навешено хотя бы один обработчик события через bind() с “правильно работающим return false”, никогда не будет вызывать никакие live()-события. ;)

Так что будьте бдительны и не забывайте об особенностях обработки событий в JS. Удачи.

written by fxposter \\ tags: , , ,

May 10

Недавно писал проект на Rails 2.3 + MongoDB + MongoMapper. Не могу сказать, что все было хорошо - для того, чтобы существующие плагины для рельсов заработали с MongoMapper-ом пришлось немного повозиться, но в итоге все закончилось хорошо. :)

А мой сегодняшний рассказ пойдет о некоторых особенностях MongoDB и MongoMapper-а, с которыми вы, скорее всего столкнетесь, если будете использовать эти библиотеки.

Для начала поговорим немного о MongoDB. Что это такое?

MongoDB — документо-ориентированная система управления базами данных (СУБД) с открытым исходным кодом, не требующая описания схемы таблиц. Написана на языке C++. СУБД управляет наборами JSON-подобных документов, хранимых в двоичном виде в формате BSON.

Если попробовать вкратце охарактеризовать эту базу данных, то получится что-то вроде этого: аналог реляционной СУБД без join-ов и транзакций, зато с поддержкой структур данных (массивов, хешей).

MongoMapper - это “ORM” для MongoDB, написанный на руби. “ORM” в кавычках потому что сложно себе представить ORM для нереляционной базы данных. Я бы скорее назвал это высокоуровневой оберткой над API, которое предоставляет MongoDB, с поддержкой ассоциаций между записями и много чем еще.

Теперь, собственно, о “особенностях” MongoDB и MongoMapper-а.

Вложенные документы

Вложенные документы в понятии MongoMapper-а - это когда одни обьекты хранят внутри себя другие. Для примера, возьмем следующую модель:

class User
  include MongoMapper::Document

  key :login
  key :password
  key :salt

  many :posts
  many :addresses
end

class Post
  include MongoMapper::Document
  key :title, String
  key :body, String
  timestamps!
end

class Address
  include MongoMapper::EmbeddedDocument
  key :type, String
  key :country_id, ObjectId
  key :city_id, ObjectId
  key :city_address, String
end

Чтобы понимать, что тут происходит нужно, во-первых знать Ruby :) и во вторых - прочесть пост John Nunemaker (даже не знаю, как правильно перевести :)) о MongoMapper-е. Вот еще один, кстати, тоже интересный.

Таким образом имеем модель пользователя, которая хранить набор постов, написанных этим пользователем и набор адресов пользователя (домашний, рабочий, etc).

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

user = User.first
user.posts

Последняя комманда вернет “scope”/”relation”, а не просто массив элементов (люди, знакомые с named_scope в ActiveRecord версии < 3 поймут).
Соответственно, можно дродолжить эту комманду, например, так:

user.posts.all(:conditions => { :created_at => { "$gt" => (Date.today - 10.days) } })

В то время как

user.addresses

вернет массив и всю дополнительную фильтрацию прийдется производить с помощью Ruby.

Манипуляции со вложенными документами

У EmbeddedDocument нет понятия “id”, т.е. отличать обьекты Address один от другого можно только по их индексу в массиве user.addresses либо вводя “свои” идентификаторы. Но даже в этом случае выбирать соответствующий обьект прийдется “вручную”:

user.addresses.detect { |address| address.identifier == params[:address_identifier] }

PS. Для тех, кто в танке: detect - это аналог select { |address| … }.first.

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

С удалением вообще интересная история. Удалить обьект имея только ссылку на него - нельзя, нужна еще и ссылка на массив, в котором он хранится:

user.addresses.first.delete/destroy

не работает, т.к. таких методов у вложенных обьектов нет. Нужно делать так:

user.addresses.delete_at(address_index)

если вы знаете индекс в массиве адресов, либо

user.addresses.delete_if { |address| address.identifier == address_identifier }

если знаете идентификатор или удаляете по какому-либо другому полю.

Несложно, но проблемы на первых порах с этим бывают.

“идентификаторы” обьектов MongoDB

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

Например, вместо такой модели:

class Tag
  include MongoMapper::Document
  key :name, String
end

иметь такую

class Tag
  include MongoMapper::Document
  # id-шник явно никогда не прописывается
end

И создавать обьект так:

Tag.create(:_id => "ruby on rails")

MongoDB это позволяет делать. Более того - драйвер Ruby для MongoDB это тоже может делать. Проблема в том, что это не умеет делать MongoMapper. Совершенно. Как обойти эту проблему я на данный момент не знаю (я в итоге сделал модель первого типа и забил), есди кто знает решение - напишите, будет интересно.

И напоследок поговорим о

Has And Belongs To Many в MongoDB

… или habtm, знакомый многим по ActiveRecord.

Приведу пример из моего проекта: есть домены, есть их модераторы, связть many-to-many. Для каждого домена хочется видеть список модераторов и для каждого модератора хочется видеть список доменов, которые он модерирует.

Честно говоря, задача поставила меня в тупик. Т.е. как все мы решаем эти задачи с помощью RDBMS? Правильно - связующей таблицей. Так же можно было поступить и здесь, но… Как-то это было некрасиво, на мой взгляд, использовать этот подход вв документоориентированной базе данных.

Первое решение, которое мне пришло в голову - хранить массив id-шников доменов для каждого пользователя и хранить массив id-шников пользователей для каждого домена и синхронизировать их.

Последние два слова очень смущали - синхронизация вносила излишнюю сложность. Т.е. реализовать-то её можно, но очень не хотелось.

Собственно, обратился за помощью к знакомым и вышел на одного человека (ник - Necromant, сайта его я не знаю :) ), который помог решить мне эту проблему.

Итак, решение - хранить только один список id-шников (или в юзерах или в доменах) и сделать по нему индекс, соответственно, вывод как доменов по юзеру так и юзеров по домену будет достаточно быстрый.

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

class Domain
  include MongoMapper::Document
  # ...
  key :moderator_ids, Array, :index => true
  def moderators
    User.find moderator_ids
  end
end

class User
  include MongoMapper::Document
  # ...
  def moderator_of?(domain)
    domain.moderator_ids.include? id.to_s
  end

  def moderated_domains
    Domain.all :moderator_ids => id.to_s
  end
end

Еще хочу рассказать о том, как подружить MongoMapper и Clearance, но пост вроде и так не маленький получился, так что ждите еще один пост о MongoMapper-е в ближайшие дни.

PS. Совсем писать разучился… :(

PPS. Если вам понравился этот пост, проголосуйте за него на Habrahabr-е. :)

written by fxposter \\ tags: , , ,

May 02

Настало время возродить этот блог. За более чем полугодие на нем не появилось ни одной статьи и я наконец-то намерен прекратить этот “застой”. Да, по сравнению с временами, которые были раньше у меня стало существенно меньше времени, чтобы писать что-то большое и серьезное - работа, университет (если быть точнее - то написание диплома) и еще один “секретный сайд проект” о котором, возможно, я когда-нибудь всем расскажу.

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

Ну, во-первых, я теперь не пишу на PHP. Вообще не пишу. И не очень сильно слежу за последними веяниями моды, связанными с этим языком. Да, я все равно иногда появляюсь в группе симфонистов (кому нужен доступ туда - пишите, там новичков любят и на многие нубские вопросы отвечают :) ). Да, я иногда посматриваю на фреймворки, которые сейчас являются популярными в сфере PHP - всякие Yii, ZF, etc. Посматриваю исключительно для того, чтобы быть в курсе происходящего. Учить их и писать на них в общем-то не очень хочется.

На что я променял PHP? Естественно на Ruby. Я не восхищаюсь этим языком, не превозношу его над остальными, просто на работе я пишу на нем (кстати, я работаю над стартапом для бизнесов - WaysGo, запуск будет, я надеюсь, очень скоро) и ни на чем другом желания писать особо не возникает - с точки зрения веб-разработки Ruby меня полностью устраивает. Этот язык удобен и достаточно экспрессивен. А Ruby On Rails - отличное средство для тех, кто хочет писать веб-приложения. Ну, в общем, вы меня поняли. ;)

Кстати говоря, я немного соврал насчет того, что я пишу на Ruby на работе. С начала этой недели я пишу iPhone-приложение на Objective-C и, в связи с этим, шустренько осваиваю Mac, Xcode, сам Objective-C и всякую прочую фигню, которая нужна для разработки под iPhone. Определенного мнения по поводу мака я пока сказать не могу, а вот Objective-C с первого подхода отличается похожестью на руби с точки зрения вызовов методов у объектов и открытых классов, но в то же время отличается несколько некрасивым синтаксисом (квадратные скобки, рррррр). С другой стороны, к синтаксису Ruby у меня тоже изначально было плохое отношение. Это отношение, кстати, не изменилось - я по прежнему считаю, что Ruby - непонятный язык для новичка, и если код на, например, Python можно просто читать, то для того, чтобы почитать код на Ruby придется сначала посмотреть туториалы, или что-то в этом роде, потому что синтаксис кроме выразительности отличается еще и неочевидностью. После пары дней работы с Ruby проблемы отпадают сами собой, так что этот аспект меня уже не очень беспокоит. Собственно, я надеюсь, что рано или поздно я перестану замечать синтаксис Objective-C и буду просто писать код, который делает что мне нужно.

Вот, собственно, и все на сегодня. Вроде ничего не забыл. До скорого! :)

written by fxposter \\ tags: , , , , ,

Sep 16

Видео от создателя Ruby On Rails и одного из первых сотрудников 37 Signals о том, что нужно делать, чтобы зарабатывать деньги в интернете (”The secret of making money online”). Видео я смотрел давно уже, но без слайдов. После того, как мне наш проджект менеджер прислал ссылку на это же видео, решил найти вариант со слайдами. Смотрим.

PS. DHH рассказывает, казалось бы, об очевидных вещах. Однако отнюдь не всем эти вещи приходят в голову без “толчка” со стороны.

written by fxposter \\ tags: ,