how to share variables in data migrations (up/down)

hi there,     How do we share variables in a given data migration?

For example, the code below fails to work because "statuses" don't seem to be in scope for both up and down.

class AddDefaultValuesToStatuses < ActiveRecord::Migration     statuses = [         {             'details' => 'details',         },         {             'fitting_profile' => 'fitting profile',         },         {             'live' => 'published on site',         },         {             'sold' => 'auction ended',         },     ]

  def self.up     statuses.each do |status|       status.each do |name, display_title|         @new_status = Status.new(           :name => name,           :display_title => display_title,           :created_by => 'admin',           :updated_by => 'admin'         ).save       end     end   end

  def self.down     statuses.each do |status|       status.each do |name, display_title|         @status = Status.searchlogic(           :name => name,           :display_title => display_title         )         @status.delete       end     end   end end

How do we share variables in a given data migration?

Personally, in your case I’d declare it as a constant instead of a variable at the class level.

good idea but i would still like to learn how to share variables in a given migration.

Ideas, gents? :slight_smile: thanks :slight_smile:

Gordon Yeong wrote:

good idea but i would still like to learn how to share variables in a given migration.

Ideas, gents? :slight_smile: thanks :slight_smile:

up and down are class methods. You share variables between them just like you would between any class methods in Ruby.

However, your original example is problematic. You appear to be using migrations for seed data. This is a terrible idea. Use seed.rb or seed-fu instead.

Best,

Marnen, thanks :slight_smile: Seed-fu and seed.rb - something for me to read on :smiley: cheers :slight_smile:

Why?

Because migration for definition structure and manage it.for fill data use seeds , or SQL files.

Just repeating it doesn't make it valid. Again, *why* do you think that?

Migrations are a "point in time" reference about the structure of a database.

If they alter *data* then they are binding tightly to the models - and you can no longer have a later migration that adds/removes/renames columns, because those columns will be set in the data in an earlier migration. Similarly, you can now no longer alter your models, because some migrations rely on the operation of the model at that "point in time" - if you create a model, with all its validations, field specifications, etc, and a migration uses that model. If you later try to change the model by adding or changing a validation, your migration will no longer work, as it is tightly bound to the old model.

Is that bad enough for you?

