Oracle sequence not updated when using rails seeds file - ruby-on-rails

Here's a part of a seeds file I have
Country.create(id: 1, name: 'Turkey', 'description' : 'Gobble gobble')
Country.create(id: 2, name: 'Hungary', 'description' : 'Blah blah blah')
There are two records for these in the countries table after this has run.
So now through the web interface or via an API I try to create a new country and I get the following error from Oracle:
OCIError: ORA-00001: unique constraint
The constraint is on the primary key. After seeding, the next two records it attempts to create with id's 1, 2 but they already exist.
I don't understand why when creating the Country by seeding with the same code as in the controller/model through an API or web interface I get a different result.
If I alter the sequence through SQLDeveloper with the following snippet, everything works fine:
ALTER SEQUENCE countries_seq INCREMENT BY 100;
select countries_seq from dual;
ALTER SEQUENCE countries_seq INCREMENT BY 1;
Note this is happening for all models, I'm just using Country as an example.
I'm using
Ruby 2.1.0
Rails 4.0.5
oracle-enhanced 1.5.5 gem
ruby-oci8 2.1.7 gem
EDIT
If I don't put id in the create arguments, everything works fine.
Country.create(name: 'Turkey', 'description' : 'Gobble gobble')
Country.create(name: 'Hungary', 'description' : 'Blah blah blah')
It'd be great to know why though...

I don't know what Country.create does, most probably: If you don't assign an id yourself it will be set by the create function or database side by a trigger. You could check the tabledefinition for a trigger.

Related

Rails Duplicate key error: How to tell Rails to continue with the ID from the database

I'm currently deploying a Ruby on Rails web application with Postgres. I'm working with Docker, just to say it.
When I deploy my application, I insert some predefined data into the database. When I want to create a new record, I get a duplicate key error.
Full error message:
ERROR: duplicate key value violates unique constraint "modelname_pkey"
DETAIL: Key (id)=(1) already exists
How can I solve this? How can I tell Rails to continue with the primary key from the last record?
You can check insert_all method from Rails 6. If you are in lower versions of Rails use activerecord-import gem.
In case of insert_all
First form the json
new_records = [{id: 1, name: 'steve'},{id: 2, name: 'george'}]
Model.insert_all(new_records)
This will insert records if its not already there and ignore if records are there.
In case of activerecord-import
new_records = [{id: 1, name: 'steve'},{id: 2, name: 'george'}]
Model.import new_records, on_duplicate_key_ignore: true
references:
activerecord-import
rails-6-insert_all
If you don't specify an ID the database will choose one for you.
person = Person.create!( name: "MacReady", thing: false )
If you need to reference a specific ID, reconsider that. Relying on special IDs is too fragile, as you're discovering. Database IDs should be considered unique identifiers with no further special meaning.
For example, instead of remembering that "user ID 1 is the admin user" add an "admin" field.
admin = User.create!( name: "Yarrow Hock", admin: true )
Now you can check if user.admin for any user, have as many admins as you want, and change whether a user is an admin at any time.
I think this can also resolve your problem. Add this in your staging or preprod console (I am using Heroku for this so I added in my heroku rails console):
connection = ActiveRecord::Base.connection
connection.execute("SELECT setval(pg_get_serial_sequence('table_name', 'id'), MAX(id)) FROM table_name;")
Check out this blog for more info and how this is been generated.

TinyTds::Error: Cannot insert the value NULL into column 'ID'

