Skip to content

Instantly share code, notes, and snippets.

@reginadiana
Last active May 15, 2023 23:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save reginadiana/a454ef891b19d00d519995646e2f34f9 to your computer and use it in GitHub Desktop.
Save reginadiana/a454ef891b19d00d519995646e2f34f9 to your computer and use it in GitHub Desktop.
Anotações sobre Ruby e Ruby on Rails

Anotações do livro Ruby on Rails - Coloque sua aplicação nos trilhos

Tipos e estruturas de dados

string.bytesize # retorna o número de bits

⚠️ Ao usar floats, nem sempre eles apresentam o valor correto após uma operação. Em aplicações de cunho financeiro isso pode se tornar um grande problema. Podemosa usar a gem BigDecimal para resolver isso.

Ao declarar uma variável com a primeira letra maiúscula, ela torna-se uma constante. Por convenção, uma constante deve estar inteira maiuscula e uma classe deve estar em CamelCase.

Podemos usar o splat operator para obter o restante de uma lista:

lista = ['pizza', 'alface', 'refrigerante']

alimentos, outros_alimentos = lista

alimentos # pizza
outros_alimentos # ['alface', 'refrigerante']

Declarando array de strings:

array = %w{a b c d} # ['a', 'b', 'c', 'd']

Para fazer operações com arrays, temos

a + b # soma o conteúdo de a com b
a | b # soma o conteúdo de a com b, eliminando elementos duplicados
a & b # soma o conteúdo de a com b, deixando apenas os elementos duplicados

Temos também metodos de objetos que podemos acessar:

lista.reverse # retorna a lista contendo os elementos em ordem invertida
lista.join('e') # retorna uma string com cada elemento separado pelo 'e'
lista.compact # retorna a lista sem nil, se houver 
lista.sort # retorna a lista de forma ordenada e vale para strings
lista.uniq # retorna a lista sem elementos duplicados
lista.flatten # retorna uma unica lista com todos os elementos, mesmo que tenhamos uma lista dentro de outra lista
lista.pop # retorna o ultimo elemento
lista.shift # retorna o primeiro elemento

📝 Podemos usar bang (!) para persistir a mudança dos metodos acima ou disparar uma exceção caso eles falharem. Exemplo ao buscar um post inesistente:

Post.find_by_title # retorna nil
Post.find_by_title # retorna ActiveError::RecordNotFound

📝 Metodos com # indicam que são usados para instancias e :: para metodos de classe

Usando Hashs

Pra quem conhece python, podemos fazer uma associação com dicionários. Podemos criar hashs de 3 formas:

frequency = { "hello" => 1, "world" => 2, 1 => 10 }
frequency = { :hello => 1, :world => 2 } # usando símbolos como chaves
frequency = { hello: 1, world: 2 }

Podemos acessar alguns métodos como:

frequency.keys # ["hello", "world"]
frequency.values # [1, 2]
frenquency.has_key?("hello") # true
frequency.has_value?(3) # false

Acessando hashs

hash['key']

Agora, vamos supor que estamos recolhendo dados de uma API e nem sempre os dados estão presentes. Para não ficar toda hora checando se um dado é nil, podemos usar o que o Rails chama de Menção Honrosa, assim nosso código fica mais limpo. Exemplos:

user = user_data.fetch('age', 'not found')
user.born # retorna 'not found'

Outro problema também, é que o Ruby não acusa o nil como uma falha, mas a presença dele pode acarretar em um erro em um passo seguinte do código, ficando dificil de detectar aonde exatamente está o bug. Portanto, podemos usar o #fatch sem fallback, disparando uma exceção KeyError quando a chave não existir:

user_data.fetch('address') # KeyError: key not found: "address"

📝 Garbage Collector é um mecanismo usado pelo interpretador Ruby para detectar os recursos que estamos usando ou não a fim de liberar espaço de memória quando necessário.

Usando simbolos

"palavra".respond_to? :upcase # retorna true, pois a string é upcase 

Símbolos devem ser usados quando estamos descrevendo o tipo do dado Strings devem ser usadas quando a chave é um valor

Convertendo strings em simbolos e vice-versa:

"string".to_sym # retorna :string
:simbolo.to_s # retorna 'simbolo'

Usando Ranges

'a'..'e' # Letras entre 'a' e 'e'
'a'...'e' # Letras entre 'a' e 'd', o 'e' fica de fora
valid_years.include? 1998 # retorna true pois 1998 está dentro de valid_years

Fluxos e Laços

📝 Todo bloco de código ruby retorna alguma coisa 📝 É sempre bom fazer trechos curtos de código

⚠️ Em ambos os exemplos, a pasta não existe

&& Se a expressão da esquerda for falsa, a expressão da direita não vai ser avaliada Se a expressão da esquerda for verdadeira, a expressão da diretira será avaliada Exemplo:

