Mais em rubyonrails.pro.br: Geral | Download | Deploy | Código | Apresentações | Documentação | Ecossistema | Comunidade | Podcasts | Blogs

Migrations

Migrations é a forma conveniente de você alterar seu banco de dados de uma maneira organizada e estruturada. Você poderia editar fragmentos de SQL na mão mas teria a responsabilidade de comunicar aos outros desenvolvedores que eles precisam executá-los. Você também necessitaria acompanhar as mudanças na máquina de produção na próxima vez que você fosse fazer um deploy.

O Active Record marca as migrations que já foram executadas, então tudo que você precisa fazer é atualizar seu código e rodar rake db:migrate. O Active Record saberá quais migrations devem ser rodadas. Ele também irá atualizar o seu arquivo db/schema.rb para refletir a estrutura da sua base de dados.

As migrations também permitem que você descreva estas transformações usando Ruby. A grande sacada disto tudo (como a maioria das funcionalidades do Active Record) é a independência do banco de dados: você não precisa se preocupar com mais nenhuma sintaxe para CREATE TABLE, ou sobre variações de SELECT * (você pode descartar o SQL puro para funcionalidades específicas dos bancos de dados). Você poderia usar SQLite 3 no desenvolvimento mas MySQL em produção, por exemplo.

Você aprenderá tudo sobre migrations incluindo:

1 Anatomia de uma Migration

Antes mergulhar nos detalhes de uma migration, aqui estão alguns exemplos de coisas que você pode fazer:

class CreateProducts < ActiveRecord::Migration def self.up create_table :products do |t| t.string :name t.text :description t.timestamps end end def self.down drop_table :products end end

Esta migration adiciona uma tabela chamada products com uma coluna string chamada name e uma coluna text chamada description. Uma chave primária de nome id também será adicionada. No entanto, por padrão, não precisamos pedir isso. As colunas timestamps created_at e updated_at que o Active Record preenche automaticamente também serão adicionadas. Reverter esta migration simplesmente remove a tabela.

Migrações não estão limitadas a alterar o schema. Você também as pode usar para corrigir dados incorretos no banco ou popular novos campos:

class AddReceiveNewsletterToUsers < ActiveRecord::Migration def self.up change_table :users do |t| t.boolean :receive_newsletter, :default => false end User.update_all ["receive_newsletter = ?", true] end def self.down remove_column :users, :receive_newsletter end end

Esta migration adiciona a coluna receive_newsletter na tabela users. Nós queremos que o padrão seja falso para novos usuários, mas para os usuários existentes, consideramos que eles já fizeram a sua opção, então usamos o model User para setar a flag como true para os usuários existentes.

Algumas ressalvas se aplicam ao utilizar models nas suas migrations.

1.1 Migrations São Classes A migration é uma subclasse de ActiveRecord::Migration que implementa dois métodos: up (para realizar as transformações exigidas) e down (reverte o que foi feito).

O Active Record fornece métodos que executam tarefas comuns para definição dos dados de um modo independente do banco de dados (você lerá sobre elas com detalhes mais tarde):

  • create_table
  • change_table
  • drop_table
  • add_column
  • remove_column
  • change_column
  • rename_column
  • add_index
  • remove_index

Se você precisar executar tarefas específicas para seu banco de dados (por exemplo, criar uma foreign key) então a função execute permite que você execute SQL arbitrárias. A migration é apenas uma classe comum em Ruby, então você não está limitado a apenas estas funções. Por exemplo, após adicionar uma coluna, você pode escrever código para setar o valor dessa coluna para dados existentes (se necessário usando seus models).

Em banco de dados que suportam transações com parâmetros para mudar o schema (como PostreSQL ou SQLite3), as migrations entram em uma transação. Se o banco de dados não suporta isso (por exemplo MySQL) então quando a migration falhar, as partes que tiveram sucesso não poderão ser revertidas. Você terá que desfazer estas mudanças manualmente.

1.2 O que há em um Nome

Migrations são armazenadas em arquivos dentro de db/migrate, um para cada classe de migration. O nome dos arquivos é tem a forma de YYYYMMDDHHMMSS_create_products.rb, ou seja, uma hora UTC identificando a migration, seguida de um underscore, seguido pelo nome da migration. O nome da classe de migration deve bater com a última parte do arquivo. Por exemplo, 20080906120000_create_products.rb deverá definir CreateProducts e 20080906120001_add_details_to_products.rb deverá definir AddDetailsToProducts. Se você acha que necessita mudar o nome do arquivo, então você deve atualizar o nome da classe dentro do arquivo, ou então o Rails irá se queixar de uma classe inexistente.