My Ruby on Rails system is moving from Oracle to Microsoft SQL Server 2012. The back end database has already been converted by a third part from Oracle to Microsoft SQL Server. I have no control over the schema structure. This cannot be changed.
Using activerecord-sqlserver-adapter, tiny_tds and Freetds I can connect to the new database. Most DB requests work well.
However, many of the tables have ID primary keys that are auto incremented by SQL Server, in other words, PK IDENTITY(1,1) columns. The same code that worked with Oracle connections fails under SQL Server with the following error:
TinyTds::Error: Cannot insert the value NULL into column 'ID'
I know why it is doing this as the ID column is indeed a primary_key, and IDENTITY(1,1) column and includes the NOT NULL restriction. That is fine.
Inserting into the table works fine if using raw SQL execute statements where of course I exclude the ID column from the INSERT statement.
However I have spent days googling and I cannot find a way of telling Ruby on Rails not to try and save the ID column.
So I have an instance of a class, #book, that contains
Library::Book(id: integer, title: string, isbn: string ...)
When I do a #book.save! it generates the error above.
#book = Library::Book.new( .. )
:
:
#book.save!
TinyTds::Error: Cannot insert the value NULL into column 'ID'
Rather that resort to bare metal SQL, how do I do things more Railsy and tell it I want to save the record but not try and save the ID field as it is auto incremented? So effectively I am trying to save
Library::Book(title: string, isbn: string ...) if a new insert entry
or
Library::Book(id: integer, title: string, isbn: string ...) if trying to update an entry.
Due to imposed restrictions I am using:
Ruby 2.3.3p222
Rails 4.0.13
activerecord (4.0.13)
activerecord-sqlserver-adapter (4.0.4)
tiny_tds (1.0.2)
Freetds 1.00.27
You can use ActiveRecord::Relation#find_or_initialize_by. This assumes that you have enough attributes known at the time to uniquely identify the record.
That will solve the last part of your question. But it sounds like the id column is not set to auto-increment. You need to set that so when Rails sends a null id the DB will set it properly.
Update: To make the column auto-increment you will need to run a migration. You can then do:
Alter table books modify column id int(11) auto_increment;
Update: If we cannot modify the database we can do:
class Book
private
def create_or_update(*args, &block)
self.id ||= Book.maximum(:id) + 1
super
rescue ActiveRecord::RecordNotUnique
self.id = nil
retry
end
end
It's pretty janky, and I would highly recommend updating the column if possible.

Seeds file - adding id column to existing data

My seeds file populated the countries table with a list of countries. But now it needs to be changed to hard-code the id (instead of rails generating the id column for me).
I added the id column and values as per below:
zmb: {id: 103,code: 'ZMB', name: Country.human_attribute_name(:zambia, default: 'Error!'), display_order: nil, create_user: user, update_user: user, eff_date: Time.now, exp_date: default_exp_date},
skn: {id: 104,code: 'SKN', name: Country.human_attribute_name(:st_kitts_and_nevis, default: 'Error!'), display_order: nil, create_user: user, update_user: user, eff_date: Time.now, exp_date: default_exp_date}
countries.each { |key, value| countries_for_later[key] = Country.find_or_initialize_by(id: value[:id]); countries_for_later[key].assign_attributes(value); countries_for_later[key].save!; }
Above it just a snippet. I have added an id: for every country.
But when I run db:seed I get the following error:
ActiveRecord::RecordInvalid: Validation failed: Code has already been taken
I am new to rails so I'm not sure what is causing this - is it because the ID column already exists in the database?
What I think is happening is you have existing data in your database ... let's say
[{id:1 , code: 'ABC'},
{id:2 , code: 'DEF'}]
Now you run your seed file which has {id: 3, 'DEF'} for example.
Because you are using find_or_initialize_by with id you are running into errors. Since you can potentially insert duplicates.
I recon you should just clear your data, but you can try doing find_or_initialize_by using code instead of id. That way you wont ever have a problem of trying to create a duplicate country code.
Country.find_or_initialize_by(code: value[:code])
I think you might run into problems with your ids, but you will have to test that. It's generally bad practice to do what you are doing. Whether they ids change or now should be irrelevant. Your seed file should reference the objects that are being created not ids.
Also make sure you aren't using any default_scopes ... this would affect how find_or_initialize_by works.
The error is about Code: Code has already been taken. You've a validation which says Code should be uniq. You can delete all Countries and load seeds again.
Run this in the rails console:
Country.delete_all
Then re-run the seed:
rake db:seed
Yes, it is due to duplicate entry. In that case run ModelName.delete_all in your rails console and then run rake db:seed again being in the current project directory. Hope this works.
ActiveRecord::RecordInvalid: Validation failed: Code has already been taken
is the default error message for the uniqueness validator for :code.
Running rake db:reset will definitely clear and reseed your database. Not sure about the hardcoded ids though.
Check this : Overriding id on create in ActiveRecord
you will have to disable protection with
save(false)
or
Country.create(attributes_for_country, without_protection: true)
I haven't tested this though, be careful with your validators.
Add the line for
countries_for_later[key].id = value[:id]
the problem is that you can't set :id => value[:id] to Country.new because id is a special attribute, and is automatically protected from mass-assignment
so it will be:
countries.each { |key, value|
countries_for_later[key] = Country.find_or_initialize_by(id: value[:id])
countries_for_later[key].assign_attributes(value)
countries_for_later[key].id = value[:id] if countries_for_later[key].new_record?
countries_for_later[key].save(false)
}
The ids data that you are using in your seeds file: does that have any meaning outside of Rails? Eg
zmb: {id: 103,code: 'ZMB',
is this some external data for Zambia, where 103 is it's ID in some internationally recognised table of country codes? (in my countries database, Zambia's "numcode" value is 894). If it is, then you should rename it to something else, and let Rails decide what the id field should be.
Generally, mucking about with the value of ID in rails is going to be a pain in the ass for you. I'd recommend not doing it. If you need to do tests on data, then use some other unique field (like 'code') to test whether associations etc have been set up, or whatever you want to do, and let Rails worry about what value to use for ID.

