domingo, 13 de novembro de 2011

Deploy de aplicações Rails e Django no Heroku

No post anterior foi mostrada a importância de se colocar o projeto num servidor de controle de versões, mesmo logo após a sua criação. Algumas preocupações puderam ser rapidamente testadas e solucionadas, por exemplo o setup em máquinas de outros desenvolvedores.

Ainda nesta fase tão precoce, podemos ir mais além: testar o deploy da aplicação num ambiente production-like. Desta forma, cria-se uma cultura de, em cada iteração, testar de ponta-a-ponta o processo de desenvolvimento.

Um dos objetivos é antecipar questões e riscos que tradicionalmente só seriam atacados ao final do projeto. Caso surjam problemas de integração, ou dificuldades técnicas que possam colocar em risco a subida do sistema para produção, as causas podem ser prontamente diagnosticadas e tratadas o mais cedo possível antes que possam comprometer o sucesso do projeto.

Setup do Heroku

O Heroku é uma opção muito popular para o deploy de aplicações Rails, que consiste simplesmente em executar:

git push heroku master

ou seja, pushando o projeto para um repositório Git remoto no Heroku.

A preparação é semelhante a da criação de uma conta no Github. É necessário criar uma conta no Heroku, e fazer o upload de uma chave pública para administração da conta via terminal.

Instalação

O primeiro passo é instalar a gem do heroku:

gem install heroku

Chave pública

Em seguida, fazer o upload da chave pública:

heroku keys:add
Enter your Heroku credentials.
Email: fabio@miranti.net.br
Password:
Could not find an existing public key.
Would you like to generate one? [Yn] Y
Generating new SSH public key.
Uploading ssh public key /home/fabio/.ssh/id_rsa.pub
view raw heroku keys hosted with ❤ by GitHub

Caso o heroku não encontre uma chave pública em ~/.ssh/id_rsa.pub, e também não exista a chave privada ~/.ssh/id_rsa correspondente, o próprio heroku é capaz de gerar estas chaves para nós (como mostrado no trecho acima).

Podemos também aproveitar a chave pública do github, bastando que ela esteja presente em ~/.ssh/id_rsa.pub para ser upada para o heroku.

Caso mais de uma chave esteja presente no diretório .ssh, o heroku pergunta qual deve ser usada:

1) heroku_test_id_rsa.pub
2) id_rsa.pub
Which would you like to use with your Heroku account? 2
Uploading ssh public key /home/fabio/.ssh/id_rsa.pub

Criação da aplicação no Heroku

Resolvida a questão das chaves, podemos criar a aplicação no heroku:

heroku create
Creating afternoon-robot-3025... done, stack is bamboo-mri-1.9.2
http://afternoon-robot-3025.heroku.com/ | git@heroku.com:afternoon-robot-3025.git
Git remote heroku added
view raw heroku create hosted with ❤ by GitHub
Este comando cria um ambiente no Heroku e o adiciona automaticamente como um repositório Git remoto do projeto, o que pode ser confirmado:
git remote
heroku
origin
Note também que, após deployada, a aplicação responderá inicialmente no endereço http://afternoon-robot-3025.heroku.com.

Deploy de uma aplicação Rails no Heroku

Dispensa comentários:

git push heroku master
Counting objects: 67, done.
Compressing objects: 100% (46/46), done.
Writing objects: 100% (67/67), 26.27 KiB, done.
Total 67 (delta 5), reused 67 (delta 5)
-----> Heroku receiving push
-----> Ruby/Rails app detected
-----> Detected Rails is not set to serve static_assets
Installing rails3_serve_static_assets... done
-----> Configure Rails 3 to disable x-sendfile
Installing rails3_disable_x_sendfile... done
-----> Configure Rails to log to stdout
Installing rails_log_stdout... done
-----> Gemfile detected, running Bundler version 1.0.7
Unresolved dependencies detected; Installing...
Using --without development:test
Fetching source index for http://rubygems.org/
Installing rake (0.9.2.2)
Using bundler (1.0.7)
...
Installing rails (3.1.1)
Installing sass (3.1.10)
Installing sass-rails (3.1.4)
Installing sqlite3 (1.3.4) with native extensions
Installing uglifier (1.0.4)
Your bundle is complete! It was installed into ./.bundle/gems/
-----> Compiled slug size is 6.3MB
-----> Launching... done, v4
http://afternoon-robot-3025.heroku.com deployed to Heroku
To git@heroku.com:afternoon-robot-3025.git
* [new branch] master -> master
view raw Rails on Heroku hosted with ❤ by GitHub

Até mesmo para abrir o navegador, o heroku ajuda:

heroku open

Rackfile e Procfile

O Heroku consegue fazer o deploy de aplicações Rails devido à presença do arquivo config.ru na raiz do projeto. Trata-se de um arquivo Rack, que instrui ao Heroku a forma como a aplicação é iniciada:

# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
run AppContatosRails::Application

Procfile e Foreman

Uma alternativa ao Rack é criar um arquivo Procfile na raiz do projeto. Trata-se de um mecanismo de declarar quais comandos devem ser executados ao deployar na plataforma Heroku. Ele consiste de um conjunto de pares informando o tipo de processo e o comando para executar um determinado processo:

web: rails s
worker: QUEUE=* bundle exec rake resque:work
urgentworker: QUEUE=urgent bundle exec rake resque:work
clock: bundle exec clockwork clock.rb
view raw Procfile hosted with ❤ by GitHub
Apesar de opcional em aplicações Rails, seu uso é recomendado pois proporciona maior controle e flexibilidade para a aplicação. Além disso, o Heroku provê o comando

heroku scale

para ajustar como escalar a aplicação, baseados no Procfile.

Para executar localmente aplicações baseadas no Procfile, é necessário instalar o Foreman.

gem install foreman

Ao iniciar a aplicação com o foreman, ele inicia cada um dos processos declarados no Procfile:

foreman start
02:22:56 web.1 | started with pid 6323
02:22:59 web.1 | => Booting WEBrick
02:22:59 web.1 | => Rails 3.1.1 application starting in development on http://0.0.0.0:3000
02:22:59 web.1 | => Call with -d to detach
02:22:59 web.1 | => Ctrl-C to shutdown server
02:23:01 web.1 | [2011-11-15 02:23:01] INFO WEBrick 1.3.1
02:23:01 web.1 | [2011-11-15 02:23:01] INFO ruby 1.9.2 (2011-07-09) [i686-linux]
02:23:01 web.1 | [2011-11-15 02:23:01] INFO WEBrick::HTTPServer#start: pid=6326 port=3000

Django e Heroku

Conforme anunciado neste post, o Heroku também oferece suporte a Python, no stack* Cedar, baseado no Ubuntu 10.04.
* o Heroku oferece diferentes stacks - ambientes completos de deploy, incluindo o sistema operacional e bibliotecas associadas.

Para criar um ambiente no stack cedar, é necessário executar o comando:

