Posts by Carlos Antônio

Two months ago we were celebrating Devise’s birthday. We were not talking about its age, Devise had just 4 months of life, but we were happy to reach the 1.0 release.

Today we are celebrating again! We are very proud and glad to have Devise in its first Railscast! Ryan Bates has done a great job explaining how to get Devise up and running in a Rails 3 application, showing how to install Devise and choose the basic modules you want to use in your application.

We also made a promise to you: get Devise up and running for Rails 3. We keep walking this road, each Rails beta version released we are releasing a new compatible Devise version. For the time being, we have Devise 1.1.rc0, a release candidate version, compatible with Rails 3 beta 2.

We are looking forward to see the second part of Devise’s Railscast, about how to configure Devise to fit the needs of your application.

Give Devise a try. You can post your questions to the mailing list and, if you find any issue, please use the Github Issue Tracker. Don’t forget to post information about your environment (like Devise, Warden and Rails versions) including the stack trace if you are facing an error, to help us help you =). Enjoy!

Update: Railscasts just released the second part of Devise screencast, Customizing Devise, going through Devise configuration options. Check it out!

Ultimamente temos trabalhado em alguns projetos que necessitaram de vários CRUDs na administração como páginas mostrando listas de atributos. E com passar do tempo, estávamos ficando entediados de tanto copiar e colar código como este para cada atributo em nossas páginas:

<p>
  <strong class="label">Name</strong><br />
  <%= @person.name %>
</p>

Nós já tinhamos criado um helper para fazer o trabalho para nós, mas copiar este helper de um projeto para outro não é DRY. Então decidimos criar uma nova gem, chamada ShowFor.

ShowFor é uma DSL para auxiliá-lo a mostrar uma lista de atributos, usando I18n, perfeito para páginas de visualização em interfaces CRUDs. Ele permite que você substitua duplicação de código/html em suas views usando uma sintaxe mais atraente. Vejamos o que podemos fazer!

Atributos

Vamos imaginar que temos um model Person, que possui os atributos first_name, last_name, age, photo, e confirmed. As linhas abaixo criam uma lista de valores para um registro específico:

<% show_for @person do |p| %>
  <%= p.attribute :first_name %>
  <%= p.attribute :last_name %>
  <%= p.attribute :confirmed? %>
  <%= p.attribute :created_at, :format => :short %>
  <%= p.attribute :age, :if_blank => "No age" %>
 
  <% p.attribute :photo do %>
    <%= image_tag(@person.photo_url) %>
  <% end %>
<% end %>

E este é o código HTML que você tem como resultado:

<div class="show_for person" id="person_1">
  <p class="wrapper person_first_name"><strong class="label">First name</strong><br />Carlos</p>
  <p class="wrapper person_last_name"><strong class="label">Last name</strong><br />Antonio</p>
  <p class="wrapper person_confirmed"><strong class="label">Confirmed?</strong><br />Yes</p>
  <p class="wrapper person_created_at"><strong class="label">Created at</strong><br />08 Mar 11:30</p>
  <p class="wrapper person_age"><strong class="label">Age</strong><br />24</p>
  <p class="wrapper person_photo"><strong class="label">Photo</strong><br />
    <img alt="Rails" src="/images/rails.png?1268047643" />
  </p>
</div>

Como se pode ver, você terá uma marcação HTML padrão, com classes e ids para ajudá-lo no design com CSS. E caso tenha notado, estamos usando opções extras em alguns atributos, vamos dar uma olhada em algumas delas:

  • :format permite que você defina um formato a ser usando com I18n, somente para atributos date/time, como você usaria com o helper l.
  • :if_blank define o que será mostrado caso o atributo esteja em branco. Pode ter um valor padrão via I18n.
  • do...end: usando blocos você mesmo pode manusear o conteúdo para o atributo específico, como fizemos no exemplo para o atributo photo.

Atributos booleanos, como nosso :confirmed, também possuem um valor padrão para true e false, e podem ser configurados através de I18n. Se você deseja mostrar “Sim” e “Não” no lugar de “Yes” e “No” respectivamente, só precisa mudar seu arquivo I18n. Você também pode passar a opção :escape para não escapar o conteúdo (true por padrão).

Associações

ShowFor também funciona com associações. Por exemplo, podemos adicionar que nosso Person agora pertence a um model City com um atributo nome, e que também possui e pertence a muitas Tags. Para gerenciar o primeiro, podemos fazer:

