Ruby on Rails - How to migrate code from float to decimal? - mysql

So I've got a ruby on rails code which use float a lot (lots of "to_f"). It uses a database with some numbers also stored as "float" type.
I would like to migrate this code and the database to decimal only. Is it as simple as migrating the database columns to decimal (adding a decimal column, copying float column to decimal one, deleting float column, renaming decimal column to old float column name), and replacing "to_f" with "to_d" in the code? Or do I need to do more than that?
Thanks a lot everyone
Raphael

You can easily use a migration to do this, and Rails will generate some of the code for you.
From your command prompt, create a new migration:
rails generate migration change_price_column_to_decimal
Rails will create the migration in the directory db/migrate. The filename will be a timestamp followed by _change_price_column_to_decimal.rb.
In the generated migration, you'll add up and down methods to convert the field:
class ChangePriceColumnToDecimal < ActiveRecord::Migration
def up
change_column :products, :price, :decimal, :precision => 15, :scale => 2, null: false
end
def down
# Either change the column back, or mark it as irreversible with:
raise ActiveRecord::IrreversibleMigration
end
end
To perform the migration, run the appropriate rake task from your command prompt:
rake db:migrate
This will convert the database for you. Keep in mind that when converting from float to decimal you will lose some significant digits, depending on what you set scale to, though if you're dealing with prices of products, this probably isn't going to be much of an issue.

Related

Disable Rails 4.2 fractional second support on MySQL

So Rails 4.2 starts to support fractional seconds for MySQL. However it requires a migration of column change from datetime to datetime(6). Currently we do not want to do this, and just want to ignore fractional seconds as we have always been doing before.
Rails is not smart enough to see that my datetime has precision 0 and change queries accordingly, so a lot of our spec broke. For example we assert to "select all Foo created within the last hour", but values are persisted without milliseconds, but select statements still uses milliseconds, so lots of records won't be selected.
Before Rails 4.2:
where("foo_at >= ?", 1.day.ago) => foo_at >= '2015-11-02 04:48:18'
In Rails 4.2:
where("foo_at >= ?", 1.day.ago) => foo_at >= '2015-11-02 04:48:18.934162'
Is there a global setting to force AR queries to strip out fractional/milliseconds as it has been doing before?
There is no official way to turn it off, but you could overwrite the behavior,
module ActiveRecord
module ConnectionAdapters
class Mysql2Adapter < AbstractMysqlAdapter
def quoted_date(value)
super
end
end
end
end
It is in https://github.com/rails/rails/blob/v4.2.5/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb#L77-L83
And you could remove it in Rails 5, the commit in rails 5 https://github.com/rails/rails/commit/bb9d20cf3434e20ac6f275e3ad29be42ebec207f should Format the time string according to the precision of the time column
From reading Rails source for 4.2.x branch, I don't see any option to change that.
I see a merged PR, which will see what the database column's precision is, and build the sql datetime string according to that precision. This should eliminate my issue that my spec broke due to mismatch of precision between persisted data and queries. However this is not merged in 4.2.x so it probably will only be available in Rails 5.

Rails - Model Validations doesn't apply on mysql insert/update command

For the reason, I've used mysql cmd insert into table_name (....) update custom_reports ...and hence I miss out on Model validations
validates_uniqueness_of :name
validates_presence_of :name, :description
How to validate now in rails way? Or, use the mysql way to validate(needs help in this way too)?
Rails validation and other ActiveRecord and ActiveModel magic don't work if you only execute custom SQL command. None of your model classes is even instantized then.
For Mysql (or any sql like DB), you can modify the column attribute to:
Unique (this would validate uniqueness)
Not null (this would validate presence)
I know doing the above with OCI8 and oracle would result in exceptions which I am guessing should be same with ActiveRecord and Mysql, so you should be handling your exceptions correctly
But as #Marek as said you should be relying on Active record and be doing things like
Model.create()
OR
model_instance.save()
If you want to find (and perhaps handle) the entries in your db that are not valid, try the following in the rails console:
ModelName.find_each do |item|
unless item.valid?
puts "Item ##{item.id} is invalid"
# code to fix the problem...
end
end
valid? runs the Validations again, but does not alter the data.

