Base value in column on id but constrained to not-null - ruby-on-rails

I want to generate a value in a column based on the database ID ActiveRecord assigns to my record. Normally, I would just add an after save callback where this value is generated and than saved to the database.
Unfortunately, I have to deal with a not-null constraint on that column, so it needs to be assigned at the same time when I get an ID. Is there a thread-safe way, to combine both?

You could write your code in transaction block, where you first find the maximum value of id column in the given table and than use that ID value to generate value of other column. Will that work for you?
ActiveRecord::Base.transaction do
id = maximum(:id)
other_column = "#{id} some string"
create(other_column: other_column)
end

Related

How to do DB migration from id to uuid?

I have table called charges which has fk of prev_charge_id
We have created new column prev_charge_uuid now want to convert prev_charge_id to prev_charge_uuid, what will be the efficient way to do it
DB columns
id name prev_charge_id prev_charge_uuid uuid
1 kv null null abcd-abcd-abcd-abcd
2 Op 1 null qbcd-aqcd-qbcd-qbcd
Have table like this
want prev_charge_uuid to get updated based on prev_charge_id
id name prev_charge_id prev_charge_uuid uuid
1 kv null null abcd-abcd-abcd-abcd
2 Op 1 abcd-abcd-abcd-abcd qbcd-aqcd-qbcd-qbcd
To do what you are requesting is not a good idea. It violates the database design principle that data should be represented in exactly one place. In your example, you could run into problems if a uuid gets changed, but the prev_charge_uuid does not.
A much better strategy is to define the method in the model:
def prev_charge_uuid
self.class.find(prev_charge_id).uuid
end
[update] I just noticed that prev_charge_id is a column in the table, so you can just use it iso calculating previous id... solves the problem that #sergio points out when there are "holes" in the id sequence.

Changing boolean column type to int

I have a column in my Postgres DB's table that's a boolean type. I want to change it to an integer because I need more than just true/false in that column.
I also want to change all the true values to 1 and the false values to 2.
Is this easily done in Rails? I was trying to do this via a migration file and migrating my DB.
Yes, you can do this change with a single migration. The only tricky part is that need to tell the database how to convert the boolean values to your integers.
The way to do this is to use a USING clause in the ALTER TABLE to provide the mapping. The raw SQL version would look like this:
alter table models
alter column c type integer
using case when c then 1 else 2 end
and that translates to a migration thusly:
def change
change_column :models, :c, :integer, :using => 'case when c then 1 else 2 end'
end
A boolean column can only contain TRUE or FALSE so that simple CASE should be sufficient. If you're allowing NULLs and want to preserve them then:
:using => 'case when c is null then null when c then 1 else 2 end'
should do the trick.
Of course, you'll have to update all your code to properly work with these new integers by hand.
Do you really need to modify the database for this? One possible solution is to just create a wrapper method that handles this for you. Let's say you have boolean column named mycol, then you could just write a wrapper method that transparently handles this logic without modifying the underlying database.
within your ActiveRecord model:
def mycol
read_attribute(:boolcol) ? 1 : 2
end
def mycol=(value)
write_attribute(:mycol, value == 1)
end
So for instance, running u.mycol = 1 && u.save would write false to the database and u.reload.mycol would return 1.
However, if it's really necessary to do a migration then create a new integer column to supercede the original boolean column. Don't remove or modify the existing column because want to make sure you're not corrupting or destroying data.
After creating the new column, create a rake task to iterate through all your existing records (use the find_each method for the iteration) and set the integer value for the new column based on the value of the original boolean column. Once you've verified the integrity of the data you can drop the original boolean column and replace it with the newly created column.

Regenerate ids of existing records

I have a Rails app with a custom algorithm for id generation for one table. Now I decided to use default incremental ids generated by Postgres as primary keys and move my custom values from id to another column like uid. And I want to regenerate values in id column for all records - as normal from 1 to n.
What is the best way to do this? I have about 1000 records. Records from other tables are associated with these records.
You can keep whatever value is in ID column but create a new column named UID and set it as a primary key and auto increment
def self.up
execute "ALTER TABLE mytable modify COLUMN uid int(8) AUTO_INCREMENT"
end
You can tell your model to use UID as primary key as
self.primary_key = 'uid'
You can simply do it by iterating on your records, and updating them (and their associated objects). 1000 records is not that much to process.
Let's say that you have a table named "my_objects", with its model named "MyObject". Let's also say that you have another table, named "related_objects" and its model "RelatedObject"
You can iterate on all your MyObjects records, and update their related objects and the record itself at the same time.
records = MyObject.all #Whatever "MyObject" you have.
i = 0
records.each do |record|
#Updating whatever associated objects the record have
record.related_objects.each do |related_object|
related_object.update_column("my_object_id", i)
end
#Updating the record itself
record.update_column("id", i)
i++
end

Reset the id field in a table in a sqlite db in Rails

Im using sqlite db in a sample rails application. From my users table, i have cleared all records using User.delete_all. But now whenever i insert a new record in the table using User.create, the id is starting at a value which is one more than the id of the last record which was there in the table. For example, if my table had 5 records and i cleared all, then when i do User.create, its starting at id 6.
Is there any way i can make the id start from 1 again ?
Thank You
Similar question : How to reset a single table in rails? . We can run the following at rails console to reset id column to 1 for a sqlite table
ActiveRecord::Base.connection.execute("DELETE from sqlite_sequence where name = '<table_name>'")
You seem to have autoincrement turned on for the id column.
Sqlite handles these values in an internal table called sqlite_sequence. You could reset the id for a particular autoincrement-enabled table by querying:
UPDATE "sqlite_sequence" SET "seq" = 0 WHERE "name" = $YOURTABLENAME
However, this is not a good idea because the autoincrement functionality is intended to be used in a way that the user does not influence its algorithm. Ideally, you should not care about the actual value of your id but consider it only as a unique identifier for a record.

Create missing auto increment attribute with rails migration

I'm writing a migration to convert a non-rails app into the right format for rails - one of the tables for some reason does not have auto increment set on the id column. Is there a quick way to turn it on while in a migration, maybe with change_column or something?
You need to execute an SQL statement.
statement = "ALTER TABLE `users` CHANGE `id` `id` SMALLINT( 5 ) UNSIGNED NOT NULL AUTO_INCREMENT"
ActiveRecord::Base.connection.execute(statement)
Note this is just an example. The final SQL statement syntax depends on the database.
If you're on postgesql, a single request won't make it. You'll need to create a new sequence in the database.
create sequence users_id_seq;
Then add the id column to your table
alter table users
add id INT UNIQUE;
Then set the default value for the id
alter table users
alter column id
set default nextval('users_id_seq');
Then populate the id column. This may be quite long if the table has many rows
update users
set id = nextval('users_id_seq');
Hope this helps postgresql users...
The Postgres answer by #jlfenaux misses out on the serial type, which does all of it for you automatically:
ALTER TABLE tbl add tbl_id serial;
More details in this related answer.

Resources