Rails reference a model with a non integer primary key - ruby-on-rails

I have a table whose migration is this:
class CreateClient < ActiveRecord::Migration
def change
create_table :clients, :primary_key => :tag do |t|
t.string :name
end
change_column :clients, :tag, :string, limit: 4
end
end
so the primary key as you can see is that tag field which is a varchar(4), now I need to reference that field from another table.
I tried in the migration to create the reference in this way:
t.references :client, index: true
but the resulting table has a field called client_id which is int, should I pass any parameter to the references method so that it will create the field as varchar?
Thank you in advance

I believe you can use execute to force rails into setting the primary key in the way you want:
class CreateClient < ActiveRecord::Migration
def change
create_table :clients, :id => false do |t|
t.string :name
end
execute "ALTER TABLE clients ADD PRIMARY KEY (name);"
And then set do set_primary_key :name in your Client model

Related

Adding reference to a table

Please how can I reference a table when not using the default table id as the table's primary key? I created two tables one having email as the primary key and I want to have this table email as the foreign key in the other table. Please How can i achieve that?
You may create table with email as primary key like this:
create_table :users, id: false do |t|
t.string :email, primary_key: true
t.string :name
t.timestamps
end
create_table :posts do |t|
t.string :title
t.text :body
t.string :user_email
t.timestamps
end
And define your models like this:
class User < ActiveRecord::Base
self.primary_key = 'email'
has_many :posts, foreign_key: :user_email
end
class Post < ActiveRecord::Base
belongs_to :user, foreign_key: :user_email
end
That's all you need to do. Also you may keep id as primary key for User model but use use email as foreign key - this may be easier way to handle your models.

generate a model with a string field as primary key