Ruby on Rails SQLite3 Table ID numbers

I am practicing with "Rails: Up and Running" book. There is written that database fills id attributes by itself if not added. Here's an example.
some_items.yml:
one:
name: stringOne
two:
name: stringTwo
Ok. Now, I'm making migration and loading data with "rake db:migrate" and "rake db:fixtures:load".
Then I am trying to check what table "some_items" contains. While sending SQL query to SQLite3 console I'm receiving output like this:
298486374|stringTwo|2015-04-06 14:00:33|2015-04-06 14:00:33
980190962|stringOne|2015-04-06 14:00:33|2015-04-06 14:00:33
I am wondering is SQLite3 fills ID with random numbers? And if so, why not in normal ascending order like 1, 2, ...? And again - if so, why Rails doesn't create ID attribute in YAML testing files to show it in proper order?
I am wondering is SQLite3 fills ID with random numbers?
No, Rails is doing it.
And if so, why not in normal ascending order like 1, 2, ...?
I believe that Rails does it for fixture in order to be as much platform-independent as possible and keep it simple.
And again - if so, why Rails doesn't create ID attribute in YAML testing files to show it in proper order?
If you want to have sequencial IDs, you can have them in your fixtures files:
one:
id: 1
name: stringOne
two:
id: 2
name: stringTwo

Rails 3.1 in production mode saves model to database but returns id zero

Hello!
It is happening a very weird thing when I save a model in rails 3.1
Same project in development / production
There is a simple model City with 2 fields (name / state).
development console
city = City.new(:name => "Sao Paulo", :uf => "SP")
city.save (true, no validations errors and record is created in database)
if I call "city.id", I get real value of ID saved in database, e.g. 123.It is working fine in development.
production console
city = City.new(:name => "Sao Paulo", :uf => "SP")
city.save (true, no validations errors and record is created in database)
if I call "city.id", I get value equal 0 (different from value created automatically in database)
City.last (check if record is created in database...)
<City id: 22257, name: "Sao Paulo", uf: "SP", created_at: "2011-11-23 00:52:53", updated_at: "2011-11-23 00:52:53">
The record is created in database but not returns ID after call save/create. Did anyone see this before? This is the first time I see this behavior in production. It happens to every model in project. I used model City because it is the simplest one.
I am running rails 3.1.
Thanks for help!
city.id = 0 will not do anything to your database, it will only modify the instance you have of City, to actually write it to your database you should use .save on your instance after changing its attributes.
Also manually setting the value of a auto-increment unique sql column seems like asking for bad things to happen.
Also I think ids start with 1 and not 0, trying to put it to 0 will probably not be accepted by MySQL.
I found the solution!!! I was using 'mysql' gem insted 'mysql2'. When I changed to mysql2 (in Gemfile and database.yml adapter), everything worked fine. It is probably an incompatibility issue betwen gem 'mysql' and rails 3.1. Thanks you all for help!

Resources