mkdir pasta && cd pasta # Cria pasta e entra nela

Se existisse, tomariamos um erro pois a pasta já foi criada.

O resultado de "mkdir pasta" é verdadeiro se a pasta for criada e falso se ela já existir

|| Se a expressão da esquerda for falsa, a expressão da direita vai ser avaliada Se a expressão da esquerda for verdadeira, a expressão da diretira não será avaliada Exemplo:

mkdir pasta || cd pasta # Somente cria a pasta

Se a pasta já existir, tomariamos um erro disendo que a pasta já existe mas entrariamos nela mesmo assim.

Devido a confusão na hora de interpretar e ler essas lógicas, é uma boa prática usar as operações que envolvam && e || em parenteses.

Booleanos e valores truthy e falsy

Se um valor é verdadeiro, o if é executado, se falso, não. No ruby, apenas false e nil são falsos, todos os outros tipos são verdadeiros, mesmo que sejam "0" ou "" (string vazia).

unless

O bloco associado a ele é executado se a expressão retornar false. É recomendado usar quando o if estiver sozinho. Se for usar else, use logo o if..else.

Ternários

Eles enxutanm a lógica if..else mas devem ser usados em pequenas linhas de código para não prejudicar a legibilidade. condição ? o que fazer se for verdade : o que fazer se for falso

Cascate em Swith Case

Em algumas linguagens como C e JavaScript, quando uma condição é verdadeira, todos os cases abaixo são executados. Por esse motivo, usamos break no final de cada um. O ruby não posui esse comportamento

While

É usado em blocos para executar algo enquanto uma condição estiver satisfeita. Exemplo: lista.lenght > 0 # algo será executado enquanto a lista for maior que zero

Until

É usado para executar uma condição até que a condição for satisfeita. Exemplo: lista.empty? # algo será executado até que a lista esteja vazia.

For

Assim como no python, também conseguimos usar o for..in em ruby.

Exemplo com hash:

frequencies = {'hello' => 10, 'world' => 20}
for key, value in frequencies
puts "A frequência da palavra '#{key}' é #{value}"
end

Podemos usar maps, each, etc tanto em uma unica linha como usando do..end. Sobre esse uso, a comunidade adotou:

Para blocos curtos, de apenas uma linha, adota-se as chaves; Para blocos longos, de duas ou mais linhas, adota-se o do ... end

Outra sacada dos loops é essa:

a = {:a => 1, :b => 2, :c => 3}
a.each do |_, value| # '_' substitui o que seria 'key'
puts value
end

Para acessar os valores do hash precisamos de dois parametros dentro de 'do', mas como não vamos usar as keys, não precisamos declarar uma variável que nao vamos usar. Ao inves disso, podemos usar o _ no lugar dela

Controle de fluxo

break para um loop next devido alguma condição, passa para a próxima interação. Exemplo:

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbers.each do |number|
next if number.odd?
puts number
end # 2; 4; 6; 8; 10

Funções, blocos, lambdas e closure

É possível omitir os parênteses de funções que levam parametros:

def print_text text, more_text
puts text + more_text
end 

Usar return quando precisamos retornar algo que não esteja referenciado como ultimo retorno do bloco.

Alias

Usando o alias, não precisamos chamar a função exatamente com o nome dela. Pode ser util na legibilidade de código. Exemplo:

def factorial(n)
return 1 if n == 1
n * factorial(n-1)
end
alias fac factorial
fac(5) # 120

Parametros

Podemos criar métodos que aceitam um número variado de argumen-tos. Conseguimos isso através do uso do operador splat:

def sum(*values)
puts values.inspect
values.reduce { |sum, value| sum + value }
end
sum(1) # [1]; 1
sum(1, 1) # [1,1]; 2
sum(1, 2, 3, 4, 10) # [1, 2, 3, 4, 10]; 20

É possível destacar alguns parâmetros caso isso seja interessante: def sum(first, *values)


A variável RAILS_ENV é usada para armazenar o ambiente de desenvolvimento e conseguimos rodar comandos em ambientes especificos. Exemplo: RAILS_ENV=production rake db:create

Migrations registram as modificações que o banco vai sofrendo ao longo do tempo. O metodo up é usado quando o banco evolui a sua versão e down quando regride.

Escrevendo testes mais rápido

  1. Use apenas dependencias necessárias.
  2. Exceto os testes de integração, os testes podem e devem ser isolados. Para fazer isso, não carregue/escreva muitas linhas de código nos controllers.
  3. Evite depender do Rails para rodar os seus testes. Fazemos isso ao carregar o arquivo spec_helper.rb. Caso precise, prefira criar um arquivo que irá chamar somente as dependencias que voce precisa.
  4. Acredito que a regra geal é pare de usar coisas que voce não precisa.