<%= p.association :city %>

ShowFor irá adivinhar o atributo correto para mostrar procurando todas as possibilidades configuradas em ShowFor.association_methods e neste caso escolhendo :name. Mas é claro, você pode modificar isso se precisar:

<%= p.association :city, :using => :full_name %>
<%= p.attribute :full_name, :in => :city %>

Ambas as possibilidades acima terão a mesma saída, apenas escolha a que você mais gostar.

Manusear coleções é tão fácil quanto associações belongs_to. Você simplesmente passa a associação para ShowFor e ele irá saber se é uma coleção ou não, gerando uma lista de elementos usando tags ul e li.

<%= p.association :tags %>

Entretando, se você quiser renderizar a coleção inline, pode usar as opções :to_sentence e :join:

<%= p.association :tags, :to_sentence => true %>
<%= p.association :tags, :join => ',' %>

Também é possível passar um bloco para a coleção. O ShowFor criará a tag wrapper (ul por padrão nesse caso) e retornará cada elemento da coleção para você gerenciá-lo:

<% a.association :tags do |tag| %>
  <li><%= link_to tag.name, tag %></li>
<% end %>

Labels

Você deve ter percebido que o ShowFor possui um label padrão usando a tag strong. Ele também expõe a você o método label como um helper, para que possa usá-lo quando desejar:

  <%= p.label :first_name %>
  <%= p.label :age, :id => 'person_age' %>
 
  <strong class="label">First name</strong>
  <strong class="label" id="person_age">Age</strong>

Instalação

O ShowFor, em sua versão 0.2.0, já é compatível com o Rails 3. Você pode seguir as instruções no README para instalá-lo.

Se você está usando o Rails 2.3.x, pode dar uma olhada no branch 0.1, e seguir as instruções no README deste branch para utilizá-lo.

E após a instalação, não se esqueça de executar o generator e dar uma olhada no initializer gerado, que permite que você configure várias partes do ShowFor.

script/generate show_for_install

Fechando

ShowFor ajuda você a mostrar os atributos de seus objetos facilmente com uma marcação html padrão, e pode ser totalmente configurado para atender sua necessidade. Ele tem nos ajudado em todos os projetos, e esperamos que ele possa ajudá-lo também. Se você tiver alguma dúvida, por favor dê uma olhada no README, existem muitos exemplos e documentação lá.

E você? Possui algum helper que usa todos os dias e que poderia ser transformado em um plugin/gem? Não hesite em fazer isto, ficaremos contentes em ver seu trabalho!

Divirta-se!

We have been having some projects lately that needed a lot of admin CRUDs and pages showing a list of attributes, and we were getting bored of copy and paste code like this in our show pages, for every single attribute:

<p>
  <strong class="label">Name</strong><br />
  <%= @person.name %>
</p>

We had already created some helper to do the work for us, but having this helper being copied from one project to another wasn’t that DRY. That’s when we decided to create ShowFor.

ShowFor is a DSL to help you showing a list of attributes, with I18n, perfect for show pages in CRUD interfaces. It allows you to replace code/html duplication in your views using a nice syntax. Let’s see what we can do.

Attributes

Let’s imagine we have a Person model, which has first_name, last_name, age, photo, and confirmed attributes. The following lines show a list of values for a specific person:

<% show_for @person do |p| %>
  <%= p.attribute :first_name %>
  <%= p.attribute :last_name %>
  <%= p.attribute :confirmed? %>
  <%= p.attribute :created_at, :format => :short %>
  <%= p.attribute :age, :if_blank => "No age" %>
 
  <% p.attribute :photo do %>
    <%= image_tag(@person.photo_url) %>
  <% end %>
<% end %>

Here is an example output you will get:

<div class="show_for person" id="person_1">
  <p class="wrapper person_first_name"><strong class="label">First name</strong><br />Carlos</p>
  <p class="wrapper person_last_name"><strong class="label">Last name</strong><br />Antonio</p>
  <p class="wrapper person_confirmed"><strong class="label">Confirmed?</strong><br />Yes</p>
  <p class="wrapper person_created_at"><strong class="label">Created at</strong><br />08 Mar 11:30</p>
  <p class="wrapper person_age"><strong class="label">Age</strong><br />24</p>
  <p class="wrapper person_photo"><strong class="label">Photo</strong><br />
    <img alt="Rails" src="/images/rails.png?1268047643" />
  </p>
