Agenda
- Migraciones
- Validaciones y callbacks
- Asociaciones
- Queries
Migraciones
Migraciones (docs)
Crear una nueva tabla:
$ rails g model Post title:string content:text
create db/migrate/20190724032006_create_posts.rb
create app/models/post.rb
class CreatePosts < ActiveRecord::Migration[5.2]
def change
create_table :posts do |t|
t.string :title
t.text :content
t.timestamps
end
end
end
Migraciones (up/down)
class CreateLinks < ActiveRecord::Migration[5.2]
def up
create_table :user_links do |t|
...
end
add_column :users, :home_page_url, :string
execute <<-SQL
ALTER TABLE products (...)
SQL
end
def down
execute <<-SQL
ALTER TABLE products (...)
SQL
remove_column :users, :home_page_url
drop_table :user_links
end
end
Migraciones
$ rails db:migrate
== 20190724032006 CreatePosts: migrating ======================================
-- create_table(:posts) -> 0.0011s
Corre todas las migraciones pendientes
$ rails db:migrate VERSION=20190724032006
Corre las migraciones (hacia arriba o abajo) hasta llegar a la VERSION
$ rails db:rollback STEP=3
Revierte las últimas 3 migraciones
$ rails db:migrate:redo STEP=3
Revierte las últimas 3 migraciones y las vuelve a ejecutar
Migraciones
$ rails generate migration AddPartNumberToProducts part_number:string:index
class AddPartNumberToProducts < ActiveRecord::Migration[5.2]
def change
add_column :products, :part_number, :string
add_index :products, :part_number
# Otras opciones:
# remove_column :products, :part_number, :string
# change_column :products, :part_number, :text
# add_reference :products, :user, foreign_key: true
# create_join_table :products, :categories (many to many)
end
end
Migraciones: Seed
# db/seeds.rb
Admin.create(username: 'admin', password: 'admin')
require "faker" # https://github.com/stympy/faker
10.times do
Book.create({
title: Faker::Book.title,
author: Faker::Book.author
})
end
$ rails db:seeds
Migraciones: otros comandos
$ rails -T db
rails db:create # Creates the database from config/database.yml [RAILS_ENV]
rails db:drop # Drops the database from config/database.yml [RAILS_ENV]
rails db:migrate # Migrate the database [RAILS_ENV, VERSION]
rails db:migrate:status # Display status of migrations
rails db:rollback # Rolls the schema back to the previous version [STEP=n]
rails db:schema:dump # Creates a db/schema.rb file
rails db:schema:load # Loads a schema.rb file into the database
rails db:seed # Loads the seed data from db/seeds.rb
rails db:setup # Creates the database, loads the schema, and seeds the data
rails db:reset # Idem db:setup, but drop the database first
rails db:version # Retrieves the current schema version number
Validaciones
Validaciones (docs)
class User < ApplicationRecord
validates :name, presence: true
validates :username, format: { message: "only letters", with: /[a-z]{4,9}/ }
validate :active_customer, on: :create
def active_customer
errors.add(:customer_id, "is not active") unless customer.active?
end
end
Validaciones
@user = User.create(params)
<% if @user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Tipos de validaciones
- presence
- uniqueness
- acceptance
- inclusion (in: [...])
- exclusion (in: [...])
- format (with: /regex/)
- length (minimum/maximum/...)
- numericality (...)
- validates_associated
Tipos de validaciones
Métodos que realizan validaciones:
- create / create!
- save / save!
- update / update!
Métodos que NO realizan validaciones:
- increment! / decrement!
- update_all
- update_column / update_columns
- save(validate: false)
Validaciones personalizadas
class Invoice < ApplicationRecord
validate :active_customer, on: :create
def active_customer
errors.add(:customer_id, "is not active") unless customer.active?
end
end
Callbacks
Callbacks (docs)
class User < ApplicationRecord
validates :username, :email, presence: true
after_create :reward_referrer
before_create do
self.name = username.capitalize if name.blank?
end
def reward_referrer
referrer_user.add_credits if referrer_user
end
end
Callbacks
- before_validation
- after_validation
- before_save
- around_save
- before_create / before_update
- around_create / around_update
- after_create / after_update
- after_save
- after_commit/after_rollback
Asociaciones
Asociaciones (docs)
class Author < ApplicationRecord; end
class Book < ApplicationRecord; end
Creamos un libro de un determinado autor...
@book = Book.create(published_at: Time.now, author_id: @author.id)
Borramos un autor, con todos sus libros...
@books = Book.where(author_id: @author.id)
@books.each do |book|
book.destroy
end
@author.destroy
Con Rails
class Author < ApplicationRecord
has_many :books, dependent: :destroy
end
class Book < ApplicationRecord
belongs_to :author
end
# Crear un libro ...
@book = @author.books.create(published_at: Time.now)
# Eliminar un autor...
@author.destroy
Tipos de Asociaciones
belongs_to
has_one
has_many
has_many :through
has_one :through
has_and_belongs_to_many
belongs_to
has_one
has_many
has_many :through
has_one :through
has_and_belongs_to_many
¿Cuándo usar?
-
belongs_to
vshas_one
-
has_many :through
vshas_and_belongs_to_many
Relaciones Polimórficas
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
Métodos para belongs_to
/has_one
association
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
reload_association
Ej: author.create_book(params)
Opciones para belongs_to
:class_name
:counter_cache
:dependent
:foreign_key
:primary_key
:polymorphic
:optional
Opciones para has_one
Además de los que tiene belongs_to, se suman:
:as
:source
:source_type
:through
Métodos generados has_many
- collection<<(object, ...)
- collection.delete(object, ...)
- collection.destroy(object, ...)
- collection_singular_ids
- collection.clear
- collection.empty?
- collection.size
- collection.find(...)
- collection.where(...)
- collection.exists?(...)
- collection.build(attributes = {}, ...)
- collection.create(attributes = {})
- collection.create!(attributes = {})
- collection.reload
Single Table Inheritance (STI)
$ rails generate model user type:string name:string
class User; end
class Writer < User
end
class Editor < User
end
Single Table Inheritance (STI)
Writer.create(name: 'Tom')
# => INSERT INTO "users" ("type", "name") VALUES ('Writer', 'Tom')
Writer.all
SELECT "users".* FROM "users" WHERE "users"."type" IN ('Writer')
Queries
Queries (docs)
# Devuelven un único objeto
User.find(1)
User.first
User.last
User.find_by name: 'David'
# Devuelven una colección
User.all
User.find_each
Filtros y condiciones
Client.where(...)
Client.where("orders_count = 0")
Client.where("orders_count = ?", params[:orders])
Client.where(orders_count: params[:orders])
# SELECT * FROM clients WHERE (orders_count == 4)
Client.where(id: [1,2,3])
# SELECT * FROM clients WHERE (clients.id IN (1,2,3))
Client.where("username = 'admin'")
Filtros y condiciones
Client.order(:created_at)
Client.order("created_at")
Client.order(created_at: :desc)
Filtros y condiciones
Client.select(:name).distinct
# SELECT DISTINCT name FROM clients
Filtros y condiciones
Client.limit(5)
# SELECT * FROM clients LIMIT 5
Joins
Author.joins("INNER JOIN posts
ON posts.author_id = authors.id
AND posts.published = 't'")
# SELECT authors.* FROM authors
# INNER JOIN posts
# ON posts.author_id = authors.id
# AND posts.published = 't'
Joins
Category.joins(:articles)
# SELECT categories.* FROM categories
# INNER JOIN articles ON articles.category_id = categories.id
Eager loading (Problema N+1)
clients = Client.limit(10)
clients.each do |client|
puts client.address.postcode
end
Eager loading (Solución)
clients = Client.includes(:address).limit(10)
clients.each do |client|
puts client.address.postcode
end
Scopes
class Article < ApplicationRecord
scope :published, -> { where(published: true) }
end
class Article < ApplicationRecord
def self.published
where(published: true)
end
end
Article.published
category.articles.published
Dynamic Finders
Client.find_by_first_name(first_name)
Finding by SQL
Client.find_by_sql("SELECT * FROM clients
INNER JOIN orders ON clients.id = orders.client_id
ORDER BY clients.created_at desc")
Calculations
Client.count
Client.average("orders_count")
Client.sum("orders_count")
Client.minimum("age")