I have a class called Order that has fields total and commission_amount, which are both decimals. In the seeds.rb file I assign both fields 100.00, as is (no quotes). When I enter rails console and type Order.all I can see that the order created by the seeds file has a commission amount of 100.00 but the total is listed as 0.0.
I've tried using rails console to assign different numbers to it, both total and commission amount are decimal attributes with precision: 10, scale: 2. I've tried using a BigDecimal constructor to assign the values in the seeds file, and I don't have any kind of verifications in place over the total attribute. Pretty stumped on what should be a trivial issue. Thanks for your help!
EDIT: here are the relevant samples of code
# the migration
class CreateOrders < ActiveRecord::Migration
def change
create_table :orders do |t|
# ...
t.decimal :total, precision: 10, scale: 2
t.decimal :commission_amount, precision: 10, scale: 2
end
end
end
# the seeds file
# Order SEEDING
# --------------
create_records([
{
# ...
total: 100.00,
commission_amount: 100.00
}
], Order)
Strange value association errors in ActiveRecord are often caused by name collisions. ActiveRecord defines setter and getter methods for all fields in the database table of a model and ruby allows you to silently override those in your class definition.
It is always a good advice to go sure not to have used a reserved word for a column name (classic candidates are 'type','object','class'), not to have called a column the same as an associated object, and finally not to define a method with the same name as a column in the database.
In your case, check if you have defined a method called total in your model definition.
Related
I am doing PDFs for invoices in my system and I would like to be able to store numbers with two decimal places in the database. I am using MoneyRails gem for dealing with currencies, I have setup precision: 10 and scale: 2 on the database level (I use postgres as my DB) but I am getting only 1 decimal place after comma. Why?
class AddPrecisionToInvoices < ActiveRecord::Migration[5.2]
def self.up
change_column :invoices, :total_net_amount_cents, :decimal, precision: 10, scale: 2, default: 0.00
change_column :invoices, :total_gross_amount_cents, :decimal, precision: 10, scale: 2, default: 0.00
end
def self.down
change_column :invoices, :total_net_amount_cents, :bigint
change_column :invoices, :total_gross_amount_cents, :bigint
end
end
invoice.rb
monetize :total_net_amount_cents
monetize :total_gross_amount_cents
In rails console,
invoice.total_gross_amount_cents = Money.new(20_000_00)
invoice.total_gross_amount.to_f #=> 2000.0
Is it possible to store numbers with two decimal places in DB, like 20,000.00?
I don't want to display the PDF in a view so I want to be able to drop the number into my DB as I got it from params from my front-end application without further formatting it in a view.
You can try following, (using in model)
ActiveSupport::NumberHelper::number_to_delimited('%.2f' % '3423432.43234', delimiter: ",", separator: ".")
# => "3,423,432.43"
Here, in above input 3423432.43234 is provided as string, you can provide it as number also.
You can directly use number_with_delimiter in view
The money-rails gem requires monetize columns to be numeric in the database. However, it comes with some helper methods that you could use to re-format as you wish in your model:
# inside Invoice model
require "money-rails/helpers/action_view_extension"
class Invoice < ApplicationRecord
include MoneyRails::ActionViewExtension
# ...
def total_gross_amount_formatted
humanized_money total_gross_amount
end
end
Then in your PDF you can just reference the new formatted attribute:
#invoice_instance.total_gross_amount_formatted
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 having an issue with a table accepting too many digits after the decimal, despite defining it's precision and scope.
rails generate model Hotel name:string 'rating:decimal{2,1}'
class CreateHotels < ActiveRecord::Migration
def change
create_table :hotels do |t|
t.string :name
t.decimal :rating, precision: 2, scale: 1
t.timestamps
end
end
end
However, I am able to do the following.
Hotel.create!(name: “The Holiday Inn”, rating: 3.75)
Additionally, I have a rooms table (Room model), with
t.decimal :rate, precision: 5, scale: 2 #this holds the room's nightly rate
I input 99.99 into this column, but it ends up storing it as 99.98999999..
Why do I have these 2 decimal issues? If I have defined my scope, why am I allowed to input more scope than I have defined?
I input 99.99 into this column, but it ends up storing it as 99.98999999...
That suggests that you're using SQLite which doesn't really have a decimal data type, if you ask SQLite to create a decimal(m,n) column it will create a REAL instead; REAL is a floating point type, not a fixed precision type. To solve this problem, stop using SQLite; odds are that you're not going to deploy a real application on top of SQLite anyway so you should be developing with the same database that you're going to deploy on.
Also, if you're using a fixed precision type for rating, you should not say this:
Hotel.create!(name: "The Holiday Inn", rating: 3.75)
as that 3.75 will be a floating point value in Ruby and it could get messed up before the database sees it. You should say one of these instead:
Hotel.create!(name: "The Holiday Inn", rating: '3.75')
Hotel.create!(name: "The Holiday Inn", rating: BigDecimal.new('3.75'))
so that you stay well away from floating point front to back.
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.
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.