Ampliando ActiveRecord::Migration y c ómo escribir en schema.rb

Como siempre, la menor chorrada se convierte en un cúmulo de lios.

He abierto la clas ActiveRecord::Migration para añadirle un par de
métodos, en concreto:

add_foreign_key y remove_foreign_key

Esto ha sido lo fácil… un par de executes ahí dentro y fuera.

( para más detalles gist:136799 · GitHub )

El lío ha venido al intentar un rake db:test:clone que pensé que cogía
el estado de la db_development y la clonaba en db_test… pero no es
así del todo… al parecer lo que hace es coger el schema.rb y volcarlo
en db_test.

El problema es que mi monkey_patch no modifica el schema.rb para
añadirle las líneas de add_foreign_key si alguna migración las ha
invocado.

Osea que el schema.rb está perfecto excepto que no ha incluido mis
llamadas a add_foreign_key.

Entonces mi pregunta es:

¿En que punto del código de Rails se vuelcan órdenes en schema.rb para
poder hacer yo lo mismo?

He rebuscado y tenemos por un lado:

Pero no veo donde accede a schema.rb

Tenemos también:

/Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/schema_dumper.rb

Que no veo que tenga documentación online.

Al parecer este SchemaDumper es el que hace el trabajo duro pero no
veo donde se le invoca…

Bueno… cualquier sugerencia es bienvenida

Saludos

f.


Fernando Guillén
Desarrollador Web Freelance

Fernando G. wrote:

¿En que punto del código de Rails se vuelcan órdenes en schema.rb para
poder hacer yo lo mismo?

Ni idea…

Bueno… cualquier sugerencia es bienvenida

Aquí van un par:

http://github.com/harukizaemon/foreign_key_migrations/tree/master

Saludos

f.

Lo propio

Que no veo que tenga documentación online.
Al parecer este SchemaDumper es el que hace el trabajo duro pero no
veo donde se le invoca…

Se invoca cuando ejecutas un db:schema:dump

El 27 de junio de 2009 01:18, Jorge Calás Lozano[email protected]
escribió:

GitHub - matthuhiggins/foreigner: Adds foreign key helpers to migrations and correctly dumps foreign keys to schema.rb
http://github.com/harukizaemon/foreign_key_migrations/tree/master

Sip Jorge… yo estuve mirando esta:

http://github.com/patientslikeme/migration_helpers/tree/

Pero en ninguno de los casos anteriores veo que modifiquen el
schema.rb…

f.


Fernando Guillén
Desarrollador Web Freelance

El 27 de junio de 2009 01:39, alarkspur[email protected] escribió:

Que no veo que tenga documentación online.

Al parecer este SchemaDumper es el que hace el trabajo duro pero no
veo donde se le invoca…

Se invoca cuando ejecutas un db:schema:dump

Muy bueno… y además esta task se invoca implícitamente cuando invocas
a db:migrate

Gracias, aquí tengo una pista.

f.


Fernando Guillén
Desarrollador Web Freelance

http://spainrb.org/fernando-guillen

2009/6/27 Fernando G. [email protected]:

objeto ‘index’ tiene todo lo que necesita:

Desarrollador Web Freelance
http://www.fernandoguillen.info
http://spainrb.org/fernando-guillen


Ror-es mailing list
[email protected]
http://lists.simplelogica.net/mailman/listinfo/ror-es

Parece que has encontrado un punto de Rails que desde luego no está
bien diseñado, o al menos no lo mejor diseñado. Lo peor: parece que en
la rama 3.0 sigue igual (aunque tiene algo más documentación, gracias
a doc-rails).

Para mí que vas a tener que hacer cambios significativos en ese
SchemaDumper para que funcione lo que quieres. Si buscas por el método
indexes lo encontrarás en cada uno de los adaptadores de bases de
datos de Rails (uno para MySQL, otro para PostgreSQL y otro para
SQLite). Cada uno hace una llamada diferente a la base de datos, y
analiza en resultado.

Por lo tanto la forma más correcta de abordar el problema sería
implementar un método “foreign_keys(table_name)” en cada adapter que
realizará la llamada correspondiente para cada base de datos (y la
implementación por defecto en el abstract adapter devolviera un array
vacio, que serviría para SQLite, que no almacena las claves foraneas).

Esa parte es sencilla. Lo que viene después (que SchemaDumper lea esos
resultados y los escriba en el schema.rb) es lo que no le veo solución
sencilla. El SchemaDumper tiene un método table, que es el que invoca
a indexes. Mi idea sería de nuevo implementar un método foreign_keys
en el SchemaDumper, y hacer un alias_method_chain (no hay otra forma)
de tables, para invocar a ese método cuando se escriba la tabla (hay
un problema que es que table imprime sus errores en el schema.rb, pero
se los “traga” en Ruby, por lo que puedes llegar a escribir tus
foreign keys y que la tabla no haya sido volcada correctamente al
schema.rb).

Es un poco feo inyectar cosas con alias_method_chain, pero es que creo
que no dejan otra.

Suerte.