heroku create --stack cedar

e o deploy também é feito via git push:

heroku create --stack cedar
Creating strong-stream-6920... done, stack is cedar
http://strong-stream-6920.herokuapp.com/ | git@heroku.com:strong-stream-6920.git
Git remote heroku added
git push heroku master
Counting objects: 39, done.
Compressing objects: 100% (26/26), done.
Writing objects: 100% (39/39), 8.29 KiB, done.
Total 39 (delta 14), reused 31 (delta 10)
-----> Heroku receiving push
! Heroku push rejected, no Rails or Rack app detected
To git@heroku.com:falling-journey-6271.git
! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'git@heroku.com:falling-journey-6271.git'

Porém, como se vê acima, o push é rejeitado, informando que o Heroku não consegue detectar nenhuma aplicação Rails/Rack. Alguns ajustes são necessários para conseguir realizar o deploy da aplicação Django no Heroku.

Procfile para rodar aplicações Django no Heroku

Aplicações que NÃO são baseadas no Rack podem ser deployadas no Heroku usando o Procfile. No caso de uma aplicação Django, basta criar um Procfile com o seguinte conteúdo:

web: python manage.py runserver

que a aplicação passa a ser inicializável via foreman:

foreman start
02:33:47 web.1 | started with pid 6483
02:33:49 web.1 | Validating models...
02:33:49 web.1 |
02:33:50 web.1 | 0 errors found
02:33:50 web.1 | Django version 1.3.1, using settings 'App-Contatos-Django.settings'
02:33:50 web.1 | Development server is running at http://127.0.0.1:8000/

Agora que está tudo rodando localmente, basta apenas fazer o deploy e correr pro abraço, vai dar até pra sair mais cedo e pegar um cineminha:

git push heroku master
Counting objects: 45, done.
Compressing objects: 100% (30/30), done.
Writing objects: 100% (45/45), 8.86 KiB, done.
Total 45 (delta 16), reused 31 (delta 10)
-----> Heroku receiving push
-----> Python app detected
! Django app must be in a package subdirectory
! Heroku push rejected, failed to compile Python app
To git@heroku.com:strong-stream-6920.git
! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'git@heroku.com:strong-stream-6920.git'
Oooops... Agora, através do Procfile, o Heroku já é capaz de reconhecer que se trata de uma aplicação Python, porém continua rejeitando o deploy, com uma enigmática mensagem "Django app must be in a subdirectory".

Ainda bem que o Google e o Stack Overflow existem, e alguém já havia postado a solução para este problema: http://stackoverflow.com/questions/7974902/deploying-existing-django-app-on-heroku.

Para deployar a aplicação Django no Heroku, ela precisa estar organizada de tal forma que o Procfile e o requirements.txt fiquem na raiz do repositório, e o projeto Django deve ser um subdiretório a partir da raiz do repositório.

Com esta nova organização, é importante assegurar que o arquivo settings.py esteja com o valor correto para a propriedade ROOT_URLCONF:

ROOT_URLCONF = 'urls'

Ajustada a estrutura do projeto, aí sim, esperamos que o deploy seja bem sucedido.

git push heroku master
Counting objects: 49, done.
Compressing objects: 100% (33/33), done.
Writing objects: 100% (49/49), 9.42 KiB, done.
Total 49 (delta 17), reused 31 (delta 10)
-----> Heroku receiving push
-----> Python/Django app detected
-----> Preparing virtualenv version 1.6.4
New python executable in ./bin/python
Installing setuptools............done.
Installing pip...............done.
-----> Django settings injection
Injecting code into App-Contatos/settings.py to read from DATABASE_URL
-----> Installing dependencies using pip version 1.0.2
Downloading/unpacking Django==1.3.1 (from -r requirements.txt (line 1))
Creating supposed download cache at /app/tmp/repo.git/.cache/pip_downloads
Storing download in cache at /app/tmp/repo.git/.cache/pip_downloads/http%3A%2F%2Fpypi.python.org%2Fpackages%2Fsource%2FD%2FDjango%2FDjango-1.3.1.tar.gz
Running setup.py egg_info for package Django
Requirement already satisfied (use --upgrade to upgrade): wsgiref==0.1.2 in /usr/local/lib/python2.7 (from -r requirements.txt (line 2))
Installing collected packages: Django
Running setup.py install for Django
changing mode of build/scripts-2.7/django-admin.py from 600 to 755
changing mode of /tmp/build_ylstetpmj562/bin/django-admin.py to 755
Successfully installed Django
Cleaning up...
-----> Discovering process types
Procfile declares types -> web
-----> Compiled slug size is 7.7MB
-----> Launching... done, v5
http://strong-stream-6920.herokuapp.com deployed to Heroku
To git@heroku.com:strong-stream-6920.git
* [new branch] master -> master

Basta agora um heroku open e ver se o navegador abre a página e... Ooops. Nada acontece:

A essas alturas, cineminha já era... Neste caso, nem com ajuda de Google e Stack Overflow surgiu alguma pista. Recorrendo ao comando

heroku logs

é possível visualizar o log da subida da aplicação no Heroku, tentando procurar alguma pista sobre o que está faltando:

2011-11-15T23:05:29+00:00 app[web.1]: raise ImproperlyConfigured("Error loading psycopg2 module: %s" % e)
2011-11-15T23:05:29+00:00 app[web.1]: django.core.exceptions.ImproperlyConfigured: Error loading psycopg2 module: No module named psycopg2
2011-11-15T23:06:40+00:00 heroku[run.8]: State changed from created to starting
2011-11-15T23:06:45+00:00 heroku[run.8]: State changed from starting to complete
2011-11-15T23:06:56+00:00 heroku[run.9]: State changed from created to starting
2011-11-15T23:07:00+00:00 app[run.9]: Starting process with command `sudo apt-get install psycopg2`
2011-11-15T23:07:00+00:00 heroku[run.9]: Process exited
2011-11-15T23:07:01+00:00 heroku[run.9]: State changed from starting to complete
2011-11-15T23:07:39+00:00 heroku[run.8]: Process exited
view raw pycopg2 missing hosted with ❤ by GitHub
Voilá, nos logs surge a pista: a aplicação crasheou durante a subida, devido à ausência do módulo psycopg2.

Googleando um pouco mais, surge este blog, com instruções simples para instalar localmente este módulo:

  • Pré-requisito:
    sudo apt-get install libpq-dev python-dev
  • Instalação: adicionar ao requirements.txt a linha
    psycopg2==2.4.2
  • Executar
    pip install -r requirements.txt

Após nova subida, é cruzar os dedos e... nada! Eis o novo log:

