/* CODIFICANDO */

Relatos de um programador em contínua aprendizagem.

Archive for Julho 2008

Usando o plugin timeago do JQuery no Rails

com 2 comentários

Atualizando (31/08/2008): Agora o timeago possui suporte à internacionalização, ao contrário do que está escrito abaixo (na época deste post o plugin não possuia tal recurso). Dentre os idiomas incluídos está o Português.

Na última edição do podcast do RailsEnvy, os caras falaram de um plugin bem legal para o JQuery, o timeago. A idéia desse plugin é criar elementos na página que indiquem tempo decorrido, mas por extenso. Esse tempo pode ser qualquer coisa: o tempo que uma página está aberta, a quantidade de dias que um chamado está aberto, etc. O interessante é que com o timeago, o elemento da página que exibe o intervalo de tempo é atualizado automaticamente. Se você abrir uma página e esperar tempo suficiente, verá que o texto muda. Todo o processamento ocorre do lado cliente, liberando o servidor desta tarefa.
No site do timeago existe um pequeno helper para o Rails, para gerar o html necessário de maneira bastante simples. Tudo o que você precisa fazer é incluir os arquivos javascript do jQuery (se você já usa o JRails o seu javascript_include_tag :defaults já faz isso) e o arquivo javascript do timeago.

Bom, o código do helper que existe na página do timeago é assim

def timeago(time, options = {})
  options[:class] ||= "timeago"
  content_tag(:abbr, time.to_s, options.merge(:title => time.getutc.iso8601)) if time
end

O que acontece é que o timeago precisa de uma string de data no formato iso8601, por exemplo 2008-07-17T09:24:17Z. O Ruby possui, na classe Time, o método Time.iso8601, o que facilita bastante nossa vida. Basta fazer meu_timestamp.iso8601 e você já tem uma string no formato iso8601 prontinha. O que o helper faz é criar uma tag “attr” e colocar no atributo “title” a string no formato iso8601.
Mas e se o atributo do meu model for uma data e não um timestamp? Ai é o pulo do gato: A classe Date não tem o método iso8601. Neste caso, vamos precisar alterar um pouco o helper, para aceitar tanto atributos que você mapeou na sua migration como :date quanto :datetime. A alteração é simples:

def timeago(time, options = {})
    options[:class] ||= "timeago"
    time = time.to_time unless time.kind_of?(Time)
    content_tag(:abbr, time.to_s, options.merge(:title => time.getutc.iso8601)) if time

Agora basta fazer assim na sua view:

<%= timeago(@seu_objeto.um_datetime) %>

Detalhe: Óbviamente todo o texto criado pelo timeago está em inglês e não há qualquer mecanismo de internacionalização. Se você quiser deixar tudo em português (foi o que eu fiz) basta abrir o arquivo jquery.timeago.js e colocar o seu texto lá. Em por exemplo fiz assim (o arquivo é pequeno e voc com certeza irá se localizar facilmente)

//....
var words = seconds < 45 && "menos de um minuto" ||
        seconds < 90 && "cerca de um minuto" ||
        minutes < 45 && Math.round(minutes) + " minutos" ||
        minutes < 90 && "cerca de uma hora" ||
        hours < 24 && "cerca de " + Math.round(hours) + " horas" ||
        hours < 48 && "um dia" ||
        days < 30 && Math.floor(days) + " dias" ||
        days < 60 && "cerca de um mês" ||
        days < 365 && Math.floor(days / 30) + " meses" ||
        years < 2 && "cerca de um ano" ||
        Math.floor(years) + " anos";
//...

Escrito por cassiomarques

Julho 25, 2008 em 6:08 am

Publicado em JQuery, rails

Etiquetado com ,

Usando a opção :through no has_many

com 5 comentários

