Rails migration to modify some values not working - ruby-on-rails

I've written a migration to clean up some items in the database rails g migration FixSourceNames, which generates the migration:
class FixSourceNames < ActiveRecord::Migration[7.0]
def change
end
end
I modify it:
class FixSourceNames < ActiveRecord::Migration[7.0]
def self.up
Dataset.where(source: "U.S. Census Bureau American Community Survey").update_all(source: "US Census Bureau American Community Survey")
Dataset.where(source: "US Census - American Community Survey").update_all(source: "US Census Bureau American Community Survey")
Dataset.where(source: "US Census Bureau - American Community Survey").update_all(source: "US Census Bureau American Community Survey")
Dataset.where(source: "HUD Office of Policy Development & Research").update_all(source: "HUD")
Dataset.where(source: "CDC - Behavioral Risk Factors Survey").update_all(source: "CDC")
Dataset.where(source: "County Health Rankings").update_all(source: "Robert Wood Johnson Foundation/University of Wisconsin")
end
def self.down
end
end
When I run rake db:migrate I see that the migration runs without incident:
% rake db:migrate
== 20221216160630 FixSourceNames: migrating ===================================
== 20221216160630 FixSourceNames: migrated (0.1593s) ==========================
but none of the values in my data change. If I go into the console to run the commands individually though things get cleaned up.
What am I doing wrong here?
I've also tried something like rake db:migrate VERSION=20221216160630 but still no joy. I had also just tried to modify the default migration:
class FixSourceNames < ActiveRecord::Migration[7.0]
def change
Dataset.where(source: "U.S. Census Bureau American Community Survey").update_all(source: "US Census Bureau American Community Survey")
...
end
end
but that also didn't work.

Another way that can you achieve what are you looking for, it's running a migration executing the SQL query for update each of datasets. Something like this:
class FixSourceNames < ActiveRecord::Migration[7.0]
def self.up
execute <<-SQL
UPDATE datasets SET source = 'US Census Bureau American Community Survey' WHERE source = 'U.S. Census Bureau American Community Survey';
UPDATE datasets SET source = 'US Census Bureau American Community Survey' WHERE source = 'US Census - American Community Survey';
UPDATE datasets SET source = 'US Census Bureau American Community Survey' WHERE source = 'US Census Bureau - American Community Survey';
etc
SQL
end
def self.down
end
end
I think that it's a valid way to do it, give it a try

Remember that you can use ruby code inside a migration. Use a hash to store the values, the key value for the where clause and the value for the new data to be updated.
class FixSourceNames < ActiveRecord::Migration[7.0]
def values
{
'U.S. Census Bureau American Community Survey' => 'US Census Bureau American Community Survey',
'US Census - American Community Survey' => 'US Census Bureau American Community Survey'
}.freeze
end
def self.up
values.map {|k,v| Dataset.where(source: k).update_all(source: v)}
end
def self.down
end
end

Related

I want to put string and array data in sqlite3 in ruby on rails

