Archive for the ‘Ruby’ category

Rails e mass assignment: como aumentar a segurança dos atributos

November 22nd, 2011

Ao utilizar o scaffold do Rails, ele criará todos os métodos necessários no controller. Depois que o usuário preenche os dados do formulário e envia, é executado o método create do controller. Este método faz algo semelhante ao código abaixo (no exemplo é um CRUD de usuário):

@usuario = Usuario.new(params[:usuario])

Na forma em que o Rails cria o formulário, os atributos do usuário são passados ao controller como um hash. O valor do params acima é algo parecido com isso:

{"authenticity_token"=>"xI1Cy+LvUZzg6FR/1Y/JHcaHVPRyWsHmRII8BhMOr0E=",
 "utf8"=>"?",
 "action"=>"create",
 "controller"=>"usuarios",
 "usuario"=>{"nome"=>"Novo usuario", "email"=>"novo@usuario.com"}}

Além da definição do token de segurança, codificação em utf-8, nome do controller e da action que será executada, o parâmetro usuario contém todos os atributos que foram preenchidos no formulário. Desta forma, é muito simples atribuir os parâmetros preenchidos a um objeto Usuario, seja criando um novo (@usuario = Usuario.new(params[:usuario])) ou editando (@usuario.update_params(params[:usuario])). O Rails chama isso de mass assignment.

O problema desta abordagem é que o usuário poderia facilmente inserir novos parâmetros neste hash, simplesmente adicionando tags input hidden no formulário (usando o Firebug, por exemplo):

<input type="hidden" name="usuario[admin]" id="usuario_admin" value="true" />

Adicionando o código acima, o novo usuário criado receberia o valor true no atributo admin, o que representa uma falha grave na segurança da aplicação.

O Rails oferece um mecanismo para garantir a segurança nestes casos, usando os métodos attr_protected e attr_accessible do ActiveModel. O primeiro permite definir atributos que não podem ser alterados através de mass assignment:

class Usuario
  attr_protected :admin
end

E o attr_accessible é uma forma mais segura: somente os atributos passados para este método poderão ser alterados com mass assignment. Os demais ficam protegidos:

class Usuario
  attr_accessible :nome, :email
end

Obviamente estes dois métodos não podem ser usados simultaneamente, pois um exclui o outro.

Se você quiser atualizar um objeto com mass assignment ignorando a segurança fornecida pelo attr_accessible e pelo attr_protected, basta utilizar o parâmetro without_protection (somente no Rails 3.1):

Usuario.create(
  {:nome => "Novo usuario", :email =>"novo@usuario.com", :admin => true},
  :without_protection => true)

No exemplo acima, o usuário criado terá o atributo admin igual a true, mesmo que tenha sido usado o método attr_protected ou o attr_accessible para evitar a alteração deste atributo.

Outra opção interessante para evitar a alteração de atributos indesejados é o método attr_readonly. Os atributos passados para este método só poderão ser definidos na criação do objeto, e não poderão ser alterados depois. Porém, este método faz parte do ActiveRecord::Base, e não do ActiveModel, ou seja, ele não estará disponível se você usar outro ORM. Há uma issue aberta no Github solicitando que este método seja movido para o ActiveModel.

Link relacionado:

Gerenciando múltiplas versões de Ruby no Windows

May 19th, 2011

O RVM já se tornou a opção padrão para gerenciar múltiplas versões de Ruby. Ele permite instalar várias versões, alternar entre elas, instalar gems em cada versão independentemente… mas não funciona em ambiente Windows. Eu sei que este não é um ambiente muito popular para desenvolvimento Ruby, mas existem usuários de Windows (seja por opção ou por obrigação) programando em Ruby. Para estes, uma alternativa ao RVM é o Pik.

O Pik funciona de forma bastante semelhante ao RVM. Para instalá-lo, primeiro é necessário instalar o Ruby Installer, um pacote pré-configurado especialmente para instalar o Ruby no Windows. A documentação do Pik recomenda a versão 1.8.7. Em seguida, instale as gems rake, isolate e o próprio pik:

gem install rake isolate pik