PD: ¿Enviaste el correo a las 04.32 de la madrugada? Vaya, desde luego
es un problema que no te deja dormir :smiley:

Pues no estoy encontrando una salida… por lo menos no una sencilla.

Como bien dice alarkspur ( lo he escrito bien? ) a través de la task
db:schema:dump llegamos a:

ActiveRecord::SchemaDumper.dump()

Y a de aquí a ActiveRecord::SchemaDumper.new.dump() y de aquí a:

SchemaDumper#tables y de aquí a SchemaDumper#indexes:

  def indexes(table, stream)
    if (indexes = @connection.indexes(table)).any?
      add_index_statements = indexes.map do |index|
        statment_parts = [ ('add_index ' + index.table.inspect) ]
        statment_parts << index.columns.inspect
        statment_parts << (':name => ' + index.name.inspect)
        statment_parts << ':unique => true' if index.unique

        '  ' + statment_parts.join(', ')
      end

      stream.puts add_index_statements.sort.join("\n")
      stream.puts
    end
  end

Los índices normales (no claves foráneas) se imprimen en el schema.rb
con el código de arriba.

Si quiero que se impriman también los de clave foránea debería hacer
un método parecido a éste… pero éste lo tiene fácil porque en el
objeto ‘index’ tiene todo lo que necesita:

class IndexDefinition < Struct.new(:table, :name, :unique,

:columns) #:nodoc:
end

Pero yo no :/… se me ocurre que habría que ampliar esta estructura
para almacenar la tabla destino y el campo del índice… además de un
hueco para diferenciar un tipo de índice de otro… y luego mirar a ver
cómo se cargan las instancias de esta clase para cargar también mis
datos…

En fín… creo que hay demasiada gente… el tiro no está claro

Si se os ocurre algo comentarlo a ver

Saludos

f.


Fernando Guillén
Desarrollador Web Freelance

http://spainrb.org/fernando-guillen

Hola Daniel,

El 27 de junio de 2009 10:41, Daniel R.
Troitiño[email protected] escribió:

SQLite). Cada uno hace una llamada diferente a la base de datos, y
sencilla. El SchemaDumper tiene un método table, que es el que invoca
a indexes. Mi idea sería de nuevo implementar un método foreign_keys
en el SchemaDumper, y hacer un alias_method_chain (no hay otra forma)
de tables, para invocar a ese método cuando se escriba la tabla (hay
un problema que es que table imprime sus errores en el schema.rb, pero
se los “traga” en Ruby, por lo que puedes llegar a escribir tus
foreign keys y que la tabla no haya sido volcada correctamente al
schema.rb).

Es un poco feo inyectar cosas con alias_method_chain, pero es que creo
que no dejan otra.

Sipi… así exactamente es como yo lo veo también, pero me parece mucho
lío, demasiada intromisión para un asunto que quería solucionar de
manera rápida y no muy estable (ya sabes, sin tests ni cosas serias),
meterme en todo este lío no me compensa por ahora sobre todo si no
existe la posibilidad de recompensa de que estas modificaciones se
incluyan algún día en Rails ya que tienen la opinión (acertada, si me
preguntan) de no incluir nada en estos módulos que sea tan DB
dependiente como es los foreign_keys (no disponibles en sqlite por
ejemplo).

Al final me decidí por la solución de indicarle a Rails que haga el
dump en formato sql:

Use SQL instead of Active Record’s schema dumper when creating the

test database.

This is necessary if your schema can’t be completely dumped by the

schema dumper,

like if you have constraints or database-specific column types

config.active_record.schema_format = :sql

Que según el comentario parece que está hecho para mí.

Pero tampoco cambia mucho las cosas… no es que genere un dump en
formato sql… simplemente no genera ninguno:

desc “Migrate the database through scripts in db/migrate and update
db/schema.rb by invoking db:schema:dump. Target specific version with
VERSION=x. Turn off output with VERBOSE=false.”
task :migrate => :environment do
ActiveRecord::Migration.verbose = ENV[“VERBOSE”] ? ENV[“VERBOSE”]
== “true” : true
ActiveRecord::Migrator.migrate(“db/migrate/”, ENV[“VERSION”] ?
ENV[“VERSION”].to_i : nil)
Rake::Task[“db:schema:dump”].invoke if
ActiveRecord::Base.schema_format == :ruby
end

Pero no ganamos nada porque al invocar a db:test:clone se pasa por el
forro el ‘ActiveRecord::Base.schema_format’ y vuelve a usar el
dump.ruby para generar la db test:

desc "Recreate the test database from the current environment's

database schema"
task :clone => %w(db:schema:dump db:test:load)

Aunque hay una salida:

rake db:test:clone_structure

Esta sí que hace un volcado en modo sql usando el MysqlAdapter (en mi
caso) y lo usa para regenerar la db test…

En fín que no veo esto nada claro… se supone que
ActiveRecord::Base.schema_format = :sql está para utilizar sql como
sistema de volcado pero simplemente cuando pones cualquier cosa que no
sea :ruby lo que hace es no hacer volcado… en las migraciones aunque
sí en otras tasks… y usando el modo :ruby aunque le indique :sql…

