Rails migration. Cast integer values into boolean with change_column - ruby-on-rails

I'm using RoR5 with PostgreSQL. I have a table with a column status. Which is of a type integer and holds three values 0, 1, 2. Those values represent three statuses allowed, not_allowed and no_tests.
I'm gonna change the logic. I want to convert two statuses allowed and not_allowed into boolean. Then I'll create a separate column for the no_tests.
Right now I have this accordance:
enum status: %i[allowed not_allowed no_tests].
How should I write a migration to have allowed as true and both not_allowed and no_tests as false in the changed column?

Actually this is two separate operations (change table structure, convert existing data), only one of would typically be done in a migration. If I were you I would first run a migration to add the new status column, and then do an update either in sql if you have easy access to the postgres console or at the rails console to recode the existing data in your new column. After you've recoded the data you can drop the old columns in another migration.

I think you will need some stuff in up method within migration.
def up
add_column :table_name, :status2, :boolean
Loop on each element of model check for status and update status2
remove_column :table_name, :status
rename_column :table_name, :status2, :status
end
You can also go with case statement in update here It will help. You can put your query in migration as well something similar to here.
You can also do one more thing like :
1. Make migration to add new column.
2. Make rake task to populate data.
3. Make one more migration to delete old column and rename newly added column.
Update : For better understanding on migration do read this article.

Related

Backfill default values to a column through migration

I have a table(Users). In one of its columns(configs) i added a default value ("A"=>0) through a migration. Now all the new users i create have default value of A but the old users don't. I want to backfill the default value of A for the old users using migration. How do I do that?
given:
t.jsonb "configs", default: {"B"=>7, "C"=>10, "D"=>10}
This is my existing column. Here B, C and D have different values for different Users. I want to make it into
t.jsonb "configs", default: {"B"=>7, "C"=>10, "D"=>10, "A"=>0}
where the values of B, C and D stays the same for all Users but just the default value of "A" gets added to the existing json in the column.
rails - 4.2.11
db - postgres
I have gone through some documentations but couldn't find a comprehensive answer. Any help is appreciated.
From your comments is sounds like you want to update a JSONB column to have a new set of defaults, and any existing json hashes get the new key/value pair of "A": 0 added to the current value. A migration can change the DB but you will need to do it programmatically to update the rows that have values already, especially because their values are not all the same. With that said it could be something like:
User.all.each do |u|
u.configs["A"] = 0
u.save
end
This will iterate through all of the users and set the value of "a" to zero. If no "a" exists in the hash it will add it with the value of zero without touching anything else in the JSON. If "a" already exists for a user it will be set to zero. So if you have users whose value for "a" has already changed from the default of zero you can avoid them with:
User.all.each do |u|
unless conifigs["A"] # if "A" already exists skip this
u.configs["A"] = 0
u.save
end
end
Please read https://nandovieira.com/using-postgresql-and-jsonb-with-ruby-on-rails for information on how to leverage JSONB in Rails. It is a very powerful tool if you put in the code to really get the most use out of it. Be sure and read the part about store_accessor, it would help you to do a lot more with that JSONB column.

Rails: Effects of changing the data type of an existing column with existing data

I could be asking this in the wrong place so go easy and point me in the right direction if I am.
I can't get my head around how changing the data type of an existing column in an existing table with existing data in Rails will effect any app I am working on.
If I have a boolean column called football. The football can be either true or false. Either, I have a football or I don't. I realise that, for example, the football can sometimes be loaned. So, I want to change the data type of the column football to be a string. The value of the string can be true, false or loaned.
How bad a time am I going to have after running the migration for this? What will happen to my existing data? What do I need to do to mitigate against messing up all my existing data? And, am I asking this question in the right place?
If you change the column type from boolean to string in rails then you have no worry because the existing data will automatically change into string. Like if you have boolean true this will automatically convert into string "true".
FYI, I checked this on my system ;)
If I were you I would do this by creating a new column, then updating everything from the old column, and then removing the old column, and renaming the new one. Also I wouldn't save a boolean as "true" or "false" either (which Rails should give you by default if you DO just change the type)... If I were you I would create an enum for this column.
First your migration:
class ChangeFootball < ActiveRecord::Migration
def change
# Add the new column. Use an integer type, so you can set up an Enum in your model
add_column :examples, :football_new, :integer, default: 0
# Set up the new column values for all your existing records:
Example.where(football: true).update_all football_new: 0
Example.where(football: false).update_all football_new: 1
# Now remove the old column:
remove_column :examples, :football
# Finally, rename the new column to the same as the old one
rename_column :examples, :football_new, :football
# Oh, and add an index:
add_index :examples, :football
end
end
Now set up the enum in your model:
enum football: { own: 0, lost: 1, misplaced: 2, loaned: 3 }