Antes de instalar o isolate, talvez seja necessário atualizar o Rubygems com o comando abaixo:

gem update --system

O próximo passo é instalar o Pik no diretório de sua preferência. Um detalhe importante que já me custou um bom tempo é que o path completo não deve conter espaços. Execute o seguinte comando para instalar em C:\Ruby\pik, por exemplo:

pik_install C:\Ruby\pik

Concluída a instalação, o comando pik help commands exibe uma lista com os comandos disponíveis. Quem já usou o RVM não terá dificuldades, pois os comandos são bem semelhantes. Os comandos mais comuns são:

  • pik install ruby/jruby/ironruby [versão] -> instala a versão desejada do Ruby, JRuby ou IronRuby. A versão é opcional; se for omitida, será instalada a versão mais recente. Importante: antes de executar este comando, instale o 7zip e adicione-o ao path
  • pik list -> exibe as versões de Ruby atualmente instaladas
  • pik use [versão] -> seleciona uma versão instalada
  • pik gem [comando] -> executa um comando do Rubygems em todas as versões instaladas
  • pik ruby [parâmetros] -> executa Ruby em todas as versões instaladas

Inicialmente, o comando pik list exibe apenas a versão 1.8.7:

C:\Ruby\pik>pik list
* 187: ruby 1.8.7 (2011-02-18 patchlevel 334) [i386-mingw32]

Decidi instalar a última versão do Ruby e do JRuby. Após instalar estas duas versões, a saída do comando pik list ficou assim (o asterisco mostra a versão atualmente em uso):

C:\Ruby\pik>pik install ruby
...
C:\Ruby\pik>pik install jruby
...
C:\Ruby\pik>pik list
  161: jruby 1.6.1 (ruby-1.8.7-p330) (2011-04-12 85838f6) (Java HotSpot(TM) Client VM 1.6.0_24) [Windows XP-x86-java]
* 187: ruby 1.8.7 (2011-02-18 patchlevel 334) [i386-mingw32]
  192: ruby 1.9.2dev (2010-05-31) [i386-mingw32]

Para selecionar o JRuby, por exemplo, use o comando pik use 161 (é importante lembrar que neste caso os comandos mudam de nome: irb vira jirb, ruby vira jruby):

C:\Ruby\pik>pik use 161

C:\Ruby\pik>ruby -v
'ruby' não é reconhecido como um comando interno
ou externo, um programa operável ou um arquivo em lotes.

C:\Ruby\pik>jruby -v
jruby 1.6.1 (ruby-1.8.7-p330) (2011-04-12 85838f6) (Java HotSpot(TM) Client VM 1.6.0_24) [Windows XP-x86-java]

Para instalar gems, basta usar o comando gem install normalmente. A gem será instalada na versão atual do Ruby (ou seja, a que você selecionou com o comando pik use). O comando pik gem permite instalar uma gem em todas as versões:

C:\Ruby\pik>pik gem install mongo
jruby 1.6.1 (ruby-1.8.7-p330) (2011-04-12 85838f6) (Java HotSpot(TM) Client VM 1.6.0_24) [Windows XP-x86-java]

Fetching: bson-1.3.1-jruby.gem (100%)
Fetching: mongo-1.3.1.gem (100%)
Successfully installed bson-1.3.1-java
Successfully installed mongo-1.3.1
2 gems installed

ruby 1.8.7 (2011-02-18 patchlevel 334) [i386-mingw32]

Fetching: bson-1.3.1.gem (100%)
Fetching: mongo-1.3.1.gem (100%)
Successfully installed bson-1.3.1
Successfully installed mongo-1.3.1
2 gems installed
Installing ri documentation for bson-1.3.1...
Installing ri documentation for mongo-1.3.1...
Installing RDoc documentation for bson-1.3.1...
Installing RDoc documentation for mongo-1.3.1...

ruby 1.9.2dev (2010-05-31) [i386-mingw32]

Successfully installed bson-1.3.1
Successfully installed mongo-1.3.1
2 gems installed
Installing ri documentation for bson-1.3.1...
Installing ri documentation for mongo-1.3.1...
Installing RDoc documentation for bson-1.3.1...
Installing RDoc documentation for mongo-1.3.1...

