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
Related
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.
I have this model called Region and Admin
# regions.yml
one:
name: test
two:
name: test2
# admins.yml
one:
name: admin1
two:
name: admin2
There's a column on admin.rb defined as json column (store_accessor: :region_ids). How can I reference regions on admins.yml?
# admins.yml
one:
name: admin1
region_ids: ?????
This is an updated answer using erb to get the id for the various regions.
This creates structure json and queries the database to get the first and second Region IDs. These would be loaded in to the DB from the regions.yml when you run your tests.
# admins.yml
one:
name: admin1
region_ids: >
[{"region_one_id":<%= Region.first.id.to_json.inspect %>},
{"region_two_id":<%= Region.second.id.to_json.inspect %>}]
I looked into Fixture label interpolation to get the id but can't figure it out with your table holding json instead of a single id for another model.
Personally I would pre-define your ids on your regions.yml and then directly reference them in your admins.yml. This will help ensure you know the IDs and you have accurate data you've structured to test against.
I asked my colleague and got the answer. This will work:
# admins.yml
one:
region_ids: <%= Region.pluck(:id) %>
rails will load regions.yml when it gets to this point.
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.
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.
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!