2011-11-15T23:27:42+00:00 heroku[web.1]: Starting process with command `python App-Contatos/manage.py runserver`
2011-11-15T23:27:42+00:00 app[web.1]: Validating models...
2011-11-15T23:27:42+00:00 app[web.1]:
2011-11-15T23:27:43+00:00 app[web.1]: 0 errors found
2011-11-15T23:27:43+00:00 app[web.1]: Django version 1.3.1, using settings 'App-Contatos.settings'
2011-11-15T23:27:43+00:00 app[web.1]: Development server is running at http://127.0.0.1:8000/
2011-11-15T23:27:43+00:00 app[web.1]: Quit the server with CONTROL-C.
2011-11-15T23:27:43+00:00 heroku[web.1]: Error R11 (Bad bind) -> Process bound to port 8000, should be 47557 (see environment variable PORT)
2011-11-15T23:27:43+00:00 heroku[web.1]: Stopping process with SIGKILL
2011-11-15T23:27:43+00:00 heroku[web.1]: Process exited
2011-11-15T23:27:44+00:00 heroku[web.1]: State changed from starting to crashed

Ao que o erro indica, o Heroku não faz o binding na porta 8000, e sugere pesquisar a variável de ambiente PORT. Um tanto enigmático. Recorrendo mais uma vez ao Google, eis uma pista melhor, nos comentários deste blog:

"Creating a Procfile and adding: "web: play run --http.port=$PORT $PLAY_OPTS" fixed the problem." (Ivar Abrahamsen)


o que nos conduz a alterar o Procfile para:

web: python manage.py runserver 0.0.0.0:$PORT

E finalmente:

Deploy de aplicação Django no Heroku - Resumo

  1. O projeto Django deve ser um subdiretório a partir da raiz que é comitada no Git
  2. Alterar o settings.py, fazendo
    ROOT_URLCONF = 'url'
  3. Adicionar um Procfile na raiz do projeto contendo:
    web: python manage.py runserver 0.0.0.0:$PORT
  4. Instalar o psycopg2
    1. Para deployar no Heroku é suficiente adicionar ao requirements.txt:
      psycopg2==2.4.2
    2. Para instalar localmente, é necessário:
      1. Instalar pré-requisitos
        sudo apt-get install libpq-dev python-dev
      2. Invocar o PIP pra instalar o pycopg2
        pip install -r requirements.txt
  5. Deploy: git push heroku master

Considerações Finais

As tentativas que envidamos para conseguir subir a aplicação Django no Heroku reforçam a tese da importância de testar o processo de desenvolvimento de ponta-a-ponta. Em cada iteração, é importante incluir o deploy para um ambiente production-like, para identificar dificuldades e riscos iterativamente, à medida que a aplicação é desenvolvida.

quarta-feira, 9 de novembro de 2011

Colaboração em projetos Rails e Django usando o Github

Nos posts anteriores, foi mostrada a criação de projetos Rails e Django, e explicado o uso do Bundler e do PIP para gerenciamento de dependências do projeto. Foi mostrado também como iniciar servidores web com os comandos

rails s

e

python manage.py runserver,

nos projetos recém criados, bem como os utilitários para acesso rápido a banco de dados

rails db

e

python manage.py dbshell.

Já temos material suficiente para iniciar um repositório Git, compartilhar no Github, e o mais importante: testar o workflow de colaboração em diferentes máquinas. Este passo é muito importante, para verificar se o setup inicial do projeto está suficientemente simples e documentado, permitindo que outros colaboradores, sem dor de cabeça, possam baixar e configurar o projeto em suas máquinas.

Adicionando o projeto Rails no Github

fabio@fabio-vbox:~/app_contatos_rails$ git init
Initialized empty Git repository in /home/fabio/app_contatos_rails/.git/
fabio@fabio-vbox:~/app_contatos_rails$ git add .
fabio@fabio-vbox:~/app_contatos_rails$ git commit -m "Initial commit"
[master (root-commit) 76a11c1] Initial commit
36 files changed, 1133 insertions(+), 0 deletions(-)
create mode 100644 .gitignore
create mode 100644 .rvmrc
create mode 100644 Gemfile
create mode 100644 Gemfile.lock
create mode 100644 README
create mode 100644 Rakefile
create mode 100644 app/assets/images/rails.png
create mode 100644 app/assets/javascripts/application.js
create mode 100644 app/assets/stylesheets/application.css
create mode 100644 app/controllers/application_controller.rb
create mode 100644 app/helpers/application_helper.rb
create mode 100644 app/mailers/.gitkeep
create mode 100644 app/models/.gitkeep
create mode 100644 app/views/layouts/application.html.erb
create mode 100644 config.ru
create mode 100644 config/application.rb
create mode 100644 config/boot.rb
create mode 100644 config/database.yml
create mode 100644 config/environment.rb
create mode 100644 config/environments/development.rb
create mode 100644 config/environments/production.rb
create mode 100644 config/environments/test.rb
create mode 100644 config/initializers/backtrace_silencers.rb
create mode 100644 config/initializers/inflections.rb
create mode 100644 config/initializers/mime_types.rb
create mode 100644 config/initializers/secret_token.rb
create mode 100644 config/initializers/session_store.rb
create mode 100644 config/initializers/wrap_parameters.rb
create mode 100644 config/locales/en.yml
create mode 100644 config/routes.rb
create mode 100644 db/seeds.rb
create mode 100644 doc/README_FOR_APP
create mode 100644 lib/assets/.gitkeep
create mode 100644 lib/tasks/.gitkeep
create mode 100644 log/.gitkeep
create mode 100644 public/404.html
create mode 100644 public/422.html
create mode 100644 public/500.html
create mode 100644 public/favicon.ico
create mode 100644 public/index.html
create mode 100644 public/robots.txt
create mode 100755 script/rails
create mode 100644 vendor/assets/stylesheets/.gitkeep
create mode 100644 vendor/plugins/.gitkeep
fabio@fabio-vbox:~/app_contatos_rails$ git remote add origin git@github.com:fabiolnm/App-Contatos-Rails.git
fabio@fabio-vbox:~/app_contatos_rails$ git push origin master
Enter passphrase for key '/home/fabio/.ssh/id_rsa':
Counting objects: 60, done.
Compressing objects: 100% (44/44), done.
Writing objects: 100% (60/60), 24.39 KiB, done.
Total 60 (delta 2), reused 0 (delta 0)
To git@github.com:fabiolnm/App-Contatos-Rails.git
* [new branch] master -> master

Clonando o projeto

