Para usar o adaptador do PostgreSQL, você precisa ter pelo menos a versão 9.3 instalada. Versões mais antigas não são suportadas.
Para começar com o PostgreSQL, dê uma olhada no guia de configuração do Rails. Ele descreve como configurar corretamente o Active Record para o PostgreSQL.
1 Tipos de dados
O PostgreSQL oferece uma série de tipos de dados específicos. A seguir está uma lista de tipos suportados pelo adaptador do PostgreSQL.
1.1 Bytea
# db/migrate/20140207133952_create_documents.rb
create_table :documents do |t|
t.binary 'payload'
end
# app/models/document.rb
class Document < ApplicationRecord
end
# Uso
data = File.read(Rails.root + "tmp/output.pdf")
Document.create payload: data
1.2 Array
# db/migrate/20140207133952_create_books.rb
create_table :books do |t|
t.string 'title'
t.string 'tags', array: true
t.integer 'ratings', array: true
end
add_index :books, :tags, using: 'gin'
add_index :books, :ratings, using: 'gin'
# app/models/book.rb
class Book < ApplicationRecord
end
# Uso
Book.create title: "Brave New World",
tags: ["fantasy", "fiction"],
ratings: [4, 5]
## Livros para uma única tag
Book.where("'fantasy' = ANY (tags)")
## Livros para várias tags
Book.where("tags @> ARRAY[?]::varchar[]", ["fantasy", "fiction"])
## Livros com 3 ou mais avaliações
Book.where("array_length(ratings, 1) >= 3")
1.3 Hstore
NOTA: Você precisa habilitar a extensão hstore
para usar o hstore.
# db/migrate/20131009135255_create_profiles.rb
class CreateProfiles < ActiveRecord::Migration[7.0]
enable_extension 'hstore' unless extension_enabled?('hstore')
create_table :profiles do |t|
t.hstore 'settings'
end
end
# app/models/profile.rb
class Profile < ApplicationRecord
end
irb> Profile.create(settings: { "color" => "blue", "resolution" => "800x600" })
irb> profile = Profile.first
irb> profile.settings
=> {"color"=>"blue", "resolution"=>"800x600"}
irb> profile.settings = {"color" => "yellow", "resolution" => "1280x1024"}
irb> profile.save!
irb> Profile.where("settings->'color' = ?", "yellow")
=> #<ActiveRecord::Relation [#<Profile id: 1, settings: {"color"=>"yellow", "resolution"=>"1280x1024"}>]>
1.4 JSON e JSONB
# db/migrate/20131220144913_create_events.rb
# ... para o tipo de dados json:
create_table :events do |t|
t.json 'payload'
end
# ... ou para o tipo de dados jsonb:
create_table :events do |t|
t.jsonb 'payload'
end
# app/models/event.rb
class Event < ApplicationRecord
end
irb> Event.create(payload: { kind: "user_renamed", change: ["jack", "john"]})
irb> event = Event.first
irb> event.payload
=> {"kind"=>"user_renamed", "change"=>["jack", "john"]}
## Consulta baseada em documento JSON
# O operador -> retorna o tipo JSON original (que pode ser um objeto), enquanto ->> retorna texto
irb> Event.where("payload->>'kind' = ?", "user_renamed")
1.5 Tipos de Intervalo
Este tipo é mapeado para objetos Range
do Ruby.
# db/migrate/20130923065404_create_events.rb
create_table :events do |t|
t.daterange 'duration'
end
# app/models/event.rb
class Event < ApplicationRecord
end
irb> Event.create(duration: Date.new(2014, 2, 11)..Date.new(2014, 2, 12))
irb> event = Event.first
irb> event.duration
=> Tue, 11 Feb 2014...Thu, 13 Feb 2014
## Todos os eventos em uma determinada data
irb> Event.where("duration @> ?::date", Date.new(2014, 2, 12))
## Trabalhando com limites de intervalo
irb> event = Event.select("lower(duration) AS starts_at").select("upper(duration) AS ends_at").first
irb> event.starts_at
=> Tue, 11 Feb 2014
irb> event.ends_at
=> Thu, 13 Feb 2014
1.6 Tipos Compostos
Atualmente, não há suporte especial para tipos compostos. Eles são mapeados para colunas de texto normais:
CREATE TYPE full_address AS
(
city VARCHAR(90),
street VARCHAR(90)
);
# db/migrate/20140207133952_create_contacts.rb
execute <<-SQL
CREATE TYPE full_address AS
(
city VARCHAR(90),
street VARCHAR(90)
);
SQL
create_table :contacts do |t|
t.column :address, :full_address
end
# app/models/contact.rb
class Contact < ApplicationRecord
end
irb> Contact.create address: "(Paris,Champs-Élysées)"
irb> contact = Contact.first
irb> contact.address
=> "(Paris,Champs-Élysées)"
irb> contact.address = "(Paris,Rue Basse)"
irb> contact.save!
1.7 Tipos Enumerados
O tipo pode ser mapeado como uma coluna de texto normal ou para um ActiveRecord::Enum
.
# db/migrate/20131220144913_create_articles.rb
def change
create_enum :article_status, ["draft", "published", "archived"]
create_table :articles do |t|
t.enum :status, enum_type: :article_status, default: "draft", null: false
end
end
Você também pode criar um tipo enum e adicionar uma coluna enum a uma tabela existente:
# db/migrate/20230113024409_add_status_to_articles.rb
def change
create_enum :article_status, ["draft", "published", "archived"]
add_column :articles, :status, :enum, enum_type: :article_status, default: "draft", null: false
end
As migrações acima são reversíveis, mas você pode definir métodos separados #up
e #down
se necessário. Certifique-se de remover quaisquer colunas ou tabelas que dependam do tipo enum antes de removê-lo:
def down
drop_table :articles
# OU: remove_column :articles, :status
drop_enum :article_status
end
A declaração de um atributo enum no modelo adiciona métodos auxiliares e impede que valores inválidos sejam atribuídos às instâncias da classe:
# app/models/article.rb
class Article < ApplicationRecord
enum status: {
draft: "draft", published: "published", archived: "archived"
}, _prefix: true
end
irb> article = Article.create
irb> article.status
=> "draft" # status padrão do PostgreSQL, conforme definido na migração acima
irb> article.status_published!
irb> article.status
=> "published"
irb> article.status_archived?
=> false
irb> article.status = "deleted"
ArgumentError: 'deleted' não é um status válido
Para renomear o enum, você pode usar rename_enum
juntamente com a atualização de qualquer uso do modelo:
# db/migrate/20150718144917_rename_article_status.rb
def change
rename_enum :article_status, to: :article_state
end
Para adicionar um novo valor, você pode usar add_enum_value
:
# db/migrate/20150720144913_add_new_state_to_articles.rb
def up
add_enum_value :article_state, "archived", # será colocado no final após published
add_enum_value :article_state, "in review", before: "published"
add_enum_value :article_state, "approved", after: "in review"
end
NOTA: Os valores do enum não podem ser removidos, o que também significa que add_enum_value
é irreversível. Você pode ler o motivo aqui.
Para renomear um valor, você pode usar rename_enum_value
:
# db/migrate/20150722144915_rename_article_state.rb
def change
rename_enum_value :article_state, from: "archived", to: "deleted"
end
Dica: para mostrar todos os valores de todos os enums que você possui, você pode executar esta consulta no console bin/rails db
ou psql
:
SELECT n.nspname AS enum_schema,
t.typname AS enum_name,
e.enumlabel AS enum_value
FROM pg_type t
JOIN pg_enum e ON t.oid = e.enumtypid
JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
1.8 UUID
NOTA: Se você estiver usando o PostgreSQL anterior à versão 13.0, talvez seja necessário habilitar extensões especiais para usar UUIDs. Habilite a extensão pgcrypto
(PostgreSQL >= 9.4) ou a extensão uuid-ossp
(para versões anteriores).
# db/migrate/20131220144913_create_revisions.rb
create_table :revisions do |t|
t.uuid :identifier
end
# app/models/revision.rb
class Revision < ApplicationRecord
end
irb> Revision.create identifier: "A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11"
irb> revision = Revision.first
irb> revision.identifier
=> "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
Você pode usar o tipo uuid
para definir referências em migrações:
# db/migrate/20150418012400_create_blog.rb
enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')
create_table :posts, id: :uuid
create_table :comments, id: :uuid do |t|
# t.belongs_to :post, type: :uuid
t.references :post, type: :uuid
end
# app/models/post.rb
class Post < ApplicationRecord
has_many :comments
end
# app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :post
end
Consulte esta seção para obter mais detalhes sobre o uso de UUIDs como chave primária.
1.9 Tipos de Bit String
# db/migrate/20131220144913_create_users.rb
create_table :users, force: true do |t|
t.column :settings, "bit(8)"
end
# app/models/user.rb
class User < ApplicationRecord
end
irb> User.create settings: "01010011"
irb> user = User.first
irb> user.settings
=> "01010011"
irb> user.settings = "0xAF"
irb> user.settings
=> "10101111"
irb> user.save!
1.10 Tipos de Endereço de Rede
Os tipos inet
e cidr
são mapeados para objetos Ruby IPAddr
. O tipo macaddr
é mapeado para texto normal.
# db/migrate/20140508144913_create_devices.rb
create_table(:devices, force: true) do |t|
t.inet 'ip'
t.cidr 'network'
t.macaddr 'address'
end
# app/models/device.rb
class Device < ApplicationRecord
end
irb> macbook = Device.create(ip: "192.168.1.12", network: "192.168.2.0/24", address: "32:01:16:6d:05:ef")
irb> macbook.ip
=> #<IPAddr: IPv4:192.168.1.12/255.255.255.255>
irb> macbook.network
=> #<IPAddr: IPv4:192.168.2.0/255.255.255.0>
irb> macbook.address
=> "32:01:16:6d:05:ef"
1.11 Tipos Geométricos
Todos os tipos geométricos, com exceção de points
, são mapeados para texto normal. Um ponto é convertido em uma matriz contendo as coordenadas x
e y
.
1.12 Intervalo
Este tipo é mapeado para objetos ActiveSupport::Duration
.
# db/migrate/20200120000000_create_events.rb
create_table :events do |t|
t.interval 'duration'
end
# app/models/event.rb
class Event < ApplicationRecord
end
irb> Event.create(duration: 2.days)
irb> event = Event.first
irb> event.duration
=> 2 days
2 Chaves Primárias UUID
NOTA: Você precisa habilitar a extensão pgcrypto
(apenas PostgreSQL >= 9.4) ou uuid-ossp
para gerar UUIDs aleatórios.
```ruby
db/migrate/20131220144913_create_devices.rb
enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto') create_table :devices, id: :uuid do |t| t.string :kind end ```
# app/models/device.rb
class Device < ApplicationRecord
end
irb> device = Device.create
irb> device.id
=> "814865cd-5a1d-4771-9306-4268f188fe9e"
NOTA: gen_random_uuid()
(de pgcrypto
) é assumido se nenhuma opção :default
foi passada para create_table
.
Para usar o gerador de modelo do Rails para uma tabela usando UUID como chave primária, passe --primary-key-type=uuid
para o gerador de modelo.
Por exemplo:
$ rails generate model Device --primary-key-type=uuid kind:string
Ao criar um modelo com uma chave estrangeira que fará referência a este UUID, trate uuid
como o tipo de campo nativo, por exemplo:
$ rails generate model Case device_id:uuid
3 Indexação
O PostgreSQL inclui uma variedade de opções de índice. As seguintes opções são suportadas pelo adaptador PostgreSQL, além das opções comuns de índice
3.1 Include
Ao criar um novo índice, colunas não-chave podem ser incluídas com a opção :include
. Essas chaves não são usadas em varreduras de índice para pesquisa, mas podem ser lidas durante uma varredura somente de índice sem precisar visitar a tabela associada.
# db/migrate/20131220144913_add_index_users_on_email_include_id.rb
add_index :users, :email, include: :id
Múltiplas colunas são suportadas:
# db/migrate/20131220144913_add_index_users_on_email_include_id_and_created_at.rb
add_index :users, :email, include: [:id, :created_at]
4 Colunas Geradas
NOTA: Colunas geradas são suportadas desde a versão 12.0 do PostgreSQL.
# db/migrate/20131220144913_create_users.rb
create_table :users do |t|
t.string :name
t.virtual :name_upcased, type: :string, as: 'upper(name)', stored: true
end
# app/models/user.rb
class User < ApplicationRecord
end
# Uso
user = User.create(name: 'John')
User.last.name_upcased # => "JOHN"
5 Chaves Estrangeiras Adiáveis
Por padrão, as restrições de tabela no PostgreSQL são verificadas imediatamente após cada instrução. Intencionalmente, não permite criar registros em que o registro referenciado ainda não esteja na tabela referenciada. É possível executar essa verificação de integridade posteriormente, quando a transação é confirmada, adicionando DEFERRABLE
à definição da chave estrangeira. Para adiar todas as verificações por padrão, pode ser definido como DEFERRABLE INITIALLY DEFERRED
. O Rails expõe esse recurso do PostgreSQL adicionando a chave :deferrable
às opções foreign_key
nos métodos add_reference
e add_foreign_key
.
Um exemplo disso é criar dependências circulares em uma transação, mesmo se você tiver criado chaves estrangeiras:
add_reference :person, :alias, foreign_key: { deferrable: :deferred }
add_reference :alias, :person, foreign_key: { deferrable: :deferred }
Se a referência foi criada com a opção foreign_key: true
, a seguinte transação falharia ao executar a primeira instrução INSERT
. No entanto, não falha quando a opção deferrable: :deferred
é definida.
ActiveRecord::Base.connection.transaction do
person = Person.create(id: SecureRandom.uuid, alias_id: SecureRandom.uuid, name: "John Doe")
Alias.create(id: person.alias_id, person_id: person.id, name: "jaydee")
end
Quando a opção :deferrable
é definida como :immediate
, permite que as chaves estrangeiras mantenham o comportamento padrão de verificar a restrição imediatamente, mas permite adiar manualmente as verificações usando SET CONSTRAINTS ALL DEFERRED
dentro de uma transação. Isso fará com que as chaves estrangeiras sejam verificadas quando a transação for confirmada:
ActiveRecord::Base.transaction do
ActiveRecord::Base.connection.execute("SET CONSTRAINTS ALL DEFERRED")
person = Person.create(alias_id: SecureRandom.uuid, name: "John Doe")
Alias.create(id: person.alias_id, person_id: person.id, name: "jaydee")
end
Por padrão, :deferrable
é false
e a restrição é sempre verificada imediatamente.
6 Restrição Única
# db/migrate/20230422225213_create_items.rb
create_table :items do |t|
t.integer :position, null: false
t.unique_key [:position], deferrable: :immediate
end
Se você deseja alterar um índice único existente para adiável, pode usar :using_index
para criar restrições únicas adiáveis.
add_unique_key :items, deferrable: :deferred, using_index: "index_items_on_position"
Assim como as chaves estrangeiras, as restrições únicas podem ser adiadas definindo :deferrable
como :immediate
ou :deferred
. Por padrão, :deferrable
é false
e a restrição é sempre verificada imediatamente.
7 Restrições de Exclusão
# db/migrate/20131220144913_create_products.rb
create_table :products do |t|
t.integer :price, null: false
t.daterange :availability_range, null: false
t.exclusion_constraint "price WITH =, availability_range WITH &&", using: :gist, name: "price_check"
end
Assim como as chaves estrangeiras, as restrições de exclusão podem ser adiadas definindo :deferrable
como :immediate
ou :deferred
. Por padrão, :deferrable
é false
e a restrição é sempre verificada imediatamente.
8 Pesquisa de Texto Completo
# db/migrate/20131220144913_create_documents.rb
create_table :documents do |t|
t.string :title
t.string :body
end
add_index :documents, "to_tsvector('english', title || ' ' || body)", using: :gin, name: 'documents_idx'
# app/models/document.rb
class Document < ApplicationRecord
end
# Uso
Document.create(title: "Gatos e Cachorros", body: "são legais!")
## todos os documentos que correspondem a 'gato & cachorro'
Document.where("to_tsvector('english', title || ' ' || body) @@ to_tsquery(?)",
"gato & cachorro")
Opcionalmente, você pode armazenar o vetor como uma coluna gerada automaticamente (a partir do PostgreSQL 12.0):
# db/migrate/20131220144913_create_documents.rb
create_table :documents do |t|
t.string :title
t.string :body
t.virtual :textsearchable_index_col,
type: :tsvector, as: "to_tsvector('english', title || ' ' || body)", stored: true
end
add_index :documents, :textsearchable_index_col, using: :gin, name: 'documents_idx'
# Uso
Document.create(title: "Gatos e Cachorros", body: "são legais!")
## todos os documentos que correspondem a 'gato & cachorro'
Document.where("textsearchable_index_col @@ to_tsquery(?)", "gato & cachorro")
9 Visualizações de Banco de Dados
Imagine que você precise trabalhar com um banco de dados legado que contenha a seguinte tabela:
rails_pg_guide=# \d "TBL_ART"
Tabela "public.TBL_ART"
Coluna | Tipo | Modificadores
------------+-----------------------------+------------------------------------------------------------
INT_ID | integer | not null default nextval('"TBL_ART_INT_ID_seq"'::regclass)
STR_TITLE | character varying |
STR_STAT | character varying | default 'draft'::character varying
DT_PUBL_AT | timestamp without time zone |
BL_ARCH | boolean | default false
Índices:
"TBL_ART_pkey" PRIMARY KEY, btree ("INT_ID")
Essa tabela não segue as convenções do Rails. Como as visualizações simples do PostgreSQL são atualizáveis por padrão, podemos envolvê-la da seguinte maneira:
# db/migrate/20131220144913_create_articles_view.rb
execute <<-SQL
CREATE VIEW articles AS
SELECT "INT_ID" AS id,
"STR_TITLE" AS title,
"STR_STAT" AS status,
"DT_PUBL_AT" AS published_at,
"BL_ARCH" AS archived
FROM "TBL_ART"
WHERE "BL_ARCH" = 'f'
SQL
# app/models/article.rb
class Article < ApplicationRecord
self.primary_key = "id"
def archive!
update_attribute :archived, true
end
end
irb> first = Article.create! title: "O inverno está chegando", status: "publicado", published_at: 1.ano.atras
irb> second = Article.create! title: "Prepare-se", status: "rascunho", published_at: 1.mês.atras
irb> Article.count
=> 2
irb> first.archive!
irb> Article.count
=> 1
NOTA: Esta aplicação só se preocupa com Artigos
não arquivados. Uma visualização também
permite condições para que possamos excluir diretamente os Artigos
arquivados.
10 Despejos de Estrutura
Se o config.active_record.schema_format
for :sql
, o Rails chamará pg_dump
para gerar um
despejo de estrutura.
Você pode usar ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags
para configurar o pg_dump
.
Por exemplo, para excluir comentários do despejo de estrutura, adicione isso a um inicializador:
ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = ['--no-comments']
Feedback
Você é incentivado a ajudar a melhorar a qualidade deste guia.
Por favor, contribua se encontrar algum erro de digitação ou factual. Para começar, você pode ler nossa contribuição à documentação seção.
Você também pode encontrar conteúdo incompleto ou desatualizado. Por favor, adicione qualquer documentação ausente para o principal. Certifique-se de verificar Guias Edge primeiro para verificar se os problemas já foram corrigidos ou não no branch principal. Verifique as Diretrizes dos Guias do Ruby on Rails para estilo e convenções.
Se por algum motivo você encontrar algo para corrigir, mas não puder corrigi-lo você mesmo, por favor abra uma issue.
E por último, mas não menos importante, qualquer tipo de discussão sobre a documentação do Ruby on Rails é muito bem-vinda no Fórum oficial do Ruby on Rails.