Use Rails Migrations to delete an index without knowing its name - ruby-on-rails

I have a table with a compound index that wasn't created through a rails migration. Now, I need to create a rails migration that will delete this index and create a new one, but I don't necessarily know what the name of the index will be.
I know that it is possible to get a list of table names and column names within a migration step. Is it possible to get a list of index names on a particular table? Or, looking at it another way, is it possible to delete all indexes on a table? Or is the only option to write my own database-specific SQL queries to get this info?

You can get details of all the indexes on a table with:
ActiveRecord::Base.connection.indexes('tablename')
This returns an array of ActiveRecord::ConnectionAdapters::IndexDefinition objects, each of which has a #name and #columns method.

To expand on #showaltb's great answer, here is a complete migration to remove all indexes on a table, without knowing their names:
ActiveRecord::Base.connection.indexes('tablename').each do |index|
remove_index 'tablename', name: index.name
end

You could get the info directly from the database. If you're using MySQL:
>> select TABLE_NAME, INDEX_NAME from information_schema.statistics WHERE TABLE_SCHEMA = 'your_database_name';
You only need to replace the your_database_name bit. You'll need priviledges for the information_schema database (or be logging in as root).

Related

Rails activeadmin cannot detect auto increment field existing table

I have existing rails app that have some tables with some data. I did the CRUD operation directly from postgresql client before using activeadmin.
I don't know whether I missed the documentation or this is a bug: activeadmin cannot detect my existing autoincrement id for table.
If I refresh the submitted form until the auto increment id surpass the existing id in my table, it works.
First think which I could think of would be that you have passed the id parameter in permit params.
Please check that and if is present then remove it.
Secondly,as mentioned in the post that there are already data in the database so there is a problem with the sequences generated, since they can be only used once.
The solution is to set the sequence for your song_artists.id column to the highest value in the table with a query like this:
SELECT setval('song_artist_id_seq', (SELECT max(id) FROM song_artists));
I am assuming that your sequence name "song_artist_id_seq", table name "song_artist", and column name "id".
To get the sequence name run the below mentioned command:
SELECT pg_get_serial_sequence('tablename', 'columname');
For Resetting the postgres sequences from rails console:
ActiveRecord::Base.connection.tables.each do |t|
ActiveRecord::Base.connection.reset_pk_sequence!(t)
end
Another solution would be to override the save() method in your song_artist class to manually set the song_artist id for new records but is not advisable.

Rails 4: How to delete join table entries via a migration?

I have a join table in rails that has a few entries that need to be deleted.
lets say the join table is named 'products_variants'
I found out i have a few entries in this join table that were created erroneously a while ago. I have their IDs, so i could go in phpmyadmin and delete them, but I want to make a migration to do it in case anyone uses an older database (which has happened before).
Since I don't have a ruby object representing this join table I cant do something like:
ProductsVariants.find(id_array)
How would i go about deleting these entries in a rails migration?
You can create AR class for this table inside of migration and use it for delete record.
How would you do it from the console? Whatever that is, put it in the migration. For example
class Cleanup < ActiveRecord::Migration
def up
execute("delete from product_variants where id in (1,2,3)")
end
def down
end
end
Barring a solution like maxd's answer, you can also delete them via plain 'ol SQL. If you already have the list of ids, you can do something like this:
ActiveRecord::Base.connection.execute("DELETE FROM products_variants WHERE id IN (...)")
Where ... is the list of ids to delete.
Semi-pointless side-note: Technically speaking, data manipulation is not typically done in the migrations for various reason; one of them being that you're usually not necessarily guaranteed that all (or even any) migrations will be run by your colleagues (speaking very generally here), or in the case of new local project setups (meaning, you've just pulled down the project code and are setting it up locally for the first time).
While it doesn't sound like this is an issue in your scenario, if you want to do this the Rails-y way, one alternative would be to put this in a Rake task, so that you or others can execute it as needed, rather than relying on the migrations.

Best way for a rails db seed file to delete existing data in certain table before seeding, to prevent duplicate data