other@vbox:~$ git clone git@github.com:otherlnm/App-Contatos-Rails.git
Cloning into App-Contatos-Rails...
Enter passphrase for key '/home/other/.ssh/id_rsa':
remote: Counting objects: 67, done.
remote: Compressing objects: 100% (47/47), done.
remote: Total 67 (delta 5), reused 66 (delta 4)
Receiving objects: 100% (67/67), 26.27 KiB, done.
Resolving deltas: 100% (5/5), done.
other@vbox:~$ ls -al App-Contatos-Rails/
total 80
drwxr-xr-x 12 other other 4096 2011-11-13 01:48 .
drwxr-xr-x 61 other other 4096 2011-11-13 01:48 ..
drwxr-xr-x 8 other other 4096 2011-11-13 01:48 app
drwxr-xr-x 5 other other 4096 2011-11-13 01:48 config
-rw-r--r-- 1 other other 166 2011-11-13 01:48 config.ru
drwxr-xr-x 2 other other 4096 2011-11-13 01:48 db
drwxr-xr-x 2 other other 4096 2011-11-13 01:48 doc
-rw-r--r-- 1 other other 617 2011-11-13 01:48 Gemfile
-rw-r--r-- 1 other other 2555 2011-11-13 01:48 Gemfile.lock
drwxr-xr-x 8 other other 4096 2011-11-13 01:48 .git
-rw-r--r-- 1 other other 49 2011-11-13 01:48 .gitignore
drwxr-xr-x 4 other other 4096 2011-11-13 01:48 lib
drwxr-xr-x 2 other other 4096 2011-11-13 01:48 log
drwxr-xr-x 2 other other 4096 2011-11-13 01:48 public
-rw-r--r-- 1 other other 281 2011-11-13 01:48 Rakefile
-rw-r--r-- 1 other other 3176 2011-11-13 01:48 README
-rw-r--r-- 1 other other 37 2011-11-13 01:48 .rvmrc
drwxr-xr-x 2 other other 4096 2011-11-13 01:48 script
drwxr-xr-x 4 other other 4096 2011-11-13 01:48 vendor
other@vbox:~$ cd App-Contatos-Rails/
other@vbox:~/App-Contatos-Rails$

Na listagem acima, o projeto é clonado do github através do comando

git clone

Em seguida, o diretório do projeto recém clonado é listado. Porém, a despeito da presença do arquivo .rvmrc na raiz do projeto, nada acontece ao entrar no diretório, sendo que era esperado que o RVM carregasse o ambiente Ruby do projeto.

Neste post foi mostrado que o .rvmrc pode ser usado para executar um script

rvm use 1.9.2@ambiente_app_contatos

para carregar o ambiente ruby ao entrar no diretório.

Nesta nova máquina, porém, o RVM ainda não está instalado, portanto, os scripts do arquivo .rvmrc não foram executados. O projeto tem, portanto, um pré-requisito de configuração de ambiente após ser clonado: instalar o RVM e o Ruby 1.9.2.

Graças a este simples teste, foi possível detectar o mais cedo possível a necessidade de documentar este fato.

O arquivo README

Neste ponto, a opção mais simples é documentar este pré-requisito, criando um arquivo README. Tudo bem, tudo bem, muitos de nós desenvolvedores nem sempre dá a atenção devida a este tipo de documentação tão primária. Porém isso não nos exime de escrevê-la, com pelo menos as informações gerais do projeto e as instruções de setup para que ele seja facilmente configurado e executado ṕor qualquer desenvolvedor.

Um ponto para o Github: o README é mostrado na página principal do projeto, o que melhora bastante sua exposição e aumenta a probabilidade de ele ser lido por novos desenvolvedores ao baixar o projeto.

Vale notar também que este arquivo pode ser escrito usando linguagens de marcação como o markdown e o textile, melhorando ainda mais a sua apresentação e legibilidade.

Colocada a importância de fornecer um arquivo README, visite a página inicial do projeto e avalie você mesmo o quanto essas instruções podem ser úteis ao clonar um projeto.

Documentação executável

Um fato interessante que comumente ocorre ao escrever um README é perceber que vários passos de setup inicial podem ser facilmente automatizados em arquivos de script. Foi exatamente o que aconteceu enquanto documentei o setup deste projeto: o README deu origem ao arquivo setup-project, que pode ser executado com o comando:

source ~/App-Contatos-Rails/setup-project

Ao final, este exercício de documentação levou também o benefício da automatização :-)

sudo apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion nodejs
if [ ! -d $HOME/.rvm ];
then
cd $HOME
bash < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)
source .bashrc
fi
if [ ! -d $HOME/.rvm/gems/ruby-1.9.2-p290 ];
then
rvm install ruby-1.9.2-p290
fi
if [ ! -d $HOME/.rvm/gems/ruby-1.9.2-p290@ambiente_app_contatos ];
then
rvm --create 1.9.2-p290@ambiente_app_contatos
fi
cd $HOME/App-Contatos-Rails
bundle install

Adicionando o projeto Django no Github

(ambiente_app_contatos)fabio@fabio-vbox:~/app_contatos_django$ git init
Initialized empty Git repository in /home/fabio/app_contatos_django/.git/
(ambiente_app_contatos)fabio@fabio-vbox:~/app_contatos_django$ git add .
(ambiente_app_contatos)fabio@fabio-vbox:~/app_contatos_django$ git commit -m "Initial commit"
[master (root-commit) 0d4a25e] Initial commit
11 files changed, 199 insertions(+), 0 deletions(-)
create mode 100644 .rvmrc
create mode 100644 __init__.py
create mode 100644 __init__.pyc
create mode 100644 contatos.sqlite
create mode 100644 contatos/__init__.py
create mode 100644 contatos/models.py
create mode 100644 contatos/tests.py
create mode 100644 contatos/views.py
create mode 100644 manage.py
create mode 100644 requirements.txt
create mode 100644 settings.py
create mode 100644 settings.pyc
create mode 100644 urls.py
(ambiente_app_contatos)fabio@fabio-vbox:~/app_contatos_django$ git remote add origin git@github.com:fabiolnm/App-Contatos-Django.git
(ambiente_app_contatos)fabio@fabio-vbox:~/app_contatos_django$ git push origin master
Enter passphrase for key '/home/fabio/.ssh/id_rsa':
Counting objects: 15, done.
Compressing objects: 100% (12/12), done.
Writing objects: 100% (15/15), 4.96 KiB, done.
Total 15 (delta 0), reused 0 (delta 0)
To git@github.com:fabiolnm/App-Contatos-Django.git
* [new branch] master -> master

Clonando o projeto

other@vbox:~$ git clone git@github.com:fabiolnm/App-Contatos-Django.gitCloning into App-Contatos-Django...
Enter passphrase for key '/home/other/.ssh/id_rsa':
remote: Counting objects: 15, done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 15 (delta 0), reused 15 (delta 0)
Receiving objects: 100% (15/15), 4.96 KiB, done.
other@vbox:~$ ls -al App-Contatos-Django/
total 52
drwxr-xr-x 4 ep ep 4096 2011-11-10 00:20 .
drwxr-xr-x 40 ep ep 4096 2011-11-10 00:20 ..
drwxr-xr-x 2 ep ep 4096 2011-11-10 00:20 contatos
-rw-r--r-- 1 ep ep 3072 2011-11-10 00:20 contatos.sqlite
drwxr-xr-x 8 ep ep 4096 2011-11-10 00:20 .git
-rw-r--r-- 1 ep ep 0 2011-11-10 00:20 __init__.py
-rw-r--r-- 1 ep ep 153 2011-11-10 00:20 __init__.pyc
-rw-r--r-- 1 ep ep 503 2011-11-10 00:20 manage.py
-rw-r--r-- 1 ep ep 29 2011-11-10 00:20 requirements.txt
-rw-r--r-- 1 ep ep 56 2011-11-10 00:20 .rvmrc
-rw-r--r-- 1 ep ep 5079 2011-11-10 00:20 settings.py
-rw-r--r-- 1 ep ep 2708 2011-11-10 00:20 settings.pyc
-rw-r--r-- 1 ep ep 604 2011-11-10 00:20 urls.py
other@vbox:~$ cd App-Contatos-Django/
other@vbox:~/App-Contatos-Django$

