Rails 3 HABTM join table migration - ruby-on-rails-3

I have a HABTM association between Users and Groups in my Rails 3 app. The book I'm following to learn Rails recommends running the following command line to create the join migration:
rails generate migration create_groups_users
However in the documentation it looks like I should've run:
rails generate migration create_groups_users_join_table
So that the following would be in my _create_groups_users.rb migration:
class CreateGroupsUsersJoinTable < ActiveRecord::Migration
Is adding join_table required?

Adding join_table at the end is not explicitly required. Your first command 'create_groups_users' is fine. I've done this in rails 3.0.9 and it works.
You can double check by opening up the migration file and checking that it looks like:
create_table :groups_users, :id => false do |t|
t.integer :group_id
t.integer :user_id
end
The :id => false is needed for a join table as it shouldn't have its own id field.

The last argument in your call to rails generate migration create_groups_users just denotes the class name and a part of the file name of the migration. So it does help to find the migration you have created, the migration file itself is (inside the class body) empty. So both versions are ok.

Related

Rails removing a column from activerecord not working

I want to remove container:references from my table, I have tried:
rails generate migration RemoveContainerfromCreateTasks container:references
followed by rails db:migrate, but it my reference field is still not removed.
Below is my ActiveRecord
class CreateTasks < ActiveRecord::Migration[6.1]
def change
create_table :tasks do |t|
t.string :title
t.text :body
t.references :container, null: false, foreign_key: true
t.text :tag
t.datetime :due
t.integer :priority
t.timestamps
end
end
end
class RemoveContainerfromCreateTasks < ActiveRecod::Migration[6.1]
def change
end
end
The issue here is really a sneaky capitalization error. Running:
rails generate migration RemoveContainerfromCreateTasks container:references
Will generate a migration with an empty change block which will do absolutely nothing when you migrate it except modify the migrations meta table (a table that AR uses to keep track of which migrations have been run). But if you properly capitalize From:
rails generate migration RemoveContainerFromCreateTasks container:references
It will generate:
class RemoveContainerFromCreateTasks < ActiveRecord::Migration[6.0]
def change
remove_reference :create_tasks, :container, null: false, foreign_key: true
end
end
Rails isn't actually intelligent. It just casts the name argument into snake case and compares it to a set of patterns like:
remove_something_from_tablename foo:string bar:integer
create_tablename foo:string bar:integer
create_foo_bar_join_table foo bar
And it then uses a template to generate the according type of migration. If you don't properly pluralize it will be cast into:
remove_containerfrom_create_tasks
Which Rails does not know what to do with as it does not match a known pattern.
Also note despite popular belief migrations are just a DSL to create SQL transformations which is completely unaware about your tables or models. In this case the resulting migration will just blow up when you attempt to run it since you don't have a create_tasks table.
I would roll the missnamed migration back. Delete it then run:
rails g migration RemoveContainerFromTasks container:references
rails db:migrate
Your issue here is that "CreateTasks" is not table in your database. "Tasks" is, however.
rails g migration RemoveContainerFromTasks container:references
will provide you
class RemoveContainerFromTasks < ActiveRecord::Migration[6.1]
def change
remove_reference :tasks, :container, null: false, foreign_key: true
end
end
A migration of this will successfully remove container from your schema.rb file, and subsequently the database system you're using.
Here's some console output, because why not:
unclecid#home:~/Desktop/sample_app$ rails db:migrate
== 20201227150512 CreateTasks: migrating ======================================
-- create_table(:tasks)
-> 0.0022s
== 20201227150512 CreateTasks: migrated (0.0023s) =============================
== 20201227151021 RemoveContainerFromTasks: migrating =========================
-- remove_reference(:tasks, :container, {:null=>false, :foreign_key=>true})
-> 0.0330s
== 20201227151021 RemoveContainerFromTasks: migrated (0.0331s) ================

Will renaming Rails migration files affect my live app?

