« HE:labs
HE:labs

Entendendo o autoload do rails

Postado por Marcio Junior em 17/11/2014

Embora não seja uma pratica legal, as vezes precisamos fazer monkey patches em algumas classes para disponibilizar novos métodos. Como por exemplo, vamos supor que customizamos alguns emails do devise, e precisamos usar alguns helpers nesses emails. Uma maneira de fazer isso é aplicando um monkey patch na classe base de todos os mailers do devise, que é Devise::Mailer, e incluindo o helper ApplicationHelper. Normalmente isso vai estar em um initializer vamos supor que esteja em config/initializers/devise_mailer_ext.rb.

1 Devise::Mailer.class_eval do
2   helper ApplicationHelper
3 end

Isso funciona, e não vai gerar problemas em produção. Mas durante o processo de desenvolvimento, principalmente quando se usa o spring, podem começar a surgir erros inesperados, como por exemplo: ao enviar um email do devise, ele dizer que um método do application helper não existe, como se o monkey patch acima fosse desfeito.

Em fato, initializers não são o lugar correto pra se fazer isso, porque esses arquivos são carregados apenas uma vez quando a aplicação sobe, seja com rails s ou rails c etc. Mas o que poderia estar fazendo com que a classe perdesse esse monkey patch?

O rails tem uma funcionalidade bem mágica que faz acharmos que isso vem do próprio ruby, que é o fato de podermos referenciar os models no controller sem precisar fazer requires, e também recarregar as classes quando fazemos alguma alteração. Por exemplo:

1 class BankBilletsController < ApplicationController
2 
3   def index
4     @bank_billets = BankBillet.all
5   end
6 
7 end

Perceba que não existe require 'app/models/bank_billet' em canto nenhum e ele simplesmente acha o BankBillet. Graças a flexibilidade da linguagem ruby é possível fazer essa mágica, e no rails o módulo responsável por isso é o ActiveSupport::Dependencies Como não é interessante aplicar isso em todos os diretórios pois esse lookup dinamico causa um certo overhead, existe um propriedade chamada autoload_paths com um array de diretórios onde essa mágica deve ser aplicada. Você pode verificar quais diretórios estão sendo carregados automaticamente inspecionando ActiveSupport::Dependencies.autoload_paths no seu rails console:

 1 $ rails c
 2 ActiveSupport::Dependencies.autoload_paths.each { |a| p a }
 3 "/home/marcio/workspace/helabs/boletosimples-app/app/assets"
 4 "/home/marcio/workspace/helabs/boletosimples-app/app/controllers"
 5 "/home/marcio/workspace/helabs/boletosimples-app/app/forms"
 6 "/home/marcio/workspace/helabs/boletosimples-app/app/helpers"
 7 "/home/marcio/workspace/helabs/boletosimples-app/app/mailers"
 8 "/home/marcio/workspace/helabs/boletosimples-app/app/middlewares"
 9 "/home/marcio/workspace/helabs/boletosimples-app/app/models"
10 "/home/marcio/workspace/helabs/boletosimples-app/app/policies"
11 "/home/marcio/workspace/helabs/boletosimples-app/app/serializers"
12 "/home/marcio/workspace/helabs/boletosimples-app/app/services"
13 "/home/marcio/workspace/helabs/boletosimples-app/app/subscribers"
14 "/home/marcio/workspace/helabs/boletosimples-app/app/validators"
15 "/home/marcio/workspace/helabs/boletosimples-app/app/workers"
16 "/home/marcio/workspace/helabs/boletosimples-app/app/controllers/concerns"
17 "/home/marcio/workspace/helabs/boletosimples-app/app/models/concerns"
18 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/xray-rails-0.1.14/app/assets"
19 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/bundler/gems/activeadmin-8b6586c23a62/app/assets"
20 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/inherited_resources-1.4.1/app/controllers"
21 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/bourbon-3.2.3/app/assets"
22 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/doorkeeper-1.4.0/app/assets"
23 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/doorkeeper-1.4.0/app/controllers"
24 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/doorkeeper-1.4.0/app/helpers"
25 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/doorkeeper-1.4.0/app/validators"
26 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/devise-3.4.0/app/controllers"
27 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/devise-3.4.0/app/helpers"
28 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/devise-3.4.0/app/mailers"
29 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/cocoon-1.2.6/app/assets"
30 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/zeroclipboard-rails-0.1.0/app/assets"
31 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/fancybox2-rails-0.2.8/app/assets"
32 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/font-awesome-rails-4.2.0.0/app/assets"
33 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/font-awesome-rails-4.2.0.0/app/helpers"
34 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/formtastic-3.0.0/app/assets"
35 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/jquery-ui-rails-5.0.2/app/assets"
36 "/home/marcio/.rvm/gems/ruby-2.0.0-p451@boletosimples/gems/secure_headers-1.3.4/app/controllers"

Perceba que os arquivos do diretório app estão sendo referenciados, e também o de outras gems. Então por exemplo se adicionamos um método novo em BankBillet, a classe é redefinida, ou seja, ela é removida e carregada de novo. Isso é importante, porque é diferente de apenas carregar o arquivo de novo, já que se removessemos um método e adicionassemos outro, ela ficaria com 2 métodos ao invéz de ficar com o último.

Voltando ao problema do monkey patch do devise, se alterarmos algum arquivo nesses diretórios, o Devise::Mailer vai ser recarregado e como o nosso initializer devise_mailer_ext não recarrega, ele vai ficar sem o ApplicationHelper, então agora que entendemos o problema como fazer pra resolve-lo?

Felizmente o rails tem um hook especialmente pra isso. Ele executa toda as vezes que os arquivos são recarregados e apenas uma vez quando a app é carregada em produção por exemplo. O nome dele é to_prepare. E como o modulo principal da nossa applicação é uma railtie, podemos adicionar o seguinte no arquivo application.rb

 1 module Boletosimples
 2   class Application < Rails::Application
 3     # others configurations ...
 4 
 5     config.to_prepare do
 6       Devise::Mailer.class_eval do
 7         helper ApplicationHelper
 8       end
 9     end
10 
11   end
12 end

Depois de reiniciar o servidor agora o problema do monkey patch não ocorre mais, já que embora o Devise::Mailer esteja sendo recarregado logo em seguida o monkey patch é aplicado já que estamos registrados no hook to_prepare.

Conclusão

Não utilize os initializers pra alterar o comportamento de classes que estão no autoload_paths. E também tire um tempo pra tentar entender como as ferramentas que você usa funcionam. Quando estudei como essa parte do rails funcionava, era mais por questão de curiosidade pois embora existam coisas muito mágicas no rails e até difíceis de entender, ter um entendimento médio de como elas funcionam é muito benéfico, pois ajuda você a ter uma visão maior do que está acontecendo.

E quando você ver um diff desse:

image

Pode acreditar que existe toda uma ciência por trás disso :D

Abraços!

Tags: rails ruby

Compartilhe

Sabia que nosso blog agora está no Medium? Confira Aqui!