Similarmente como ocorreu ao clonar o projeto Rails em uma outra máquina (que não tinha o RVM instalado), ao entrar no diretório do projeto, não ocorreu o carregamento automático do VirtualEnv configurado no arquivo .rvmrc.

As instruções de setup - para instalação do RVM e do VirtualEnv - também foram documentadas num README, cujas instruções foram facilmente aproveitadas para a criação de um script setup-project:

if [ ! -d $HOME/.rvm ];
then
cd $HOME
bash < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)
source .bashrc
fi
if [ ! -d $HOME/.virtualenv ];
then
mkdir $HOME/.virtualenv
fi
cd $HOME/.virtualenv
if [ ! -f virtualenv.py ];
then
curl -O https://raw.github.com/pypa/virtualenv/master/virtualenv.py
fi
if [ ! -d ambiente_app_contatos ];
then
python virtualenv.py ambiente_app_contatos --no-site-packages
fi
cd $HOME/App-Contatos-Django
pip install -r requirements.txt

O arquivo .gitignore em projetos Django

À medida que vamos trabalhando na aplicação, são gerados alguns arquivos que não precisamos versionar no Git. Por exemplo, os arquivos compilados do python (*.pyc) e o arquivo do banco de dados sqlite local. Para evitar que estes arquivos sejam submetidos para o Git, podemos adicionar um arquivo .gitignore ao projeto:

*~
*.pyc
*.sqlite

Nota: Em aplicações Rails, este arquivo é gerado junto com a criação da aplicação. Isso pode ser notado na listagem do início deste post que mostra o commit inicial do projeto, com o arquivo .gitignore já presente.

Apesar de esta versão inicial estar bem simples, novas adições podem ser feitas todas as vezes que se perceber que um diretório ou um padrão de arquivos não precisa ser controlado pelo Git.

Procurando no Google por palavras chave [Django .gitignore] são listados vários exemplos de arquivos .gitignore mais abrangentes - para este post, prefiro manter as coisas simples.

Caso já tenha comitado arquivos indesejados para o Git, além de adicionar o padrão no arquivo .gitignore, é necessário também remover os arquivos comitados - nada que não poassa ser resolvido com o git rm.

Considerações Finais

Ao iniciar um projeto, é muito importante colocá-lo o mais cedo possível num repositório de controle de versão. São inúmeros os benefícios: backup, versionamento desde as primeiras versões, compartilhamento, e geração da documentação mínima necessária para que o projeto seja facilmente configurável e acessível para outros desenvolvedores, pra citar alguns.

terça-feira, 8 de novembro de 2011

Gerenciamento de Dependências com Bundler e PIP

No post anterior, foi mostrado como criar novos projetos em Rails e Django. Neste estágio inicial de projeto, algumas preocupações começam a surgir: que bibliotecas usar no projeto? Como irei baixar e instalar essas bibliotecas? Meus colegas de time conseguirão baixar o projeto de um repositório e configurar facilmente seus ambientes com as bibliotecas selecionadas?

Neste momento, fica muito evidente o poder do RVM e do VirtualEnv, e seus companheiros Bundler e PIP.

Em Rails, as dependências são configuradas no arquivo Gemfile, gerado automaticamente na raiz do projeto quando ele é criado. Em Django, um arquivo de configuração de dependências pode ser gerado através do comando pip freeze.

Bundler e o arquivo Gemfile

Um projeto Rails 3.1 é criado inicialmente com as seguintes dependências:

source 'http://rubygems.org'
gem 'rails', '3.1.1'
gem 'sqlite3'
group :assets do
gem 'sass-rails', '~> 3.1.4'
gem 'coffee-rails', '~> 3.1.1'
gem 'uglifier', '>= 1.0.3'
end
gem 'jquery-rails'
view raw gemfile hosted with ❤ by GitHub

Note que as versões requeridas de cada dependência são especificadas. Isto é importante para evitar que a aplicação deixe de funcionar devido à atualização de alguma das bibliotecas das quais ela depende.

Neste caso, foi declarada uma dependência exata ao Rails na versão 3.1.1. Para o uglifier, a aplicação exige no mínimo (>=) a versão 1.0.3. Para o sass-rails e para o cofee-rails, são exigidas no mínimo as versões 3.1.4 e 3.1.1, respectivamente. O operador (~>) tem um significado especial: para o sass, ele denota "qualquer versão 3.1.X maior que 3.1.4, ou posto de outra forma, maior ou igual a 3.1.4 e menor que 3.2".

Outro ponto a ser notado é que, além das versões, o Gemfile permite também o agrupamento de dependências. no trecho acima, o sass-rails, o coffee-rails e o uglifier foram colocados no grupo :assets. Outro grupo bastante comum é o :tests. Por exemplo, num projeto usando cucumber, teríamos:

group :tests do
gem "database_cleaner"
gem "cucumber-rails"
gem "rspec-rails"
end
view raw group tests hosted with ❤ by GitHub

Através do Gemfile, também é possível adicionar uma dependência direta ao repositório de código de uma biblioteca - desta forma, o projeto estará sempre usando o "latest build", o "nightly snapshot" dessas dependências.

gem 'rails', :git => 'git://github.com/rails/rails.git'

Agora que já conhecemos o Gemfile, resta dizer que a instalação das dependências é simples quanto executar na linha de comando:

bundle install
view raw bundle install hosted with ❤ by GitHub

Este comando oferece também a opção de filtrar grupos de pacotes para não serem instalados:

bundle install --without test

PIP e o arquivo requirements.txt

Apesar de o django não gerar automaticamente um arquivo de dependências quando o projeto é criado - muito provavelmente para não amarrar o desenvolvedor ao uso do PIP - podemos usar o comando pip freeze para ter benefícios semelhantes aos proporcionados pelo Gemfile no Rails. Execute na raiz no projeto django:

pip freeze > requirements.txt
view raw pip freeze hosted with ❤ by GitHub

E um arquivo com o seguinte conteúdo será gerado:

Django==1.3.1
wsgiref==0.1.2

O nome requirements.txt não é obrigatório, mas é comumente usado. Assim como o Gemfile, pode-se especificar quais as versões são requeridas para cada dependência, porém a sintaxe é bem diferente.

