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>

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.

  http://api.rubyonrails.org/classes/ActiveRecord/Migration.html

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.