I want to create a User model inside my Ruby on Rails application.
I use following command:
rails generate model User email:string name:string role:string
It is possible to define the email as primary key with this command? Or I must modify the database migration file that I create with this command? And how?
No, you can't. By default the primary key is an auto-increment integer.
However, you can open the migration that was generated from the command, and change it (before running the rake db:migrate command). The migration will likely have a create_table command:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
# ...
end
end
end
If you read the create_table documentation, you will notice you can pass two options. Specifically, you need to set :id to false to not generate an id field, and you will need to specify the name of the primary key field.
create_table :users, id: false, primary_key: :email do |t|
To add to #Simone Carletti's answer, you may need to use execute to set the primary key (if it's obscure). This would be especially true if you're modifying an existing table, which you're obviously not doing:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users, id: false do |t|
t.string :email, null: false
t.timestamps
end
execute "ALTER TABLE users ADD PRIMARY KEY (email);"
end
end
We use uuid's in some of our apps, and that's what we had to do (primary_key: :uuid didn't work)...
In Migration 6.0 or Rails 6
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users, id: false do |t|
t.string :email, null: false, primary_key: true
t.timestamps
end
end
end
In recent versions of ActiveRecord you can use --primary-key-type to specify the primary key type in the generate command:
rails generate model User email:string --primary-key-type=string
produces the migration:
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users, id: :string do |t|
t.string :email
t.timestamps
end
end
end
However, as far as I can tell there's still no way to change the name of the primary key column, except by hand.

Changing model id to uuid in Rails using uuid-ossp

I need to change the id of the existing model to uuid. I'm using this guide:
http://rny.io/rails/postgresql/2013/07/27/use-uuids-in-rails-4-with-postgresql.html
But no idea how to adapt the migration below to changing (not creating a new one):
class CreateDocuments < ActiveRecord::Migration
def change
create_table :documents, id: :uuid do |t|
t.string :title
t.string :author
t.timestamps
end
end
end
Try This.
class documents < ActiveRecord::Migration
def change
add_column :documents, :uuid, :uuid, default: "uuid_generate_v4()", null: false
change_table :documents do |t|
t.remove :id
t.rename :uuid, :id
end
execute "ALTER TABLE documents ADD PRIMARY KEY (id);"
end
end
source
Rails automatically handles uuid, so you just need to change id to uuid, set the column type (uuid for PGSQL, string for MYSQL) and repopulate the table with the new uuid.
In doing this myself, I've only ever changed the id column to uuid for Rails to populate it automatically.
$ rails g migration ChangeID
#db/migrate/change_id______.rb
class ChangeId < ActiveRecord::Migration
def change
rename_column :documents, :id, :uuid
change_column :documents, :uuid, :uuid #-> will only work for PGQL, will have to make it string for MYSQL
end
end
$ rake db:migrate
This will rename your :id column to uuid, assigning the respective column type to it.
And, yes, I've used :uuid before...
--
A good ref:
http://labria.github.io/2013/04/28/rails-4-postgres-uuid-pk-guide/

How to migrate from using one foreign key to another in ActiveRecord?

Using Postgres as the backing store, I have a table which (at least for the time being) has both an integer primary key and a uuid with a unique index.
It looks something like this in my schema.rb (simplified for example):
create_table "regions", force: cascade do |t|
t.integer "region_id"
t.uuid "uuid", default: "uuid_generate_v4()"
t.string "name"
end
add_index "regions", ["uuid"], name "index_regions_on_uuid", unique: true, using :btree
I then have a table which has a reference to the integer id, something like this:
create_table "sites", force:cascade do
t.integer "site_id"
t.integer "region_id"
t.string "name"
end
What I want to do is to switch from region_id to uuid as the foreign key in the second table. How should I god about writing this migration?
Just create a migration, and inhale some SQL magic into it:
def up
# Create and fill in region_uuid column,
# joining records via still existing region_id column
add_column :sites, :region_uuid
if Site.reflect_on_association(:region).foreign_key == 'region_id'
# We won't use 'joins(:regions)' in case we will need
# to re-run migration later, when we already changed association
# code as suggested below. Specifying join manually instead.
Site.joins("INNER JOIN regions ON site.region_id = regions.id").update_all("region_uuid = regions.uuid")
end
drop_column :sites, :region_id
end
Then you just need to fix your association:
class Site < ActiveRecord::Base
belongs_to :region, primary_key: :uuid, foreign_key: :region_uuid
end
class Region < ActiveRecord::Base
has_many :sites, primary_key: :uuid, foreign_key: :region_uuid
end
From your comment, its seems that you want to modify the primary key referenced by the association, not the foreign key. You actually don't need a migration to do this. Instead, just specify the primary key on the association definitions in each model:
Class Region << ActiveRecord::Base
has_many :sites, primary_key: :uuid
end
Class Site << ActiveRecord::Base
belongs_to :region, primary_key: :uuid
end
The foreign key, since it follows rails convention of being named as the belongs_to relation with an appended "_id" (in this case, region_id), does not need to be specified here.
ETA: You will also need to ensure that the type of sites.region_id matches the type of regions.uuid, which I assume is uuid. I'm also going to assume that this field was previously indexed (under ActiveRecord convention) and that you still want it indexed. You can change all this in a migration like so:
def up
remove_index :sites, :region_id
change_column :sites, :region_id, :uuid
add_index :sites, :region_id
end
def down
remove_index :sites, :region_id
change_column :sites, :region_id, :integer
add_index :sites, :region_id
end

Model associations in RoR

I'm trying to understand how rails works in respect to foreign key and primary keys. Coming from a pure SQL background the Rails method seems very alien to me.
I have the following two migrations:
Groups
class CreateGroups < ActiveRecord::Migration
def self.up
create_table :groups do |t|
t.string :title
t.text :description
t.string :city
t.integer :event_id
t.string :zip
t.string :group_id
t.text :topics
t.timestamps
end
end
def self.down
drop_table :groups
end
end
and Events:
class CreateEvents < ActiveRecord::Migration
def self.up
create_table :events do |t|
t.string :title
t.string :description
t.string :city
t.string :address
t.time :time_t
t.date :date_t
t.string :group_id
t.timestamps
end
end
def self.down
drop_table :events
end
end
A Group can have many events and an event can belong to a single group. I have the following two models:
class Event < ActiveRecord::Base
belongs_to :group, :foreign_key => 'group_id'
end
and
class Group < ActiveRecord::Base
attr_accessible :title, :description, :city, :zip, :group_id, :topics
has_many :events
end
not sure how to specify foreign keys and primary keys to this. For example a group is identified by the :group_id column and using that I need to fetch events that belong to a single group!
how do i do this!
I see you have group_id and event_id as strings in your migration, so I think you might be missing a rails convention. The rails convention is that all tables have a primary key named id of type integer, and any foreign keys reference it by the name of the model, singular, + _id:
table groups:
id: integer
name: string
table events:
id: integer
name: string
group_id: integer
From this convention, all you have to specify in your models is:
class Event < ActiveRecord::Base
belongs_to :group
end
class Group < ActiveRecord::Base
has_many :events
end
At this point, rails knows what to do by convention over configuration: To find an event's group, it knows to look for group_id (singular) to refer to groups.id (plural table name)
event = Event.first #=> returns the first event in database
group = event.group #=> returns the group object
Similarly, it know how to find all the events in a group
group = Group.first #=> returns first group in database
group.events #=> returns an Enumerable of all the events
For more reading, read the rails guide on associations

Resources