Possui apenas os operadores ==, >=, >, <=, <. Para funcionalidade similar ao ~, permite combinar operadores, por exemplo: Django>=1.3,<1.4.

Também permite configurar as dependências para serem baixadas diretamente de repositórios, usando o operador -e (de "editable"):

-e svn+svn://svn.myproject.org/svn/MyProject#egg=MyProject
-e svn+http://svn.myproject.org/svn/MyProject/trunk@2019#egg=MyProject
-e git://git.myproject.org/MyProject.git#egg=MyProject
-e git+http://git.myproject.org/MyProject/#egg=MyProject
-e git+ssh://git@myproject.org/MyProject/#egg=MyProject

Mais detalhes sobre como usar o -e com Mercurial, Bazar, etc em http://www.pip-installer.org/en/latest/requirement-format.html.

Diferente do Gemfile, o requirements.txt não fornece a opção de criar grupos. Para este comportamento, costuma-se agrupar as dependências em diferentes arquivos, por exemplo, um arquivo test-requirements.txt para testes. Dada esta filosofia, existe a opção de incluir arquivos dentro de outros, através do -r

# a test-requirements file can refer to the main requirements file using -r
-r requirements.txt

Agora que já conhecemos o requirements.txt, podemos executar o comando pip install -r para instalar as dependências do projeto:

(ambiente_app_contatos)fabio@fabio-vbox:~/app_contatos_django$ pip install -r requirements.txt
Requirement already satisfied (use --upgrade to upgrade): Django==1.3.1 in
/home/fabio/.virtualenv/ambiente_app_contatos/lib/python2.7/site-packages (from -r requirements.txt (line 1))
Requirement already satisfied (use --upgrade to upgrade): wsgiref==0.1.2 in
/usr/lib/python2.7 (from -r requirements.txt (line 2))

Considerações Finais

Com o Bundler e o PIP conseguimos resolver parte do problema: gerenciar as dependências do projeto. No próximo post, será simulado um segundo e grande benefício: a facilitação do setup do projeto em outras máquinas, por exemplo, entre os vários integrantes do time.

domingo, 6 de novembro de 2011

Começando aplicações Rails versus Django

No post anterior, foi mostrado como criar ambientes de desenvolvimento Rails e Django, comparando o RVM (Ruby Version Manager) com o PIP (Python Install Packages).

Este artigo dá um pequeno passo adiante, mostrando as similaridades e diferenças durante a criação aplicações usando estes dois frameworks web.

Não se preocupe se não leu o artigo anterior, este post mostrará todos os passos necessários para criar as aplicações em Rails e em Django

Criação do Projeto Rails

Instalar RVM:

bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)

Desta forma, o RVM é instalado no diretório ~/.rvm, como pode ser notado abaixo:

fabio@fabio-vbox:~$ ls .rvm/
archives examples lib patchsets tmp
bin gems LICENCE README user
config gemsets log rubies usr
contrib help man scripts VERSION
environments hooks patches src wrappers
view raw .rvm dir hosted with ❤ by GitHub

É necessário configurar o PATH adicionando a seguinte linha no ~/.bash_profile.

[[ -s "~/.rvm/scripts/rvm" ]] && source ~/.rvm/scripts/rvm # This loads RVM into a shell session

Para atualizar o PATH, pode-se simplesmente abrir um novo terminal, ou executar
source ~/.bash_profile

Instalar o Ruby (instale a versão que for aplicável ao seu projeto).

rvm install ruby-1.8.7-p352
rvm install ruby-1.9.2-p290

Criar um ambiente "ambiente_app_contatos" baseado no Ruby 1.9.2

rvm --create use 1.9.2@ambiente_app_contatos

Uma forma de saber quais ambientes estão disponíveis para usar com o RVM é listar o conteúdo do diretório ~/.rvm/gems:

fabio@fabio-vbox:~$ ls .rvm/gems
cache ruby-1.9.2-p290@ambiente_app_contatos
ruby-1.9.2-p290
view raw .rvm gems hosted with ❤ by GitHub

O RVM possui também vários comandos utilitários. Por exemplo, para saber qual ambiente está atualmente em uso:

fabio@fabio-vbox:~$ rvm current
ruby-1.9.2-p290@ambiente_app_contatos
view raw rvm current hosted with ❤ by GitHub

Instalar Rails

gem install rails
view raw instalar rails hosted with ❤ by GitHub

Criar a aplicação de Contatos.

rails new app_contatos_rails -T

O argumento -T serve para pular a criação dos testes de unidade. Existem várias frameworks de teste que podem ser usadas com o Rails (UnitTest, RSpec, Cucumber, etc...), elas podem ser adicionadas no projeto num momento posterior.

Veja a estrutura do projeto criado pelo Rails:

fabio@fabio-vbox:~/app_contatos_rails$ ls
app config.ru doc Gemfile.lock log Rakefile script vendor
config db Gemfile lib public README tmp
view raw rails project hosted with ❤ by GitHub

Criar um arquivo .rvmrc para carregar o ambiente correto quando navegar para o diretório da aplicação

echo "rvm use 1.9.2@ambiente_app_contatos" > app_contatos_rails/.rvmrc
view raw arquivo rvmrc hosted with ❤ by GitHub

O comando acima cria um arquivo .rvmrc no diretório do projeto. Ao entrar no diretório (por exemplo cd /path/to/app_contatos_rails), o ambiente correto é carregado:

fabio@fabio-vbox:~$ cd app_contatos_rails
Using /home/fabio/.rvm/gems/ruby-1.9.2-p290 with gemset ambiente_app_contatos
view raw rvm cd app hosted with ❤ by GitHub

Uma vez criado o projeto, é possível executar comandos rails de dentro do diretório da aplicação, por exemplo:

  • rails console ou rails c, para iniciar o interpretador de comandos;
  • rails server ou rails s para iniciar o servidor web;
    fabio@fabio-vbox:~/app_contatos_rails$ rails s
    => Booting WEBrick
    => Rails 3.1.1 application starting in development on http://0.0.0.0:3000
    => Call with -d to detach
    => Ctrl-C to shutdown server
    [2011-11-07 00:23:09] INFO WEBrick 1.3.1
    [2011-11-07 00:23:09] INFO ruby 1.9.2 (2011-07-09) [i686-linux]
    [2011-11-07 00:23:09] INFO WEBrick::HTTPServer#start: pid=22401 port=3000
    view raw rails server hosted with ❤ by GitHub
  • rails dbconsole ou rails db para iniciar o console do banco de dados (por padrão o SQLite).
    fabio@fabio-vbox:~/app_contatos_rails$ rails db
    SQLite version 3.7.4
    Enter ".help" for instructions
    Enter SQL statements terminated with a ";"
    sqlite>
    view raw rails db hosted with ❤ by GitHub

Criação de um projeto Django

A primeira diferença que vamos ao trabalhar com Django em relação ao Rails é que, enquanto o Ruby poderia ser instalado usando o próprio RVM, a instalação do Python deve ser feita sem contar com a ajuda do gerenciador de pacotes VirtualEnv.