Being "decoupled" (separating things from each other, so that they're all free to change without breaking other things) is the fundamental principle of MVC - models do their thing, as do views, as do controllers - and it's made *deliberately* awkward to mix them up... because it's *bad*. So keep your migrations away from seed data, and also separate your tests from fixtures.

Of course this assumes that you're not going back and editing migrations... but you wouldn't do that, because that's bad too! :slight_smile:

Migrations are a "point in time" reference about the structure of a database.

If they alter *data* then they are binding tightly to the models - and you can no longer have a later migration that adds/removes/renames columns, because those columns will be set in the data in an earlier migration.

Huh? I can't even parse that last part - "columns set in the data in an earlier migration"?? What does that mean?

I can't see how a db column being populated with data is going to keep you from using a migration to update/change it, structure- or content-wise.

Similarly, you can now no longer alter your models, because some migrations rely on the operation of the model at that "point in time"

Likewise, sorry, that makes zero sense to me. I can't alter a model because it was altered before? What??

Maybe an example would help...

Because migration for definition structure and manage it.

Just repeating it doesn't make it valid. Again, *why* do you think that?

Migrations are a "point in time" reference about the structure of a database.

If they alter *data* then they are binding tightly to the models - and you can no longer have a later migration that adds/removes/renames columns, because those columns will be set in the data in an earlier migration. Similarly, you can now no longer alter your models, because some migrations rely on the operation of the model at that "point in time" - if you create a model, with all its validations, field specifications, etc, and a migration uses that model. If you later try to change the model by adding or changing a validation, your migration will no longer work, as it is tightly bound to the old model.

Is that bad enough for you?

So, the solution is to define a point-in-time version of your Model that is just enough for you to use to accomplish the migration (incl. seeding data).

For example, if you are only working with the new columns of a table, you can (and I'll argue you SHOULD!) define an empty class inside the migration.

class CreateGames < ActiveRecord::Migration    class Game < ActiveRecord::Base    end    def self.up      create_table :games do |t|        t.string :name        t.integer :prize        t.timestamps      end      Game.reset_column_information      Game.create(:name => "Jackpot", :prize => 1000000)      Game.create(:name => "Instant Win", :prize => 100)      Game.create(:name => "Pick 4", :prize => 5000)    end

   def self.down      drop_table :games    end end

If you have later code (and another migration) that, say, validates that prizes must be a minimum of 500, your migration can find and fix existing data, but THIS migration will still work.

This reduces the coupling to the current schema which is what ActiveRecord is going to reflect upon. If you need some of the functionality from the model, then it should be duplicated here in the migration.

-Rob

Being "decoupled" (separating things from each other, so that they're all free to change without breaking other things) is the fundamental principle of MVC - models do their thing, as do views, as do controllers - and it's made *deliberately* awkward to mix them up... because it's *bad*. So keep your migrations away from seed data, and also separate your tests from fixtures.

Of course this assumes that you're not going back and editing migrations... but you wouldn't do that, because that's bad too! :slight_smile:

-- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk@googlegroups.com. To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.

Rob Biedenharn http://agileconsultingllc.com Rob@AgileConsultingLLC.com +1 513-295-4739 Skype: rob.biedenharn

Yesterday I created this model and it's migration:

class Post < ActiveRecord::Base end

class CreatePosts < ActiveRecord::Migration def self.up    create_table :posts do |t|      t.string :summary      t.string :user_id      t.timestamps    end   # default record for Posts    Post.reset_column_information    Post.create(:name => "My First Post", :user_id => 1) end

def self.down    drop_table :posts end end

Today I decide that I need to have an "expired flag" on my posts, and it's required, so I create a new migration and edit my model: class Post < ActiveRecord::Base   validates_presence_of :expires end

class AlterPostsAddExpired < ActiveRecord::Migration def self.up    add_column :posts, :expires, :datetime end

def self.down    remove_column :posts, :expires end end

But now, when I roll back my migrations and try to roll them forward again, the "create" breaks because the "point in time" Post record *didn't* require an "expires" value (or indeed, even have a field for it!).

As Rob says, the way around this is to duplicate the model code (or at least the minimum you need to achieve your result) in the migration - but this is a smelly, non-DRY, and non-testable way of putting data in the tables (remember where I said it was "deliberately awkward" to do) - when the alternative is to use a seed or seed-fu (like, 30 seconds work), I can't believe you're making me go through these hoops to show you how bad a practice it is to put data in your migrations :-/

Similarly, you can now no longer alter your models, because some migrations rely on the operation of the model at that "point in time"

Likewise, sorry, that makes zero sense to me. I can't alter a model because it was altered before? What??

Maybe an example would help...

Yesterday I created this model and it's migration:

class Post < ActiveRecord::Base end

take that model...

class CreatePosts < ActiveRecord::Migration

and put it right here INSIDE the migration class

def self.up   create_table :posts do |t|     t.string :summary     t.string :user_id     t.timestamps   end # default record for Posts   Post.reset_column_information   Post.create(:name => "My First Post", :user_id => 1) end

def self.down   drop_table :posts end

Today I decide that I need to have an "expired flag" on my posts, and it's required, so I create a new migration and edit my model: class Post < ActiveRecord::Base validates_presence_of :expires end

class AlterPostsAddExpired < ActiveRecord::Migration def self.up   add_column :posts, :expires, :datetime end

def self.down   remove_column :posts, :expires end

But now, when I roll back my migrations and try to roll them forward again, the "create" breaks because the "point in time" Post record *didn't* require an "expires" value (or indeed, even have a field for it!).

As Rob says, the way around this is to duplicate the model code (or at least the minimum you need to achieve your result) in the migration - but this is a smelly, non-DRY, and non-testable way of putting data in the tables (remember where I said it was "deliberately awkward" to do) - when the alternative is to use a seed or seed-fu (like, 30 seconds work), I can't believe you're making me go through these hoops to show you how bad a practice it is to put data in your migrations :-/

You can have the same kind of problems in migrations that aren't trying to seed data, but just do the kind of things to change existing data that happen in the real world.

I've helped projects that got themselves into trouble with too many migrations happening between production deployments and the *set* of migrations can't even be run forward during the deploy.

-Rob

Rob Biedenharn http://agileconsultingllc.com Rob@AgileConsultingLLC.com

Similarly, you can now no longer alter your models, because some migrations rely on the operation of the model at that "point in time"

Today I decide that I need to have an "expired flag" on my posts, and it's required, so I create a new migration ...

But now, when I roll back my migrations and try to roll them forward again, ...

Well, sorry, why are you "rolling back" at all? Why not just run the new migration? I can't remember every needing to do a rollback for other than a single most recent migration (due to some obvious d'oh! screwup like a typo).

I can't believe you're making me go through these hoops to show you how bad a practice it is to put data in your migrations

This seems to be a lesson in "at some point old migrations will fail if (when) your models change enough" rather than anything to do with migrations and data.

Yes, I did agree with you that that's possible. It's just not very pleasant, or the simplest thing to do.

Well, sorry, why are you "rolling back" at all? Why not just run the new migration? I can't remember every needing to do a rollback for other than a single most recent migration (due to some obvious d'oh! screwup like a typo).

So why are you using migrations at all? And why have data in them?!

I work in a team with other developers, on code that gets deployed onto test, staging and live servers - migrations get run all the time. Of course, on my "bedroom" projects where I'm the only person doing anything on the code, I almost never re-run migrations.

I can't believe you're making me go through these hoops to show you how bad a practice it is to put data in your migrations

This seems to be a lesson in "at some point old migrations will fail if (when) your models change enough" rather than anything to do with migrations and data.

No - old migrations only fail if you access your models from them.

You asked for an example... you got one. If you choose to ignore the advice (or you think it doesn't apply to you) that's okay - it's only advice; a convention; a recommendation. Not a command :slight_smile: That's the beauty of Rails; convention over configuration. Stick to the conventions and you make everyone's life easier (including your own)... but hey ho, it's your choice.

I work in a team with other developers, on code that gets deployed onto test, staging and live servers - migrations get run all the time.

So do I -- but migrating down multiple levels and back up has never entered the picture. What circumstances require that?

You asked for an example... you got one. If you choose to ignore the advice (or you think it doesn't apply to you) that's okay - it's only advice; a convention; a recommendation.

And yet I still fail to see the evidence supporting this as a "convention", especially given that the API includes examples of using migrations to *change data*, *not* just structure.

  <http://api.rubyonrails.org/classes/ActiveRecord/Migration.html&gt;

Hence my question :slight_smile:

Hassan Schroeder wrote:

And yet I still fail to see the evidence supporting this as a "convention", especially given that the API includes examples of using migrations to *change data*, *not* just structure.

  ActiveRecord::Migration

Well... Alright - one migration creates an index, and another one removes empty tags. This is a one-time change to the data, which would be necessary as a migration but would not show up in the schema.rb file. The original comment was "Don't seed with migrations - seed with the seeding process". This is still a good idea. :slight_smile:

Just because. Sweet ${DEITY} I give up.

Thanks anyway.

well, in one of my other migrations (which was large), i populated FACT data with raw sqls. That worked really well but this time round, I wanted to use ruby all the way.