Internamente, o Rails usa apenas o número da migration (data) para identificá-la. Antes do Rails 2.1, os números das migrations eram iniciados com 1 e incrementados cada vez que era gerada uma nova migration. Com vários desenvolvedores, era fácil haver conflitos, o que te forçava a reverter as migrations e renumerá-las. Com o Rails 2.1, isto é evitado usando a hora de geração da migration para identificá-las. Você pode mudar para o esquema da velha numeração setando config.active_record.timestamped_migrations para false no environment.rb.

A combinação de timestamps e o registro de quais migrations já foram rodadas permite que o Rails trate situações comuns que ocorrem com múltiplos desenvolvedores.

Por exemplo, Alice adiciona as migrations 20080906120000 e 20080906123000 e Bob adiciona a 20080906124500 e a executa. Alice finaliza suas mudanças, comita suas migrations e Bob puxa as últimas atualizações. O Rails sabe que ele não rodou as duas migrations da Alice, então rake db:migrate deverá executá-las (mesmo que a migration com um timestamp maior do Bob já tenha sido rodada), e, similarmente, reverter as migrations não deverá executar os seus métodos down.

Claro que isso não substitui a comunicação dentro da equipe. Por exemplo, se a migration da Alice remove uma tabela que a migration do Bob assume que existe, então certamente problemas vão acontecer.

1.3 Alterando Migrations

Ocasionalmente, você comete um erro enquanto escreve uma migration. Se você já tiver executado a migration, então você não pode simplesmente editar e executá-la novamente: o Rails sabe que a migration já foi executada, então ele não fará nada quando você rodar rake db:migrate. Você deve reverter a migration (com rake db:rollback, por exemplo), editar sua migration e então executar rake db:migrate para rodar a versão corrigida.

Geralmente, editar migrations existentes não é uma boa idéia: você estará criando trabalho extra para você mesmo e para seus parceiros, e mais dor de cabeça se a versão existente da migration já tiver sido rodada em máquinas de produção. Em vez disso, você deve escrever uma nova migration para realizar as alterações que você queira. Editar uma migration recém-gerada e que ainda não foi comitada no controlador de versão (ou, de modo geral, que ainda não foi propagada para além da sua máquina de desenvolvimento) é relativamente inofensivo. Apenas use o bom senso.

2 Criando uma Migration

2.1 Criando um Model

Os models e os geradores de scaffold criarão as migrations apropriadas para a geração de um novo model. Esta migration já terá instruções para a criação da tabela relacionada. Se você disser pro Rails as colunas que você precisa, então as declarações já estarão criadas. Por exemplo, executar

ruby script/generate model Product name:string description:text

gerará uma migration como esta

class CreateProducts < ActiveRecord::Migration def self.up create_table :products do |t| t.string :name t.text :description t.timestamps end end def self.down drop_table :products end end

Você pode adicionar quantas colunas quiser. Por padrão t.timestamps (que cria as colunas updated_at e created_at que são populadas automaticamente pelo Active Record) será adicionado para você.

2.2 Criando uma Migration Standalone

Se você está criando migrations para outros propósitos (por exemplo, adicionar uma coluna em uma tabela já existente) então você pode usar o gerador de migrations:

ruby script/generate migration AddPartNumberToProducts

Isto irá criar uma migration vazia mas já nomeada apropriadamente:

class AddPartNumberToProducts < ActiveRecord::Migration def self.up end def self.down end end

Se o nome da migration é na forma “AddXXXtoYYY” ou “RemoveXXXtoYYY” e for seguida de uma lista de nomes de colunas e seus tipos então uma migration contendo as declarações add_column e remove_column será criada.

ruby script/generate migration AddPartNumberToProducts part_number:string

vai gerar

class AddPartNumberToProducts < ActiveRecord::Migration def self.up add_column :products, :part_number, :string end def self.down remove_column :products, :part_number end end

Analogamente,

ruby script/generate migration RemovePartNumberFromProducts part_number:string

gera