</div>

As you can see, you are going to get a default html markup, with classes and ids to help you design with CSS. And if you noticed, we are using extra options in some attributes, lets take a look at some of them:

  • :format lets you pass any format that will be used together with i18n, for date/time attributes only, as you would use with the l helper.
  • :if_blank specifies what to show if the attribute is blank. It may have a default value from i18n.
  • do...end: using blocks you can handle the content for that specific attribute by yourself, as we have done in this example for the photo attribute.

Boolean attributes, as our :confirmed, also have a default for true and false values, and can be configured through i18n. If you want to say “Yup” and “Nope” instead of “Yes” and “No” respectively, just change your i18n file. You can also pass a :escape option to not escape the value (true by default).

Associations

ShowFor also works with associations. For instance, we can easily add that a Person belongs to a City with a name attribute and that it has and belongs to many Tags. To handle the former, we can do:

<%= p.association :city %>

ShowFor will guess the right attribute to show by looking into all possibilities configured in ShowFor.association_methods and in this case choosing :name. But of course, you can change it as you need, on-the-fly:

<%= p.association :city, :using => :full_name %>
<%= p.attribute :full_name, :in => :city %>

Both calls above will have the same output, just choose the one which suits you better.

Handling collections is easy as belongs_to associations. You can just pass the association to ShowFor and it will know whether it’s a collection or not, generating a list of elements using ul and li tags.

<%= p.association :tags %>

However, if you want to render the collection inline, you can use :to_sentence or :join as options:

<%= p.association :tags, :to_sentence => true %>
<%= p.association :tags, :join => ',' %>

It’s also possible to pass a block to the collection. ShowFor will create the wrapper tag (ul by default in this case) and will yield each element in the collection for you to handle it:

<% a.association :tags do |tag| %>
  <li><%= link_to tag.name, tag %></li>
<% end %>

Labels

You may have noticed ShowFor has a default label using the strong html tag. It also exposes you this as a helper, so you can use it whenever you wish:

  <%= p.label :first_name %>
  <%= p.label :age, :id => 'person_age' %>
 
  <strong class="label">First name</strong>
  <strong class="label" id="person_age">Age</strong>

Installation

ShowFor is already compatible with Rails 3, at the time of this writing in version 0.2.0. You can just follow the instructions in the README to get it installed.

If you are using Rails 2.3.x, take a look at the 0.1 branch, and follow the installations in the README for this branch to get it up and running.

Please don’t forget to run the generator and take a look at the initializer, it will allows you configure several parts of ShowFor.

script/generate show_for_install

Closing

ShowFor helps you to show your objects easily with a default html markup, and can be totally configured to suit your needs. It has been helping us in every project, and we hope it may help you too. If you have any doubt, please take a look at the README, there are a lot of examples and documentation there.

And what about you? Do you have any helper you use every single day that might be transformed in a plugin/gem? Do not hesitate on doing this, we would be glad to see your work.

Enjoy!

Today we are celebrating Devise‘s birthday. But wait, if you have started watching Devise since the beginning you may be asking: has Devise already completed one year of life? Nope. Today we are completing exactly 4 months since Devise was released at Rails Summit Latin America 2009. And we are very proud and glad to say that we have just reached version 1.0! Yeah! Let’s celebrate and talk a bit about history.

The beginning

We decided to build Devise based on some requirements we had in a project last year. The majority of our projects usually require an admin interface to configure the application with some CRUD information. And in this specific project, we were needing two different roles in the same application, the admin itself with all its powers to manage the application, and the end users that should be signed in to do some stuff inside the application. Usually only one model would be required in this situation, with some “type” column or flag to determine if the user is an admin or not, right? Okay, but we didn’t like this approach that much. So we started looking at some of the possibilities:

  • Authlogic: a really great piece of code and functionality, but a bit messy by handling the session in a model. It also only handles the model part.
  • Clearance: full stack, Rails Engine, extra modules, everything we needed. However, packaged with a User model and without the ability to customize it.

Okay, we could pick one of these and with a bit of extra work we would achieve our requirements. But would we need to do it every time this situation happens? We said no. It’s not DRY enough. And we like DRY. So Devise was born.