O comando pik ruby permite executar um código em todas as versões instaladas, útil para verificar incompatibilidades entre as versões, como por exemplo o construtor com parâmetros da classe Time:

C:\Ruby\pik>pik ruby -e "puts Time.new('2011-01-01')"
jruby 1.6.1 (ruby-1.8.7-p330) (2011-04-12 85838f6) (Java HotSpot(TM) Client VM 1.6.0_24) [Windows XP-x86-java]

ArgumentError: wrong number of arguments (1 for 0)
  (root) at -e:1

ruby 1.8.7 (2011-02-18 patchlevel 334) [i386-mingw32]

-e:1:in `initialize': wrong number of arguments (1 for 0) (ArgumentError)
        from -e:1:in `new'
        from -e:1

ruby 1.9.2dev (2010-05-31) [i386-mingw32]

2011-01-01 00:00:00 -0200

Mais detalhes sobre o funcionamento do Pik podem ser encontrados no Github do projeto.

Representação de data e hora em Ruby 1.8.7 e 1.9

March 1st, 2011

A classe Time do Ruby é usada, como esperado, para representar uma data e hora. Existem várias formas de criar uma nova instância, a mais óbvia é usando Time.new (ou Time.now), que cria uma nova instância com a data e hora atuais:

irb(main):007:0> Time.new
=> Wed Feb 09 13:23:52 -0200 2011
irb(main):008:0> Time.now
=> Wed Feb 09 13:23:53 -0200 2011

Também é possível criar uma instância usando métodos como Time.at(time), que recebe um timestamp como parâmetro, Time.local(year, month, day, hour, min, sec_with_frac) e outros. Também há um construtor que recebe como parâmetro uma string representando a data:

irb(main):001:0> Time.new('2011-01-01')
=> Sat Jan 01 00:00:00 -0200 2011

Eu havia escrito um código que usava esse construtor no pidgin-logs-compressor, mas quando testei no JRuby 1.5.5, recebi uma exceção “ArgumentError: wrong number of arguments (1 for 0)”. Achei que fosse algum bug do JRuby, e realmente havia esse bug na versão que eu usava, mas já estava corrigido no JRuby 1.6RC1. Atualizei para esta versão, mas o erro continuou. Descobri que o erro que eu estava cometendo era relacionado com a versão do Ruby, e não do JRuby. Esse construtor com parâmetros é aceito no Ruby 1.9, mas não no 1.8.7, que é a versão usada por padrão pelo JRuby. Para fazer com que ele utilize a versão 1.9, basta acrescentar o parâmetro –1.9 na linha de comando:

jruby --1.9 -e 'puts Time.new("2011-02-16")'

Para manter compatibilidade com versões anteriores, é necessário usar algum dos outros métodos, como eu optei por fazer no pidgin-logs-compressor.

Para um overview de algumas novidades do Ruby 1.9: New Ruby 1.9 Features, Tips & Tricks.

Ruby no Ubuntu – erro na instalação de Rubygems

February 21st, 2011

Dica rápida de Ruby: se ao tentar instalar uma Rubygem no Ubuntu você receber uma mensagem como essa:

guilherme@guilherme-desktop:~$ sudo gem install mechanize
Building native extensions.  This could take a while...
ERROR:  Error installing mechanize:
    ERROR: Failed to build gem native extension.