class RemovePartNumberFromProducts < ActiveRecord::Migration def self.up remove_column :products, :part_number end def self.down add_column :products, :part_number, :string end end

Você não está limitado a uma coluna gerada magicamente, por exemplo

ruby script/generate migration AddDetailsToProducts part_number:string price:decimal

irá gerar

class AddDetailsToProducts < ActiveRecord::Migration def self.up add_column :products, :part_number, :string add_column :products, :price, :decimal end def self.down remove_column :products, :price remove_column :products, :part_number end end

Como sempre, o que foi gerado para você é apenas um ponto de partida. Você pode adicionar ou remover o conteúdo gerado caso veja alguma necessidade.

3 Escrevendo uma Migration

Uma vez que você criou a sua migration usando um dos geradores, é hora de trabalhar!

3.1 Criando uma Tabela

O método create_table da migration será um dos mais usados. Um uso comum seria

create_table :products do |t| t.string :name end

que cria uma tabela products com uma coluna chamada name (e como discutido anteriormente, implicitamente criará uma coluna id).

O objeto passado para o bloco permite criar colunas na tabela. Existem duas formas de se fazer isso. A primeira (tradicional) é algo assim

create_table :products do |t| t.column :name, :string, :null => false end

a segunda forma, que é chamada de “sexy” migration, remove o método redundante column. Em vez de string, integer, etc os métodos criam uma coluna daquele tipo. Parâmetros subseqüentes são idênticos.

create_table :products do |t| t.string :name, :null => false end

Por padrão, create_table criará uma chave primária chamada id. Você pode alterar o nome da chave primária com a opção :primary_key (não esqueça de atualizar o modelo correspondente) ou se você não quiser uma chave primária (por exemplo para uma tabela join HABTM) você pode passar :id ⇒ false. Se você precisa passar alguma informação específica do banco de dados, você pode passar um fragmento SQL na opção :options. Por exemplo:

create_table :products, :options => "ENGINE=BLACKHOLE" do |t| t.string :name, :null => false end

vai concatenar ENGINE=BLACKHOLE na sql usada para criar a tabela (quando se usa MySQL por padrão é usado “ENGINE=InnoDB”).

Os tipos que o Active Record suporta são :primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean.

Eles vão ser mapeados apropriadamente para cada banco de dados, por exemplo com MySQL :string é mapeada para VARCHAR(255). Você pode criar colunas e tipos não suportados pelo Active Record usando a sintaxe não-sexy, por exemplo:

create_table :products do |t| t.column :name, 'polygon', :null => false end

Isto, no entanto, pode dificultar a portabilidade para outros bancos de dados.

3.2 Alterando Tabelas

Um primo próximo de create_table é change_table, usado para alterar tabelas existentes. É usado de um modo parecido com o create_table mas o objeto passado pelo bloco conhece mais truques. Por exemplo

change_table :products do |t| t.remove :description, :name t.string :part_number t.index :part_number t.rename :upccode, :upc_code end

remove as colunas description e name, adiciona a coluna part_number e adiciona um index nesta mesma coluna. E por ultimo altera o nome da coluna upccode. É o mesmo que fazer

remove_column :products, :description remove_column :products, :name add_column :products, :part_number, :string add_index :products, :part_number rename_column :products, :upccode, :upc_code

Você não precisa ficar repetindo o nome da tabela e de seus grupos em todas as expressões relacionadas à alteração de uma tabela em particular. Os nomes individuais de alteração são também mais curtos, por exemplo, remove_column se torna somente remove e add_index se torna index.

3.3 Helpers Especiais

O Active Record fornece alguns atalhos para as funcionalidades mais corriqueiras. Por exemplo, é muito comum adicionar as colunas created_at e updated_at e há um método que faz exatamente isso:

change_table :products do |t| t.timestamps end

vai criar uma nova tabela products com aquelas duas colunas (mais a coluna id) enquanto

change_table :products do |t| t.timestamps end

adiciona as colunas em uma tabela existente.

O outro helper é chamado de references (também disponível como belongs_to). Na sua forma mais simples, adiciona alguma legibilidade

create_table :products do |t| t.references :category end

criará uma coluna category_id do tipo apropriado. Note que você passa o nome do model, não o nome da coluna. O Active Recorde adiciona o _id pra você. Se você tem associações polimórficas belongs_to, então references irá adicionar as duas colunas necessárias:

create_table :products do |t| t.references :attachment, :polymorphic => {:default => 'Photo'} end

irá adicionar a coluna attachment_id e a coluna attachment_type do tipo string com o valor padrão ‘Photo’.

O helper references realmente não cria restrições de chave estrangeira. Você precisará usar execute para isso ou um plugin que adiciona foreign key support.

Se os helpers fornecidos pelo Active Record não forem suficientes, você pode utilizar a função execute para rodar SQL arbitrárias.

Para mais detalhes e exemplos de métodos individuais dê uma olhada na documentação da API, em particular a documentação para ActiveRecord::ConnectionAdapters::SchemaStatements (que fornece os métodos disponíveis nos métodos up e down), ActiveRecord::ConnectionAdapters::TableDefinition (que fornece os métodos disponíveis no objeto passado pelo create_table) e ActiveRecord::ConnectionAdapters::Table (que fornece os métodos disponíveis no objeto passado pelo change_table).

3.4 Escrevendo o seu Método down

O método down da sua migration deve reverter as transformações realizadas pelo método up. Em outras palavras, o schema do banco de dados deve se manter inalterado se você fizer um up seguido de um down. Por exemplo, se você criar uma tabela no método up você deverá excluí-la no método down. É uma estratégia inteligente fazer as coisas na ordem inversa do que feito no método up. Por exemplo

class ExampleMigration < ActiveRecord::Migration def self.up create_table :products do |t| t.references :category end #add a foreign key execute <<-SQL ALTER TABLE products ADD CONSTRAINT fk_products_categories FOREIGN KEY (category_id) REFERENCES categories(id) SQL add_column :users, :home_page_url, :string rename_column :users, :email, :email_address end def self.down rename_column :users, :email_address, :email remove_column :users, :home_page_url execute "ALTER TABLE products DROP FOREIGN KEY fk_products_categories" drop_table :products end end

Algmas vezes a sua migration fará algumas coisas irreversíveis. Por exemplo, ela pode apagar alguns dados. Em casos como esse em que você não pode reverter uma migration, você pode lançar IrreversibleMigration no seu método down. Se alguém tentar reverter a sua migration uma mensagem de erro será mostrada informando que a reversão não poderá ser realizada.

4 Executando Migrations

O Rails fornece um conjunto de tasks rake para trabalhar com migrations, o que se resume a rodar certos conjuntos de migrations. A primeiríssima task rake que você usará será provavelmente a db:migrate. Na sua forma mais básica, ela simplesmente executa o método up para todas as migrações que ainda não foram rodadas. Se não existirem estas migrations ainda não executadas, ela se encerra.

Note que ao rodar a task db:migrate, também será executada a db:schema:dump, que irá alterar o arquivo db/schema.rb para bater com a estrutura do seu banco de dados.

Se você especificar uma versão, o Active Record irá rodar as migrações requeridas (anteriores ou posteriores) até que ele tenha chegado nessa versão específica. A versão é o prefixo numérico do nome de uma migration. Por exemplo, para migrar para a versão 20080906120000 execute

rake db:migrate VERSION=20080906120000

Se a versão especificada for mais recente do que a versão atual (ou seja, estiver migrando para cima), o comando irá executar o método up em todas as migrations acima (posteriores) da atual, incluindo a versão 20080906120000. Se a migration for para baixo, o comando executará o método down de todas as migrations abaixo da atual, mas não incluirá a 20080906120000.

4.1 Revertendo

Uma tarefa comum é reverter a última migration se, por exemplo, você cometeu um engano e deseja corrigi-lo. Ao invés de procurar o número da versão associado com a migration anterior, você pode rodar

rake db:rollback

Isto irá rodar o método down da migration mais recente. Se você precisa desfazer várias migrations, você pode fornecer um parâmetro STEP:

rake db:rollback STEP=3

irá rodar o método down das 3 últimas migrations.

A task db:migrate:redo é um atalho para fazer uma reversão e então executar a migration novamente. Assim como na task db:rollback você pode usar o parâmetro STEP se precisar voltar mais de uma versão, por exemplo

rake db:migrate:redo STEP=3

Nenhuma dessas tasks Rake faz qualquer coisa que você não poderia fazer com db:migrate, elas são simplesmente mais convenientes, uma vez que você não precisa especificar explicitamente a versão da migration para a qual você deseja ir.