ActiveRecord is treating attribute as Integer when it's Decimal in MySQL

I feel like I'm taking crazy pills here. I have a MySQL-backed ActiveRecord class and a number of the attributes are reported in Rails as type Integer:
Device.last.score.class # returns `Fixnum`
MySQL reports the column being of type decimal(10,0).
What am I missing here? Even tried a call to reset_column_information. Migration looks correct and schema file looks OK too.
Note: this issue wasn't happening in dev, where I'm using SQLite.
Here is my migration:
class AddScoreColumnToDevices < ActiveRecord::Migration
def change
add_column :devices, :score, :decimal
end
end
The migration did it for me! Thank you!
The defaults for a newly migrated :decimal are :precision => 0 and :scale => 0, which will result in a MySQL decimal accepting nothing after the decimal point.
Ruby will see that number, with nothing after the decimal point, as a Fixnum, which is what you are observing.
So, you need to write a new migration to alter the column and give the decimal precision and scale > 0.
change_column :devices, :score, :decimal, :precision => 10, :scale => 10
References:
ActiveRecord ConnectionAdapter TableDefinition #column
decimal precision and scale
old reference on MySQL column<->Ruby datatype mapping

Add a column to a table

how to add a column to my table Users.
because I ran the migration, I have to do something like:
rails generate migration AddShowmsgColumnToUsers show_msg:boolean
and then:
rake db:migrate
but I'm not sure about "AddShowmsgColumnToUsers". how can I know how it suppose to be? why not: AddShow_msgColumnToUsers? if the problem was pluralization and singularization, I can run the rails console and check that, but how can I know about the uppercase letter: ShowMsg/Show_msg/Show_Msg/Showmsg? is there a command that helps me to check it?
In answer to your first question, it doesn't matter, as long as the table name is correct - Rails uses the arguments you specify for the columns rather than the name of the migration.
Also, you should only really be asking one question at a time... ;-)
If you generate a migration to add an column, you should use either camelcase or underscores. Besides you dont have to put "Column" inside your migration generator, with Add...To... the migration already knows you are adding a column.
So either:
rails generate migration AddShowMsgToUsers show_msg:boolean
or:
rails generate migration add_show_msg_to_users show_msg:boolean
Is the way to go. The migration-generator will result in the following migration:
class AddShowMsgToUsers < ActiveRecord::Migration
def change
add_column :users, :show_msg, :boolean
end
end
Of course you could also do it all manually, but the whole point of generators is that you don't need to write everything yourself.

Rails unit tests fail because of unique constraint on schema_migrations

I'm trying to run rake test:units and I keep getting this:
Mysql::Error: Duplicate entry '2147483647' for key 1: INSERT INTO `ts_schema_migrations` (version) VALUES ('20081008010000')
The "ts_" is there because I have ActiveRecord::Base.table_name_prefix set. I'm confused because there is no value '20081008010000' already in the table, and there is no migration with the value '2147483647' (though the value does appear in the table).
In Rails' schema_statments.rb, there is the following:
def initialize_schema_migrations_table
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
unless tables.detect { |t| t == sm_table }
create_table(sm_table, :id => false) do |schema_migrations_table|
schema_migrations_table.column :version, :string, :null => false
end
...
In my development database, ts_schema_migrations.version is a VARCHAR. In test, though it's an INTEGER. I've dropped the tables and re-run the migrations (and/or a rake db:schema:load RAILS_ENV=test) several times. No changes.
Is something wrong with my MySQL adapter?
It looks as though your test schema is Rails 1.x somehow, whereas development is Rails 2. Perhaps you could set RAILS_ENV to test and run rake db:reset
It looks like you skipped some steps when upgrading from Rails 1.x to 2.0.
Go through and read the upgrade notes:
http://www.slashdotdash.net/2007/12/03/rails-2-upgrade-notes/
And the release notes:
http://weblog.rubyonrails.org/2007/12/7/rails-2-0-it-s-done
They will tell you all the steps you need to follow. Particularly regenerating all the scripts and migrating your database to the new system of database migrations by timestamp instead of incrementing migration id.