-
-
Save bergfelipe/49cc1cce00fdfaee1cf8441d807ecd3b to your computer and use it in GitHub Desktop.
Active Record
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ########################################### | |
| where pt 1 | |
| ########################################### | |
| Transfer.where(amount_cents: 1500) | |
| O Active Records cria cláusulas IN na consulta SQL quando passamos um array de valores | |
| Transfer.where(amount_cents: [1000, 1500, 2000]) #SELECT "products".* FROM transfers WHERE "transfer.amount_cents" IN (100, 150, 200) | |
| você pode passar também um range como parâmetro: | |
| Transfer.where(amount_cents: 1000..10000) | |
| Você pode passar uma string como paramêtro, e escrever sua condição SQL na mão, por exemplo: | |
| User.where('name LIKE ? AND email = ?', "%#{var_name}%", email) | |
| Nesse caso as interrogações são substituídas pelas variáveis na mesma ordem que foram inseridas e declaradas(tome cuidado) | |
| A melhor abordagem para o exemplo acima é fazer um bind das suas variáveis: | |
| User.where('name LIKE :name AND email = :email', name: "%#{var}%", email: var_email) # uma vantagem é poder usar a mesma variável em locais diferentes da query | |
| ########################################### | |
| where pt 2 | |
| ########################################### | |
| User.where(name: "John").or(User.where(last_name: 'Miller').first) | |
| SELECT * FROM users WHERE users.name = 'John' OR users.last_name = 'Miller' | |
| User.where(name: "John").where(gender: 'Male') | |
| SELECT * FROM users users.name = 'John' AND users.last_name = 'Miller' | |
| Você também pode criar uma busca de negação usando .where.not | |
| Company.where.not('balance_cents > 10000') | |
| O .where.not também aceita arrays e strings de consultas SQL | |
| ########################################### | |
| Order | |
| ########################################### | |
| .order(*clauses) | |
| Company.order('created_at desc') | |
| User.order(:created_at) | |
| Transfer.order(:created_at).take(3) # Em ambos casos, por default, a listagem é feita de forma ascendente | |
| User.order(age: :desc) # lista de forma decrescente | |
| ########################################### | |
| select vs pluck | |
| ########################################### | |
| .select(campos) e .pluck(campos) | |
| Por padrão o Active Records faz <SELECT *> nas chamadas, e muitas vezes precisamos de campos específicos da tabela, o select resolve esse problema para nós: | |
| O correto é usar o .select quando você precisa de um ActiveRecord::Relation | |
| Product.select(:price) ######terminar | |
| User.select(:first_name, :last_name).where("users"."age" >= 30) | |
| User.where(status: 'offline').select(:email) | |
| User.select(:email) | |
| Parecido com o .select, o método .pluck difere apenas no seu retorno, onde ao invés de um ActiveRecord::Relation, você recebe um array de de strings | |
| O pluck possui uma performance melhor do que o .select devido a isso: | |
| Company.where(status: "Active").pluck(:name) #will return something like ["Company A", "Company B"] | |
| Company.where(status: "Active").pluck(:id, :name) #will return something like [ [1, "Company A"], [2, "Company B"] ] | |
| ########################################### | |
| each | |
| ########################################### | |
| .each | |
| No ruby existe o loop "for", que aprendemos em outras linguagens, porém para iterar um array é mais aconselhável usarmos o .each | |
| juices_flavors = ['orange', 'grape', 'lemon'] | |
| juice_flavors.each { |flavor| puts flavor.upcase } | |
| o .each não altera o valor dos elementos iterados | |
| n = [10, 20, 30] | |
| n.each do |number| | |
| puts number * 2 | |
| end | |
| puts n # o .each não altera o valor da variável, então be careful =) | |
| ########################################### | |
| find_each | |
| ########################################### | |
| .find_each | |
| Iterar uma grande quantidade de records, pode ser prejudicial, trazendo lentidão para seu servidor e podendo causar até um crash dependendo da quantidade de records. | |
| Para evitar isso o ActiveRecords possui um método find_each, que possui uma opção batch_size. Digamos que você possui 10.000 records na tabela Transfer, usando find_each sua busca ficaria assim: | |
| Transfer.all.find_each(batch_size: 2500) do |transfer| | |
| puts transfer.user.name | |
| end | |
| # A busca retornará 4 batches de 2500 records | |
| Ao invés de carregar um grande número de records, o find_each carregará cada batch de uma vez, e após seu uso irá "jogar fora" da memória o batch usado liberando memória ram. Dessa forma você até aumenta o número de queries no seu banco de dados, porém você ganha performance e desempenho. Lembre-se de usá-lo apenas quando tiver um grande número de records a serem carregados. | |
| ########################################### | |
| map | |
| ########################################### | |
| .map | |
| As maiores diferenças entre o map e o each, é que no .map é que no .map você recebe um Array com o resultado do bloco aplicado para cada elemento recebido | |
| então o loop Transfer.all.map { |transfer| transfer.amount_cents * 2 } retorna um array pegando cada value de um payment multiplicado por 2. | |
| você pode usar o map pra retornar campos específicos de um Model com esse shortcut | |
| User.all.map(&:email) | |
| porém por questões de beleza e performance, nesses casos sempre é melhor usar o método .pluck | |
| User.pluck(:email) | |
| Usando .pluck você tem uma performance cerca de 8 vezes maior do que o map nesse caso | |
| ########################################### | |
| select loop | |
| ########################################### | |
| .select | |
| O método .select também aceita um bloco de iteração. Ele executa a lógica passada dentro do bloco para cada elemento do Array, caso a expressão retorne true ele adiciona o elemento ao Array de retorno | |
| t = Transfer.select { |transfer| transfer.amount_cents > 5000 } | |
| [#<Transfer id: "1", user_id: "15", amount_cents: 8087, created_at: "2019-03-04 12:49:02", updated_at: "2019-03-04 12:49:02">, #<Transfer id: "2", user_id: "19", amount_cents: 8457, created_at: "2019-03-04 12:49:03", updated_at: "2019-03-04 12:49:03">] | |
| Como já vimos o .select sempre retorna um Array de ActiveRecord::Relation, caso você precise de retornar um campo específico, você pode maximizar a performance da sua query passando um pluck | |
| t = Transfer.select { |transfer| transfer.amount_cents > 5000 }.pluck(:amount_cents) | |
| ['8534', '8457'] | |
| ########################################### | |
| n+1 parte 1 | |
| ########################################### | |
| Avoid N+1 queries | |
| entendendo; | |
| # Assuming User has the following association | |
| class User | |
| has_many :transfers | |
| end | |
| # Running this activerecord code | |
| users = User.all | |
| users.each do |user| | |
| user.transfers | |
| end | |
| # Will generate n+1 queries, where n is the number of users, namely | |
| # SELECT * FROM users; | |
| # SELECT * FROM transfers WHERE transfers.user_id = 1; | |
| # SELECT * FROM transfers WHERE transfers.user_id = 2; | |
| # SELECT * FROM transfers WHERE transfers.user_id = 3; | |
| # ..... | |
| # SELECT * FROM transfers WHERE posts.user_id = n; | |
| ########################################### | |
| sum | |
| ########################################### | |
| .sum | |
| Quando você precisa somar uma coluna do seu banco de dados(muito útil para mostrar informações em dashboards) você pode usar o método .sum | |
| Transfer.sum(:amount_cents) | |
| Se você quiser uma condição, use dessa forma: | |
| Payment.where("payments.amount_cents > 5000").sum(:amount_cents) | |
| #Nesse exemplo passamos um where usando uma string para usar o simbolo >(maior) do SQL | |
| você pode fazer cálculos usando o sum, digamos que você salva o value de um Payment em centavos, para obter sua forma no formato correto, você poderia fazer | |
| user.transfers.sum(:amount_cents) / 100.0 #é uma boa prática armazenar valores monetários em centavos, uma boa gem para lidar com esse tipo de field é a gem money-rails | |
| ########################################### | |
| find_or_create_by | |
| ########################################### | |
| .find_or_create_by E .find_or_initialize_by | |
| Esses métodos são muito úteis quando você não quer criar dados duplicados no seu banco de dados. | |
| User.find_or_create_by(name: 'Bruce', last_name: 'Wayne') # O record só será criado se não existir um User com name Bruce com sobrenome Wayne | |
| a diferença entre os 2 métodos acima é que o _create_by faz um Model.create se o find não der match, o _initialize_by faz um Model.new | |
| user = User.find_or_initialize_by(name: 'Bruce', last_name: 'Wayne') # user.persisted? -> false | |
| Caso você queira verificar a existência apenas de um campo para criar um record com vários outros, você pode usar o .create_with | |
| user = User.create_with(name: 'Bruce', last_name: 'Wayne', power: 'money').find_or_create_by(hero: 'Batman') | |
| user = User.create_with(name: 'Bruce', last_name: 'Wayne', power: 'money').find_or_initialize_by(hero: 'Batman') | |
| Passando o create_with com todos os outros dados, o método só irá verificar a existencia de um hero 'Batman' no database, caso não encontre, ele irá ser criado ou inicializado com todos dados passados dentro do create_with e do .find_or_create_by/.find_or_initialize_by | |
| ########################################### | |
| bulk | |
| ########################################### | |
| Tratando Records em massa | |
| Tratar Records em massa(bulk) é uma ótima maneira de ganhar performance, pois dessa forma, você faz apenas uma chamada no seu database ao invés de criar record por record dentro de um loop. | |
| .delete_all | |
| Você pode destruir records específicos com o método .delete_all, tenha cuidado ao usar esse tipo de método para não se arrepender depois, mas vamos à um exemplo | |
| sleeping_users = User.where(status: 'sleeping') | |
| sleeping_users.delete_all | |
| # DELETE FROM users WHERE users.status = 'sleeping'; --- Apenas 1 query | |
| ----- | |
| .update_all | |
| a mesma ideia para updates: | |
| best_payers = User.where(status: 'top_payer') | |
| best_payers.update_all(grade: 5) <-- ou --> User.where(status: 'top_payer').update_all(grade: 5) | |
| # | |
| ----- | |
| Criando em massa | |
| O ActiveRecord::Base#create aceita um hash de valores, o que torna muito mais prático a criação de records em massa quando possível: | |
| new_members = [ | |
| { first_name: "Nasus", email: "nasus@bla.net" }, | |
| { first_name: "Xogath", email: "xo@bla.com" }, | |
| { first_name: "Annie", email: "annie@tibbers.com" } | |
| ] | |
| Member.create(new_members) | |
| INSERT INTO users (name, email) VALUES ('Nasus', 'nasus@bla.net'), ('Xogath', 'xo@bla.com'), ('Annie', 'annie@tibbers.org') | |
| ########################################### | |
| joins parte 1 | |
| ########################################### | |
| joins no Rails. | |
| Restituicao.joins(:conta_bancaria).where(conta_bancaria: { cpf_cnpj: doc } | |
| Caso você não conheça o conceito de Joins SQL, por favor leia o artigo do devmedia: https://www.devmedia.com.br/sql-join-entenda-como-funciona-o-retorno-dos-dados/31006 | |
| Se temos um Model Band que belongs_to Genre podemos fazer | |
| Band.joins(:genre) # essa query retorna todas bandas que possuem um gênero | |
| Genre.joins(:bands) # retorna todos gêneros que possuem bandas | |
| Band.joins(:genre).uniq # retorna todas bandas que possuem um gênero sem duplicar bandas que já foram listadas | |
| digamos que uma banda has_many musics, você também pode filtrar por multiplas associações | |
| Band.joins(:genre, :musics) # retorna todas bandas que possuem um gênero e pelo menos uma música cadastrada | |
| o .uniq continua sendo útil para evitar duplicações nesse caso | |
| Band.joins(:genre, :musics).uniq | |
| Ao ter uma associação considerada nested você também consegue fazer joins dessa forma | |
| Genre.joins(bands: :musics) # retorna todos os gêneros que possuem bandas que tem músicas cadastradas | |
| você pode fazer esse join para mais de um nested attributes também. | |
| Genre.joins(bands: [:musics, :reviews]) # retorna todos os gêneros que possuem bandas que tem músicas e reviews cadastrados | |
| ########################################### | |
| joins parte 2 | |
| ########################################### | |
| joins com .where | |
| A principal ideia do join sempre é pegar um model baseado em uma condição de outro | |
| exemplo: | |
| Genre.joins(:bands).where('bands.description = null') | |
| # retorna todos os gêneros que possuem bandas com description vazia | |
| Band.joins(:musics).where(musics: {duration: 9..16}).uniq | |
| # retorna todas as bandas com músicas de duração entre 9 e 16 minutos sem duplicações. | |
| ########################################### | |
| métodos uteis | |
| ########################################### | |
| métodos uteis | |
| .exists?(condition) ou Model.where(condition).exists? | |
| .any? também é útil para arrays | |
| [].any? #false | |
| [1, 2, 3].any? #true | |
| .empty? | |
| .many? | |
| .one? | |
| .to_a | |
| .to_json | |
| .respond_to? :method_name | |
| .is_a? Class | |
| ########################################### | |
| Base.connection | |
| ########################################### | |
| exec_query | |
| def self.user(recebedor) | |
| sql = ActiveRecord::Base.sanitize_sql_array(["SELECT * FROM USER s WHERE s.PESSOA_ID = ?", recebedor]) | |
| ActiveRecord::Base.connection.exec_query(sql) | |
| end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment