Rails. ActiveRecord::Migration. Reversible - ruby-on-rails

I read RoR guide and I don't understand the next line:
If you wish for a migration to do something that Active Record doesn't know how to reverse, you can use reversible
What does it mean "doesn't know how to reverse"? How to distinguish what Active Record can reverse and what cannot?

It's simply, there are two kinds of operations in AR migrations:
1) Rails automatically knows how to rollback (revert) those operations in migrations, for example:
def change
add_column :users, :age, :integer
end
migration means add column age, rollback means remove column age. Or create_table, reverse operation is drop_table. This operations you can put into change method in migration and rails knows what to do when rollback / reverse those migrations.
2) Rails needs to help how to process migration and rollback process, usually it is operations which somehow modifies data.
def self.up
add_column :users, :name, :string
add_column :users, :surname, :string
say_with_time 'Split username into name and surname' do
Users.select(:username).all.each do |user|
user.name = user.username.split(/ /)[0]
user.surname = user.username.split(/ /)[1]
user.save!
end
end
remove_column :users, :username
end
As you can see, this operation is quite complicated. Rails does not know how to reverse this operation, so you have to write reverse operation code in self.down:
def self.down
add_column :users, :username, :string
say_with_time 'Join name and surname into username' do
User.all.each do |user|
user.update_attributes(username: "#{user.name} #{user.surname}")
end
end
remove_column :users, :name
remove_column :users, :surname
end
and thats all...

Related

Ruby on Rails - reverse add_column, add_index migration

I have associations set up in survey model as follows
attr_accessible :name, :questions_attributes
has_many :questions
and in question model
attr_accessible :content, :survey_id, :answers_attributes
belongs_to :survey
I have a migration that adds question_id to survey table
but I came to realise that this migration wouldn't be necessary as
I have already defined their associations in the model.
Is my understanding correct?
If my understanding is correct, I would like to reverse that migration.
I have added the last 2 lines starting with remove and ran rake db:migrate
but it doesn't do anything.
def change
add_column :surveys, :question_id, :integer
add_index :surveys, :question_id
remove_column :surveys, :question_id, :integer
remove_index :surveys, :question_id
end
def up
add_column :surveys, :question_id, :integer
add_index :surveys, :question_id
end
def down
remove_index :surveys, :question_id
remove_column :surveys, :question_id
end
The up method is executed on rake db:migrate, the down method is executed on rake db:rollback
You cannot rely only on the change method, because of the order in which the rollback needs to be done(first remove index, then remove column)
edit:
Actually, you can do it with just change in Rails 3.2+
def change
add_column :surveys, :question_id, :integer
add_index :surveys, :question_id
end
As per my understanding you want to rollback the migration. If so then you can use the below syntax for that
rake db:rollback STEP=n
or
rake db:migrate:down VERSION=<version_number_of_migration>
which rollsback the particular migration
Yes its always neccessary to write a migration to add question_id in survey table
Only Declaring Associations is not enough
Your migrtaion should like this
def change
add_reference :surveys,:question ,index : true
end
At any point rollback the migration if you have changes only in your system. Dont try to rollback once these migrations are ran in different environments
To rollback rake db:migrate:rollback VERSION="timestamp of migration file"

How to merge columns in a rails table?

I have a rails table of users, with columns first_name and last_name etc.
How do i merge these two together? Or how do i create a new column called name and add data from these two columns?
Basically i need a column called name which is the concatenation of the first_name and last_name.
As Richard Brown has answered, you should create a migration if you want to save the concatenated string to the database.
rails g migration add_fullname_to_users fullname
then run an sql inside the generated migration to update all records
# mysql
User.update_all('fullname = CONCAT(first_name, " ", last_name)')
# postgre
User.update_all("fullname = (first_name || ' ' || last_name)")
But I'd suggest you just keep your current setup and just create a method called fullname in your model
# user.rb
def fullname
"{first_name} #{last_name}"
end
which is better since you have access to first_name and last_name
If you want to make sure you can rollback, you can do something like this in your migration file.
def up
add_column :your_table, :name, :string
YourClass.all.each do |person|
person.update_attributes! :name => person.first_name + " " + person.last_name
end
remove_column :your_table, :first_name
remove_column :your_table, :last_name
end
And your down method for the roll back:
def down
add_column :your_table, :first_name, :string
add_column :your_table, :last_name, :string
YourClass.all.each do |person|
person.update_attributes! :first_name => person.name.match(/\w+/)[0]
person.update_attributes! :last_name => person.name.match(/\w+/)[1]
end
remove_column :your_table, :name
end
Create a migration to add the new field:
rails g migration AddNameToTableName name
You can write a rake task to do the join.
TableName.all.each do { |row| row.update_attributes(name: "#{row.first_name} #{row.last_name}") }
You can execute that from the rails console, but I like repeatable database updates so I prefer a rake task.
The \w regex matcher and concatenating with spaces as in the above examples were problematic for me, although I had the opposite problem, splitting a name into first and last names. Instead, consider using what I ended up with. Back up your databases first, and test this in development!
def up # Split name into first and last
add_column :people, :first_name, :string
add_column :people, :last_name, :string
Person.all.each do |person|
person.update_column(:first_name, person.attributes["name"].rpartition(" ").first)
person.update_column(:last_name, person.attributes["name"].rpartition(" ").last)
person.save(:validate => false)
end
remove_column :people, :name
end
def down # Merge first and last into name
add_column :people, :name, :string
Person.all.each do |person|
person.update_column(:name, [person.attributes["first_name"].to_s, person.attributes["last_name"].to_s].reject(&:blank?).join(" "))
person.save(:validate => false)
end
remove_column :people, :first_name
remove_column :people, :last_name
end
This avoids inserting stray spaces in case one of the names is nil, and also splits a fullname more effectively into first and last names; specifically, by splitting on the last space in the name, since "First Middle" "Last" is more of a human-compatible split than "First" "Middle Last". See examples:
# Before split
> puts p.name.inspect
"Prince"
"Test User"
"Jean Luc Picard"
# After split
> puts p.first_name.inspect + " " + p.last_name.inspect
"" "Prince"
"Test" "User"
"Jean Luc" "Picard"
My main complaint is that "Prince" should probably be a first name not a last name, but then again depending on your database constraints it might need to go into the last_name field anyway.
In my rails 4 app with a User model I generated my migration file
rails g migration RemoveFirstLastNameColumnsFromUser
Then I put in
class RemoveFirstLastNameColumnsFromUser < ActiveRecord::Migration
def up
add_column :users, :name, :string
User.all.each do |u|
u.update! name: "#{u.first_name} #{u.last_name}"
end
remove_column :users, :first_name
remove_column :users, :last_name
end
def down
add_column :users, :first_name, :string
add_column :users, :last_name, :string
User.all.each do |u|
n = u.name
u.update! last_name: n.slice!(/\w+\z/)
u.update! first_name: n.strip
end
remove_column :users, :name, :string
end
end
This #down method will account for names with more than one space.