I have several tables in a rails app.
One table contains seeded data that I now want to update, without touching other tables.
However, simply using rake db:seed will duplicate my data, if I'm not mistaken.
Can I include a command like
ModelName.delete_all
at the beginning of a seed file?
What is the best way to do this?
According to my best practices, I will make use of first_or_initialize method to make sure we create update existing data without destroying them. For example, you have created several users in your seed data like this
3.times do |i|
User.create(email: "abc#example.com", name: "Test #{i}")
end
Then you should do like this
3.times do |i|
user = User.where(email: "abc#example.com").first_or_initialize
user.name = "New Test #{i}"
user.save
end
You could, but if ModelName has any associations at all you're going to screw up your foreign key IDs. Sounds kind of risky.
Now that said... I have some code in a seed file to load a small set of very specific data into a model. I have a hash containing all the fields that I want and then I loop through that and either find-and-update or create the rows.
providers = {
foo: {name: 'Foo', ...},
bar: {name: 'Bar', ...}
}
providers.each do |(code, attrs)|
p = Provider.find_by(code: code) || Provider.new(code: code)
p.update!(attrs)
end
Can I include a command like ModelName.delete_all at the beginning of a seed file?
Off course you can, seed.rb is just a series of ruby (and rails) commands, so you can do anything you want with it.
In some of my projects I delete the entire database from seed.rb with the following snipet:
tables = ActiveRecord::Base.connection.tables - ['schema_migrations']
tables.each do |table|
ActiveRecord::Base.connection.execute "DELETE FROM `#{table}`"
ActiveRecord::Base.connection.execute "ALTER TABLE `#{table}` AUTO_INCREMENT = 1"
end
With this snipet, no need to worry about association or linked ids. The only garbage that would remain is linked files (through carrierware or paper_trail for example) in your public folder. For that case you would need to manually clean those folders.
Another advantage of that snipet is that you don't have to update it while adding new tables in your project, because it takes care of deleting all table, except schema_migrations which is used by rails to keep track of the migration that was included/are pending

Rails and Active Record: is possible to mass assign ID?

I'm migrating a Bulletin Board from PHP (mysql) to Rails (PG) and for SEO I must keep the same IDs for topics.
My migration script use the standard class methods, so I create a new topic with mass assignment. Using this solution postgres give me an incremental ID for my topic that is different from my original ID.
Is there any way to force the ID? I know that id are unique (they come from my mysql db) but I need to keep identity for SEO and for url rewrite. I found a solution searching here but now is deprecated.
Create your migration like this:
create_table :table_name, {id: false} do |t|
t.int :your_custom_id
# .. other columns ...
end
execute "ALTER TABLE `table_name` ADD PRIMARY KEY (your_custom_id);"
Finally, to let Rails view this non-standard id field as your primary ID, add the following code in your model:
set_primary_key :your_custom_id # for Rails 3
self.primary_key = :your_custom_id # for Rails 4
Now, set this field to be free from mass-protection, and use it like a normal field.
However, I will really advise you to simply setup a new table column (i.e. field), something like old_id and not to mess with default Rails id field. Once you do that, use that field in your routes.

Where do indexes go when using STI?

I am using Rails and postgres.
I have a couple of models using STI and i was wondering where i should put indexes on the tables and why?
For example lets say i have the following setup:
class Comment < AR; end
class MovieComment < Comment; end
class MagazineComment < Comment; end
# Fake Comment Table
id:integer
title:string
body:text
type:string
Thanks!
On the type field, if you want only one of MovieComment or MagazineComment. If you don't do that, you won't need the index here. I'm not sure if AR does use type every time, but just to make sure.
Because the id field is a primary key, an index should already be there.
If you want to query by both type and id make sure you have a combined index.
On the other fields: Depends what you query on, but I suppose you want to retrieve these only.
In general, you need indices in the columns that you will use when performing queries.
If you do MovieComment.find(10), the database will use the index in the id field that Rails will add automatically for you. Same if you do Comment.find(30): Rails will retrieve the commend with id 30 using the index, then it will read the type column and it will return a MovieComment or a MagazineComment.
If you are going to add a feature to search by title, for instance, you will have to create an index in this column as well. In this case, probably a :fulltext index.
An Index in the type column would make a query like MagazineComment.all faster because it is equivalent to Comment.where(type: 'MagazineComment').all, but it is probably not worth it.

Resources