/usr/bin/ruby1.8 extconf.rb
extconf.rb:5:in `require': no such file to load -- mkmf (LoadError)
    from extconf.rb:5

A solução é simples: basta instalar o pacote ruby-dev: sudo apt-get install ruby-dev. Este pacote é necessário para a instalação de gems nativas, que precisam ser compiladas na instalação.

Classes abstratas em Ruby?

December 4th, 2009

Como eu estava há algum tempo sem mexer com Ruby, resolvi fazer o ótimo curso online gratuito Core Ruby, do Satish Talim (também conhecido como Indian Guru) para relembrar algumas coisas. Ao chegar no tópico Ruby Overriding Methods, há um item sobre classes abstratas, que diz o seguinte:

Abstract class

In Ruby, we can define an abstract class that invokes certain undefined “abstract” methods, which are left for subclasses to define. For example:

# This class is abstract; it doesn't define hello or name
# No special syntax is required: any class that invokes methods
# that are intended for a subclass to implement is abstract
class AbstractKlass
  def welcome
    puts "#{hello} #{name}"
  end
end

# A concrete class
class ConcreteKlass < AbstractKlass
  def hello; "Hello"; end
  def name; "Ruby students"; end
end

ConcreteKlass.new.welcome # Displays "Hello Ruby students"

Assim que li esse código, fiquei com uma pulga atrás da orelha. Ele mostra como um exemplo de classe abstrata uma classe que faz referência a métodos não definidos, explicando que seria necessário criar uma classe concreta estendendo esta classe e implementando os métodos necessários.

Eu sempre pensei que classes abstratas fossem classes que não poderiam ser instanciadas, o que não é o caso do exemplo. É perfeitamente possível criar objetos da classe AbstractKlass. Só ocorrerá uma exceção se o método welcome do objeto criado for executado:

irb(main):006:0> obj = AbstractKlass.new
=> #<AbstractKlass:0x37d490>
irb(main):007:0> obj.class
=> AbstractKlass
irb(main):008:0> obj.welcome
NameError: undefined local variable or method `hello' for #<AbstractKlass:0x37d490>
        from (irb):9

Resolvi levantar esta questão no forum do curso, e recebi uma resposta de um dos participantes dizendo que em Ruby o conceito de classes abstratas seria diferente daquele que apresentei acima. De acordo com a definição da Wikipedia: “An abstract class, or abstract base class (ABC), is a class that cannot be instantiated”.

Pesquisando sobre o assunto, encontrei referências apresentando algumas sugestões de como implementar classes abstratas em Ruby de diferentes maneiras (herança, módulos e até um gem):

Uma das possibilidades mostradas nos links acima seria desta forma:

class AbstractClass
  class AbstractClassInstiationError < RuntimeError
  end

  def initialize
    raise AbstractClassInstiationError, "Cannot instantiate this class directly"
  end
end

class ConcreteClass < AbstractClass
  def initialize
  end
end

Isso teoricamente resolveria o problema:

irb(main):043:0> obj1 = AbstractClass.new
AbstractClass::AbstractClassInstiationError: Cannot instantiate this class directly
        from (irb):36:in `initialize'
        from (irb):44
irb(main):044:0> obj1.class
=> NilClass
irb(main):045:0> obj2 = ConcreteClass.new
=> #<ConcreteClass:0x309f9f>
irb(main):046:0> obj2.class
=> ConcreteClass

Porém, há um detalhe importantíssimo: em Ruby todas as classes são abertas, ou seja, sempre será possível reimplementar métodos ou adicionar módulos que alteram o comportamento da classe, tornando impossível proibir completamente a instanciação e, consequentemente, a implementação de classes abstratas (pelo menos de acordo com o conceito apresentado aqui).

A linguagem Ruby possui alguns conceitos diferentes dos utilizados em outras linguagens, em função de algumas de suas características, como classes abertas e meta programação. Isso nos força a pensar em maneiras diferentes de implementar soluções para os mesmos problemas, o que é muito bom.

Para concluir, segue um trecho do livro “Programming Ruby” que foi apresentado na discussão sobre este assunto no forum do curso:

The issue of types is actually somewhat deeper than an ongoing debate between strong typing advocates and the hippie-freak dynamic typing crowd. The real issue is the question, what is a type in the first place?

If you’ve been coding in conventional typed languages, you’ve probably been taught that the type of an object is its class—all objects are instances of some class, and that class is the object’s type. The class defines the operations (methods) that the object can support, along with the state (instance variables) on which those methods operate.

In Ruby, the class is never (OK, almost never) the type. Instead, the type of an object is defined more by what that object can do. In Ruby, we call this duck typing. If an object walks like a duck and talks like a duck, then the interpreter is happy to treat it as if it were a duck.