How to manage non-autoincrement primary key in Rails? - ruby-on-rails

I have lots of situations where I'd like to have a non-autoincrement primary key when using Rails.
Example: I have one-to-one relationship between A and B. B describes some specific features added to A thus can't exist without A. So we have:
A has one B
B belongs to A
The natural thinking would be having B.A_id as primary key. So I tried create_table b, :id=>false in migration and set_primary_key :a_id in B's model, but it doesn't create actual primary keys in the database. And I want them (as well as foreign keys), as the database will be used not only by this Rails app.
If I create primary keys with execute they don't land in
schema.rb, which hurts. Now I'm thinking about a workaround: I can live without PK constraint as long as there's unique constraint for that column, so I can use Rails' add_index in the migration which seems more elegant.
Any suggestions?

A very similar question on StackOverflow suggests trying something like:
create_table(:b, :id => false) do |t|
t.integer :a_id, :options => 'PRIMARY KEY'
end

Related

rails non-integer primary key

I have a table called Contracts. Its current default primary key is the :id field that Rails automatically generates, which is an integer. I want to have a field called contractId that is a string type and use it as a primary key instead. What I want to know is:
Is this a best practice? Are there any potential issues with doing this?
How I would go about it
Ruby on Rails (RoR) likes to emphasise the concept of convention over configuration. Therefore, it seeks to minimialise the amount of configuration.
So if you want contractId that is a string type then you can add one extra field in your table and use it wherever you want and let the Rails use id as primarykey.
Change PrimaryKey
Generate a new migration file name it "ChangePrimaryKey" (You can give any name).
class ChangePrimaryKey < ActiveRecord::Migration
def up
remove_column :table, :id # remove existing primary key
rename_column :table, :udid, :id # rename existing UDID column
execute "ALTER TABLE table ADD PRIMARY KEY (id);"
end
def down
# Remove the UDID primary key. Note this would differ based on your database
execute "ALTER TABLE table DROP CONSTRAINT table_pkey;"
rename_column :table, :id, :udid
add_column :table, :id, :primary_key
end
end
If you are creating a new table, your migration might look like this:
class AddTableWithDifferentPrimaryKey < ActiveRecord:Migration
def change
create_table :table, id: false do |t|
t.string :id, null: false
# other columns
t.timestamps
execute "ALTER TABLE table ADD PRIMARY KEY (id);"
end
end
end
Notice the id: false options you pass into the table — this asks Rails not to create a primary key column on your behalf.
Changes to Model
In the model, it is essential that you add the following line in order for
Rails to programmatically find the column you intend to use as your primary key.
class Table < ActiveRecord::Base
self.primary_key = :id
# rest of span
end
I hope you can do rest of the things.
Don't change default id if you want to see Rails real Magics :)
As you may know, Rails supports changing the primary id column out of the box:
class Contract < ActiveRecord::Base
self.primary_key = "contractId"
end
Please note that even if the contractId column has a unique index, an index on a string column will always be a bit slower than an index in an integer column.
Furthermore, this is not the Rails way and might confuse other developers that work with this application. Especially building associations or routes are error-prone when your table has a non-standard primary key. IMHO that is a good reason to avoid using this technic as long as possible.

Rails - Using Different primary key