I edited to contents of some of my migration files to solve an issue between Paperclip and an Attachment model, renaming it to Upload.
01234_create_attachments.rb
class CreateAttachments < ActiveRecord::Migration
def change
create_table :attachments do |t|
t.string :name
t.string :attachment_url
t.timestamps
end
end
end
became this:
01234_create_attachments.rb
class CreateUploads < ActiveRecord::Migration
def change
create_table :uploads do |t|
t.string :name
t.string :upload_url
t.timestamps
end
end
end
Note that I only edited the file contents, not the file name.
The existing app runs fine but now I can't git clone the repo to a new server because rake db:migrate fails. If I then edit the actual migration filenames on the new server it runs properly:
01234_create_attachments.rb > 01234_create_uploads.rb
My question is if I rename the migration files in my master branch will it cause problems with my existing live app when I rake db:migrate in the future?
Your file name and class name of migration should match.
schema_migrations table is used to determine if the migration should run or not. Since that table only has timestamp, unless you change the timestamp, it wouldn't rerun the migration.
Its not advised to change existing migration after it has been run on production environment. Add a new migration if you have to change anything.

Rails Article.find 1 raises ActiveRecord::StatementInvalid on legacy database

I'm creating a rails app over a legacy database table. Everything works fine locally, but on the server I hit this error whenever I do Article.find(1)
Could not log "sql.active_record" event. NoMethodError: undefined method `name' for nil:NilClass
ActiveRecord::StatementInvalid: PG::Error: ERROR: zero-length delimited identifier at or near """"
LINE 1: SELECT "articles".* FROM "articles" WHERE "articles"."" = $1 LIMIT 1
The legacy database has an id field, however the schema describes the id with
create_table "articles", :id => false, :force => true do |t|
t.decimal "id", :precision => 10, :scale => 0, :null => false
...
Note that Article.find_by_id(1) returns the record without any problem.
Any ideas how to fix this?
UPDATED - See below
It turns out, adding the following to the model fixes this:
class Article < ActiveRecord::Base
set_primary_key :id
...
Presumably, because rails didn't create the table, it doesn't know what field the primary key is. It seems a little odd though, an educated guess with a warning - or even a more friendly error message - would be appreciated.
UPDATE:
Rails 3.2+ the above method is deprecated. The new method is as follows:
class Article < ActiveRecord::Base
self.primary_key = :id
...
Thanks to #lboix for pointing this out!
Seems that this option has been updated guys : set_primary_key error in Rails
Hope it will help :)
Take care

How do i make rails 3 model type cast according to migration?

The problem that i have is when i run my migrations the updates is applied to the database, but rails does not do the same.
To be more accurate. I have a address model with a house number. Recetly i was told that the house number should be able to contain letters, like (35B). Therefore i would like to convert the integer colum to a column of strings. This is no problem in any case with my data, red. only integers.
The migration that i applied works as expected. It changes the type of the column in the postgres database and preserves the data content. I was using this migration.
class ConvertIntToStringOnNumber < ActiveRecord::Migration
def change
change_table :addresses do |t|
t.change :number, :string
end
end
end
Migration result with this schema.rb
create_table "addresses", :force => true do |t|
t.string "street"
t.string "number"
t.integer "zip"
t.string "floor"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
After running the migration on my heroku server i am not able to query the database using data from the form, this was no problem before. Rails is trying to look for an integer, but the database is containing strings.
Rails is trying to run this query, even though the schema.rb says something different. This is where the party stops.
SELECT "addresses".* FROM "addresses" WHERE "addresses"."street" = 'xxxx' AND "addresses"."number" = 63 AND "addresses"."floor" = '' AND "addresses"."zip" = 9000 LIMIT 1):
I have seen a lot of problems with forigen keys, but this is NOT one of those problems.
Did you restart your application after running the migration?
ActiveRecord loads information about your tables into each class when they are instantiated. See #columns for more info. Since Heroku runs your app in production mode, your classes won't be automatically reloaded on each request.
Try running heroku restart on your application - Rails should pick up the changes then.

Adding a column to an existing table in a Rails migration

I have a Users model which needs an :email column (I forgot to add that column during the initial scaffold).
I opened the migration file and added t.string :email, did rake db:migrate, and got a NoMethodError. Then I added the line
add_column :users, :email, :string
again rake db:migrate, again NoMethodError. Am I missing a step here?
Edit: here's the migration file.
class CreateUsers < ActiveRecord::Migration
def self.up
add_column :users, :email, :string
create_table :users do |t|
t.string :username
t.string :email
t.string :crypted_password
t.string :password_salt
t.string :persistence_token
t.timestamps
end
end
def self.down
drop_table :users
end
end
If you have already run your original migration (before editing it), then you need to generate a new migration (rails generate migration add_email_to_users email:string will do the trick).
It will create a migration file containing line:
add_column :users, email, string
Then do a rake db:migrate and it'll run the new migration, creating the new column.
If you have not yet run the original migration you can just edit it, like you're trying to do. Your migration code is almost perfect: you just need to remove the add_column line completely (that code is trying to add a column to a table, before the table has been created, and your table creation code has already been updated to include a t.string :email anyway).
Use this command on the terminal:
rails generate migration add_fieldname_to_tablename fieldname:string
and
rake db:migrate
to run this migration
Sometimes rails generate migration add_email_to_users email:string produces a migration like this
class AddEmailToUsers < ActiveRecord::Migration[5.0]
def change
end
end
In that case you have to manually an add_column to change:
class AddEmailToUsers < ActiveRecord::Migration[5.0]
def change
add_column :users, :email, :string
end
end
And then run rake db:migrate
You can also do
rake db:rollback
if you have not added any data to the tables.Then edit the migration file by adding the email column to it and then call
rake db:migrate
This will work if you have rails 3.1 onwards installed in your system.
Much simpler way of doing it is change let the change in migration file be as it is.
use
$rake db:migrate:redo
This will roll back the last migration and migrate it again.
To add a column I just had to follow these steps :
rails generate migration add_fieldname_to_tablename fieldname:string
Alternative
rails generate migration addFieldnameToTablename
Once the migration is generated, then edit the migration and define all the attributes you want that column added to have.
Note: Table names in Rails are always plural (to match DB conventions). Example using one of the steps mentioned previously-
rails generate migration addEmailToUsers
rake db:migrate
Or
You can change the schema in from db/schema.rb, Add the columns you want in the SQL query.
Run this command: rake db:schema:load
Warning/Note
Bear in mind that, running rake db:schema:load automatically wipes all data in your tables.
You can also add column to a specific position using before column or after column like:
rails generate migration add_dob_to_customer dob:date
The migration file will generate the following code except after: :email. you need to add after: :email or before: :email
class AddDobToCustomer < ActiveRecord::Migration[5.2]
def change
add_column :customers, :dob, :date, after: :email
end
end
You also can use special change_table method in the migration for adding new columns:
change_table(:users) do |t|
t.column :email, :string
end
When I've done this, rather than fiddling the original migration, I create a new one with just the add column in the up section and a drop column in the down section.
You can change the original and rerun it if you migrate down between, but in this case I think that's made a migration that won't work properly.
As currently posted, you're adding the column and then creating the table.
If you change the order it might work. Or, as you're modifying an existing migration, just add it to the create table instead of doing a separate add column.
You can also do this ..
rails g migration add_column_to_users email:string
then rake db:migrate
also add :email attribute in your user controller ;
for more detail check out http://guides.rubyonrails.org/active_record_migrations.html
You can also force to table columns in table using force: true, if you table is already exist.
example:
ActiveRecord::Schema.define(version: 20080906171750) do
create_table "authors", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
end
You could rollback the last migration by
rake db:rollback STEP=1
or rollback this specific migration by
rake db:migrate:down VERSION=<YYYYMMDDHHMMSS>
and edit the file, then run rake db:mirgate again.