The main objective of the first Devise version was to get up and running the entire sign in process, including password recovery and confirmation stuff. And everything should work with different roles, which mean you could easily plug and play an Admin, User, Account, or whichever role you needed, in your application, without much effort. And we had a deadline: Rails Summit. It took almost 1 month of work before Rails Summit. I still remember the days before the event, we putting a lot of effort to have Devise up and running, and the README being written during the event. So, we were ready there and presenting Devise to the world.

How it works

Devise was born with the intuit of handling different roles without effort, automatically, and it is achieved with Rails Engines. In addition, Devise is build in top of Warden, a great rack authentication framework. It allowed us to be flexible enough and add different strategies, hooks, and modules easily. In short, Devise:

  • Is Rack based;
  • Is a complete MVC solution based on Rails Engines;
  • Allows you to have multiple roles (or models/scopes) signed in at the same time;
  • Is based on a modularity concept: use just what you really need.

The road so far

Devise has started with the basic modules needed for that specific application:

  • Authenticatable: responsible for signing users in through a basic login form, handling password validation and authentication.
  • Confirmable: manages confirming users account.
  • Recoverable: handles password recovery.
  • Validatable: basic email and password validation.

Okay, everything we needed were there. Everything else was in a wish list, nicely called TODO. And we decided from the beginning to not add features to Devise until us or somebody else really needed them. But people asked, people needed new features. And they were always there to help, to fork and fix a bug, to comment. We started soon to add new features, the first was:

  • Rememberable: handles signing users in automatically from cookies.

After people were asking for easier ways to create their migrations, so we introduced a new module with Devise 0.2:

  • Migratable: hooks into ActiveRecord migrations to add helper methods for creating Devise colums. So you can do stuff like t.authenticatable, t.confirmable and so on.

To help people getting up and running faster, we created some generators. Now they are:

  • script/generate devise_install: create devise initializer file to enable some configs and copy default locale file.
  • script/generate devise MyModel: create a model with setup for Devise, routes and migration.
  • script/generate devise_views: copy all Devise views to your app/views folder, so you can change it as needed.

Devise 0.3 and 0.4 came soon after, with a lot of bug fixes in generators, I18n, initialization, some deprecations and a bunch of code refactored.

Contributions from community were coming more and more. The first big contribution came with the addition of encryptors. Nowadays Devise supports encrypting passwords with SHA1, SHA512, and BCrypt. It has also support for using the same encryptors as Authlogic, Clearance and Restful Authentication, to help you migrating from these solutions.

At this point we thought: okay, that should be enough. It wasn’t. People needed different ORMs, other than ActiveRecord. So we introduced support to MongoMapper and then we were reaching Devise 0.5.

We were receiving a lot of issues with functional tests, so we introduced some test helpers to help people with the sign in/out process in this kind of tests called Devise::TestHelper.

As applications grow, more roles may be needed. So we created the possibility to scope your views for each role/scope in your application. This way your user can have a different sign in view than the admin, for example. Reaching Devise 0.6, a lot of improvements on routes and DataMapper support were added.

We were full of nice ideas to add new features, and our TODO was bigger than we like. So we came up with:

  • Timeoutable: verify each request to timeout the user session after a certain period of inactivity, asking for credentials again.
  • Trackable: records how many times each user has signed in, last timestamps and ips.

Also the loading process and ORM support received a lot of work before launching Devise 0.7.

For Devise 0.8 we looked at tests for MongoMapper and the code got a great review. Also some work was done to get Devise up and running with latest version of Warden and its new features, such as serializers. We also extracted a new base module from Confirmable:

  • Activatable: allow setting up extra activation methods for your models.

We were receiving a lot of feedback from the community, and then we merged a cool new feature:

  • Lockable: lock accounts based on a number of invalid sign in attempts.

Following the same pattern from Rails in this commit we moved flash messages to :notice and :alert, and released Devise 0.9. Step by step the 1.0 version was coming.

What is new

Devise 1.0 introduces a lot of cool features. The community seems to be really appreciating Devise, and we’ve received another great contribution:

  • Token Authenticatable: validates authenticity of a user while signing in using an authentication token (also known as “single access token”).

In addition, we created the two most requested features for Devise:

  • Registerable: handles sign up users through a registration process, and also editing/updating user info and deleting user account.
  • HTTP Authenticatable: http basic authentication.

We also added the possibility to use Migratable while editing a table using change_table, among other fixes.

What comes next