Que os métodos para associações entre models do ActiveRecord quebra um galhão todo mundo sabe. O interessante é que quando não conhecemos e/ou estamos habituados a utilizar os recursos do ActiveRecord, existe uma forte tendência a não utilizar estes métodos para associação ou ainda escrever métodos de pesquisa com sentenças SQL puras, usando o famigerado find_by_sql. O fato é que sempre que possível deve-se usar os recursos disponíveis pelo AR, pois isso gera um código mais limpo, legível e, para os puristas de plantão, mais OO.

O AR tem muitos recursos para facilitar a associação entre os models e tornar automáticas (ou quase) buscas que de outra forma exigiriam a criação de sentença SQL complexas cheias de joins. Um destes recursos é a opção has_many, através da qual podemos dizer coisas como “um carro tem muitas peças”.

class Peca < ActiveRecord::Base
  belongs_to :carro
end

class Carro < ActiveRecord::Base
  has_many :pecas
end

E podemos usar isso assim

@carro = Carro.find(1)
@pecas = @carro.pecas # &lt;= retorna uma lista de objetos da classe Peca

Essa busca é feita através das chaves primárias/estrangeiras presentes nas tabelas. Na verdade voc6e não precisa ter essas contraints realmente criadas no banco, desde que obedeça certos critérios de nomenclatura para as colunas das suas tabelas. No exemplo acima, o Rails vai assumir que uma coluna chamada carro_id na tabela pecas indica a coluna id da tabela carros e assim relaciona os dois models.

Um exemplo mais complexo seria o caso onde temos um esquema do tipo:

  • Uma empresa tem vários clientes
  • Cada cliente possui várias faturas

Neste cenário, podemos querer pesquisar todas as faturas de uma determinada empresa. O problema é que não existe na tabela faturas uma coluna chamada empresa_id. Como o Rails vai então relacionar as coisas?
A tabela clientes possui essa coluna empresa_id!
Podemos então utilizar a opção :through da associação has_many, e fazer com que o ActiveRecord utilize um join para associar as tabelas “empresas” e “faturas” através da tabela “clientes”. Assim:

class Empresa < ActiveRecord::Base
  has_many :clientes
  has_many :faturas, :through => :clientes
end

class Cliente < ActiveRecord::Base
  belongs_to :empresa
  has_many :faturas
end

class Fatura < ActiveRecord::Base
  belongs_to :cliente
end

E podemos fazer coisas como

@empresa = Empresa.find(1)
@faturas = @empresa.faturas

O Rails vai pesquisar o banco usando um SQL mais ou menos assim (que você não precisou escrever!):

SELECT "faturas".* FROM "faturas" INNER JOIN clientes ON faturas.cliente_id = clientes.id WHERE (("clientes".empresa_id = 1))

Escrito por cassiomarques

Julho 24, 2008 em 6:10 pm

Publicado em activerecord, rails

Etiquetado com ,

Helper para usar o datepicker do JQuery no Rails

com 5 comentários

Nos dois projetos Rails em que estou trabalhando atualmente deixei de usar a dupla Prototype + Scriptaculous (padrão do Rails) e instalei o plugin jrails. Esse plugin possibilita substituir o Prototype e o Scriptaculous pelo JQuery nas aplicações Rails, mantendo funcionalidade da grande maioria dos helpers para javascript (toda aquelas coisas de rjs, etc), mas gerando código javascript compatível com o JQuery.
Fiz essa mudança porque o JQuery facilita muito a criação de interfaces mais atrativas, coisa muito importante para quem cria aplicações web corporativas. Com o esquema de plugins do JQuery, é possível criar aplicações muito responsivas, funcionais e bonitas.
Recentemente foi criada uma biblioteca para o JQuery chamada JQuery-UI, ou seja, um conjunto de funcionalidades específicos para a criação de interfaces com o usuário. Um dos componentes que acho realmente interessantes nesta biblioteca é o Datepicker, o qual cria um calendário bem bacana em campos de formulário que devem receber datas.

