Não é novidade que RubyOnRails, hoje conhecido somente como Rails, tomou de assalto o mundo do desenvolvimento web. Sendo uma verdadeiro canivete suíço para o desenvolvedor full stack, Rails, aliado a simplicidade da linguagem Ruby, tornou-se referência para desenvolvimento de apps de maneira rápida e com qualidade. Não é a toa que o Rails é tão utilizado em startups. Neste post, vamos entender o porquê.
Ferramentas de linha de comando
Infelizmente, ainda temos pessoas com a mentalidade de que a linha de comando é algo ultrapassado, quando, na verdade, esta mentalidade é que é ultrapassada. A tela preta, ou branca se você usa mac :p, é uma ferramenta que pode nos proporcionar uma grande produtividade.
Como disse acima, o rails é um canivete suíço. Dentro dele, podemos encontrar algumas ferramentas que serão importantes durante o ciclo de desenvolvimento. O Rake é um software de gerenciamento de tarefas similar ao Make do Unix. Ele irá nos auxiliar na automatização de tarefas. Aquelas tarefas chatas, como executar migrações de banco agora poderão ser executadas de maneira mais ágil. Ele já vem com algumas tasks pré-definidas, mas também é possível criar as próprias tasks. Pra quem usa banco de dados relacional, é uma mão na roda. O rake foi criado pelo saudoso Jim Weirich, falecido recentemente.
O Bundler é outra ferramenta de linha de comando que vem com o rails. Sua função é gerenciar as dependências do projeto. Não gosto de fazer essa comparação, mas pra quem vem do java, seria similar ao Maven. Sim, o rails possui dependências. Bibliotecas, que no universo ruby são conhecidas como gems. O Bundler gerencia essas gems que ficam definidas em um arquivo na raiz do projeto chamado Gemfile. Através dele, podemos ter controle sobre versões, instalação e atualizações das gems, organizando-as de acordo com o environment, promovendo um ambiente mais consistente.
Com o comando rails server ou simplesmente rails s, o rails executa seu servidor embutido WEBrick. Uma maneira simples de rodar a aplicação em ambiente de desenvolvimento.
Assets Pipeline
Esta é uma das ferramentas mais poderosas para a produtividade do desenvolvedor web, existente na atualidade. Ela, na verdade, é um conjunto de bibliotecas voltadas para resolver a questão dos assets nos projetos rails. Com a necessidade de se criar apps que proporcionam a melhor experiência possível ao usuário, não é novidade que abusamos de arquivos css, imagens e plugins do jquery. Logo é uma boa prática, minificar esses arquivos, para melhorar o carregamento das páginas e evitar requests desnecessárias ao servidor. Mesmo hoje em dia, ainda encontramos sites e web apps que carregam tudo sem mificar. Esse é justamente o problema que o assets pipeline veio resolver com o comando: bundle exec rake assets:precompile.
Após a execução, o rails calcula o digest MDi do arquivo e adiciona ao nome do arquivo ficando assim application-e049a640704156e412f6ee79daabc7f6.css. Esse nome depende do conteúdo do arquivo, logo quando uma nova versão do asset for gerada, o nome irá mudar fazendo com que o cache do navegador não seja acionado, resolvendo o famigerado problema de o usuário ter de limpar o cache cada vez que tiver uma atualização nos arquivos estáticos.
Além de minificar, o assets pipeline compacta os arquivos no formato gzip, o que significa um arquivo com tamanho em bytes muito menor, se o servidor web aceitar o formato, claro. Hoje já existem ferramentas ótimas que auxiliam o desenvolvedor front-end como o Yeoman, mas em se tratando de rails, o assets pipeline ainda é uma excelente opção.
RubyGems
A comunidade ruby é extremamente ativa e moldada pela filosofia open source. A infinidade de gems existentes é um absurdo. Essas bibliotecas se tornaram grandes aliadas na produtividade do desenvolvedor. Para todo problema que você pensar, é possível que alguém já tenha criado uma gem. Existem algumas que considero tops e merecem uma atenção extra.
O Devise é uma biblioteca que resolve o problema da autenticação no rails. Autenticação é algo que, quase sempre, é uma implementação repetitiva, ou seja, para todo app que você fizer, a implementação será a mesma. O que considero até uma tarefa meio chata e que não tem valor agregado. O Devise nos proporciona implementar uma autenticação robusta de forma fácil e rápida. Ele possui encripitação por Bcrypt, suporte a Omniauth, confirmação por email, recuperar senha, track através de contador, timestamp e ip, suporte a internacionalização, entre outras coisas. Ele gera automaticamente as views com os formulários, nos poupando de uma tarefa meio \\\’sacal\\\’, com pouquíssimas etapas de configuração.
O Simple Form é uma ferramenta que nos fornece helpers com componentes para a criação de formulários de forma simples e rápida. O rails já vem, por default, com helpers de formulário. Eles nos auxiliam a criar formulários com código ruby, sem os div hell, tornando o código mais legível. O Simple Form nos proporciona uma DSL mais elegante, e componentes mais semânticos. Ele possui integração com o Twitter bootstrap e com o Foundation, nos possibilita criar formulários estilizados sem tocar em nenhum css.
Abaixo um exemplo de formulário html puro utilizando twitter bootstrap:
[code language=\\\”html\\\”]
<form accept-charset="UTF-8" action="/articles/6" class="simple_form form-horizontal" id="edit_article_6" method="post" novalidate="novalidate">
<legend>Article</legend>
<div class="control-group string required article_name">
<label class="string required control-label" for="article_name">
<abbr title="required">*</abbr> Name
</label>
<div class="controls">
<input class="string required span6" id="article_name" name="article[name]" type="text" value="SimpleForm 2.0" />
<p class="help-block">add your article title here</p>
</div>
</div>
<div class="control-group string required article_name">
<div class="controls">
<div class="input-prepend">
<span class="add-on">Name</span>
<input class="string required" id="article_name" maxlength="255" name="article[name]" size="255" type="text" value="SimpleForm 2.0" /></div>
</div>
</div>
<div class="control-group boolean optional article_published">
<label class="boolean optional control-label" for="article_published">
Published
</label>
<div class="controls">
<input name="article[published]" type="hidden" value="0" />
<label class="checkbox">
<input class="boolean optional" id="article_published" name="article[published]" type="checkbox" value="1" />
</label></div>
</div>
<div class="control-group check_boxes optional article_content_type">
<label class="check_boxes optional control-label">
Stacked checkboxes
</label>
<div class="controls">
<label class="checkbox">
<input class="check_boxes optional" id="article_content_type_blog" name="article[content_type][]" type="checkbox" value="Blog" />
Blog
</label>
<label class="checkbox">
<input class="check_boxes optional" id="article_content_type_editorial" name="article[content_type][]" type="checkbox" value="Editorial" />
Editorial
</label>
<label class="checkbox">
<input class="check_boxes optional" id="article_content_type_announce" name="article[content_type][]" type="checkbox" value="Announce" />
Announce
</label>
<label class="checkbox">
<input class="check_boxes optional" id="article_content_type_advertisement" name="article[content_type][]" type="checkbox" value="Advertisement" />
Advertisement
</label>
<input name="article[content_type][]" type="hidden" value="" /></div>
</div>
<div class="control-group check_boxes optional article_content_type">
<label class="check_boxes optional control-label">
Inline checkboxes
</label>
<div class="controls">
<label class="checkbox inline">
<input class="check_boxes optional" id="article_content_type_blog" name="article[content_type][]" type="checkbox" value="Blog" />
Blog
</label>
<label class="checkbox inline">
<input class="check_boxes optional" id="article_content_type_editorial" name="article[content_type][]" type="checkbox" value="Editorial" />
Editorial
</label>
<label class="checkbox inline">
<input class="check_boxes optional" id="article_content_type_announce" name="article[content_type][]" type="checkbox" value="Announce" />
Announce
</label>
<label class="checkbox inline">
<input class="check_boxes optional" id="article_content_type_advertisement" name="article[content_type][]" type="checkbox" value="Advertisement" />
Advertisement
</label>
<input name="article[content_type][]" type="hidden" value="" /></div>
</div>
<div class="control-group radio_buttons optional article_content_type">
<label class="radio_buttons optional control-label">
Stacked radios
</label>
<div class="controls">
<label class="radio">
<input class="radio_buttons optional" id="article_content_type_blog" name="article[content_type]" type="radio" value="Blog" />
Blog
</label>
<label class="radio">
<input class="radio_buttons optional" id="article_content_type_editorial" name="article[content_type]" type="radio" value="Editorial" />
Editorial
</label>
<label class="radio">
<input class="radio_buttons optional" id="article_content_type_announce" name="article[content_type]" type="radio" value="Announce" />
Announce
</label>
<label class="radio">
<input class="radio_buttons optional" id="article_content_type_advertisement" name="article[content_type]" type="radio" value="Advertisement" />
Advertisement
</label></div>
</div>
<div class="control-group radio_buttons optional article_content_type">
<label class="radio_buttons optional control-label">
Inline radios
</label>
<div class="controls">
<label class="radio inline">
<input class="radio_buttons optional" id="article_content_type_blog" name="article[content_type]" type="radio" value="Blog" />
Blog
</label>
<label class="radio inline">
<input class="radio_buttons optional" id="article_content_type_editorial" name="article[content_type]" type="radio" value="Editorial" />
Editorial
</label>
<label class="radio inline">
<input class="radio_buttons optional" id="article_content_type_announce" name="article[content_type]" type="radio" value="Announce" />
Announce
</label>
<label class="radio inline">
<input class="radio_buttons optional" id="article_content_type_advertisement" name="article[content_type]" type="radio" value="Advertisement" />
Advertisement
</label></div>
</div>
<div class="control-group select optional article_content_type">
<label class="select optional control-label" for="article_content_type">
Content type
</label>
<div class="controls">
<input name="article[content_type][]" type="hidden" value="" />
<select class="select optional" id="article_content_type" multiple="multiple" name="article[content_type][]">
<option value="Blog">Blog</option>
<option value="Editorial">Editorial</option>
<option value="Announce">Announce</option>
<option value="Advertisement">Advertisement</option>
</select>
<p class="help-block">multiple select</p>
</div>
</div>
<div class="control-group select optional article_category">
<label class="select optional control-label" for="article_category">
Category
</label>
<div class="controls">
<select class="select optional" id="article_category" name="article[category]">
<option value=""></option>
<option value="Blog">Blog</option>
<option value="Editorial">Editorial</option>
<option value="Announce">Announce</option>
<option value="Advertisement">Advertisement</option>
</select>
<p class="help-block">simple select box</p>
</div>
</div>
<div class="control-group text optional article_content">
<label class="text optional control-label" for="article_content">
Content
</label>
<div class="controls">
<textarea class="text optional span6" id="article_content" name="article[content]">
</textarea></div>
</div>
<div class="control-group string optional disabled article_disabled_text">
<label class="string optional control-label" for="article_disabled_text">
Disabled text
</label>
<div class="controls">
<input class="string optional disabled" disabled="disabled" id="article_disabled_text" name="article[disabled_text]" type="text" />
<p class="help-block">an example of disabled input</p>
</div>
</div>
<div class="control-group string optional article_disabled_text">
<label class="string optional control-label" for="article_disabled_text">Disabled text</label>
<div class="controls">
<div class="input-prepend">
<span class="add-on">
<input id="remove" name="remove" type="checkbox" value="true" />
</span>
<input class="string optional disabled" disabled="disabled" id="article_disabled_text" maxlength="255" name="article[disabled_text]" size="255" type="text" /></div>
<span class="help-block">This is the hint text</span></div>
</div>
<div class="control-group string optional article_disabled_text">
<label class="string optional control-label" for="article_disabled_text">
Disabled text
</label>
<div class="controls">
<div class="input-append">
<input class="string optional disabled mini" disabled="disabled" id="article_disabled_text" maxlength="255" name="article[disabled_text]" size="255" type="text" />
<span class="add-on active">
<input id="remove" name="remove" type="checkbox" value="true" />
</span></div>
<span class="help-block">This is the hint text</span></div>
</div>
<div class="form-actions">
<input class="btn btn-primary" name="commit" type="submit" value="Update Article" />
<input class="btn btn-danger" name="commit" type="reset" value="Cancel" /></div>
</form>
[/code]
E agora, o mesmo formulário com código ruby com Simple Form:
[code language=\\\”ruby\\\”]
<% simple_form_for(@article, :html => { :class => \\\’form-horizontal\\\’ }) do |f| %>
<%= f.input :name, :input_html => { :class => "span6" }, :hint => "add your article title here" %>
<%= f.input :name, :wrapper => :prepend, :label => false do %>
<%= content_tag :span, "Name", :class => "add-on" %>
<%= f.input_field :name %>
<% end %>
<%= f.input :published, :as => :boolean %>
<%= f.input :content_type, :collection => content_type_options, :as => :check_boxes, :label => \\\’Stacked checkboxes\\\’ %>
<%= f.input :content_type, :collection => content_type_options, :as => :check_boxes, :item_wrapper_class => \\\’inline\\\’, :label => \\\’Inline checkboxes\\\’ %>
<%= f.input :content_type, :collection => content_type_options, :as => :radio_buttons, :label => \\\’Stacked radios\\\’ %>
<%= f.input :content_type, :collection => content_type_options, :as => :radio_buttons, :item_wrapper_class => \\\’inline\\\’, :label => \\\’Inline radios\\\’ %>
<%= f.input :content_type, :collection => content_type_options,
:hint => "multiple select", :input_html => { :multiple => true } %>
<%= f.input :category, :collection => content_type_options,
:hint => "simple select box" %>
<%= f.input :content, :input_html => { :class => "span6" } %>
<%= f.input :disabled_text, :disabled => true,
:hint => "an example of disabled input" %>
<%= f.input :disabled_text, :wrapper => :prepend, :hint => \\\’This is the hint text\\\’ do %>
<%= content_tag :span, :class => "add-on" do %>
<%= check_box_tag :remove, \\\’true\\\’ %>
<% end %>
<%= f.input_field :disabled_text, :disabled => true %>
<% end %>
<div class="form-actions">
<%= f.button :submit, :class => \\\’btn-primary\\\’ %>
<%= submit_tag \\\’Cancel\\\’, :type => :reset, :class => "btn btn-danger" %></div>
[/code]
Existem outras gems muito utilizadas: paperclip, uma solução simples para upload de arquivos; resque para se criar background jobs; omniauth, autenticador multi-provider; Rspec, suite de testes automatizados; Capybara ajuda a criar testes automatizados que rodam no navegador e muitas outras. O próprio rails é uma gem.
RestFul
Dependendo do cenário, quando no início do projeto vem a dúvida de fazer ou não mobile first. Hoje, a web mobile ainda possui certas limitações, como performance, usabilidade e acesso a dispositivos do smartphone. Nesse caso, normalmente opta-se por apps nativos. No entanto, mesmo apps nativos se comunicam com recursos externos. Se não houver necessidade de um módulo web, o rails talvez não seja a melhor solução. Um cenário mais conhecido é apps web tendo necessidade de uma versão mobile.
É considerado uma boa prática expor uma interface restful que poderá ser acessada por um eventual aplicativo mobile ou desktop. Criar rotas restful no rails é muito simples. Basta utilizar o método resources no arquivo config/routes.rb.
Com o comando rake routes é possível visualizar as rotas criadas:
Exemplo de controller restful:
[code language=\\\”ruby\\\”]
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
# GET /products
# GET /products.json
def index
@products = Product.all
end
# GET /products/1
# GET /products/1.json
def show
end
# GET /products/new
def new
@product = Product.new
end
# GET /products/1/edit
def edit
end
# POST /products
# POST /products.json
def create
@product = Product.new(product_params)
respond_to do |format|
if @product.save
format.html { redirect_to @product, notice: \\\’Product was successfully created.\\\’ }
format.json { render :show, status: :created, location: @product }
else
format.html { render :new }
format.json { render json: @product.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /products/1
# PATCH/PUT /products/1.json
def update
respond_to do |format|
if @product.update(product_params)
format.html { redirect_to @product, notice: \\\’Product was successfully updated.\\\’ }
format.json { render :show, status: :ok, location: @product }
else
format.html { render :edit }
format.json { render json: @product.errors, status: :unprocessable_entity }
end
end
end
# DELETE /products/1
# DELETE /products/1.json
def destroy
@product.destroy
respond_to do |format|
format.html { redirect_to products_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_product
@product = Product.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def product_params
params.require(:product).permit(:name, :price, :category)
end
end
[/code]
Controllers restful também são muito úteis quando usamos algum framework javascript para single-page applications como AngularJs ou Backbone.js.
Testes automatizados
No vocabulário de startups existe um termo conhecido como \\\”pivotar\\\” que nada mais é quando o empreendedor decide mudar o plano de negócios depois de ter testado uma estratégia e não ter obtido os resultados esperados. E em se tratando de produtos web, muitas vezes há a necessidade de realizar mudanças no modelo de domínio da aplicação. Agora, imagine não possuir testes automatizados em um cenário como este. Seria catastrófico.
A comunidade ruby, é muito exigente nesse aspecto. É preciso ter cobertura de testes. Tanto que o rails vem com uma suite de testes embutida, o minitest. Apesar disso, muitos desenvolvedores preferem adotar uma suite de testes externa e mais robusta, o Rspec. A api do Rspec nos fornece uma DSL simples e elegante para BDD (Behaviour-Driven Development). Logo, além de uma cobertura de testes, ele nos fornece uma documentação da nossa aplicação sempre atualizada.
Abaixo, um exemplo de teste de model com rspec:
[code language=\\\”ruby\\\”]
require "spec_helper"
describe User do
it "orders by last name" do
lindeman = User.create!(first_name: "Andy", last_name: "Lindeman")
chelimsky = User.create!(first_name: "David", last_name: "Chelimsky")
expect(User.ordered_by_last_name).to eq([chelimsky, lindeman])
end
end
[/code]
A linha 8 é a assertiva. Repare como é fácil entender o código.
Conclusão
Essa foi uma abordagem bem superficial. É claro que o rails possui muito mais recursos. Sem dúvida é uma excelente ferramenta que ajuda a resolver problemas de maneira prática e elegante, possui uma comunidade muito ativa, com excelentes profissionais que prezam pela qualidade e boas práticas.
referências:
http://guides.rubyonrails.org/
http://www.akitaonrails.com/2012/07/01/asset-pipeline-para-iniciantes
Deixe um comentário