We are preparing a new release of Devise fully compatible with Rails 3. It means Devise has now closed its development for new features in Rails 2.x. We are still going to maintain a 1.0 branch in github for bug fixes to keep everything up and running.

In the end

We would like to say thank you to everyone who has helped us achieve 1.0 version and who is using Devise, testing, creating issues, and giving all this feedback.

Also, for those who were at Rails Summit last year, we proposed something: get the biggest number of watchers on github as fast as possible! When we presented Devise, there were 7 watchers, and if I am right we were 4 of them. At the time of this writing we have 762 watchers. Yeah! I think I can take the risk and say we accomplished it. Thanks!

Let’s celebrate Devise 1.0, and look forward to see Devise and Rails 3. Enjoy!

Happy birthday Devise!

Hoje estamos comemorando o aniversário do Devise. Mas espere um pouco, se você está seguindo o Devise desde o início pode estar se perguntando: “O Devise já completou um ano de vida?”. Não. Hoje estamos completando exatamente 4 meses desde que o Devise foi lançado no Rails Summit Latin America 2009. E estamos muito orgulhosos e felizes em dizer que alcançamos a versão 1.0! Yeah! Vamos comemorar e falar um pouquinho sobre a história do projeto.

O início

Decidimos criar o Devise com base em alguns requisitos de um projeto que tivemos no ano passado. A grande maioria de nossos projetos normalmente precisam de uma interface de administração para configurar a aplicação e alguma informação com CRUD. E neste projeto em específico, precisávamos de dois papéis diferentes na mesma aplicação: o administrador com todos os poderes para gerenciar a aplicação, e os usuários finais que deveriam estar devidamente logados para executarem algumas coisas na aplicação. Normalmente essa situação requer um modelo, com alguma coluna de “tipo” ou flag para determinar se o usuário é admin ou não, certo? Ok, mas não gostávamos muito deste método. Então começamos a analisar as possibilidades:

  • Authlogic: um código ótimo e cheio de funcionalidade, mas um pouco estranho gerenciando a sessão através de um modelo. Além disso, ele gerenciava apenas a parte do modelo.
  • Clearance: completo, funciona como Engine do Rails, módulos adicionais, tudo que precisávamos. Entretanto, ele nos obriga a usar o modelo User, sem possibilidade de customizar isto.

Certo, poderíamos escolher um deles e com um pouco de trabalho extra alcançaríamos nossos requisitos. Mas nós precisaríamos fazer isto toda vez que uma situação dessas acontecesse? Nós respondemos não. Não era DRY o suficiente. E nós gostamos de ser DRY. Então nasceu o Devise.

O principal objetivo da primeira versão do Devise era proporcionar o processo de login completo, incluindo recuperação de senha e confirmação de conta. E tudo deveria funcionar com papéis diferentes, o que significa que você poderia facilmente adicionar em sua aplicação um papel de Admin, User, Account, ou qualquer outro que precisasse, sem muito trabalho. Também tínhamos um prazo: o Rails Summit. Levou quase 1 mês de trabalho antes do Rails Summit. Ainda me lembro os dias que antecederam o evento, nós trabalhando quase como loucos para deixar o Devise redondo, e também o README que foi escrito durante o evento. Então, estávamos prontos e apresentando o Devise para o mundo.

Como ele funciona

O Devise nasceu com o intuito de gerenciar papéis diferentes sem esforço, automaticamente, e isso é conseguido através das Engines do Rails. Além disso, o Devise é construído sobre o Warden, um ótimo framework de autenticação para o Rack. Isso nos permitiu ser bastante flexíveis e adicionar estratégias diferentes, hooks, a módulos facilmente. Em resumo, o Devise:

  • É baseado no Rack;
  • É uma solução MVC completa com base em Rails Engines;
  • Permite que você tenha múltiplos papéis (modelos/escopos) logados ao mesmo tempo;
  • É totalmente baseado no conceito de modularidade: use somente o que você realmente precisa.

A estrada até agora

Devise foi lançado com os módulos básicos necessários para aquela aplicação específica:

  • Authenticatable: responsável por logar usuários atráves de formulários, gerenciando validação de senha e autenticação.
  • Confirmable: controla a confirmação de conta de usuários.
  • Recoverable: gerencia a recuperação de senhas.
  • Validatable: validação básica de email e senha.