rename_column deletes all content of the columns

Hi I have got a rails migration problem:
When I run a migration like this:
class RenameColumn < ActiveRecord::Migration
def change
rename_column :users, :hotel_stars, :rating_stars
rename_column :users, :restaurant_stars, :price_stars
end
end
and do a rake db:migrate it works fine. The columns are renamed and the data of those columns is in there. But when I then do a rake db:drop, create, migrate the columns are renamed and the data of those columns is gone... (One of my migration files fills the data base and it also fills those two columns before they get renamed)
What's the problem here?
Another question: I know its not a good idea to change former migration files, but is that ok when I run rake db:drop db:create and db:migrate afterwards - or will that cause problems?
The order of my migration files is the following:
add_devise_to_users -> creates a table users
add_columns_to_default_user -> adds columns and updates the whole table
class AddColumnsToDefaultUser < ActiveRecord::Migration
def change
add_column :users, :name, :string
add_column :users, :dob, :date
add_column :users, :address, :string
add_column :users, :hotel_stars, :integer
add_column :users, :restaurant_stars, :integer
add_column :users, :profile_picture_url, :string
add_column :users, :selected_car, :integer
User.reset_column_information
User.find(1).update_attributes!( :name => 'Alexander MacDonald', :dob => '1984-08-20', :address => '900 Highschool Way, Mountain View, CA 94041', :hotel_stars => '3', :restaurant_stars => '2', :profile_picture_url => 'user1.png', :selected_car => 1)
end
end
and then rename_column
class RenameColumn < ActiveRecord::Migration
def change
rename_column :users, :hotel_stars, :rating_stars
rename_column :users, :restaurant_stars, :price_stars
end
end
When you do db:drop you're erasing your DB.
So, when you do that the data is wiped. That data isn't going into a temporary place to be re-inserted. If you want to retain the data that's in the DB before/after a db:drop you need to store it somewhere and re-insert it yourself.
When you're changing structure only, unless you have a specific reason to remove the data that's in there already, all you need to do is the db:migrate.
Alternatively you can use something like populator in order to empty/fill you DB with test data following a migration.

Alter existing model with a Redmine plugin

The Redmine plugin tutorials explain how to wrap core models but what I need is to add another column to the journals table.
I need a boolean field inserted in the journals model. Creating another model with a 'belongs_to :journal' relation seems like an overkill.
Can this be done with a plugin?
I should note that I am a rails newbie.
You just have to create the appropriate migration.
In your plugin's directory, create the file db/migrate/update_journal.rb with the following :
class UpdateJournal < ActiveRecord::Migration
def self.up
change_table :journal do |t|
t.column :my_bool, :boolean
end
end
def self.down
change_table :journal do |t|
t.remove :my_bool
end
end
end
Then you can execute the task rake db:migrate_plugins RAILS_ENV=production to update your database with the new field.
After executing the migration, your journal database will have the my_bool field that you'll be able to call like every other field.
I was able to extend the existing user model using the following code:
class UpdateUsers < ActiveRecord::Migration
def up
add_column :users, :your_new_column, :string, :default => ''
add_column :users, :your_other_new_column, :string, :default => ''
end
def down
remove_column :users, :your_new_column
remove_column :users, :your_other_new_column
end
end
Also I needed to name the migration file in way that it began with a number eg. myplugin/db/migrate/001_update_user.rb

authlogic rails help

Im trying to set up my app to work with authlogic.. the thing is, other than the fields authlogic is supposed to use I want to keep in my database other attributes
Like Name, Last Name, PIN, etc... is there a way to do this?
You say "keep" - do you mean you have an existing database of users and you want to keep this info as you migrate to AuthLogic, or do you mean you just want to store this additional info?
Either way is possible but I'm going to assume you mean that you just want to store additional information - all you have to do is script/generate migration AddFieldsToUser then edit the migration:
class AddFieldsToUser < ActiveRecord::Migration
def self.up
add_column :users, :name, :string
add_column :users, :last_name, :string
add_column :users, :pin, :integer
end
def self.down
remove_column :users, :name
remove_column :users, :last_name
remove_column :users, :pin
end
end
Then run rake db:migrate

Resources