A idéia então foi: Como usar isso “The Rails Way”, sem ter que ficar escrevendo toda hora o código javascript necessário para criar uma instância do datepicker associada a um campo dos meus formulários? Simples: Criei um helper para isso. Mais precisamente, criei um helper no ApplicationHelper, para que o código fique disponível nas views de toda a aplicação.

Vamos ao código!

module ApplicationHelper
  def datepicker_tag(model, attribute, options ={}, datepicker_options = {})
    field_id = "#{model}_#{attribute}"
    field_name = "#{model}[#{attribute}]"
    field = ::ActionView::Helpers::InstanceTag.new(model, attribute, self)
    options = {:id => field_id, :name => field_name}.merge(options)
    datepicker_options = options_for_javascript(datepicker_options)
    js = "$(document).ready(function() { $(\"\##{field_id}\").datepicker(#{datepicker_options});});"
    field.tag(:input, options) + javascript_tag(js)
  end
end

O método acima cria uma tag html input e gera o código javascript necessário para fazer com que este input torne-se um datepicker. Basicamente eu utilizo helpers já existentes no Rails e crio uma instância de ActionView::Helpers::InstanceTag, a qual fica associada ao model do form. Isso é importante porque quando você precisar trazer o formulário para edição, por exemplo, o campo já virá preenchido certinho.
Em seguida utilizo essa instância para criar uma tag do tipo input e concateno a saída disso tudo com o código javascript necessário para criar um datepicker associado ao nosso input.
Notem que as opcões recebidas como argumentos do método são utilizadas para inicializar o input e o objeto javascript criados.

Os parâmetros para o método são:
model: O model que está sendo criado/editado no form.
attribute: O atributo do model ao qual o datepicker será associado
options: hash de opções que devem ser passadas para a criação do input. Basicamente as mesmas utilizadas em helpers como o text_field_tag, etc.
datapicker_options: hash com opções que serão passadas para a criação do javascript que inicializa o datepicker. Possíveis valores para este campo podem ser obtidas na documentação do datepicker.

Para usar isso, precisamos incluir os arquivos do JQuery no seu layout. Como o JQuery-UI pode ser baixado de forma personalizada, ou seja, somente os arquivos que lhe interessam (para diminuir o footprint da biblioteca), o nome dos arquivos podem variar um pouco. De qualquer forma, basta baixar os arquivos javascript necessários (o core do jquery-ui e o arquivinho para o datepicker) e os arquivos CSS. Coloque tudo em seu lugar dentro da pasta public do seu projeto e adicione tudo na sua view, usando javascript_include_tag e stylesheet_include_tag.

Considere agora que você quer criar um formulário para um (adivinhem!) aluno, e que este cara possui uma data de nascimento. Seria legal que esse campo para a data de nascimento tivesse um datepicker bonitinho né?

<% form_for(@aluno) do |form| %>
   <%= datepicker_tag :aluno, :nascimento, :size => 10 %>
<% end %>

A saída disso é algo assim:

<input id="aluni_nascimento" name="aluno[nascimento]" size="10" />
<script type="text/javascript">
//<![CDATA[
$(document).ready(function() { $("#aluno_nascimento").datepicker({});});
//]]>
</script>

Se quiser pode passar opções específicas do datepicker em um segundo hash.

Estou pensando em juntar isso com mais algumas coisas que usam o JQuery-UI e criar um plugin, vamos ver como anda meu tempo…

Escrito por cassiomarques

Julho 16, 2008 em 8:41 pm

Publicado em Desenvolvimento, JQuery, rails

Etiquetado com

Não confunda ActiveRecord.find com Enumerable.find no Rails!

sem comentários

Eu primeiro aprendi Ruby para depois começar a brincar com o Rails. Esse deveria ser o processo natural e correto, mas sei que nem todo mundo tem começado a usar Rails dessa forma. De qualquer maneira, antes de escrever meus primeiros pet-projects com o Rails eu escrevi muito código Ruby, basicamente scripts de propósito geral.