Certo, tínhamos tudo que precisávamos até então. O restante estava em uma wishlist, a qual chamamos de TODO. E decidimos desde o princípio não adicionar funcionalidades ao Devise até que nós ou outra pessoa realmente precisasse. Mas as pessoas pediram, as pessoas precisavam de novas funcionalidades. E elas sempre estiveram lá para ajudar, para criar um fork e corrigir um bug, para comentar. Então logo começamos a adicionar novas funcionalidades, a primeira era:

  • Rememberable: gerencia o login automático de usuários a partir de cookies.

Depois as pessoas pediram por maneiras mais fáceis de criar as migratios, então introduzimos um novo módulo com o Devise 0.2:

  • Migratable: cria hooks nas migrations do ActiveRecord adicionando alguns métodos para criar os campos para o Devise. Assim você pode fazer coisas como t.authenticatable, t.confirmable e assim por diante.

Para ajudar as pessoas a iniciarem mais rapidamente, criamos então alguns geradores. Atualmente são eles:

  • script/generate devise_install: cria um arquivo de inicialização para habilitar algumas configurações e também copia o arquivo padrão de locale.
  • script/generate devise MyModel: cria um modelo já configurado para o Devise, adiciona rotas e uma migration.
  • script/generate devise_views: copia todas as views do Devise para seu diretório app/views, permitindo que você altere como achar necessário.

As versões 0.3 e 0.4 do Devise vieram logo em seguida, com vários bugs corrigidos nos geradores, I18n, inicialização, alguns métodos deprecados e uma porção de código refatorado.

As contribuições da comunidade estavam aparecendo cada vez mais. A primeira grande contribuição veio com a adição dos encryptors. Atualmente o Devise suporta a criptografia de senhas com SHA1, SHA512 e Bcrypt. Ele também possui suporte às mesmas rotinas de criptografia do Authlogic, Clearance e Restful Authentication, para auxiliá-lo a migrar dessas soluções.

Neste ponto pensamos: certo, já deve ser o bastante. Não era. As pessoas precisavam de ORMs diferentes do ActiveRecord. Então adicionamos suporte ao MongoMapper e estávamos alcançando o Devise 0.5.

Estávamos recebendo muito feedback sobre problemas com testes funcionais, então construímos o Devise::TestHelper, que contém métodos para auxiliar as pessoas com o processo de sign in/out nestes testes.

Conforme as aplicações crescem, mais papéis podem ser necessários. Então adicionamos a possibilidade de criar views específicas para cada papel/escopo de sua aplicação. Assim seu usuário poderia ter uma view para logar diferente do administrador, por exemplo. Chegando na versão 0.6, várias melhorias foram feitas na geração de rotas e o suporte ao DataMapper foi adicionado.

Estávamos cheio de idéias legais para adicionar novas funcionalidades, e nosso TODO estava maior do que gostamos. Então surgimos com:

  • Timeoutable: faz uma checagem em cada request para destruir a sessão do usuário após um certo período de inatividade, pedindo por credenciais novamente.
  • Trackable: registra o número de logins do usuário, última data/hora e ip usados para acessar.

Também o processo de carregamento e o suporte a diferentes ORMs receberam uma boa atenção com o lançamento do Devise 0.7.

Para a versão 0.8 do Devise melhoramos os testes para o MongoMapper e fizemos uma grande revisão no código. Algum esforço também foi feito para ter o Devise rodando com a última versão do Warden e as novas features, tais como os serializers. Outro módulo base foi extraído do Confirmable:

  • Activatable: permite configurar métodos adicionais para ativação de um modelo.

Com muito feedback vindo da comunidade, fizemos merge de uma nova funcionalidade bacana:

  • Lockable: bloqueia contas com base no número de tentativas de login inválidas.

Seguindo o mesmo padrão do Rails neste commit alteramos as mensagens flash para :notice e :alert, e lançamos o Devise 0.9. Passo a passo a versão 1.0 estava chegando.

O que há de novo

O Devise 1.0 traz uma grande variedade de funcionalidades legais. A comunidade parece estar realmente apreciando o Devise, e recebemos outra grande contribuição:

  • Token Authenticatable: valida a autenticidade de um usuário atráves do login com um token de autenticação (também conhecido como “token de acesso único”).

Além disso, criamos as duas funcionalidades mais pedidas para o Devise:

  • Registerable: controla o registro de usuários (sign up), e também edição/atualização das informações do usuário e cancelamento de conta.
  • HTTP Authenticatable: autenticação http basic.