Por fim, a task db:reset irá apagar a sua base de dados, recriá-la e carregar o schema atual dela.

Isto não é o mesmo que você rodar todas as migrations – veja a seção em schema.rb.

4.2 Sendo Específico

Se você precisar rodar uma migration específica para cima ou para baixo, as tasks db:migrate:up e db:migrate:down irão fazer isso. Basta especificar a versão apropriada e a migration correspondente terá seu método up ou down invocado, por exemplo

rake db:migrate:up VERSION=20080906120000

irá rodar o método up da migration 20080906120000. Estas tasks checam se a migration foi executada, então, por exemplo, db:migrate:up VERSION=20080906120000 não irá fazer nada se o Active Record acreditar que a migration 20080906120000 já tenha sido executada.

4.3 Sendo Comunicativo

Por padrão, as migrations te dizem exatamente o que elas estão fazendo e o tempo que levaram para rodar. Uma migration criando uma tabela e adicionando um índice produz uma saída como esta

20080906170109 CreateProducts: migrating -- create_table(:products) -> 0.0021s -- add_index(:products, :name) -> 0.0026s 20080906170109 CreateProducts: migrated (0.0059s)

Vários métodos permitem que você controle tudo isto:

  • suppress_messages suprime qualquer mensagem gerada pelo bloco
  • say mostra o texto (o segundo argumento controla se é indentado ou não)
  • say_with_time mostra texto com o tempo utilizado para que o bloco rodasse. Se o bloco retornar um inteiro, assume-se que este é o número de linhas afetadas.

Por exemplo, esta migration

class CreateProducts < ActiveRecord::Migration def self.up suppress_messages do create_table :products do |t| t.string :name t.text :description t.timestamps end end say "Created a table" suppress_messages {add_index :products, :name} say "and an index!", true say_with_time 'Waiting for a while' do sleep 10 250 end end def self.down drop_table :products end end

gera a seguinte saída

20080906170109 CreateProducts: migrating Created a table -> and an index! Waiting for a while -> 10.0001s -> 250 rows 20080906170109 CreateProducts: migrated (10.0097s)

Se você quiser que o Active Record não diga nada, então execute rake db:migrate VERBOSE=false e você irá suprimir qualquer saída.

5 Usando Models Nas Suas Migrations

Ao criar ou atualizar dados em uma migration, muitas vezes é tentador utilizar um de seus models, afinal eles existem para fornecer acesso fácil aos seus dados. Isto pode ser feito, mas um certo cuidado deve ser tomado.

Considere, por exemplo, uma migration que usa o model Product para atualizar uma linha na tabela correspondente. Alice, mais tarde, atualiza o model Product, adicionando uma nova coluna e uma validação. Bob volta do feriado, atualiza o código e roda as migrations pendentes com rake db:migrate, incluindo aquela que utilizou o model Product. Quando a migration roda, o código é atualizado e o model Product possui a validação adicionada pela Alice. O banco de dados, entretanto, não permanece inalterado, não contém aquela coluna, então um erro acontece porque porque aquela validação ocorre em uma coluna que ainda não existe.

Frequentemente, eu só preciso atualizar algumas linhas no banco de dados sem escrever SQL manualmente. Eu não estou usando nada específico para o model. Um padrão para isso, é definir uma cópia do modelo dentro da própria migration, por exemplo:

class AddPartNumberToProducts < ActiveRecord::Migration class Product < ActiveRecord::Base end def self.up ... end def self.down ... end end

A migration tem a sua cópia mínima do model Product e não se importa mais com o model definido na aplicação.

5.1 Lidando com Models em Mudança

Por razões de performance, as informações sobre as colunas de um modelo são cacheadas. Por exemplo, se você adicionar uma coluna em uma tabela e tentar usar o modelo correspondente para inserir uma nova linha, ele pode usar a informação antiga da coluna. Você pode forçar o Active Record a reler a informação da coluna com o método reset_column_information. Um exemplo:

class AddPartNumberToProducts < ActiveRecord::Migration class Product < ActiveRecord::Base end def self.up add_column :product, :part_number, :string Product.reset_column_information ... end def self.down ... end end

6 Armazenando Schemas e Você

6.1 Qual a função dos arquivos de Schema?

