Seeds file - adding id column to existing data - ruby-on-rails

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.

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.

How can I get a hash of the contents of a Rails fixture instance?

This is using Rails 4.2.0, Ruby 2.2.0.
What I'd like to do is use the data contained in a fixture object to verify that duplicates are caught before insertion into the same database:
test "identical entries should be impossible to create" do
dup_entry = Entry.new(entries(:test_entry))
assert_not dup_entry.save
end
(where Entry is a well-defined model with a controller method .new, and test_entry is a fixture containing some valid Entry instance.)
Unfortunately, this doesn't work because entries(:test_entry) is going to be an Entry, not a hash accepted by Entry.new.
I know that I can access fixture properties with an expression of the form fixture_objname.property in the associated tests, since whatever is specified in the YAML will automatically be inserted into the database and loaded. The problem with this is that I have to manually retype a bunch of property names for the object I just specified in the YAML, which seems silly.
The documentation also says I can get the actual model instances by adding self.use_instantiated_fixtures = true to the test class. However, there don't seem to be any instance_methods that will dump out the fixture's model instance (test_entry) in a hash format to feed back into the .new method.
Is there an idiomatic way to get what I want, or a different, easier way?
I believe you're looking for something like:
entries(:test_entry).attributes
entries(:test_entry).attributes.class # => Hash
You can also remove properties if needed:
entries(:admin).attributes.except("id")
Hope this helps.

Rails refuses to store a value in a column named `authorization`

I've fought a few hours now to store a string in a database column in Rails.
I had to rename authorization to transaction so that Rails would store the value.
Why does Rails interfere while saving the value?
Example:
# Works
self.update_attribute(:transaction, result) rescue nil
# Does not work
self.update_attribute(:authorization, result) rescue nil
What is your underlying database? It might have "authorization" as a reserved word.
See the generated sql and run it directly to your db. If it runs without problems, then my assumption is invalid.
Both mySQL and SQLserver use authorization as a reserved word.
So you'll just need to use the different word.
You could also use something close like 'authorized' or 'auth'.
maybe try prefixing the column using the table name? For example:
UPDATE my_table
SET my_table.authorization = "new authorization"
WHERE id = 5

Ruby on Rails - Testing Database

Is there a way to prevent Rails from clearing the test database prior to running the test? I want to use a copy of my production database for testing so I need to prevent rails from clearing out the data every time.
There is quite a bit of data so I would like to avoid using fixtures if possible since I'm assuming it would take a long time to populate the db every time.
Thanks,
Mike
You can avoid it by running the tests manually
ruby -Itest test/unit/some_test.rb
It is the rake task which does the test db recreation (you can run it manually like so)
rake db:test:prepare
But my suggestion is that you're doing it wrong.
The general idea in testing is that you know the state of the database, and therefore know what to expect from a function.
eg.
test "search_by_name" do
expected = User.all.select{|u| u.name =~ /arthur/i}
assert_equal expected, User.search_by_name("Arthur")
end
is a fine test
however, if you don't know the state of the db, how do you know there is an arthur?
The test above would pass in three bad cases;
there are no User records
all Users are called "Arthur"
there are no users called arther.
So its better to create a false reality,
where we know the state of the database.
We want;
at least one user with the name "Arthur"
at least one user with the name "Arthur" as part of another word
at least one non-Arthur user.
a better test, assuming the db is empty, and using factory girl, may be.
test "search_by_name" do
expected = [
Factory.create(:user, :name => "Arthur"),
Factory.create(:user, :name => "MacArthur")
]
not_expected = [Factory.create(:user, :name => "Archer")]
assert_equal expected, User.search_by_name("Arthur")
end
Let me preface this by saying you generally don't want to use production data for testing. That being said...
You could load "seed" data but make sure you don't have any fixtures for it otherwise it will get deleted for every test run.
See this answer for ways to automatically seed data.

Rails - Data Migration vs. value in DB

I have a question about where values in dropdowns are coming from:
I have a migration that set up the original table with some initial values:
add_column :contracts, :signature_status_id, :integer
# lookup data
sig = SignatureStatus.new(:name => "Delivered")
sig.save!
sig = SignatureStatus.new(:name => "Signed")
sig.save!
I have a table called signature_statuses that contains the updated values:
id, name
1, 'Delivered; awaiting signature'
2, 'Delivered; awaiting full execution'
3, 'Terms being negotiated'
4, 'Fully executed and filed'
I have a form that contains the code to pull out the signature status:
<%= collection_select(:contract, :signature_status_id, #signature_statuses, :id, :name) %>
The collection select is pulling in "Signed" and "Delivered" when I want it to be from the DB. How do I make it do that.
Note: I think that the data was edited manually rather than a migration, but I'm not sure. I also searched the code for "signed" and "delivered", but the only place it shows up is in the migration.
I'm just wondering how are you getting that list of values in the signature_statuses table? Are you querying your development database? Is your application running in development mode? Is the database.yml file setup correctly to point to your development database?
Also can you post the controller code that populates the #signature_statuses variable.
A little more info and I'm sure people will be able to help.
Hmmm, this is a bit odd, but i suspect the following: there might be a method called name inside your signature_status model which is overriding the default one and which returns yes and no.
The key to debugging this is to look where
#signature_statuses
is being set in the controller. If it's pulling from the database, then that is what is in the database. I wonder if there is more than one database involved, where your migration updated the development database, but you are running the query against production (or something like that).
It turns out I needed to run "rake db" and that fixed it.

Resources