Também adicionamos a possibilidade de usar o Migratable ao editar uma tabela, usando change_table, dentre outras correções.

O que vem agora

Estamos preparando um novo release do Devise totalmente compatível com o Rails 3. Isso significa que a partir de agora o Devise está fechado para desenvolvimento de novas funcionalidades no Rails 2.x. Vamos manter um branch da versão 1.0 no github para correção de bugs mantendo tudo funcionando.

Para terminar

Gostaríamos de dizer muito obrigado a todos que nos ajudaram a chegar a versão 1.0 e a quem está utilizando o Devise, testando, criando issues e nos dando todo este feedback.

Também, para aqueles que estavam no Rails Summit do ano passado, nós propusemos algo: alcançar o maior número de seguidores no github tão rápido quanto possível! Quando apresentamos o Devise, haviam 7 seguidores, e se estou correto nós eramos 4 deles. No momento da redação deste post nós temos 762 seguidores. Yeah! Acho que posso assumir o risco e dizer que nós conseguimos. Obrigado!

Vamos comemorar o Devise 1.0, e aguardar ansiosos para ver o Devise com o Rails 3. Aproveite!

Feliz aniversário Devise!

We have been working on an application that allows administrators to create accounts for their users. Each account will be accessible under a subdomain, so we needed to setup a subdomain environment inside our application, and also in our development machine. In addition, we must be able to let the users signed in among several subdomains, as a user can access other accounts when allowed.

After some research we decided to go with the subdomain-fu gem, which is great to give your application the ability of handling subdomains. Another great resource we have used is Ryan Bates’ screencast about the subject. But they did not solve our problem completely, so here we are going to document a few steps to help you get up and running easily with subdomains and sessions.

Setup a development environment

As we need to test all the subdomain stuff in our application while developing it, we are going to need some extra setup for our development environment. However, our local machine knows nothing about subdomains, so how do we do that? We need to tell our machine which subdomains we will be using, manually. You can do this by editing your hosts file, located at /etc/hosts, and configuring the subdomains you are goint to need:

127.0.0.1     localhost local.host test.local.host subdomain.local.host xxx.local.host

We are making explicit all domains and subdomains that will point to our local machine, under IP 127.0.0.1. By default localhost was already there, so we have just added the needed subdomains. When working with this subdomains I personally like to create a domain like my_app.local, and point subdomains to it like sub.my_app.local. There are some other ways to create this setup, but we would rather go with this due to readability and easy configuration.

To make sure everything is fine we need to clear our dns cache:

Under linux:

/etc/init.d/dns-clean start

Under OS X:

dscacheutil -flushcache

And that is it. Restart your application and try it out. Access it under the new domain or subdomain you have created, for instance local.host:3000, to see if everything is working fine.

You may be asking why we could not be using just subdomain.localhost instead of subdomain.local.host as subdomain configuration. And you are right, we could use it, and it should work fine to access subdomains. However, there are some “bumps in the road” while sharing sessions, so keep reading!

Installing subdomain-fu

Subdomain-fu is easy to install as any other gem:

gem install subdomain-fu

Configure it in your environment:

config.gem 'subdomain-fu', :version => '0.5.3'

Then we are going to need a config file to setup subdomain-fu, with basically the following code:

SubdomainFu.tld_sizes = {
  :development => 1, # local.host
  :test => 1,
  :production => 1 # my_app.com
}

This config file can be placed at config/initializers folder. Basically it tells subdomain-fu the sizes of the domain for each environment, i.e. 0 for localhost, 1 for foo.com, 2 for foo.bar.com.

The gem gives you some special powers through the current_subdomain method in your controllers, and also some adds to your routes like using the :subdomain option: root_url(:subdomain => 'foo') #=> foo.local.host.

Routing

Chances are you are going to need specific actions to be handled only under subdomains, but not in the root domain or a specific subdomain, like admin.foo.com. And subdomain-fu will help you here: you just need to setup your routes using the :subdomain condition, like the example below:

ActionController::Routing::Routes.draw do |map|
  map.with_options :conditions => { :subdomain => /^[A-Za-z0-9-]+$/ } do |app|
    app.resources :posts
    app.root :controller => 'posts'
  end
  # ... other routes
end

This will ensure your routes require a subdomain to be recognized.