Suerte.

:wink:

PD: ¿Enviaste el correo a las 04.32 de la madrugada? Vaya, desde luego
es un problema que no te deja dormir :smiley:

Más bien era el calor quien no me dejaba dormir, esto simplemente me
mantenía entretenido :slight_smile:

Gracias Daniel.

f.


Fernando Guillén
Desarrollador Web Freelance

http://spainrb.org/fernando-guillen

No he usado el dump SQL pero en principio la tarea db:test:prepare
cargaria el dump SQL si ActiveRecord::Base.schema_format es :sql.
(Vease databases.rake).

Pero de todos modos no ganarias mucho porque por ejemplo en MySQL se
dice explicitamente:

ActiveRecord::Base.connection.execute(‘SET foreign_key_checks = 0’)

El motivo es que la creacion de tablas y carga/borrado de fixtures no
estan preparados para hacer las cosas en el orden que esas constraints
necesitan.

El 28 de junio de 2009 13:51, Xavier N.[email protected] escribió:

No he usado el dump SQL pero en principio la tarea db:test:prepare
cargaria el dump SQL si ActiveRecord::Base.schema_format es :sql.
(Vease databases.rake).

No ví esto…

Sip… lo que hace es llamar al db:test:clone_structure que es el que
me gustó por que usa sql… osea que es mejor llamar siempre a
db:test:prepare :slight_smile:

Por cierto … what in the hell is this?

Rake::Task[{ :sql => “db:test:clone_structure”, :ruby =>
“db:test:load” }[ActiveRecord::Base.schema_format]].invoke

(databases.rake 378)

Estos tíos del core no escribe código hacen balet acrobático.

Veo que es lo mismo que:

options = { :sql => “db:test:clone_structure”, :ruby => “db:test:load”
}
schema_format = ActiveRecord::Base.schema_format]
rake = options[schema_format]

Rake::Task[rake].invoke

uauu… que peña :slight_smile:

Pero de todos modos no ganarias mucho porque por ejemplo en MySQL se
dice explicitamente:

 ActiveRecord::Base.connection.execute(‘SET foreign_key_checks = 0’)

El motivo es que la creacion de tablas y carga/borrado de fixtures no
estan preparados para hacer las cosas en el orden que esas constraints
necesitan.

Hombre… pero luego se vuelven a activar los foreign_key_checks = 1 en
alguna parte no?.

Está muy bien esa llamada yo lo usaba siempre cuando quería cargar un
dump… pero luego las foreign_keys se restablecen y vuelven a estar
activas.

f.


Fernando Guillén
Desarrollador Web Freelance

http://spainrb.org/fernando-guillen

El 28 de junio de 2009 15:00, Daniel R.
Troitiño[email protected] escribió:

las comprobaciones de integridad
referencial se deberían ejecutar.

Claro claro… son un fuerte atributo de nuestar aplicación, no puede
ser que en los tests estén desactivadas y activadas en producción…
cosa que ya me pasó cuando en desarrollo tenía una sqlite y en
producción una mysql… en producción petaba todo porque la aplicación
no estaba preparada para la integridad referencial y aunque los tests
pasaban estaban haciendo cosas que no deberían

:confused:

f.


Fernando Guillén
Desarrollador Web Freelance

http://spainrb.org/fernando-guillen

2009/6/28 Daniel R. Troitiño [email protected]:

Por lo que he encontrado eso se llama dentro de un método que se llama
disable_referencial_integrity en el adaptador de MySQL (y Postgre).
Ese método pone el check a 0, ejecuta el bloque argumento, y vuelve a
poner el check al valor que estaba antes. Y po lo que veo luego
únicamente se le llamada desde el create_fixtures, por lo que durante
los test, si estaba activado (supongo que se podrá configurar MySQL
para que el check sea por defecto 1) las comprobaciones de integridad
referencial se deberían ejecutar.

Aunque quizá esté equivocado.

Ahhh es verdad gracias. No era consciente de que habia cambiado esto,
viene de este commit (de 2007, ya hace año y medio):

Foxy fixtures. Adapter#disable_referential_integrity. Closes #9981. · rails/rails@49eafd8 · GitHub

Entonces parece que si generas las constraints en testing las fixtures
no serian un problema a dia de hoy.

2009/6/28 Xavier N. [email protected]:

estan preparados para hacer las cosas en el orden que esas constraints
necesitan.

Por lo que he encontrado eso se llama dentro de un método que se llama
disable_referencial_integrity en el adaptador de MySQL (y Postgre).
Ese método pone el check a 0, ejecuta el bloque argumento, y vuelve a
poner el check al valor que estaba antes. Y po lo que veo luego
únicamente se le llamada desde el create_fixtures, por lo que durante
los test, si estaba activado (supongo que se podrá configurar MySQL
para que el check sea por defecto 1) las comprobaciones de integridad
referencial se deberían ejecutar.

Aunque quizá esté equivocado.