MultiPolygon column by migration

I'm trying to change my polygon column type to a multipolygon column type.
My code is a simple line.
change_column :messages, :area_shape, :multipolygon, srid: 3785
But Postgres doesn't know this type. I thought that I missed something on my PostGIS configuration but I can't see it.
This my exact error:
rake aborted!
PG::UndefinedObject: ERROR: type "multipolygon" does not exist
: ALTER TABLE "messages" ALTER COLUMN "area_shape" TYPE multipolygon
This is how I've created my area_shape as a polygon type:
add_column :messages, :area_shape, :polygon, srid: 3785
Thank you for your help.
I have no idea how RGeo attempts to implement change_column (is there documentation for it?), but it isn't correct since there is no such multipolygon type.
If you have direct access to PostgreSQL, following from this answer, use this DDL:
ALTER TABLE my_table
ALTER COLUMN area_shape TYPE geometry(MultiPolygon,3785)
USING ST_Multi(area_shape);
Finally I had to remove then recreate my column:
remove_column :messages, :area_shape
add_column :messages, :area_shape, :multi_polygon, srid: 3785
I think I can now understand why it's not possible. Indeed, it seems difficult to change a polygon type to a multi_polygon type without losing data logique.
If you really need to change the type, you can use what was said by #Mike (manually) and create a small method to convert polygon to multi_polygon but it's not really safe in my mind.
Tip: a multi_polygon type is an Enumerable that means multi_polygon accepts Array type.

activerecord (rails) migration -- set option values for a column

I'm using the rgeo gem in conjunction with the activerecord-postgis-adapter gem for some columns, and I'm wanting the change an option for an existing column
Allow me to show it this way, in the schema.rb file, currently this is the line recording the column
t.spatial "shape", :limit => {:srid=>0, :type=>"multi_polygon"}
So this column has some special metatag options recognized by postgis.
I want to set the default srid of these :shape columns from 0 to 4326. I'd either like to do this with a migration, or even better, to be able to set the values for these points individually. Currently, I haven't located a setter method in the RGeo documentation for the :srid tag, only a reader. So I'm thinking my best bet is a migration.
I've tried this:
change_column :parcels, :shape, :srid, 4326
But got this error:
== AddSridOptToShapes: migrating =============================================
-- change_column(:zones, :shape, :srid, 4326)
rake aborted!
An error has occurred, this and all later migrations canceled:
can't convert Symbol into Integer
I know that its possible to solve this problem by executing a string of SQL, but am hoping active record provides a way as well
I solved this problem by simply creating a new column, and in my case, on for every projection I needed to use with a particular shapefile record
:proj_shape_0
:proj_shape_3361
:proj_shape_2264
etc where the number is the srid code

How to assign my own auto-incrementing field?

My application needs to automatically assign a number (starting at 1001) to records of a certain type (think of something like a check register). The number should be displayed as part of the input screen, but of course not modifiable.
How do I do this in Rails?
Note: this has nothing to do with the id field.
As I know you can only set one auto_increment column to a table and that needs to be defined as the primary keytoo. So this isnt an option for you because the id field is the primary key of your table.
So you need to do this application internal. Just add an integer column to the table:
add_column :table_name, :column_name, :integer
Then add a method to your model that figures out the value of the column in the last dataset created.
def self.last_<column_name>_used
return last.<column_name> unless last.nil?
return 1000
end
def self.next_<column_name>_to_use
last_<column_name>_used+1
end
Then you can use filter to automaticly assign the value of Model.next__to_use to the which should be incremended and you can also use it to get the value that should be displayed in the form. The form can easyly be disabled using jQuery.
$('#model_<column_name>').attr("disabled", true);
Hope this is what you needed!

Resources