Finally, everything is perfect, you are now able to test subdomains in your environment, create some filters to ensure the subdomain exists and your user has access to it, sessions work as expected, and so on, right? Nope. We are not ready yet. Sessions are our pain.

Sharing sessions between subdomains

Due to security issues, browsers do not allow sharing cookies using only .com, just under the complete domain like foo.com, and of course it is totally right. Could you imagine sharing sessions between gmail.com and hotmail.com, just because they are both .com? I could not. The same rule applies while trying to share sessions using localhost only. Browsers will not allow you sharing sessions between subdomains under localhost, and some weird issues may appear. We have had AuthenticityToken errors while trying to use localhost.

Anyway, we were smart enough to create our own subdomain configuration using local.host instead of localhost, remember? Our configuration actually simulates a full domain like the foo.com example instead of .com only. Think this way: localhost => com, local.host => foo.com.

We are going to use this setup now. To enable sharing sessions in your application you need to configure the :domain option in your session config hash, for each environment. Here is how to do it in development config:

config.action_controller.session = { :domain => '.local.host' }

Please note the dot prepended to the domain. It will enable sharing sessions between all subdomains in your application. Think about it as *.local.host, including local.host itself. By doing this, you are now able to sign a user in one subdomain, or even the root domain, and redirect it to any other subdomain, for instance. The session will be kept and the user will stay signed in as expected.

Do not forget to setup the production environment with the same config, pointing to the real domain of your application.

Testing

We are using cucumber in this application together with celerity/culerity, and at the beginning it was kind of pain to get it up and running. The first thing you must bear in mind is that you always have to setup the host you are testing. By default, cucumber uses host example.com. And that is okay for default Rails integration tests, except when we use “real browsers” tests like celerity or selenium. You have to set it up by yourself. Just create two steps like this:

Given /^I am visiting the root application$/ do
  host! 'local.host'
end
 
Given /^I am visiting the subdomain "([^\"]*)"$/ do |subdomain|
  host! "#{subdomain}.local.host"
end

And use then inside your features:

Given I am visiting the subdomain "my_sub"

That should do the trick. Make sure you use only subdomains you have configured in your hosts file, or you will get some weird errors =).

Mailers

We had some issues while creating mailers with links pointing to the subdomain. As our users has many subdomains, we don’t know where we should point the user inside the mailer, due to default lack of request context as we have in the controller. To solve this we could not use any class accessor, because they are not thread safe. So we decided to go with Thread.current. Just create a filter in your application controller:

before_filter :set_current_subdomain
protected
  def set_current_subdomain
    Thread.current[:current_subdomain] = current_subdomain
  end

Then create a new helper called MailHelper inside your app/helpers folder, adding a method to obtain the subdomain:

def current_subdomain
  Thread.current[:current_subdomain]
end

This MailHelper is a module provided by Rails which is included in all mailers. Now you are able to create links inside your mailers using the current_subdomain method, just like you do in your controllers:

link_to "Go to application", root_url(:subdomain => current_subdomain)

What about Devise?

Devise has been doing a great work while authenticating a user under a specific subdomain. There are two cases to be handled: the first one is when your User model has a subdomain attribute and you want the authentication process take into account this subdomain with the current_subdomain. First of all you need to update devise call inside your user model to add the :authentication_keys option:

devise :all, :authentication_keys => [:email, :subdomain]

This will tell devise to find the user based on both subdomain and email. Then you have to add the subdomain to your sign in form as a hidden field, so Devise will be able to get this information easily from params while authenticating the user:

f.hidden_field :subdomain, :value => current_subdomain

The second case happens when your subdomain data is inside another model associatied with the user, let’s say a user has many accesses. In addition to what we have done in the first case, we must override a class method from Devise to add our own condition for finding the user:

def self.find_for_authentication(conditions={})
  conditions[:accesses] = { :subdomain => conditions.delete(:subdomain) }
  find(:first, :conditions => conditions, :joins => :accesses)
end

Devise is now totally capable of handling authentication based on subdomains. Remember: Devise is managing authentication, so it will not be able to do anything after the user signs in. Be sure to also add filters to your controllers to ensure a user will never access a subdomain it has no access.

Here we go!

A few steps are needed to get our development machine up and running to create an application using subdomains, but they are key steps to ensure you are not going to have problems while starting.

What about you? Have you ever developed an application using subdomains? Have you run through any of these issues, or maybe another you want to share? Do you have any tip?