Migrations, poderosas como devem ser, não são a fonte mais confiável para o schema do seu banco de dados. Esse papel cabe ao schema.rb ou a um arquivo SQL gerado pelo Active Record pela análise do banco de dados. Eles não foram projetados para serem editados, eles somente representam o estado atual da base de dados.

Não é necessário (e isto é um erro), para implantar uma nova versão de uma aplicação, rodar o histórico inteiro de migrations. É muito mais simples e rápido apenas carregar dentro do banco de dados uma descrição do schema atual.

Por exemplo, esta é a forma pela qual o banco de dados de testes é criado: o banco de dados atual de desenvolvimento é extraído (ou para o schema.rb ou para o development.sql) e carregado dentro do banco de dados de teste.

Arquivos de schema também são úteis se você deseja olhar rapidamente quais atributos o objeto do Active Record possui. Esta informação não está no código do model, frequentemente está espalhada pelas várias migrations, mas no arquivo de schema ela está concentrada. O plugin annotate_models, que automaticamente adiciona (e atualiza) comentários no início de cada model resumindo o schema, também pode ser interessante para este propósito.

6.2 Formas de Extração do Schema

Existem duas formas de extrair o schema. Isto está configurado no config/environment.rb pela propriedade config.active_record.schema_format, que pode ser :sql ou :ruby.

Se :ruby é selecionada, então o schema é armazenado em db/schema.rb. Se você olhar este arquivo vai perceber que ele se parece muito com uma grande migration:

ActiveRecord::Schema.define(:version => 20080906171750) do create_table "authors", :force => true do |t| t.string "name" t.datetime "created_at" t.datetime "updated_at" end create_table "products", :force => true do |t| t.string "name" t.text "description" t.datetime "created_at" t.datetime "updated_at" t.string "part_number" end end

E de certa forma é exatamente isto. Este arquivo é criado pela inspeção do banco de dados e expressa a sua estrutura usando create_table, add_index e assim por diante. Por causa da independência do banco de dados, ele pode ser carregado em qualquer banco que o Active Record suporta. Isto poderia ser muito útil se você fosse distribuir uma aplicação que seria rodada em muitas bases de dados.

Existe, porém, uma desvantagem: schema.rb não consegue expressar itens específicos dos bancos de dados, como constraints de chaves estrangeiras, triggers ou stored procedures. Enquanto numa migration você pode executar SQL customizadas, o extrator do schema não consegue reconstituir essas atribuições a partir do banco de dados. Se você está usando recursos como estes, então você deve atribuir o formato do schema para :sql.

Em vez de usar o extrator de schema do Active Record, a estrutura do banco de dados será armazenada usando uma ferramenta específica do banco de dados (pela task Rake db:structure:dump), dentro de db/#\{RAILS_ENV\}_structure.sql. Para o PostgreSQL, por exemplo, a funcionalidade pg_dump é utilizada e para o MySQL este arquivo irá conter a saída de SHOW CREATE TABLE para as várias tabelas. Carregar este schema é simplesmente uma questão de executar as declarações SQL contidas nele.

Por definição, esta será uma cópia perfeita da estrutura do banco de dados, mas isso geralmente vai impedir que se carregue o schema em um banco de dados diferente daquele usado para criá-lo.

6.3 Dumps de Schemas e Controle de Versão

Porque os dumps são uma fonte confiável para o schema do seu banco de dados, é altamente recomendado que você os mantenha no seu sistema de controle de versão.

7 Active Record e Integridade Referencial

O estilo do Active Record pede que exista lógica nos seus models e não no seu banco de dados. Alguns recursos como triggers ou constraints de chaves estrangeiras, que coloca um pouco da lógica no banco de dados, não são muito usados.

Validações como validates_uniqueness_of são uma forma pela qual os seus models podem garantir a integridade dos dados. A opção :dependent em associações permite que os models destruam automaticamente objetos filhos quando seus pais são apagados. Como tudo que funciona no nível da aplicação, estes recursos não podem garantir integridade referencial, então algumas pessoas aumentam isso usando constraints de chaves estrangeiras.

Apesar de o Active Record não fornecer qualquer ferramenta para trabalhar diretamente com estes recursos, o método execute pode ser usado para executar SQL arbitrárias. Há também uma série de plugins como redhillonrails que adicionam suporte a chaves estrangeiras ao Active Record (incluindo suporte para extrair as chaves estrangeiras no schema.rb)

8 Changelog

Lighthouse ticket