Is using column limit options worth it? - ruby-on-rails

Is there any point to specifying a limit option on string in migrations...
class CreateAccounts < ActiveRecord::Migration
def self.up
create_table :accounts do |t|
t.string :name, :limit => 64
end
end
end
Should this be applied to all strings in the DB? What's the significance?

Strings are usually 255 characters length but not all databases treat the string field in the same way. For instance, PostgreSQL can create string column of different sizes.
There are at least 2 very good reasons to specify the value of the string field:
cross-database compatibility
database performance
If you need a string column to store the country code that is 2 chr length, why you want the database to reserve additional 253 characters for... nothing?
Also note you should always validate the length of the field value in your model.
If you try to create a record with a name that exceeds your maximum length:
SQLIte3 will silently trim the value
MySQL will silently trim the value
PostgreSQL will raise an exception
So, always validates_length_of your attribute.

The first thing that comes to mind - when you have millions of accounts, that limit will actually affect the size of your DB a lot.

Related

Ruby on Rails ignoring integer limit

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

Integer out of range in PostgreSQL database

I'm trying to save a number representing the length of a file (4825733517). The column is set to type integer. I don't have any validations or restrictions set.
RangeError: 4825733517 is out of range for ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Integer with limit 4
Should I be using some other column type for this value? (on rails 4.2.4)
For columns of type integer, the :limit value is the maximum column length in bytes (documentation).
With 4 byte length, the largest signed integer you can store is 2,147,483,647, way smaller than your value of 4,825,733,517. You can increase the byte limit, for example to 8 bytes to be a long integer (a bigint PostgreSQL type), this will allow you to store signed values up to 9,223,372,036,854,775,807.
You can do this with a migration create it with something like rails generate migration change_integer_limit_in_your_table, and the following code:
class ChangeIntegerLimitInYourTable < ActiveRecord::Migration
def change
change_column :your_table, :your_column, :integer, limit: 8
end
end
According to the PostgreSQL documentation an integer have a range from -2147483648 to +2147483647. So your number is to big for this type.
Update your column and use the parameter limit to indicate that you want to have a bigint.
change_column :table, :column, :integer, limit: 8
You should change the length of the column in your database with a migration :
update_column :my_table, :my_column, :integer, limit: 12
It will allow you to store bigger integers.

Limit integer size in Rails migration