Hi I'm new at programming but I try to make my own service
I'm using cloud9 Ide, ruby on rails and sqlite3
Anyway I'll parse data of a dictionary and I'm about to design database like this(just for example)
[col-1] [col-2] [col-3]
[row-1] fruit apple [a,p,p,l,e]
[row-2] fruit kiwi [k,i,w,i]
...
[row-50] flower lily [l,i,l,y]
[row-51] flower rose [r,o,s,e]
...
3 column and thousands of rows
To give you more details, when an user types "fruit" in text area I want to show word list from "apple" to "kiwi"!!
I learned about storing 'string' that only users submit like this
class CreateFans < ActiveRecord::Migration
def change
create_table :fans do |t|
t.string :username
t.timestamps null: false
end
end
end
But I really don't know how to store my own data
I want to know how to add column and rows and storing local data, not user input!
Actually, I studied yesterday reading .xlsx file and show in ruby on rails through gem 'roo' but I don't know how to use this properly in database. and I want to know there is alternatives...
Thanks for reading my question and I appreciate if you give me advices :)
You can add columns to databases with migrations.
The columns don't have to be only from user input.
for example you could have a migration...
class CreateMyWord < ActiveRecord::Migration
def change
create_table :my_words do |t|
t.string :genre
t.string :word
t.string :letters
t.timestamps null: false
end
end
end
When you define your model you specify that the attribute letters is actually an array...
class MyWord < ActiveRecord::Base
serialize :letters
end
serialize will automatically convert an array to a string representation when storing the record, and will automatically convert it back when retrieving the record.
You can then populate the table yourself in your seeds.db file which you can execute with the command rake db:seed
the seeds db would possibly look like...
my_initial_words = [
['fruit', 'apple', ['a','p','p','l','e'],
['fruit', 'kiwi', ['k','i', 'w', 'i'],
...
]
my_iniital_words.each do |word_data|
MyWord.create(genre: word_data[0], word: word_data[1], letters: word_data[2])
end
Note that if the letters of the word always match the word, you don't really need a column letters in the database, just have a method in the model that creates the letters array for you when you need it.
class MyWord < ActiveRecord::Base
def letters
word.split('')
end
end

migrate data in rails when deploying

I have two tables I want to populate with data - countries, and cities. The user doesn't add/update these tables. I want them to be part of a migration so when I deploy to Heroku the data is transferred too. Up until now, I've only been migrating structure (not data). Is this possible?
Yes it is possible. Populate your initial data in db/seeds.rb like this:
Country.create(name: 'Germany', population: 81831000)
Country.create(name: 'France', population: 65447374)
Country.create(name: 'Belgium', population: 10839905)
Country.create(name: 'Netherlands', population: 16680000)
and do rake db:seed in production to load data from your seeds.
Tutorial.
As emaillenin said those data are seeds, anyway you can use a migration if you want, nothing difficult:
class ImportCountriesAndCities < ActiveRecord::Migration
def self.up
import_countries_and_cities
end
def self.down
remove_countries_and_cities
end
private
def self.import_countries_and_cities
..
end
def self.remove_countries_and_cities
...
end
end

Rails: change database model on production enviroment

i need to convert some data, but not sure how to do it.
I have a professional model, which had an foreign key. We decided that wasn't enough and changed the "has many" to a HABTM model, but now in the production environment, i need to convert the data from the foo_id field to the professional_foo joint table.
The "add table" migration will be executed before the "drop column" one, but how should i set up a conversion knowing that i have databases in use that use the old form and i will have new setups of the system that will be made straight to the last code version and because that, wont need any conversion to be done. (initializes scripts on newest version are already fixed.
I recommend doing the conversion of data in the migration between the add table and the drop column. This migration should be run when the new code is deployed so the new code will work with the new data structure and new installations that don't have any data yet will just run the migration very fast since there won't be data to convert.
The migration would look something like:
class OldProfessional < ActiveRecord::Base
self.table_name = "professionals"
has_many :foos
end
class NewProfessional < ActiveRecord::Base
self.table_name = "professionals"
has_and_belongs_to_many :foos
end
class MigrateFoosToHasAndBelongsToMany < ActiveRecord::Migration
def up
OldProfessional.all.each do |old_pro|
new_pro = NewProfessinoal.find(old_pro.id)
old_pro.foos.each do |foo|
new_pro.foos << foo
end
new_pro.save!
end
end
def down
NewProfessional.all.each do |new_pro|
old_pro = OldProfessional.find(new_pro.id)
new_pro.foos.each do |foo|
old_pro.foos << foo
end
old_pro.save!
end
end
end

add a database column with Rails migration and populate it based on another column

I'm writing a migration to add a column to a table. The value of the column is dependent on the value of two more existing columns. What is the best/fastest way to do this?
Currently I have this but not sure if it's the best way since the groups table is can be very large.
class AddColorToGroup < ActiveRecord::Migration
def self.up
add_column :groups, :color, :string
Groups = Group.all.each do |g|
c = "red" if g.is_active && is_live
c = "green" if g.is_active
c = "orange"
g.update_attribute(:type, c)
end
end
def self.down
end
end
It's generally a bad idea to reference your models from your migrations like this. The problem is that the migrations run in order and change the database state as they go, but your models are not versioned at all. There's no guarantee that the model as it existed when the migration was written will still be compatible with the migration code in the future.
For example, if you change the behavior of the is_active or is_live attributes in the future, then this migration might break. This older migration is going to run first, against the new model code, and may fail. In your basic example here, it might not crop up, but this has burned me in deployment before when fields were added and validations couldn't run (I know your code is skipping validations, but in general this is a concern).
My favorite solution to this is to do all migrations of this sort using plain SQL. It looks like you've already considered that, so I'm going to assume you already know what to do there.
Another option, if you have some hairy business logic or just want the code to look more Railsy, is to include a basic version of the model as it exists when the migration is written in the migration file itself. For example, you could put this class in the migration file:
class Group < ActiveRecord::Base
end
In your case, that alone is probably sufficient to guarantee that the model will not break. Assuming active and live are boolean fields in the table at this time (and thus would be whenever this migration was run in the future), you won't need any more code at all. If you had more complex business logic, you could include it in this migration-specific version of model.
You might even consider copying whole methods from your model into the migration version. If you do that, bear in mind that you shouldn't reference any external models or libraries in your app from there, either, if there's any chance that they will change in the future. This includes gems and even possibly some core Ruby/Rails classes, because API-breaking changes in gems are very common (I'm looking at you, Rails 3.0, 3.1, and 3.2!).
I would highly suggest doing three total queries instead. Always leverage the database vs. looping over a bunch of items in an array. I would think something like this could work.
For the purposes of writing this, I'll assume is_active checks a field active where 1 is active. I'll assume live is the same as well.
Rails 3 approach
class AddColorToGroup < ActiveRecord::Migration
def self.up
add_column :groups, :color, :string
Group.where(active: 1, live: 1).update_all(type: "red")
Group.where(active: 1, live: 0).update_all(type: "green")
Group.where(active: 0, live: 0).update_all(type: "orange")
end
end
Feel free to review the documentation of update_all here.
Rails 2.x approach
class AddColorToGroup < ActiveRecord::Migration
def self.up
add_column :groups, :color, :string
Group.update_all("type = red", "active = 1 AND live = 1")
Group.update_all("type = red", "active = 1 AND live = 0")
Group.update_all("type = red", "active = 0 AND live = 0")
end
end
Rails 2 documentation
I would do this in a
after_create
# or
after_save
in your ActiveRecord model:
class Group < ActiveRecord::Base
attr_accessor :color
after_create :add_color
private
def add_color
self.color = #the color (wherever you get it from)
end
end
or in the migration you'd probably have to do some SQL like this:
execute('update groups set color = <another column>')
Here is an example in the Rails guides:
http://guides.rubyonrails.org/migrations.html#using-the-up-down-methods
In a similar situation I ended up adding the column using add_column and then using direct SQL to update the value of the column. I used direct SQL and not the model per Jim Stewart's answer, since then it doesn't depend on the current state of the model vs. the current state of the table based on migrations being run.
class AddColorToGroup < ActiveRecord::Migration
def up
add_column :groups, :color, :string
execute "update groups set color = case when is_active and is_live then 'red' when is_active then 'green' else 'orange' end"
end
def down
remove_column :groups, :color
end
end

Rails migration and column change

working with sqlite3 for local dev. Prod DB is MySql.
Have a migration file for a column change.
class ChangeDateToOrders < ActiveRecord::Migration
def self.up
change_column(:orders, :closed_date, :datetime)
end
def self.down
change_column(:orders, :closed_date, :date)
end
end
Errors out saying index name 'temp_index_altered_orders_on_closed_location_id_and_parent_company_id' on table 'altered_orders' is too long; the limit is 64 characters
Know there is a limitation on index name with sqlite, but is there a workaround for this?
EDIT
Workaround I used.
class ChangeDateToOrders < ActiveRecord::Migration
def self.up
remove_index(:orders, [:closed_location_id, :parent_company_id])
change_column(:orders, :closed_date, :datetime)
add_index(:orders, [:closed_location_id, :parent_company_id], :name => "add_index_to_orders_cli_pci")
end
def self.down
remove_index(:orders, :name => "add_index_to_orders_cli_pci")
change_column(:orders, :closed_date, :date)
add_index(:orders, [:closed_location_id, :parent_company_id])
end
end
Personally, I like my production and development environments to match as much as possible. Its helps avoid gotchas. If I were deploying MySQL I would run my development environment with MySQL too. Besides, I am also not super familiar with SQLite so this approach appeals to my lazy side - I only need to know the ins and outs of one db.
You could hack your copy of active record; Add the following
opts[:name] = opts[:name][0..63] # can't be more than 64 chars long
Around line 535 (in version 3.2.9) of $GEM_HOME/gems/activerecord-3.2.9/lib/active_record/connection_adapters/sqlite_adapter.rb
It's a hack but it might get you past a hurdle. If I had more time, I'd look in to writing a test and sending a pull request to rails core team.

Resources