O VirtualEnv nada mais é que um arquivo virtualenv.py, que deve ser executado por um interpretador Python previamente instalado. Como normalmente as distribuições Linux já vêm com o Python, este fato acaba sendo transparente, mas pode-se rodar o apt-get (ou yum, ou rpm) para verificar a versão instalada ou mesmo atualizar para uma versão mais recente:

fabio@fabio-vbox:~$ sudo apt-get install python
[sudo] password for fabio:
Reading package lists... Done
Building dependency tree
Reading state information... Done
python is already the newest version.
0 upgraded, 0 newly installed, 0 to remove and 151 not upgraded.
view raw check python hosted with ❤ by GitHub

Não é obrigatório, mas para fins de melhor organização, a criação de ambientes python será feita dentro de um diretório .virtualenv

fabio@fabio-vbox:~$ mkdir .virtualenv
fabio@fabio-vbox:~$ cd .virtualenv/
fabio@fabio-vbox:~/.virtualenv$ curl -O https://raw.github.com/pypa/virtualenv/master/virtualenv.py
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 85728 100 85728 0 0 56108 0 0:00:01 0:00:01 --:--:-- 70732
fabio@fabio-vbox:~/.virtualenv$ python virtualenv.py ambiente_app_contatos --no-site-packages
New python executable in ambiente_app_contatos/bin/python
Installing setuptools............................done.
Installing pip.....................done.
fabio@fabio-vbox:~/.virtualenv$ ls
ambiente_app_contatos virtualenv.py virtualenv.pyc
fabio@fabio-vbox:~/.virtualenv$ ls ambiente_app_contatos
bin include lib local
fabio@fabio-vbox:~/.virtualenv$ ls ambiente_app_contatos/bin/
activate activate.csh activate.fish activate_this.py easy_install easy_install-2.7 pip pip-2.7 python
fabio@fabio-vbox:~/.virtualenv$ ls ambiente_app_contatos/lib/python2.7/site-packages/
easy-install.pth pip-1.0.2-py2.7.egg setuptools-0.6c11-py2.7.egg setuptools-0.6c12dev_r88846-py2.7.egg setuptools.pth

Como se pode ver, o virtualenv cria uma instalação python no diretório ambiente_app_contatos, contendo apenas o pip e o easy_install, que serão usados para gerenciar a instalação de novos pacotes.

Para ativar este ambiente, é necessário carregar o arquivo bin/activate

fabio@fabio-vbox:~/.virtualenv$ source ambiente_app_contatos/bin/activate
(ambiente_app_contatos)fabio@fabio-vbox:~/.virtualenv$

Note que o terminal passa a indicar qual ambiente python está atualmente ativado! Chega a hora de instalar o Django, usando o pip:

(ambiente_app_contatos)fabio@fabio-vbox:~/.virtualenv$ pip install django
Downloading/unpacking django
Downloading Django-1.3.1.tar.gz (6.5Mb): 6.5Mb downloaded
Running setup.py egg_info for package django
Installing collected packages: django
Running setup.py install for django
changing mode of build/scripts-2.7/django-admin.py from 644 to 755
changing mode of /home/fabio/.virtualenv/ambiente_app_contatos/bin/django-admin.py to 755
Successfully installed django
Cleaning up...

E com o Django instalado, vamos criar a aplicação. Enquanto no Rails a aplicação é criada em um único comando (rails new) - criando toda a estrutura para rodar um webserver e acessar um banco de dados, juntamente com a estrutura da aplicação - em Django, isto ocorre em duas etapas:

1) python django-admin.py startproject

(ambiente_app_contatos)fabio@fabio-vbox:~/.virtualenv$ cd ..
(ambiente_app_contatos)fabio@fabio-vbox:~$ django-admin.py startproject app_contatos_django
(ambiente_app_contatos)fabio@fabio-vbox:~$ ls app_contatos_django/
__init__.py manage.py settings.py urls.py

Ao criar o projeto são gerados os arquivos manage.py, settings.py e urls.py. Com esta estrutura mínima, já é possível executar alguns comandos, por exemplo, para iniciar um webserver (python manage.py runserver) ou acessar um banco de dados (python manage.py dbshell).

(ambiente_app_contatos)fabio@fabio-vbox:~/app_contatos_django$ python manage.py runserver
Validating models...
0 errors found
Django version 1.3.1, using settings 'app_contatos_django.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Para acessar um banco de dados, antes é necessário completar a configuração no arquivo settings.py. No trecho abaixo, foi ajustada uma configuração para sqlite, persistindo dados num arquivo contatos.sqlite.

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'contatos.sqlite', # Or path to database file if using sqlite3.
'USER': '', # Not used with sqlite3.
'PASSWORD': '', # Not used with sqlite3.
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}
}

Com a configuração ajustada, basta executar python manage.py dbshell para iniciar uma sessão com o banco de dados.

(ambiente_app_contatos)fabio@fabio-vbox:~/app_contatos_django$ python manage.py dbshell
SQLite version 3.7.4
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .databases
seq name file
--- --------------- ----------------------------------------------------------
0 main /home/fabio/app_contatos_django/contatos.sqlite
sqlite> create table teste(id int, desc varchar(100));
sqlite> .tables
sqlite> insert into teste(id, desc) values(1, "Hello");
sqlite> select * from teste;
1|Hello
sqlite>
view raw python dbshell hosted with ❤ by GitHub

2) python manage.py startapp

Por fim, após a criação do projeto, podemos proceder com a criação da aplicação

(ambiente_app_contatos)fabio@fabio-vbox:~/app_contatos_django$ python manage.py startapp contatos
(ambiente_app_contatos)fabio@fabio-vbox:~/app_contatos_django$ ls contatos
__init__.py models.py tests.py views.py
view raw django startapp hosted with ❤ by GitHub

Como se vê, a estrutura inicial de uma aplicação Django é bem mais simples que a de uma aplicação Rails, contendo apenas 3 arquivos relevantes - models.py, tests.py e views.py.

No Django, não existe similar ao arquivo .rvmrc (vimos na primeira parte deste post que a presença deste arquivo no diretório da aplicação rails permite o carregamento automático do ambiente ruby adequado). Portanto, ao iniciar uma nova sessão de terminal, será necessário ativar novamente o ambiente. Isto pode se tornar um inconveniente.

Um artifício engenhoso pode ser tirar vantagem pelo fato de o rvm estar instalado, e criar um arquivo .rvmrc no diretório do projeto django, contendo o comando para ativação do ambiente virtualenv.

echo "source ~/.virtualenv/ambiente_app_contatos/bin/activate" > app_contatos_django/.rvmrc

Veja como esta "malandragem" funciona bem:

fabio@fabio-vbox:~$ cd app_contatos_django
(ambiente_app_contatos)fabio@fabio-vbox:~/app_contatos_django$

Conclusão