I am building a rails 3 application with a products model. I've been trying to find a way to use the product id number as the identifier rather that using the models default ID.
I initially tried to use the to_param method, however I could not get it to work correctly.
I then rebuilt my database using the products ID number instead of the primary key as so:
class CreateProducts < ActiveRecord::Migration
def change
create_table :products, :id => false, :primary_key => :prod_id do |t|
t.string "prod_id"
t.string "upc"
t.text "title"
t.text "description"
t.timestamps
end
end
end
And changed my code to find/create using the prod_id. This seems to be working great, however I was hoping to find out a little more about any consequences this may have, and what the disadvantages of not using a primary key maybe.
I was hoping to find out a little more about any consequences this may have, and what the disadvantages of not using a primary key maybe.
You are using a primary key - it's prod_id. You're just not using a default setting auto-incrementing key (presumably though, prod_id is auto-incrementing and uniquely constrained? So you might as well have just left it as id for convention's sake...).
If you tell your model that it's using a different primary key, you shouldn't need to do any other configuration (or specification in associations - the prod_id will be picked up by reflection).
class Product < ActiveRecord::Base
set_primary_key :prod_id
...
end
(different versions of Rails have twiddled with the methods for doing this... check the API for whatever you're using)
There's no magic to choosing to use a non-default primary key - you just need some field that uniquely identifies each row. What it's called, and what its data-type is is practically irrelevant - if you really want to, you can configure your app to use GUID strings as IDs :-)

Does Rails 3.2+ require a join table *in addition to* declaring the has_and_belongs_to_many relation?

I have a number of many-to-many relationships in my app.
I do not need to store information about the relationships themselves, so am using the has_and_belongs_to_many relation in my models.
I've read the Active Record documentation and it seems to confirm my strategy, BUT I'm not clear if I still need to create join tables in the database or if ActiveRecord in Rails 3.2 is smart enough to handle it using the model relations alone.
Any references or explanations would be appreciated.
----- Break -----
If I did need to store data about the relationship itself and I were using has_many => through in my model, would I need to remove the Primary Key from the "through" table (e.g. so that it only has the two foreign keys?)
Thank you!
Yes, you need to create the join table for a has_and_belongs_to_many association. Remember in Rails you need to 'migrate to create'. Using this article as an example, say we have an Account model and a Role model, we can create a join table through this migration:
rails generate migration create_accounts_roles_join_table
Now we will edit the migration file that was just created
create_table :accounts_roles, :id => false do |t|
t.integer :account_id
t.integer :role_id
end
It is important to include :id => false as this will leave off the primary key that is normally generated when you create a table. Also, we specified the two foreign keys account_id and role_id.
run rake db:migrate and add the HABTM associations in both models and everything is set up.
Also, as a side note, adding join_table to the end of the migration generator is not required but is more descriptive and integer can be replaced with references when adding the foreign keys. They are equivalent but maybe a bit more descriptive.
On the second part of your question, you don't need to remove the primary key from the table.

rails migration columns name with _id suffix

Want to do rails migration but i want the column name to be something like external_id but I don't want to any model with external . i think rails by default , whenever sees _id as suffix it looks for foreign key association and if it doesn't exist migration is canceled.
what's the solution for that. I have
def self.up
create_table :external_mappings do |t|
t.string :external_name
t.integer :external_id
t.timestamps
end
end
Thanks for your help
when i make it t.integer :externalId migration works. that make me think external_id is looking for foreiegn key reference, is there any way we can suppress foriegn key reference. I am using rails 2.3.5
You can give to any column a _id name and reference foreign keys without the _id also, so feel free to continue. It is just a convention on which Rails relies to target the model-name_id, but with no trouble it can be defined differently.
No, rails does not cancel any migration due non existent model.
I wonder if the original poster created the new model using scaffolding and the "references" type. If that is the case, the migration would have failed during it's attempt to creating the foreign key.
Otherwise, if it's just an integer, there is nothing wrong with adding an _id to the end of an otherwise acceptable column name.

Shoulda tests failing on model with no id

I created a new model in my rails app. Since it's a one-to-one relation with another table, there's no need for the new model to have an id column. Everything is working fine, but for some reason, all of my Shoulda tests on this model are failing. For example:
should_validate_presence_of :first_name
is throwing this error:
ActiveRecord::StatementInvalid: Mysql::Error: Unknown column 'my_new_table.id'
in 'field list': SELECT `my_new_table`.id FROM `my_new_table` WHERE
(`my_new_table`.`some_other_column` IS NULL) LIMIT 1
Does Shoulda require an ID column? If so, is there a way around this?
Did you alias the primary key for Rails purposes?
set_primary_key :my_fk_id
Rails expects your models to have an integer id column as a primary key. You can set it to the foreign key like #MattMcKnight suggests but I'd recommend you create the id column even if you don't strictly need it. In my experience it will save you tons of headaches like this, with basically no downside.
Did you run rake db:migrate and rake db:test:prepare after you created the new model?
All tables require an id column that's just the way that ActiveRecord works. As MattMcKnight points out, you can designate another column as the primary key. You can also specify that a table have no id column is creatied by providing :id => false as an option to create_table.
Matt's answer covers renaming the primary key in the model definition. Here's how to do it in a migration.
Note, providing the primary key option without giving the id option as false means that you do not have to add to column's definition to the block. Rails will create it for you. And automatically use it in any join tables.
Example:
class CreateTableWithOUtID < ActiveRecord::Migration
def self.up
create_table :my_new_table, :primary_key => :another_table_id do |t|
t.string :some_other_column
end
end
def self.down
drop_table, :my_new_table
end
end

Resources