How do I specify a limit on an integer size in a Rails 4 migration? (My database is PostgreSQL.)
I have fields for phone_number, which should be 8 digits long. If I specify :limit => 8, then that is byte size rather than the length of the numbers.
Is there a way to do this?
You're going about this all wrong. A phone number is not a number at all, a phone number is a string that contains (mostly) digit characters. You don't do anything numeric – such as arithmetic – with phone numbers so they're not numbers, they're strings.
Make your phone_number column a string of length eight:
t.string :phone_number, limit: 8
and clean it up and validate the format in your model:
before_validation :clean_up_phone_number
validates :phone_number,
format: { with: /\A\d{8}\z/ },
length: { maximum: 8 },
allow_nil: true
def clean_up_phone_number
# Do whatever you want or need to strip out spaces, hyphens, etc. in here
end
Or better with PostgreSQL, don't worry about the size in the database at all and use t.string :phone_number. The size limit just adds pointless overhead and using a plain varchar rather than a varchar(8) makes it easier to allow for different phone number formats (area codes, extensions, international numbers, ...) later.
Or you can do it with mv-core gem (https://github.com/vprokopchuk256/mv-core) right in a migration in this way (syntax is almost identical to AciveModel::Validations one):
def change
update_table :users do |table|
t.string :phone_number,
validates: { length: 8,
format: /\A\d{8}\z/,
allow_blank: true,
allow_nil: true }
end
end
And then bubble up that validation to your model:
class User < ActiveRecord::Base
enforce_migration_validations
end
By default your validation will be defined as CHECK constraint for PostgreSQL. But you can change that to trigger constraint, for ex.
See details in the documentation.

What datatype to use for Facebook user id in Rails and PostgreSQL

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.

How to add sequences to a migration and use them in a model?

I want to have a "Customer" Model with a normal primary key and another column to store a custom "Customer Number". In addition, I want the db to handle default Customer Numbers. I think, defining a sequence is the best way to do that. I use PostgreSQL. Have a look at my migration:
class CreateAccountsCustomers < ActiveRecord::Migration
def up
say "Creating sequenze for customer number starting at 1002"
execute 'CREATE SEQUENCE customer_no_seq START 1002;'
create_table :accounts_customers do |t|
t.string :type
t.integer :customer_no, :unique => true
t.integer :salutation, :limit => 1
t.string :cp_name_1
t.string :cp_name_2
t.string :cp_name_3
t.string :cp_name_4
t.string :name_first, :limit => 55
t.string :name_last, :limit => 55
t.timestamps
end
say "Adding NEXTVAL('customer_no_seq') to column cust_id"
execute "ALTER TABLE accounts_customers ALTER COLUMN customer_no SET DEFAULT NEXTVAL('customer_no_seq');"
end
def down
drop_table :accounts_customers
execute 'DROP SEQUENCE IF EXISTS customer_no_seq;'
end
end
If you know a better "rails-like" approach to add sequences, would be awesome to let me know.
Now, if I do something like
cust = Accounts::Customer.new
cust.save
the field customer_no is not pre filled with the next value of the sequence (should be 1002).
Do you know a good way to integrate sequences? Or is there a good plugin?
Cheers to all answers!
I have no suggestions for a more 'rails way' of handling custom sequences, but I can tell you why the customer_no field appears not to be being populated after a save.
When ActiveRecord saves a new record, the SQL statement will only return the ID of the new record, not all of its fields, you can see where this happens in the current rails source here https://github.com/rails/rails/blob/cf013a62686b5156336d57d57cb12e9e17b5d462/activerecord/lib/active_record/persistence.rb#L313
In order to see the value you will need to reload the object...
cust = Accounts::Customer.new
cust.save
cust.reload
If you always want to do this, consider adding an after_create hook in to your model class...
class Accounts::Customer < ActiveRecord::Base
after_create :reload
end
I believe that roboles answer is not correct.
I tried to implement this on my application (exactly the same env: RoR+PostgreSQL), and I found out that when save is issued on RoR with the object having empty attributes, it tries to perform an INSERT on the database mentioning that all VALUES shall be set to NULL. The problem is the way PostgreSQL handles NULLs: in this case, the new row will be created but with all values empty, i.e. the DEFAULT will be ignored. If save only wrote on the INSERT statement attributes filled on RoR, this would work fine.
In other words, and focusing only on the type and customer_no attribute mentioned above, this is the way PostgreSQL behaves:
SITUATION 1:
INSERT INTO accounts_customers (type, customer_no) VALUES (NULL, NULL);
(this is how Rails' save works)
Result: a new row with empty type and empty customer_no
SITUATION 2:
INSERT INTO accounts_customers (type) VALUES (NULL);
Result: a new row with empty type and customer_no filled with the sequence's NEXTVAL
I have a thread going on about this, check it out at:
Ruby on Rails+PostgreSQL: usage of custom sequences
I faced a similar problem, but I also put :null => false on the field hopping that it will be auto-populated with nextval.
Well, in my case AR was still trying to insert NULL if no attribute was supplied in the request, and this resulted in an exception for not-null constraint violation.
Here's my workaround. I just deleted this attribute key from #attributes and #changed_attributes and in this case postgres correctly put the expected sequence nextval.
I've put this in the model:
before_save do
if (#attributes["customer_no"].nil? || #attributes["customer_no"].to_i == 0)
#attributes.delete("customer_no")
#changed_attributes.delete("customer_no")
end
end
Rails 3.2 / Postgres 9.1
If you're using PostgreSQL, check out the gem I wrote, pg_sequencer:
https://github.com/code42/pg_sequencer
It provides a DSL for creating, dropping and altering sequences in ActiveRecord migrations.

Resources