Através deste artigo, busquei apontar que, apesar de serem grandes as diferenças entre projetos Rails e Django, existem muitas funcionalidades e formas de trabalho comuns, que justificam aprender os frameworks em paralelo.

Ambos os frameworks oferecem facilidades para rodar interpretadores de comandos Ruby/Python; para iniciar websersers; e para iniciar sessões com o banco de dados, via terminal - tarefas que fazem parte da rotina do desenvolvimento de uma aplicação web.

As diferenças começaram a ficar bastante evidentes. A anatomia de uma aplicação Rails, por exemplo, é relativamente complexa: em um único comando (rails new) é criada toda a infra-estrutura de suporte do projeto (para rodar o webserver e o banco de dados), juntamente com a estrutura da própria aplicação. Em Django, isto é feito em duas etapas - e ainda assim, a quantidade de artefatos gerados pelo Django é bem menor.

Não é minha intenção afirmar que uma abordagem é melhor que a outra - são apenas diferentes, e essas diferenças serão abordadas em futuros posts.

No próximo post, avançaremos mais um passo, mostrando os arquivos Gemfile, usado no Rails em conjunto com o Bundler; e o arquivo requirements, usado em conjunto com o PIP no Django; ambos para gerenciamento das dependências da aplicação.

Ainda no roadmap próximo, será mostrado como criar models, e como utilizar migrations para versionar a evolução do schema de banco de dados em ambos os frameworks.

sábado, 5 de novembro de 2011

RVM e VirtualEnv

Em Ruby uma das primeiras coisas que se aprende é a instalar novos pacotes através do RubyGems:

gem install rails
gem install heroku
gem install vagrant
...

Em Python, o gerenciamento de pacotes (eggs) era feito tradicionalmente com o easy_install, mas que sendo substituído pelo PIP.

pip install django
pip install nose
...

Mesmo com tamanha facilidade para instalar e gerenciar pacotes que ambas as linguagens oferecem, existirão casos onde será necessário trabalhar simultaneamente com conjuntos distintos de gems / eggs - por exemplo ao trabalhar em projetos que possuem conjuntos distintos de pacotes, ou testar o upgrade de pacotes de um determinado o projeto.

RVM - Ruby Version Manager

Em Ruby, a criação de ambientes independentes pode ser feita com o RVM. A própria instalação do Ruby pode ser feita usando o RVM. Veja como é simples:

Pré-requisitos

Se estiver no Ubuntu, é necessário instalar os seguintes pacotes adicionais

sudo apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion
  • Baixe o RVM:
    bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)
  • A instalação modifica o .bashrc, alterando o PATH do terminal:
    [[ -s "~/.rvm/scripts/rvm" ]] && source ~/.rvm/scripts/rvm # This loads RVM into a shell session
  • É necessário iniciar um novo terminal para a alteração no PATH fazer efeito.

Instalado o RVM, podemos finalmente instalar multiplas versões do Ruby.

rvm install ruby-1.8.7-p352
rvm install ruby-1.9.2-p290

Para criar gemsets com diferentes versões do Ruby:

rvm --create 1.9.2@meu_ambiente_1
rvm --create 1.8.7@meu_ambiente_2

Para saber quais ambientes foram criados através do RVM, basta listar o diretório HOME/.rvm/gems ($ ls ~/.rvm/gems)

Para usar um gemset específico

rvm use 1.9.2@meu_ambiente_1
gem install rails
gem install heroku

VirtualEnv

Em Python, o virtualenv desempenha um papel semelhante. Porém, diferente do RVM, capaz de gerenciar a própria instalação do Ruby em múltiplas versões, o virtualenv foca apenas no gerenciamento de pacotes - não se preocupando com a instalação do Python.

A forma mais simples de usar o virtualenv é baixando o arquivo virtualenv.py no diretório home:

curl -O https://raw.github.com/pypa/virtualenv/master/virtualenv.py

E executando-o com o python previamente instalado:

$ python virtualenv.py --no-site-packages meu_ambiente_1
New python executable in meu_ambiente_1/bin/python
Installing setuptools............................done.
Installing pip.....................done.

O que o virtual_env faz é criar uma nova instalação do python no diretório meu_ambiente_1. O argumento --no-site-packages serve para que o virtualenv ignore completamente quaisquer pacotes que tenham sido instalados globalmente em /usr/lib/pythonX.X/site-packages, tornando o ambiente totalmente novo e independente.

Para usar este ambiente, é necessário ativá-lo:

source meu_ambiente_1/bin/activate

O prompt de comando passa a indicar qual ambiente está sendo usado:

(meu_ambiente_1)fabio@fabio-vbox:~$

Daí é só usar normalmente o pip para instalar os pacotes necessários.

pip install django nose mock

A instalação ocorre toda nos respectivos diretórios meu_ambiente_1/bin e meu_ambiente_1/lib/pythonX.X/site-packages, não impactando na instalação global do python (/usr/lib/pythonX.X), permitindo uma separação completa entre diferentes ambientes.

Conclusão

Espero que a abordagem proposta neste artigo - mostrando similaridades e diferenças entre características dessas duas poderosas linguagens e frameworks (Ruby/Rails e Python/Django) - seja uma forma didática de aprender e apreciar os recursos que elas oferecem, proporcionando um aprendizado poliglota prático e divertido.

Carreira Poliglota

Em 2011, as experiências de trabalho na Concrete Solutions e na Globo.com têm sido um grande incentivo para buscar uma carreira poliglota. Apesar de ainda trabalhar boa parte do tempo com Java, já pude passear por vários mundos:
  • Ainda em Java, finalmente conheci o Maven (indispensável), Spring3 (muito bom!) e o ActiveMQ.
  • Objective-C e Android: desenvolvimento mobile é desafiador e divertido!
  • PHP, WordPress, WP-Commerce: desfiz um antigo preconceito, e passei a respeitar PHP! (ainda preciso conhecer o Magento).
  • JavaScript: em pensar que há alguns anos se dizia que "JavaScript não é linguagem" - é possível até mesmo fazer TDD e Integração Contínua! Alguns caminhos interessantes: Knockout.js, Flot.js, qUnit, Jasmine, Mootools.

Mas o que me motivou a escrever este post foi o desafio de aprender duas grandes frameworks web: Django (Python) e Rails (Ruby).

Comecei a estudar Rails através dos videos no Rails For Zombies. Minha admiração por este frame ganhou grandes proporções com o livro Rails Tutorial - o primeiro livro onde já no primeiro capítulo vc é ensinado a colocar o seu projeto num Controle de Versão, incentivado ao TDD, e a fazer deploy tão logo vc tenha um build executável de sua aplicação.

Django é também uma opção poderosa. Para aprender, é obrigatório passar pelo Tutorial Oficial, e sua "quinta parte não oficial", que trata de uma forma bem didática como fazer TDD.

Nos próximos posts tentarei documentar um pouco desse aprendizado de Rails e Django.