Update a column, all rows - ruby-on-rails

I added a new column to my table but I forgot to add the :default option. Now I want to populate that column on every single row.
Is there a way to do with using the console? I've been searching google for the past hour but I can't find anything.
I know how to do it for a single object, but not for all rows of a model.
Foo.find(1).update_attribute(:myattribute, 'value')

Try this:
Foo.update_all(some_column: "bar")
This will generate SQL query to database:
UPDATE "foos" SET "some_column" = "bar";

Since you already created the new field in a previous migration, create a brand new migration:
rails g migration UpdateFoos
Modify the migration:
def self.up
say_with_time "Updating foos..." do
Foo.find(:all).each do |f|
f.update_attribute :myattribute, 'value'
end
end
end
# from command line
Rake db:migrate
Let me know if this works, it might need a few adjustments. See rails docs for more:

you can do like this:
Foo.update_all(new_column: "bar")

Of course you can use smth like Foo.update_all(:myattribute => "value"), but it'll modify only already created data. To set default value for all "future" data it's a good way to create a separate migration like this:
rails generate migration AddDefaultValueToFoo
Modify new migration (for ex. myattribute has a string type) like this:
class AddDefaultValueToFoo < ActiveRecord::Migration
def self.up
change_column :foos, :myattribute, :string, :default => "value"
Foo.update_all(:myattribute => "value")
end
end

Related

How to add a new field to database (with default value)?

I’ve started learning Ruby on Rails recently. I did a blogger tutorial. I want to add one more field archive to my database, but not sure is it possible just to write manually and which command to call?
Here is my database where I want to add code:
class CreateArticles < ActiveRecord::Migration[6.1]
def change
create_table :articles do |t|
t.string :title
t.text :body
t.timestamps
end
end
end
And I want to add a new field archive which is boolean and by default false?
Also is it okay to add in this schema new field or is it better in some other?
1.Run the migration from command line to add the new column
$ rails g migration add_column_archive_to_articles archive:boolean
The above command will create a new migration file in your db/migrate folder.
2.As of now there's no option to add default value to a column, which can be defined through terminal. Set the new column value to true/false by editing the new migration file created. Your migration file will look like this.
class AddColumnArchiveToArticles < ActiveRecord::Migration
def change
add_column :articles, :archive, :boolean, default: false
end
end
3.Then do
$ rails db:migrate
Migrations should never be changed afterwords. That is why they are migrations, and not a static schema definitions.
Just generate a new migration using rails g migration AddArchiveToArticles and then check the rails documentation for add_column to see how you can alter a table to add a column. It also supports default values :)

How to make migration to update table RAILS

I am stuck with making migration which will update my table Users. I need to set country_code with 1 everywhere where i have "" or NULL for that column.
Thanks
class UpdateCountryCodeColumnUsers < ActiveRecord::Migration
def up
execute %Q(
UPDATE users
SET country_code = 1
WHERE country_code IS NULL OR country_code = ""
)
end
end
You probably shouldn't alter the data in a migration and only use it to alter the schema.
A lot of devs use rake db:reset which won't run this migration.
A better solution is to create a rake or thor task as a one off or simply just execute the SQL.
Sergei's answer is your best bet and will update the data (which would be needed first - and is very important) and it lets the database do the work. If you also need to set the default for going forward (after the update Sergei proposed) you can make a separate migration (to separate activities) and include the below...
You can also use the rails migration helper method change_column_default
change_column_default :users, :country_code, from: nil, to: 1
If you want to make it reversible just use change_column...
def up
change_column :users, :country_code, :string, default: 1
end
def down
change_column :users, :country_code, :string, default: nil
end
def UpdateCountryCodeForUsers < ActiveRecord::Migration
def up
Users.where("country_code = '' or country_code = NULL")
.update_attributes({country_code: 1})
end
end
On rails 5.2 , I needed to update some text attributes in a column & had to use update_all instead of update_attributes.
class UpdateTableColumn < ActiveRecord::Migration[5.2]
def up
TableName.where("my_column = 'my column old value'")
.update_all({keywords: 'my column new value'})
end
end

renaming a column in Rails

Is it possible to rename a column using a command like:
script/generate migration AddColumnToTable column:type
? Thanks.
Rails does have a migration command on the ActiveRecord ConnectionAdapter called rename_column. You can generate a migration and then write the code yourself. example (MySQL):
script/generate migration rename_my_column_by_hand
Then edit the file it creates:
class RenameMyColumnByHand < ActiveRecord::Migration
def self.up
rename_column :my_table, :old_name, :new_name
end
def self.down
rename_column :my_table, :new_name, :old_name
end
end
It executes SQL like:
ALTER TABLE my_table CHANGE old_name new_name BIGINT;
Note This only renames the column, it won't rename any references you have to it on other tables.
Great question. The answer is, unfortunately, no. See Rails 2.3.5 source code:
lib/rails_generator/generators/components/migration/migration_generator.rb
The only keywords that are recognized by the migration generator are add, remove, and to/from.
I use a bit of trickery here. Say I want to change column foo to bar.
Create a migration with the following steps
Add a temporary column temp_foo
Update all records, saving foo's value in temp_foo
Add a bar column
Update all records, saving temp_foo's value in bar
Drop column foo
Drop column temp_foo
This is ex-tre-me-ly brittle. If one step fails, you might loose data..

