Recently I upgraded my application from Rails 4.2 to Rails 5.0.3. Then I am trying to run a migration:
class AddInvitationToJoinToContests < ActiveRecord::Migration
def change
add_column :goods, :new_field, :string
end
end
The migration ran correctly, but other table fields's limit option are gone:
create_table "users", force: :cascade do |t|
- t.string "name", limit: 191, null: false
- t.string "language", limit: 191
- t.string "image_id", limit: 191
+ t.string "name", null: false
+ t.string "language"
+ t.string "image_id"
end
Any idea why?
Related issue:
Rails 4.2 stops to add limit - rails/rails#19001
This could be because Rails 5 does not list the "limit" for columns if the limit matches the default for that column's data type.
https://github.com/rails/rails/commit/835617b71d2e829c27dbd16a82f22c186c821a0f
You can see in the comments of this changeset that this is to keep the schema.rb database-agnostic. I would suspect that for whichever database adapter you're using, the default 'limit' for string types is 191.
Related
Description
I'm on Ruby version 2.6.6 and Ruby on Rails version 6.0.3.2.
Models
Book
Author
Associations
A Book belongs to an Author.
An Author has many Books.
Goal
Remove the Author model and add it as a column to a Book (of type string).
What I've Done
I created and ran 3 migrations, in the following order:
AddAuthorToBooks
class AddAuthorToBooks < ActiveRecord::Migration[6.0]
def change
add_column :books, :author, :string
end
end
DropAuthors
class DropAuthors < ActiveRecord::Migration[6.0]
def change
drop_table :authors do |t|
t.string "full_name", null: false
t.timestamps null: false
end
end
end
RemoveAuthorForeignKeyFromBooks
class RemoveAuthorForeignKeyFromBooks < ActiveRecord::Migration[6.0]
def change
remove_foreign_key :books, :authors
end
end
Schema
Unfortunately, I don't have the schema before I ran the migrations. (I tried checking out an older commit, but the schema file stubbornly refuses to change.)
Here is the current version:
ActiveRecord::Schema.define(version: 2020_08_11_125724) do
create_table "books", force: :cascade do |t|
t.string "title"
t.text "description"
t.string "cover_url"
t.decimal "price"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "author_id", null: false
t.string "author"
t.index ["author_id"], name: "index_books_on_author_id"
end
create_table "books_genres", id: false, force: :cascade do |t|
t.integer "book_id", null: false
t.integer "genre_id", null: false
t.index ["book_id", "genre_id"], name: "index_books_genres_on_book_id_and_genre_id"
t.index ["genre_id", "book_id"], name: "index_books_genres_on_genre_id_and_book_id"
end
create_table "genres", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "reviews", force: :cascade do |t|
t.string "username"
t.decimal "rating"
t.text "body"
t.integer "book_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["book_id"], name: "index_reviews_on_book_id"
end
add_foreign_key "reviews", "books"
end
Problem
The author table has been removed, the add_foreign_key "books", "authors" (I think that's how it went) is gone, too, but the author_id stubbornly remains in the books table.
Furthermore, there's an integer author_id and an index of the same name.
What I'm Planning
I thought of just deleting these 2 columns with another migration, but I don't know if that would...
...fix the issue and erase the old Author model completely,
...and that it's the clean/recommended way of doing things. If needed, I could roll back the migrations and try a better method.
About the unusual `schema.rb` behavior
I tried checking out an older commit, but the schema file stubbornly refuses to change.
This is quite unusual. Do verify that you're tracking the db/schema.rb file using git. If it is tracked, there's no reason why checking out an older commit shouldn't return it to the older state. At that point, you should be able to:
$ rails db:drop
$ rails db:create
$ rails db:schema:load
...to load the old schema into the database. Then, you should be able to return to the latest code with git, and run pending migrations after the date at which the older schema was created.
About a cleaner way to implement this
Before writing the below migration, the first step would be to remove any existing relationship written in the Book class. For example:
# app/models/book.rb
class Book < ApplicationRecord
# The line below should be deleted! Otherwise, it will probably interfere
# with the `book.update!(author: ...)` line in the migration.
belongs_to :author
end
I've taken to writing related migrations in a single file, since they're all related. To me, this looks like:
class MoveAuthorToBooks < ActiveRecord::Migration[6.0]
class Author < ApplicationRecord
end
class Book < ApplicationRecord
end
def up
# Start by adding a string column.
add_column :books, :author, :string
# Let's preserve existing author names.
Book.all.each do |book|
author = Author.find(book.author_id)
book.update!(author: author.name)
end
# Now that the names have been moved to the books table, we don't
# need the relationship to `authors` table anymore. This should
# also delete any related foreign keys - manual foreign key deletion
# should not be required.
remove_column :books, :author_id
# Alternative: If you'd created the `authors_id` column using the
# `add_reference` command, then it's probably best to use the opposite
# `remove_reference` command.
#
#remove_reference :books, :author, index: true, foreign_key: true
# Finally, remove the `authors` table.
drop_table :authors
end
def down
# This can be technically be reversed, but that'll need some more code that
# reverses the action of the `up` function, and it may not be needed.
raise ActiveRecord::IrreversibleMigration
end
end
I am trying to add a new column active to my table students.
I ran rails g migration add_active_to_students active:boolean to generate this migration:
class AddActiveToStudents < ActiveRecord::Migration[5.0]
def change
add_column :students, :active, :boolean, default: true
end
end
But when I run rails db:migrate I get this error:
PG::DuplicateColumn: ERROR: column "active" of relation "students" already exists
: ALTER TABLE "students" ADD "active" boolean DEFAULT 't'`
As you can see there is not actually an active column in students:
create_table "students", force: :cascade do |t|
t.integer "club_id"
t.string "email"
t.string "address_line_1"
t.string "address_line_2"
t.string "city"
t.string "state"
t.integer "postcode"
t.string "phone1"
t.string "phone2"
t.string "first_name"
t.string "last_name"
t.date "dob"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "picture"
t.integer "payment_plan_id"
t.string "parent1"
t.string "parent2"
t.string "size"
t.text "notes"
t.index ["club_id"], name: "index_students_on_club_id", using: :btree
end
So why would I be getting this error?
I followed the steps that #demir posted and found that, yes, the column was in the database without being listed in the schema. ALTER TABLE students DROP COLUMN active did not give an error message however it also didn't remove the column.
In the end I removed it by:
Entering the console
rails console
Deleting the column
ActiveRecord::Base.connection.remove_column :students, :active
You may have added it somehow. Have you checked the PG database? Connect to the application database and see if there is an active field.
List databases
\l
Connect database
\c your_app_database_name
List table columns
\d+ students
Check active field, remove it if exist.
ALTER TABLE students DROP COLUMN active
You have this column in your DB, but it wasn't dumped to your schema.rb. Maybe a migration was stopped after it added the column, but before it wrote to schema.rb?
You can remove this column manually, running rails dbconsole and then:
ALTER TABLE students DROP COLUMN active
You can follow the above solutions i.e. drop the column from DB first and then run the migration. Most probably what has happened is, you created and run some migration but later deleted that migration file without doing a db:rollback.
One more option that you can consider is to put a conditional migration like:
class AddActiveToStudents < ActiveRecord::Migration[5.0]
def change
unless column_exists? :students, :active
add_column :students, :active, :boolean, default: true
end
end
end
I have a Rails5 api that was built from scratch and all of my models' database sequences for id autoincrement where automatically created. When a colleague attempted to db:setup the database on a fresh machine, the schema was loaded, but the sequences were not created and, therefore, our db:seeds were unable to be added because there is a "not null" constraint on the tables.
The original table id column looks like this
id | integer | not null default nextval('users_id_seq'::regclass) | plain |
After running db:setup, the "new" database on the fresh machine looks like this
id | integer | not null | plain |
I've never had this issue with previous Rails versions and wonder if it may be a v5 issue. What are we doing wrong?
Thanks for any tips!!
On the new database, there are no sequences shown in postgres when running "\ds;"
The migration for the users table looks looks like this
class CreateUsers < ActiveRecord::Migration[5.0]
def change
create_table :users do |t|
t.string :first_name, limit: 25
t.string :last_name, limit: 25
t.string :mobile_phone, limit: 25
t.string :auth_token, limit: 36
t.integer :failed_login_attempts, limit: 2, :default => 0
t.boolean :account_locked, :default => false
t.timestamps
end
end
end
schema.rb for this table looks like this ( after some later migrations )
create_table "users", id: :integer, force: :cascade do |t|
t.string "first_name", limit: 25
t.string "last_name", limit: 25
t.string "mobile_phone", limit: 25
t.string "auth_token", limit: 36
t.integer "failed_login_attempts", limit: 2, default: 0
t.boolean "account_locked", default: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "email", default: "", null:
t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
end
I am running into SQLiteException that seems to be causing problem.
Schema.rb
create_table "features", force: :cascade do |t|
t.string "name_key"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "features", ["name_key"], name: "index_features_on_name_key"
create_table "organizations", force: :cascade do |t|
t.string "name"
t.string "code"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "organizations_features", id: false, force: :cascade do |t|
t.integer "organization_id"
t.integer "feature_id"
end
This is current schema and i explicity created table organizations_features(still through migration but a separate migration that references a join table) as otherwise create_join_table would create "features_organizations". In that process, if i run
rake db:drop db:create db:schema:load
I still keep getting the following error even without loading a single record in any tables(i never ran db:seed).
ActiveRecord::StatementInvalid: SQLite3::SQLException: near ")": syntax error: INSERT INTO "organizations_features" () VALUES ()
The other question seem to suggest to make the join table name all singular as in organization_feature, but since we share the schema with other services, it is imperative that we use the same naming conventions.
Note: even i tried to create table using migration "create_join_table" the problem seem to persist"
Update : seeds.rb
organization = Organization.create!(name: 'XYZ', code: 'xyz')
feature = Feature.create!(name_key: 'Some Feature')
user = User.create!(name: "user1",
email: "user#abcd.org",
password: "password123",
password_confirmation: "password123",
profile_id: profile.id)
OrganizationsFeature.create!(feature_id: feature.id, organization_id: organization.id)
where OrganizationsFeature looks like this
class OrganizationsFeature < ActiveRecord::Base
belongs_to :organization
belongs_to :feature
end
I found answer to the question in case if someone else runs into the same issue. The test/fixtures/organizations_features.yml file has null records
one :{}
two: {}
which was causing null records to be inserted into the table. This data is loaded every time and hence insert error.
Having proper data/deleting the file solved the issue.
I'm migrating a very old app from friendly_id 3.2 to 5.1.
I have a user model which currently has a cached_slug field. I created a new field called just slug. Initially, I thought I could just copy the data over from cached_slug to slug, but I noticed that there's also a whole other table called slugs which looks like this:
create_table "slugs", force: :cascade do |t|
t.string "name", limit: 255
t.integer "sluggable_id"
t.integer "sequence", default: 1, null: false
t.string "sluggable_type", limit: 40
t.string "scope", limit: 255
t.datetime "created_at"
end
Following the directions in the FriendlyID README for the Rails Quickstart, I ran rails generate friendly_id which created this table:
create_table "friendly_id_slugs", force: :cascade do |t|
t.string "slug", null: false
t.integer "sluggable_id", null: false
t.string "sluggable_type", limit: 50
t.string "scope"
t.datetime "created_at"
end
So now I'm all confused about what I need to do to complete migration. I tried creating a new user with the console and the friendly_id_slugs table is still empty, so I'm not sure when or what it's used for.
I have two questions:
1) What is this other table used for
2) What do I need to do to migrate my old slugs to the new table/field so it will continue to work moving forward?
Thanks!
If you don't mind losing the FriendlyId's History records (i.e: the old slugs),
drop the old slug table
in the rails console, run <ModelName>.find_each(&:save) for all friendly_id models.
Step 2 should regenerate new slugs.
Warning: Test before doing this on production servers!
Update: You can also perform step 2 in a migration file
class RegenerateSlugs < ActiveRecord::Migration
def change
# <ModelName>.find_each(&:save)
User.find_each(&:save)
Article.find_each(&:save)
end
end