See this answer for how I implemented string primary keys in my rails app: https://stackoverflow.com/a/1434819/4585520
In the migrations, id: false is specified and some custom postgres execution code is added (i.e. execute "ALTER TABLE users ADD PRIMARY KEY (uid);" where uid is a string). Also, in the models, self.primary_key = :uid is usedThe result is that I have no 'id' columns, just 'uid' columns which are all strings.
However, when I deploy this to Heroku and run my seed generation, I get errors because of duplicate uids. I notice that the added uid values are integers. Perhaps the string values I'm providing are being converted to integers (I have UIDs in a JSON file and am importing those, so no uid generation is occurring), which would explain why they're producing duplicate values.
Why are my uids integers on heroku when they're correctly running as strings in development?
I don't believe this is a Heroku issue, but rather an issue with how the migration tweaked the database.
I've got a Rails app that uses UUID's for primary keys as well. Here's a look at my DB on Heroku Postgres:
Column | Type | Modifiers
------------+-----------------------------+-------------------------------------------
id | uuid | not null default uuid_generate_v4()
data | json | not null
Here was my migration:
enable_extension 'uuid-ossp'
create_table :notifications, id: :uuid do |t|
t.json :data, null: false
end
I'm guessing that the column type may not have been set correctly in your migration. Take a look at your DB on heroku (heroku pg:psql) -- is the uid column an integer that's using a sequence? Do you see something like nextval('users_uid_seq'::regclass)? If so, this requires the column to be an integer. I'd try the following migration to modify your database:
enable_extension 'uuid-ossp'
remove_column :users, :uid
add_column :users, :uuid, :uuid, default: 'uuid_generate_v4()'
This will:
ensure that UUID generation for PG is enabled
remove the current uid column (just to get proper naming)
create a new uuid column, with the type set as uuid and the generation handled on the PG side.
If you're already using Rails to generate UUID's (not recommended, though), you could just go into your database and change the column type from integer to string.
I hope that helps!
Related
I can't use bigint in my Model's id as you normally would if using Rails 5, as I am using rails 4.1
I want my model to have an auto incrementing id as normal, but I want it to be a bigint not a regular integer.
The transaction table is going to contain millions of records and migrating ids later will be a headache, requiring downtime.
I tried first to use a rails generator (like a good lazy dev)
Which gave me
class CreateTransactions < ActiveRecord::Migration
def change
create_table :transactions do |t|
t.bigint :id
#more stuff omitted
end
end
end
Which fails to migrate with
undefined method `bigint' for #<ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::TableDefinition:0x00007fe8df5c9b88>
I already tried modifying that to be
create_table :transactions, id: false do |t|
t.bigint :id
end
This works. The migration runs, but then it fails to auto-increment the id. Meaning, I can't do Transaction.create
or it shouts at me from the db layer about a null constraint on the id.
Apparently you can create a table and then modify it with change_column but that had no reflection in the schema.rb so that worries me.
Also apparently, that ^^ (change_column on id) is an irreversible migration so I want to avoid that.
I know there must be an easy / railsy, nice-nice way to make this work.
EXPECTED RESULT:
Transaction.create
Gives me a new transaction where the id sets itself as normal but it is a big int 8 bits not a normal int 4 bits
I just figured it out from reading postgres' supported data types and guessing how rails would handle it.
https://www.postgresql.org/docs/10/datatype-numeric.html
create_table :transactions, id: :bigserial do |t|
#other model stuff here
end
Verified by connecting directly to postgres
Table "public.transactions"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------------+-----------------------------+-----------+----------+-----------------------------------------------------+----------+--------------+-------------
id | bigint | | not null | nextval('ghost_card_transactions_id_seq'::regclass) | plain | |
I need to index a table of users using an externally sourced id, which is a 64-bit integer. Rails is perfectly capable of storing such a number, unless it's the primary key it seems. I have the following migration:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users, :id => false do |t|
t.integer :id, limit: 8
t.string :name
t.timestamps null: false
end
end
end
The migration works fine, no errors reported, but when I attempt to seed it with a 64-bit integer, I'm told off by this:
RangeError: 76561198054432981 is out of range for ActiveRecord::Type::Integer with limit 4
Obviously Rails is ignoring the limit field, so long as it's the primary key/the :id field? How should I go about dealing with this?
For what it's worth I'm using sqlite3 (default), but to my knowledge, sqlite is perfectly capable of storing 64-bit integers.
Here's the table_info from sqlite:
0|id|integer(8)|0||0
1|name|varchar|0||0
2|created_at|datetime|1||0
3|updated_at|datetime|1||0
The limit value you gave is correct; it corresponds to BIGINT type
Make sure your migration is applied; open you database in some CLI or GUI software and verify the col-type
Addition:
Changing a column's length or datatype in a migration will invalidate the column as a primary key. Rather, creating an initializer that overrides the site's default primary key datatype should provide the behavior you're looking to implement:
# config/initializers/change_primary_key_datatype.rb
require 'active_record/connection_adapters/postgresql_adapter'
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:primary_key] = "bigserial primary key"
This is what we would do for PG database; This is possible because of
however in the code base of SQLite there is
I'm trying to perform the following up migration to change the column "number" in the "tweet" model's table
class ChangeDataTypeForTweetsNumber < ActiveRecord::Migration
def up
change_column :tweets do |t|
t.change :number, :integer
end
end
def down
change_table :tweets do |t|
t.change :number, :string
end
end
end
Upon performing the the following up migration to heroku....
heroku rake db:migrate:up VERSION=20120925211232
I get the following error
PG::Error: ERROR: column "number" cannot be cast to type integer
: ALTER TABLE "tweets" ALTER COLUMN "number" TYPE integer
Any thoughts you have would be very much appreciated.
Thanks everyone.
Same as above but a little bit more concise:
change_column :yourtable, :column_to_change, 'integer USING CAST("column_to_change" AS integer)'
From the fine manual:
[ALTER TABLE ... ALTER COLUMN ...]
The optional USING clause specifies how to compute the new column value from the old; if omitted, the default conversion is the same as an assignment cast from old data type to new. A USING clause must be provided if there is no implicit or assignment cast from old to new type.
There is no implicit conversion from varchar to int in PostgreSQL so it complains that column "number" cannot be cast to type integer and the ALTER TABLE fails. You need to tell PostgreSQL how to convert the old strings to numbers to match the new column type and that means that you need to get a USING clause into your ALTER TABLE. I don't know of any way to make Rails do that for you but you can do it by hand easily enough:
def up
connection.execute(%q{
alter table tweets
alter column number
type integer using cast(number as integer)
})
end
You'll want to watch out for values that can't be cast to integers, PostgreSQL will let you know if there are problems and you'll have to fix them before the migration will succeed.
Your existing down-migration should be fine, converting integer to varchar should be handled automatically.
I have a PostgreSQL database for a Rails application.
I want to store the Facebook user id so I thought I could use integer but its not big enough so I chose float.
However now Rails adds .0 to the end of my user id's
What datatype can I use so this does not happen for Facebook user ids which are very long example: 100002496803785
You can use :limit => 8 on your integer column to get a bigint. For example:
class Pancakes < ActiveRecord::Migration
def change
create_table :pancakes do |t|
t.integer :c, :limit => 8
end
end
end
And then, from psql:
=> \d pancakes
Table "public.pancakes"
Column | Type | Modifiers
--------+---------+-------------------------------------------------------
id | integer | not null default nextval('pancakes_id_seq'::regclass)
c | bigint | not null
Indexes:
"pancakes_pkey" PRIMARY KEY, btree (id)
And there's your eight byte bigint column.
You could also use a string for the Facebook ID. You're not doing any arithmetic on the IDs so they're really just opaque bags of bits that happen to look like large integers, strings will sort and compare just fine so they might be the best option. There would be some storage and access overhead due to the increased size of a string over the integer but it probably wouldn't be enough to make any noticeable difference.
Never use a double for something that needs to be exact. You'd probably be fine (except for the trailing .0 of course) in this case because you'd have 52 bits of mantissa and that means that the double would act like a 52 bit integer until your values got large enough to require the exponent. Even so, using double for this would be an awful idea and an abuse of the type system.
I don't use postgresql but in mysql I use BIGINT
According to postgresql data types, BIGINT for postgresql as well.
mu is too short has a great answer, I only want to add that if you want to use the ID as a foreign key between tables then you should stick to the BIGINT solution he describes, not use a string. This is what I use, essentially:
Example:
create_table(:photos) do |t|
t.integer :fb_uid, :limit => 8 # Facebook ID of the photo record
t.integer :facebook_profile_uid, :limit => 8, :null => false # foreign key to user
# ...
end
create_table(:users) do |t|
t.integer :fb_uid, :limit => 8, :null => false # Facebook ID of the user record
t.integer :photos_count, :integer, :default => 0
# ...
end
class User < ActiveRecord::Base
has_many :photos, foreign_key: :facebook_profile_uid, primary_key: :fb_uid
# ...
end
class Photo < ActiveRecord::Base
belongs_to :facebook_profile, foreign_key: :facebook_profile_uid, primary_key: :fb_uid, :counter_cache => true
end
Ran into this problem while using the Google uid which also is quite large.
I found the this answer to be most useful:
Getting error indicating number is "out of range for ActiveRecord::Type::Integer with limit 4" when attempting to save large(ish) integer value
Run a migration to change your table column.
Edit the generated migration -> add, limit: 8
Run db:migrate to migrate to the database.
Restart the rails server.
This will allow you to change the limit of your table column.
I’m trying to use a UUID as the primary key for a Rails app, and am running into problem after problem.
I am specifying in migration this:
create_table :users, :id => false do |t|
then this:
execute("ALTER TABLE users ADD PRIMARY KEY(uuid)")
In my user model:
set_primary_key "uuid"
Using the UUID tools to generate the UUID.
This is all working great, the problem I currently have is that the schema.rb that gets generated looks like this:
create_table "users", :primary_key => "uuid", :force => true do |t|
Which assumes that the primary key column is an 11 character integer rather than a 36 character string, so running migrations produces a correct database, but test database is generated incorrectly, and if I were to run rake db:schema:load, it would fail as well...
Need to figure out how to override the way that schema.rb assumes that if there’s a primary key column that it will be an integer....
I think the best approach is to switch from managing your schema in Ruby (schema.rb) to managing it in SQL (development_structure.sql).
To do this:
In application.rb set config.active_record.schema_format = :sql
Delete your schema.rb file.
Every time you run rake db:migrate, run rake db:dump:structure as well. That will dump your schema to db/development_structure.sql. When you run rake db:test:prepare it will now use the development_structure.sql file instead of the scheam.rb file.
You can read more about this in section 6.2 of the migrations guide (http://guides.rubyonrails.org/migrations.html).
We have been using UUID as the primary key for a long time (currently with Rails 3.0.0), but so far have not found a way to make Schema dumper understand that there is no integer id.
So our solution has been fixing the schema by hand, when needed. For example always after rake db:migrate. :D
We just change the line
create_table "people", :force => true do |t|
to
create_table "people", :id => false, :force => true do |t|
t.string "id", :limit => 22, :null => false
It's rather annoying but then everything works. So the problem is not in Rails not allowing UUID primary keys, but schema dumper not understanding those.
I ran into this problem. As far as i can tell, it's impossible to override rails' requirement that the PK be an integer. What i did to bypass this was add a key to unique on the database and then setup my default scopes on each model to search via my unique string instead of the regular integer