Migrating Data in a Rails Migration

Let's say you have "lineitems" and you used to define a "make" off of a line_item.
Eventually you realize that a make should probably be on its own model, so you create a Make model.
You then want to remove the make column off of the line_items table but for every line_item with a make you want to find_or_create_by(line_item.make).
How would I effectively do this in a rails migration? I'm pretty sure I can just run some simple find_or_create_by for each line_item but I'm worried about fallback support so I was just posting this here for any tips/advice/right direction.
Thanks!
I guess you should check that the Make.count is equal to the total unique makes in lineitems before removing the column, and raise an error if it does not. As migrations are transactional, if it blows up, the schema isn't changed and the migration isn't marked as executed. Therefore, you could do something like this:
class CreateMakesAndMigrateFromLineItems < ActiveRecord::Migration
def self.up
create_table :makes do |t|
t.string :name
…
t.timestamps
end
makes = LineItem.all.collect(:&make).uniq
makes.each { |make| Make.find_or_create_by_name make }
Make.count == makes.length ? remove_column(:line_items, :make) : raise "Boom!"
end
def self.down
# You'll want to put logic here to take you back to how things were before. Just in case!
drop_table :makes
add_column :line_items, :make
end
end
You can put regular ruby code in your migration, so you can create the new table, run some code across the old model moving the data into the new model, and then delete the columns from the original model. This is even reversible so your migration will still work in both directions.
So for your situation, create the Make table and add a make_id to the lineitem. Then for each line item, find_or_create with the make column on lineitem, setting the returned id to the new make_id on lineitem. When you are done remove the old make column from the lineitem table.

Add Rows on Migrations

I'd like to know which is the preferred way to add records to a database table in a Rails Migration. I've read on Ola Bini's book (Jruby on Rails) that he does something like this:
class CreateProductCategories < ActiveRecord::Migration
#defines the AR class
class ProductType < ActiveRecord::Base; end
def self.up
#CREATE THE TABLES...
load_data
end
def self.load_data
#Use AR object to create default data
ProductType.create(:name => "type")
end
end
This is nice and clean but for some reason, doesn't work on the lasts versions of rails...
The question is, how do you populate the database with default data (like users or something)?
Thanks!
The Rails API documentation for migrations shows a simpler way to achieve this.
http://api.rubyonrails.org/classes/ActiveRecord/Migration.html
class CreateProductCategories < ActiveRecord::Migration
def self.up
create_table "product_categories" do |t|
t.string name
# etc.
end
# Now populate the category list with default data
ProductCategory.create :name => 'Books', ...
ProductCategory.create :name => 'Games', ... # Etc.
# The "down" method takes care of the data because it
# drops the whole table.
end
def self.down
drop_table "product_categories"
end
end
Tested on Rails 2.3.0, but this should work for many earlier versions too.
You could use fixtures for that. It means having a yaml file somewhere with the data you want to insert.
Here is a changeset I committed for this in one of my app:
db/migrate/004_load_profiles.rb
require 'active_record/fixtures'
class LoadProfiles < ActiveRecord::Migration
def self.up
down()
directory = File.join(File.dirname(__FILE__), "init_data")
Fixtures.create_fixtures(directory, "profiles")
end
def self.down
Profile.delete_all
end
end
db/migrate/init_data/profiles.yaml
admin:
name: Admin
value: 1
normal:
name: Normal user
value: 2
You could also define in your seeds.rb file, for instance:
Grid.create :ref_code => 'one' , :name => 'Grade Única'
and after run:
rake db:seed
your migrations have access to all your models, so you shouldn't be creating a class inside the migration.
I am using the latest rails, and I can confirm that the example you posted definitely OUGHT to work.
However, migrations are a special beast. As long as you are clear, I don't see anything wrong with an ActiveRecord::Base.connection.execute("INSERT INTO product_types (name) VALUES ('type1'), ('type2')").
The advantage to this is, you can easily generate it by using some kind of GUI or web front-end to populate your starting data, and then doing a mysqldump -uroot database_name.product_types.
Whatever makes things easiest for the kind of person who's going to be executing your migrations and maintaining the product.
You should really not use
ProductType.create
in your migrations.
I have done similar but in the long run they are not guaranteed to work.
When you run the migration the model class you are using is the one at the time you run the migration, not the one at the time you created the migration. You will have to be sure you never change your model in such a way to stop you migration from running.
You are much better off running SQL for example:
[{name: 'Type', ..}, .. ].each do |type|
execute("INSERT INTO product_types (name) VALUES ('#{type[:name]} .. )
end

Resources