Uma coisa com a qual me habituei bastante foi a utilizar o método find do módulo Enumerable. Esse método é uma mensagem que pode ser enviada a qualquer objeto de uma classe que inclua o módulo Enumerable em sua definição e serve, óbviamente, para procurar por algo dentro deste objeto. O caso mais comum é a classe Array, ou seja, uma lista de objetos quaisquer.

array = [1, 2, 3, 4, 5]
resultado = array.find { |i| i%2 == 0 }

No exemplo acima o método find vai retornar o primeiro elemento da lista que seja par. Retornaria nil caso nenhum valor atendesse à condição valor%2 == 0.

O problema é que eu fui tentar usar algo parecido dentro de um model em um dos projetos Rails no qual venho trabalhando.
Esse model segue um esquema mais ou menos assim:

class User < ActiveRecord::Base
   has_many :roles, :through => :permissions
end

class Role < AtiveRecord::Base
   has_many :users, :through => :permissions
end

class Permission < ActiveRecord::Base
   belongs_to :user
   belongs_to :role
end

Em um determinado momento eu preciso verificar se um usuário possui um determinado role, entao tentei, ingenuamente, fazer algo como

class User < ActiveRecord::Base
   def has_role?(role)
      roles.find{ |r| r.description == role }
   end
end

Simples, deveria funcionar perfeitamente. Só que quando eu rodava esse código, recebia uma mensagem do tipo “can’t find Role without an id”. Hummmmmm… Bingo! O problema é que quando acesso uma lista que representa um associação com outro objetos de outro model, o active record faz um pesquisa no banco e usa joins em cima dos ids dos objetos, ou seja, o código acima espera receber algo do tipo

def has_role?(role)
   roles.find :conditions => "blablabla..."
end

Dessa forma o active record usa o valor recebido em :conditions para criar a cláusula “where” do sql enviado ao banco… E não é isso que eu quero, não quero que ele busque filtrando pelo banco, quero que ele busque todos os roles do usuário de uma vez só e pesquise na aplicação.

Mas se eu não posso usar o método find, porque o dito cujo existe na classe ActiveRecord::Base e no módulo Enumerable com o mesmo nome mas funcionalidade completamente diferente, o que fazer?

Simples: O método Enumerable.find na verdade é um alias (um sinônimo) do método Enumerable.detect. Ou seja: se eu enviar para minha lista a mensagem minha_lista.detect { …} eu tenho a funcioalidade que eu quero. E o código fica assim:

def has_role?(role)
   roles.detect { |r| r.description == role }
end

Problema resolvido e mais uma lição aprendida.

Escrito por cassiomarques

Julho 14, 2008 em 6:18 pm

Publicado em Desenvolvimento, rails, ruby

Rails Summit Brazil 2008

sem comentários

O Fábio Akita anunciou em seu blog a realização de um evento sobre Rails aos moldes de grandes conferências internacionais, como a RailsConf e outras.
Serão dois dias de evento, onde grandes nomes da comunidade Rails nacional e internacional estarão presentes e o local, pelo que vi, será do mais alto nível.
Realmente muito bacana a iniciativa de promover um evento deste porte no Brasil. Isso mostra às diversas pessoas que não acreditam no potencial do Ruby e do Rails o quão forte é nossa comunidade.
É interessante notar que se é possível realizar um evento deste tamanho no Brasil, é porque tem muita gente usando Rails por aqui e o melhor, com ótimos resultados.

Segundo a notícia, as inscrições devem ser abertas até o começo do mês de agosto e terão um preço inicial, por tempo limitado, de R$ 300,00, incluindo almoço para os dois dias.

Vejo vocês lá!

Escrito por cassiomarques

Julho 5, 2008 em 6:09 am

Publicado em Eventos, rails

Etiquetado com , ,