Aprimorando o uso do case usando hash

def identify_class(obj)
    className = obj.class.to_s # Pega o nome da classe do objeto e converte em string
    
    classResult = {
      'Hacker' => "It's a Hacker!",
      'Submission'  => "It's a Submission!",
      'TestCase' => "It's a TestCase!",
      'Contest'  => "It's a Contest!"
    }
    
    puts classResult[className] || "It's an unknown model" # Consegue devolver o conteúdo quando a chave existe, se não mostra um valor diferente, que seria o caso do else ou default
end

Tempfile

Vai criar um arquivo temporário com nome unico. É necessário tomar cuidado para que esses arquivos não se acumulem, gerando acumo no arquivos no filesystem. Para usar esse recurso não é necessário nenhuma gem, pois é nativo do ruby.

Active Storage

Se não for provido o parametro content type o active storage não vai poder determinar o tipo do arquivo automaticamente, então ele irá atribuir o valor application/octer-stream

O active cria tabelas em sua migration:

  1. Armazena informações do arquivo.

  2. Armazena informações sobre o model que armazenará esse arquivo (que fará o attach). Ex: Tabela de usuários, onde cada um terá um avatar.

O arquivo storage.yml é usado para informar ao active onde ele vai armazenar os arquivos.

Podemos usar o método purge para deletar o attach. Ex:

@bucket.file.find(params[:id]).purge

Também temos o método purge_later para excluir arquivos grandes.

Serviços de armazenamento de imagem: AWS S3, Google Cloud Storage, Microsoft Azure Storage.

Podemos usar a gem Image Magic para redimensionar as imagens.

config.active_storage.service determinal qual serviço vamos utilizar e este precisa estar definido em storage.yml

Mirror service serve para realizar cópidas entre serviços.

Minio

É um servidor object storage 100% compátivel com o protocolo S3 da AWS.

Precisamos configurar o nosso storage para fazer um upload para algum serviço de cloud. É necessário mudar as configurações de CORS na AWS para permitir o upload feito pelo nosso dominio.

Direct Upload

Serve para fazermos upload dos arquivos de forma assincrona.

Criando um formulário de pesquisa em Rails 🔎

Na view, use:

<%= form_tag(children_path, :method =>:get) do %>
 <%= text_field_tag 'search', nil, placeholder: 'Enter search term ...' %>
 <%= submit_tag 'Search' %>
<% end %>

Vamos usar o metodo get para capturar os dados preenchidos no campo após submeter os dados clicando no botão 'Search'. Os dados serão enviados para a rota children_path. Exemplo: http://localhost:3000/children?search=lu&commit=Search

Veja que search é a chave e 'lu' foi o conteúdo submetido.

No controller, use:

def index
  return result_search unless params[:search] == ''
   
  @children = Child.all
end

def result_search
  @children = Child.search(params[:search])
end

Basicamente, estamos dizendo que a menos que a pesquisa seja nula, vamos mostrar o resultado dela. Se nada for preenchido no campo, todos os dados serão mostrados.

No model, trataremos a regra de negocio, que no caso, seria a pesquisa de fato:

Aqui existem duas opções:

Usando metodo de classe:

# Criando um método de classe
def self.search(query)
  where("name like ?", "%#{query}%")
end

Usando scope, podemos encadear pesquisas futuramente:

# Usando scope 
scope :search, ->(query) { where("name like ?", "%#{query}%") }

Basicamente, estamos dizendo: onde o nome for algo como o conteúdo da query ... retorne os dados

Criando formulários avançados com Nested Attributes

Temos um model chamado Child que tem possui como parametros o nome e sexo, mas queremos adicionar alguns campos referentes ao seu endereço. Para isso, ao invés de simplesmente adcionar outros campos vamos associar com outra tabela chamada Address, que receberá como parametro os itens street e zip.

Vamos começar pelo terminal criando esse novo model:

rails g model Address street zip child:references

Estamos pedindo que o Rails gere o model Adress com os parametros street e zip como strings e associando ao model Child. No seu model, teremos:

class Address < ApplicationRecord
  belongs_to :child
end

Aqui, estamos dizendo: Address belongs to Child (Endereço pertence ao Filho). Já o model de Child, teremos:

class Child < ApplicationRecord
  has_one :address

  # Vai aceitar os atributos de address
  accepts_nested_attributes_for :address
end

Estamos dizendo que Child has one Address (Filho tem um endereço) e que aceita os atributos do model Adress.

No controller de Child, além de permitir os parametros name e sex, teremos também os atributos para o endereço:

# Only allow a list of trusted parameters through.
def child_params
  params.require(:child).permit(:name, :sex, :address_attributes => [:street, :zip])
end

Ao criar um novo Child, temos:

def new
  @child = Child.new
  @child.build_address
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment