Rails migration create table w/ string based primary key - ruby-on-rails

What is the proper way to create a table in rails via a migration in which the primary key is a string instead of an int?
I've tried setting primary_key as #oldergod suggested in the answer below but baz seems to get set to an int still:
class CreateFoos < ActiveRecord::Migration
def change
create_table :foos, primary_key: 'baz' do |t|
end
end
end
UPDATE
I've since tried
class CreateFoos < ActiveRecord::Migration
def change
create_table :foos, primary_key: false do |t|
t.string :baz
end
end
end
which gets me a little closer but still missing the PRIMARY index on the column. I've tried add_index :foos, :baz, type: :primary but this generates the following error:
SQLite3::SQLException: near "primary": syntax error: CREATE primary INDEX "index_foos_on_baz" ON "foos" ("baz")/Users/kyledecot/.rvm/gems/ruby-1.9.3-p392/gems/sqlite3-1.3.8/lib/sqlite3/database.rb:91:in `initialize'
It seems like this should work after looking at http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_index_options

What's different if it's a string? See the create table doc.
create_table :foos, primary_key: 'baz' do |t|
t.column :baz, :string
end
Also note that this just sets the primary key in the table. You
additionally need to configure the primary key in the model via
self.primary_key=. Models do NOT auto-detect the primary key from
their table definition.

Related

Rails 5: How can I change an existing model's ID type to UUID?

I'm trying to change a Devise User model's id type to a uuid.
My migration looks like this:
class ChangeUserIdTypeToUuid < ActiveRecord::Migration[5.2]
def up
change_column :users, :id, :uuid
end
def down
change_column :users, :id, :integer
end
end
But when I run the migration I get an error:
== 20180909205634 ChangeUserIdTypeToUuid: migrating ===========================
-- change_column(:users, :id, :uuid)
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:
PG::DatatypeMismatch: ERROR: column "id" cannot be cast automatically to type uuid
HINT: You might need to specify "USING id::uuid".
: ALTER TABLE "users" ALTER COLUMN "id" TYPE uuid
There's a hint in there but I don't know what it's suggesting I do. It's not this:
change_column :users, :id, id::uuid
Why is the migration failing? What is the hint suggesting? How do I change the ID type to UUID?
Referring to the post this was suggested to be a duplicate of, I have managed to change the id type to uuid like so:
class ChangeUserIdTypeToUuid < ActiveRecord::Migration[5.2]
def change
add_column :users, :uuid, :uuid, default: "gen_random_uuid()", null: false
change_table :users do |t|
t.remove :id
t.rename :uuid, :id
end
execute "ALTER TABLE users ADD PRIMARY KEY (id);"
end
end
The difference from the best answer in the linked question is the way the UUID is generated. I'm using postgresql as my development db so I'm using gen_random_uuid() which is provided by the pgcrypto extension which I had previously enabled in preparation of using UUIDs in another model:
class EnablePgcryptoExtension < ActiveRecord::Migration[5.2]
def change
enable_extension 'pgcrypto'
end
end
In my case, I wanted to change the type of existing uuid fields from string to uuid, after having enabled the pgcrypto extension already. I had to specify a using: parameter like so:
def up
change_column :my_table, :uuid_field_name, :uuid, using: "uuid::uuid"
end
def down
change_column :my_table, :uuid_field_name, :string
end

Rails migration create_join_table with uuids

Newer versions of rails let you specify that tables should be created with a uuid primary key like so:
create_table :foos, id: :uuid do |t|
# ...
end
Which is great. And for a long time rails has supported creating join tables like so:
create_join_table :foos, :bars do |t|
# ...
end
Also great. Except my tables have uuid primary keys and that generates foreign key columns of type integer instead of type uuid.
Looking over the documentation for create_join_table, I can't find anything obvious to change the column type. Is it possible to use create_join_table with uuids?
Or do I have create the join table manually:
create_table :bars_foos, id: false do |t|
t.uuid :bar_id
t.uuid :foo_id
end
Within Rails 5.0 you can use an additional option column_options on the create_join_table method to specify the type of your id columns. Your migration would then look like:
create_join_table :foos, :bars, column_options: {type: :uuid} do |t|
t.index [:foo_id, :baar_id]
end
I should have looked at the code...
def create_join_table(table_1, table_2, options = {})
join_table_name = find_join_table_name(table_1, table_2, options)
column_options = options.delete(:column_options) || {}
column_options.reverse_merge!(null: false)
t1_column, t2_column = [table_1, table_2].map{ |t| t.to_s.singularize.foreign_key }
create_table(join_table_name, options.merge!(id: false)) do |td|
td.integer t1_column, column_options
td.integer t2_column, column_options
yield td if block_given?
end
end
Columns are explicitly created as integers with no means to change them. Too bad...
There is no way of creating join tables with uuids.
As pointed out in the question create_table is the only option. The best way of emulating create_join_tables with uuid is by using create_tables as follows:
Run: rails g migration CreateFoosBars bars:references foos:references
The command will produce the following output which you will need to modify
generate output
class CreateBarFoos < ActiveRecord::Migration
def change
create_table :bars_foos, id: :uuid do |t|
t.references :bars, foreign_key: true
t.references :foo, foreign_key: true
end
end
end
Change id: uuid => id: false
Add type: uuid, index: true to the end of the references
final migration
class CreateBarFoos < ActiveRecord::Migration
def change
create_table :bars_foos, id: false do |t|
t.references :bars, foreign_key: true, type: :uuid, index: true
t.references :foo, foreign_key: true, type: :uuid, index: true
end
end
end
It would be good if Rails could add extra support for different id types in create_join_table, this could even be inferred by an existing migration.
Until then hopefully these steps will achieve the same result.

Migrate postgresql database to uuid rails 3.1

I am using rails 3.1 and ruby 1.9.3,Now i want to use uuid concept in rails 3
so i did like :-
create_table :posts, :id => false do |t|
t.string :uuid, :limit => 36, :primary => true
end
ActiveRecord::Base.class_eval do
set_primary_key 'uuid'
before_create :generate_uuid
def generate_uuid
self.id = UUIDTools::UUID.random_create.to_s
end
end
This is working for new data,now i want to migrate existing data with relation.for uuid they are using datatype as string,in postgresql the data type used for primary_key and foreign key is integer ,so if i am trying to change foreign key integer to string it is throwing error.
Could you please tell me some example,how to do this.
kingston.s
First of all, to use UUIDs in ActiveRecord you need to enable the uuid-ossp extension. Create a new migration.
class EnableUuidOssp < ActiveRecord::Migration
def change
enable_extension 'uuid-ossp'
end
end
Second, you do do not need to use string type in your migrations, there is a uuid type. When creating a new table:
create_table :posts, id: :uuid do |t|
end
This will automatically generate a UUID in the same way that an incremental Integer ID is normally generated. When you want to add a UUID field to an existing table:
change_table :posts do |t|
t.uuid :uuid, default: 'uuid_generate_v4()'
end
The default: 'uuid_generate_v4()' will ensure that a new UUID is generated for you by Postgres.
Third, to actually migrate existing data I guess you would need to create migrations that 1) add UUID fields to all of the models 2) create new UUID foreign keys 3) associate the models using the UUID foreign keys 4) remove the old foreign keys 5) rename the new foreign keys:
class AddUuidToPosts < ActiveRecord::Migration
def change
change_table :posts do |t|
t.uuid :uuid, default: 'uuid_generate_v4()'
end
end
end
# assuming you have a comments table that belongs to posts
class AddUuidToComments < ActiveRecord::Migration
def change
change_table :comments do |t|
t.uuid :uuid, default: 'uuid_generate_v4()'
end
end
end
class AssociateCommentsWithPostings < ActiveRecord::Migration
def change
# Add a uuid_posting_id field to comments relate comments to postings
# through the posting UUID
change_table :comments do |t|
t.uuid :uuid_posting_id
end
# Loop through all existing postings and update all associated comments
# new foreign key field
Posting.all.each do |posting|
# Not sure about this, but you might need to touch the posting to generate
# a UUID
posting.touch
Comment.where(posting_id: posting.id).
update_all(uuid_posting_id: posting.uuid)
end
remove_column :comments, :posting_id
rename_column :comments, :uuid_posting_id, :posting_id
end
end
# You don't need to override ActiveRecord::Base to set the primary key to :uuid.
# Only do this in existing models that you migrated to UUIDs. In any new tables
# that you create, the id column will be a UUID, as long as you use the migration
# format that I showed you at the top.
class Posting < ActiveRecord::Base
set_primary_key :uuid
end
You should probably go the extra mile and actually remove the old Integer id fields and rename the new UUID ids to "id" but I'm not sure how to do that off the top of my head. Anyway, I think this approach should work. There could be a couple of errors or bits missing though, it's a bit late over here.

How can I add a column to reference to another table on RoR?

Here is the Customer:
class CreateCustomer < ActiveRecord::Migration
def self.up
create_table :customers do |t|
t.column :email, :string, :null => false
end
end
def self.down
drop_table :customers
end
end
And this is the customer Info:
class CustomerInfo < ActiveRecord::Migration
def self.up
create_table :statuses do |t|
t.column :statuses, :string, :null => false
end
end
def self.down
drop_table :status
end
end
What I would like to do is the customer and customer Info have a one to one relationship. How can I do it in a new migration? thank you.
When you want a 1 to 1 in Rails, you have to decide which one of the models will store the foreign key. In your case, you probably want status to store the fk, so add an integer column called customer_id to the status table. Then you can add the has_one/belongs_to on Customer and Status. belongs_to always goes on the model with the foreign key.
Also I'm not sure if Rails will like you calling your table with the singular name, so you will probably have to do some extra work if you really want to call it 'status' instead of 'statuses'
You can try following thing in your next migration
add_column :customer_infos , :customer_id , :integer ,:references=>"customers" , :null=>:true
Then you can add the has_one/belongs_to on Customer and Cusomer_infos .
You can also execute an SQL statement.
statement = "ALTER TABLE users CHANGE id id SMALLINT( 5 ) UNSIGNED NOT NULL AUTO_INCREMENT" ActiveRecord::Base.connection.execute(statement)
you can entry manually in your migration
Note this is just an example. The final SQL statement syntax depends on the database.

Foreign Key Issues in Rails

Took me a while to track down this error but I finally found out why. I am modeling a card game using the Rails framework. Currently my database looks (mostly) like this:
cards cards_games games
----- ----------- -----
id id id
c_type card_id ...
value game_id other_stuff
And the Rails ActiveRecord card.rb and game.rb currently look like this
#card.rb
class Card < ActiveRecord::Base
has_and_belongs_to_many :player
has_and_belongs_to_many :game
has_and_belongs_to_many :cardsInPlay, :class_name => "Rule"
end
#game.rb
class Game < ActiveRecord::Base
has_and_belongs_to_many :cards
has_many :players
has_one :rules, :class_name => Rule
end
When I attempt to run a game and there are multiple games (more than 1), I get the error
ActiveRecord::StatementInvalid in GameController#start_game
# example
Mysql::Error: Duplicate entry '31' for key 1: INSERT INTO `cards_games` (`card_id`, `id`, `game_id`) VALUES (31, 31, 7)
Every time the action fails, cardid == id. This, I assume, has something with how Rails inserts the data into the database. Since there is no cardsgames object, I think it is just pulling card_id into id and inserting it into the database. This works fine until you have two games with the same card, which violates the primary key constraint on cardsgames. Being affluent with databases, my first solution to this problem was to try to force rails to follow a "real" definition of this relationship by dropping id and making cardid and gameid a primary key. It didn't work because the migration couldn't seem to handle having two primary keys (despite the Rails API saying that its okay to do it.. weird). Another solution for this is to omit the 'id' column in the INSERT INTO statement and let the database handle the auto increment. Unfortunately, I don't know how to do this either.
So, is there another work-around for this? Is there some nifty Rails trick that I just don't know? Or is this sort of structure not possible in Rails? This is really frustrating because I know what is wrong and I know several ways to fix it but due to the constraints of the Rail framework, I just cannot do it.
has_and_belongs_to_many implies a join table, which must not have an id primary key column. Change your migration to
create_table :cards_games, :id => false do ...
as pointed out by Matt. If you will only sleep better if you make a key from the two columns, create a unique index on them:
add_index :cards_games, [ :card_id, :game_id ], :unique => true
Additionally, your naming deviates from Rails convention and will make your code a little harder to read.
has_and_belongs_to_many defines a 1:M relationship when looking at an instance of a class. So in Card, you should be using:
has_and_belongs_to_many :players
has_and_belongs_to_many :games
Note plural "players" and "games". Similarly in Game:
has_one :rule
This will let you drop the unnecessary :class_name => Rule, too.
To drop the ID column, simply don't create it to begin with.
create_table :cards_rules, :id => false do ...
See Dr. Nics composite primary keys
http://compositekeys.rubyforge.org/
I found the solution after hacking my way through. I found out that you can use the "execute" function inside of a migration. This is infinitely useful and allowed me to put together an non-elegant solution to this problem. If anyone has a more elegant, more Rails-like solution, please let me know. Here's the solution in the form of a migration:
class Make < ActiveRecord::Migration
def self.up
drop_table :cards_games
create_table :cards_games do |t|
t.column :card_id, :integer, :null => false
t.column :game_id, :integer, :null => false
end
execute "ALTER TABLE cards_games DROP COLUMN id"
execute "ALTER TABLE cards_games ADD PRIMARY KEY (card_id, game_id)"
drop_table :cards_players
create_table :cards_players do |t|
t.column :card_id, :integer, :null => false
t.column :player_id, :integer, :null => false
end
execute "ALTER TABLE cards_players DROP COLUMN id"
execute "ALTER TABLE cards_players ADD PRIMARY KEY (card_id, player_id)"
drop_table :cards_rules
create_table :cards_rules do |t|
t.column :card_id, :integer, :null => false
t.column :rule_id, :integer, :null => false
end
execute "ALTER TABLE cards_rules DROP COLUMN id"
execute "ALTER TABLE cards_rules ADD PRIMARY KEY (card_id, rule_id)"
end
def self.down
drop_table :cards_games
create_table :cards_games do |t|
t.column :card_id, :integer
t.column :game_id, :integer
end
drop_table :cards_players
create_table :cards_players do |t|
t.column :card_id, :integer
t.column :player_id, :integer
end
drop_table :cards_rules
create_table :cards_rules do |t|
t.column :card_id, :integer
t.column :rule_id, :integer
end
end
end
You might want to check out this foreign_key_migrations plugin

Resources