Provavelmente essa cobertura será uma das maiores até então, pois tratarei de explicar os Objetos em ruby. Apesar de extensa, ela é essencial para se entender ruby. Pesquisei algumas referências na internet, mas a maior parte delas será a do livro que estou acompanhando: The Ruby Programming Language, com um foco direcionado ao Ruby 1.9.x .
Inicialmente, o ruby é uma linguagem quase toda orientada a objetos, eu disse “quase” porque existem uns que dizem que não, outros dizem que sim, a discussão é bem longa. Todavia, em ruby todos os valores são objetos e não há uma distinção entre valores primitivos e objetos, como acontece em outras linguagens. Todos os objetos herdam da classe Object e compartilham entre si métodos definidos por essa classe.
Não.
Não vou ensinar orientação a objetos. Para ler esse artigo, presume-se que já entenda os conceitos básicos da orientação a objetos.
Bora lá?
Referências a objetos.
A classe Object define dois métodos muito parecidos para copiar objetos.
Quando trabalhamos com objetos em ruby, estamos nada mais nada a menos do que trabalhando com a sua referência, não o objeto em si.
Nada melhor do que um código para visualizar o que acontece:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
ruby-1.9.2-p136 :090 > s = "Ruby"
=> "Ruby"
ruby-1.9.2-p136 :091 > t = s
=> "Ruby"
ruby-1.9.2-p136 :092 > t[-1] = ""
=> ""
ruby-1.9.2-p136 :093 > print s
Rub => nil
ruby-1.9.2-p136 :094 > t = "Java"
=> "Java"
ruby-1.9.2-p136 :095 > print s,t
RubJava => nil
ruby-1.9.2-p136 :096 >
|
ruby-1.9.2-p136 :090 > s = "Ruby"
=> "Ruby"
ruby-1.9.2-p136 :091 > t = s
=> "Ruby"
ruby-1.9.2-p136 :092 > t[-1] = ""
=> ""
ruby-1.9.2-p136 :093 > print s
Rub => nil
ruby-1.9.2-p136 :094 > t = "Java"
=> "Java"
ruby-1.9.2-p136 :095 > print s,t
RubJava => nil
ruby-1.9.2-p136 :096 >
Na ordem, criamos um objeto do tipo String e guardamos uma referência para ele em s. Em seguida, copiamos a mesma referência para o t, onde s e t agora estão fazendo uma referência para o mesmo objeto.
Na terceira linha nós modificamos o objeto em que a referência está guardada em t e mandamos printá-lo na linha seguida, retornando modificado.
Na penúltima linha, referenciamos para um novo objeto (s), diferente de t.
E por fim, na última linha printamos os dois objetos.
Nota interessante: Engraçado é que se você está acostumado com C ou C++, fatalmente pensará que estamos lidando diretamente com ponteiros. Ô, ô! Engana-se quem pensa, pois ruby não permite você usar ponteiros para manipulação , aritimética e desreferenciação, como alguns já devem ter visto. As referências em ruby são implícitas na implementação, completamante abstraídas.
Quando passamos um objeto para um método uma referência ao objeto é passada, não o objeto em si e nem mesmo uma referência para a referência. A maneira mais segura de dizer é que os argumentos são passados por valor.
Tempo de vida do objeto.
Para dar início ao ciclo de vida de um objeto em Ruby necessitamos de um método chamado “new” existente na classe Class. Ele aloca memória para o novo objeto e então inicializa seu estado como vazio ao chamar um outro método “initialize”. Como padrão, boa parte das classes possuem um inicializador para executar qualquer tipo de inicialização (caso necessário) para as instâncias.
Os objetos em ruby não precisam ser explicitamente desalocados, assim como é feito em C ou C++ onde precisamos executar um free() para desalocar. O ruby usa uma técnica já conhecida, a de garbage collection, para destruir automaticamente objetos que não estão sendo necessários. Um objeto se torna candidato do garbage quando ele está “inalcançável” , isto é, quando não existem referências para aquele objeto a não ser de outros objetos “inalcançáveis”.
Obs1: Aqueles que desejam um aprofundamento sobre o assunto, existem alguns artigos na Ruby Inside sobre o GC, tal como esse aqui, por exemplo.
Obs2: Apesar de usar o GC , isto não significa que problemas com alocação de memória não existam em Ruby.
A classe Object e o tipo Object.
Há várias maneiras de determinar uma classe de um objeto em Ruby. A forma mais simplista é acrescentar um .class no objeto para o interpretador devolver a classe.
Ex:
1
2
3
4
|
ruby-1.9.2-p136 :022 > objeto = "teste"
=> "teste"
ruby-1.9.2-p136 :023 > objeto.class
=> String
|
ruby-1.9.2-p136 :022 > objeto = "teste"
=> "teste"
ruby-1.9.2-p136 :023 > objeto.class
=> String
A não ser que você queira saber a hierarquia de um objeto, você pode navegar pelas superclasses, assim:
1
2
3
4
5
6
|
ruby-1.9.2-p136 :024 > o.class.superclass
=> Object
ruby-1.9.2-p136 :025 > o.class.superclass.superclass
=> BasicObject
ruby-1.9.2-p136 :026 > o.class.superclass.superclass.superclass
=> nil
|
ruby-1.9.2-p136 :024 > o.class.superclass
=> Object
ruby-1.9.2-p136 :025 > o.class.superclass.superclass
=> BasicObject
ruby-1.9.2-p136 :026 > o.class.superclass.superclass.superclass
=> nil
Obs: No 1.9, a classe Object não é a classe raiz dos objetos, mas sim a BasicObject (Veja mais na documentação).
Há várias maneiras de saber se tal objeto pertence a uma determinada classe, desde comparativos diretos (objeto.class == String) até o mais elegante de todos, o objeto.instance_of?String.
Geralmente, quando testamos uma classe de um objeto, também desejamos saber se o objeto é uma instância de alguma subclasse. Para fazer essa verificação, usa-se o is_a? ou kind_of?. Ex:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
ruby-1.9.2-p136 :027 > object = 1
=> 1
ruby-1.9.2-p136 :028 > object.instance_of?Fixnum
=> true
ruby-1.9.2-p136 :029 > object.instance_of?Numeric
=> false
ruby-1.9.2-p136 :030 > object.is_a?Fixnum
=> true
ruby-1.9.2-p136 :031 > object.is_a?Integer
=> true
ruby-1.9.2-p136 :032 > object.is_a?Numeric
=> true
ruby-1.9.2-p136 :033 > object.is_a?Comparable
=> true
ruby-1.9.2-p136 :034 > object.is_a?Object
=> true
|
ruby-1.9.2-p136 :027 > object = 1
=> 1
ruby-1.9.2-p136 :028 > object.instance_of?Fixnum
=> true
ruby-1.9.2-p136 :029 > object.instance_of?Numeric
=> false
ruby-1.9.2-p136 :030 > object.is_a?Fixnum
=> true
ruby-1.9.2-p136 :031 > object.is_a?Integer
=> true
ruby-1.9.2-p136 :032 > object.is_a?Numeric
=> true
ruby-1.9.2-p136 :033 > object.is_a?Comparable
=> true
ruby-1.9.2-p136 :034 > object.is_a?Object
=> true
Perceba que na situação em que object.instance_of?Numeric retorna falso, ele não chega a herança. Porém, se você faz o comparativo com object.is_a?Numeric, ele retorna true.
Cada objeto tem uma classe bem definida em Ruby, tal classe nunca muda durante o ciclo de vida de um objeto. Porém, um tipo de objeto é mais flexível, pois está relacionado a sua classe, mas a classe é apenas uma parte de um tipo de objeto. Quando falo sobre tipos de objetos, estou me referenciando a um conjunto de comportamentos que caracterizam o objeto.
Algo importante para lembrar ao programar ruby é o fato de que não se dá tanta importância as classes de um objeto, queremos apenas saber se podemos chamar um método dela. Considere,por exemplo, o operador “<<”. Arrays, strings, arquivos e outras classes responsáveis por entrada/saída definem genericamente esse operador como um anexador. Para esclarecer, não damos importância aos argumentos passados para a classe, sabemos que pode ser anexado usando o operador <<. Até podemos testar se a classe responde a esse operador utilizando o respond_to?
1
2
3
|
ruby-1.9.2-p136 :004 > object = "Teste"
=> "Teste"
ruby-1.9.2-p136 :005 > object.respond_to? :"< true
|
ruby-1.9.2-p136 :004 > object = "Teste"
=> "Teste"
ruby-1.9.2-p136 :005 > object.respond_to? :"< true
Aí sim, estamos testando apenas se o operador pode ser chamado na classe String, não os parâmetros para o método.
Semelhanças entre os objetos
O ruby fornece várias maneiras de comparar objetos pela sua semelhança e é importante saber como alguns métodos funcionam.
O método equal?
O método equal? (Fornecido pela classe Object) é responsável por verificar se dois valores referem-se exatamente ao mesmo objeto.
1
2
3
4
5
6
7
8
|
ruby-1.9.2-p136 :006 > a = "Ruby"
=> "Ruby"
ruby-1.9.2-p136 :007 > b = c = "Ruby"
=> "Ruby"
ruby-1.9.2-p136 :008 > a.equal?(b)
=> false
ruby-1.9.2-p136 :009 > b.equal?(c)
=> true
|
ruby-1.9.2-p136 :006 > a = "Ruby"
=> "Ruby"
ruby-1.9.2-p136 :007 > b = c = "Ruby"
=> "Ruby"
ruby-1.9.2-p136 :008 > a.equal?(b)
=> false
ruby-1.9.2-p136 :009 > b.equal?(c)
=> true
a e b são objetos diferentes, mas b e c possuem referências para o mesmo objeto.
Outra maneira para realizar a mesma operação é verificar se o object_id é igual.
1
2
3
4
|
ruby-1.9.2-p136 :014 > a.object_id == b.object_id
=> false
ruby-1.9.2-p136 :015 > b.object_id == c.object_id
=> true
|
ruby-1.9.2-p136 :014 > a.object_id == b.object_id
=> false
ruby-1.9.2-p136 :015 > b.object_id == c.object_id
=> true
Conversão de Objetos
Várias classes no ruby definem métodos que retornam uma representação do objeto como um valor de uma classe distinta. O método to_s, por exemplo, é utilizado para obter uma representação em String de um objeto.
Algumas conversões são explícitas: to_s (String), to_i (Integer), to_f (Float), to_a (Array). Outras são implícitas, em alguns casos quando a classe possui características marcantes de outra classe.
Copiando Objetos
A classe Object possui dois métodos bastante parecidos para lidar com cópias de objetos. Tanto o clone ou dup retorna uma cópia superficial do objeto sobre o qual eles são chamados. Se o objeto copiado possuir um estado interno que se referencie a outros objetos, apenas as referências do objeto serão copiadas, não os próprios objetos referenciados.
Se o objeto que está sendo copiado em questão define um método initialize_copy, clone e dup alocam uma instância nova e vazia da classe e chamam o initialize_copy nessa instância vazia. O objeto em questão a ser copiado é passado como um argumento e essa “cópia do construtor” pode inicializar a cópia como ela desejar.. Por exemplo, o método initialize_copy poderia copiar recursivamente os dados internos de um objeto de modo a que o objeto resultante não seja uma simples cópia superficial do original.
As classes também podem sobrescrever diretamente o método clone e dup para resultarem qualquer cópia que desejarem.
Existem duas diferenças importantes entre os métodos clone e dup que são definidos pela classe Object. Em primeiro lugar, clone copia tanto o estado freeze e o tainted (definido em primeira instância) de um objeto, enquanto que dup apenas copia o estado tainted; chamando dup em um objeto freeze retorna uma cópia sem o estado freeze. Em segundo lugar, clone copia todos os métodos singleton do objeto, enquanto o dup não.
Empacotando (serializando) objetos
Você pode salvar um estado de um objeto ao passá-lo como parâmetro para um método de classe Marshal.dump. Caso você passe um objeto de Entrada/Saída como segundo argumento, Marshal.dump determina o estado do objeto (e recursivamente, qualquer objeto que esteja sendo referenciado), caso contrário retorna um estado codificado como String binária.
Para recuperar um objeto serializado, passe uma string ou um fluxo de entrada/saída contendo o objeto para o método Marshal.load.
Serializar um objeto é um jeito bacana de guardar o estado para um uso futuro, além do que esses métodos podem ser utilizados para fornecer um formato automático de arquivos para apps em Ruby.
Congelando Objetos
Qualquer objeto pode ser “congelado” ao chamar o método freeze. Um objeto “congelado” é considerádo imutável – isto é, nenhum de seus estados podem ser modificados e a tentativa de chamar qualquer método que seja capaz de modificar será falha.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
ruby-1.9.2-p136 :001 > s = "ice"
=> "ice"
ruby-1.9.2-p136 :002 > s.freeze
=> "ice"
ruby-1.9.2-p136 :003 > s.frozen?
=> true
ruby-1.9.2-p136 :004 > s.upcase!
RuntimeError: can't modify frozen string
from (irb):4:in `upcase!'
from (irb):4
from /Users/urieljuliatti/.rvm/gems/ruby-1.9.2-p136@rails-3.0.1/gems/railties-3.0.1/lib/rails/commands/console.rb:44:in `start'
from /Users/urieljuliatti/.rvm/gems/ruby-1.9.2-p136@rails-3.0.1/gems/railties-3.0.1/lib/rails/commands/console.rb:8:in `start'
from /Users/urieljuliatti/.rvm/gems/ruby-1.9.2-p136@rails-3.0.1/gems/railties-3.0.1/lib/rails/commands.rb:23:in `'
from script/rails:6:in `require'
from script/rails:6:in `'
ruby-1.9.2-p136 :005 > s[0] = "ni"
RuntimeError: can't modify frozen string
from (irb):5:in `[]='
from (irb):5
from /Users/urieljuliatti/.rvm/gems/ruby-1.9.2-p136@rails-3.0.1/gems/railties-3.0.1/lib/rails/commands/console.rb:44:in `start'
from /Users/urieljuliatti/.rvm/gems/ruby-1.9.2-p136@rails-3.0.1/gems/railties-3.0.1/lib/rails/commands/console.rb:8:in `start'
from /Users/urieljuliatti/.rvm/gems/ruby-1.9.2-p136@rails-3.0.1/gems/railties-3.0.1/lib/rails/commands.rb:23:in `'
from script/rails:6:in `require'
from script/rails:6:in `'
ruby-1.9.2-p136 :006 >
|
ruby-1.9.2-p136 :001 > s = "ice"
=> "ice"
ruby-1.9.2-p136 :002 > s.freeze
=> "ice"
ruby-1.9.2-p136 :003 > s.frozen?
=> true
ruby-1.9.2-p136 :004 > s.upcase!
RuntimeError: can't modify frozen string
from (irb):4:in `upcase!'
from (irb):4
from /Users/urieljuliatti/.rvm/gems/ruby-1.9.2-p136@rails-3.0.1/gems/railties-3.0.1/lib/rails/commands/console.rb:44:in `start'
from /Users/urieljuliatti/.rvm/gems/ruby-1.9.2-p136@rails-3.0.1/gems/railties-3.0.1/lib/rails/commands/console.rb:8:in `start'
from /Users/urieljuliatti/.rvm/gems/ruby-1.9.2-p136@rails-3.0.1/gems/railties-3.0.1/lib/rails/commands.rb:23:in `'
from script/rails:6:in `require'
from script/rails:6:in `'
ruby-1.9.2-p136 :005 > s[0] = "ni"
RuntimeError: can't modify frozen string
from (irb):5:in `[]='
from (irb):5
from /Users/urieljuliatti/.rvm/gems/ruby-1.9.2-p136@rails-3.0.1/gems/railties-3.0.1/lib/rails/commands/console.rb:44:in `start'
from /Users/urieljuliatti/.rvm/gems/ruby-1.9.2-p136@rails-3.0.1/gems/railties-3.0.1/lib/rails/commands/console.rb:8:in `start'
from /Users/urieljuliatti/.rvm/gems/ruby-1.9.2-p136@rails-3.0.1/gems/railties-3.0.1/lib/rails/commands.rb:23:in `'
from script/rails:6:in `require'
from script/rails:6:in `'
ruby-1.9.2-p136 :006 >
Objetos Estranhos ou Duvidosos.
*Tentei encontrar alguma tradução eficiente para Tainted Objects, sem sucesso.
Geralmente, as aplicações web devem manter o controle de dados derivados da entrada de um usuário não confiável para evitar ataques de injeção SQL e tasks de segurança semelhantes.
O Ruby fornece uma solução simples para esse problema: Qualquer objeto pode ser marcado como “duvidoso”, chamando-o pelo método taint. E isso gera uma reação em cadeia, pois a partir do momento que um objeto é setado como taint, qualquer objeto derivado será taint também.
Segue o exemplo para analisarmos o que acontece quando setamos um objeto como taint:
1
2
3
4
5
6
7
8
9
10
|
ruby-1.9.2-p136 :001 > s = "untrusted"
=> "untrusted"
ruby-1.9.2-p136 :002 > s.taint
=> "untrusted"
ruby-1.9.2-p136 :003 > s.tainted?
=> true
ruby-1.9.2-p136 :004 > s.upcase.tainted?
=> true
ruby-1.9.2-p136 :005 > s[3,4].tainted?
=> true
|
ruby-1.9.2-p136 :001 > s = "untrusted"
=> "untrusted"
ruby-1.9.2-p136 :002 > s.taint
=> "untrusted"
ruby-1.9.2-p136 :003 > s.tainted?
=> true
ruby-1.9.2-p136 :004 > s.upcase.tainted?
=> true
ruby-1.9.2-p136 :005 > s[3,4].tainted?
=> true
É uma abordagem que será interessante ter uma cobertura específica, pois envolve questões de segurança e de como o Ruby lida com isso. Portanto, resumi apenas demonstrá-los do poder que a linguagem possui, pois infelizmente não terá como cobrir todo o tema. Futuramente pretendo estudar alguns critérios específicos sobre essa abordagem do Ruby
Por fim, gostaria de informar que a próxima abordagem será sobre Classes e Módulos.
Bons estudos a todos!
Referências:
http://www.devarticles.com/c/a/Ruby-on-Rails/Ruby-Classes-and-Objects/1/
http://rubylearning.com/blog/2010/11/03/do-you-understand-rubys-objects-messages-and-blocks/
http://www.hokstad.com/ruby-object-model.html
http://www.linuxtopia.org/online_books/programming_books/ruby_tutorial/Locking_Ruby_in_the_Safe_Tainted_Objects.html