Rails migrate with different databases - ruby-on-rails

I need use Devise with a existing user table in another database, so I config my database.yml:
development:
adapter: postgresql
encoding: unicode
database: almoxarifado
pool: 5
timeout: 5000
username: user
password: pass
host: localhost
users_development:
adapter: postgresql
encoding: unicode
database: portal
pool: 5
timeout: 5000
username: user
password: pass
host: localhost
schema_search_path: users
In my model I wrote this code:
class User < ActiveRecord::Base
establish_connection "users_#{Rails.env}"
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
When I run Rails c and call model class this work very well.
A migration file:
class AddDeviseToUsers < ActiveRecord::Migration
def self.up
ActiveRecord::Base.establish_connection "users_#{Rails.env}"
change_table(:users) do |t|
## Database authenticatable
t.string :email, :null => false, :default => ""
t.string :encrypted_password, :null => false, :default => ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, :default => 0, :null => false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, :default => 0, :null => false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
# Uncomment below if timestamps were not included in your original model.
# t.timestamps
end
add_index :users, :email, :unique => true
add_index :users, :reset_password_token, :unique => true
# add_index :users, :confirmation_token, :unique => true
# add_index :users, :unlock_token, :unique => true
ActiveRecord::Base.establish_connection "#{Rails.env}"
end
def self.down
# By default, we don't want to make any assumption about how to roll back a migration when your
# model already existed. Please edit below which fields you would like to remove in this migration.
raise ActiveRecord::IrreversibleMigration
end
end
But rake db:migrate return this error:
== AddDeviseToUsers: migrating =======================================
-- change_table(:users)
rake aborted!
An error has occurred, this and all later migrations canceled:
undefined method `rollback' for #<ActiveRecord::ConnectionAdapters::ClosedTransaction:0xbb06767c>
Tasks: TOP => db:migrate
(See full trace by running task with --trace)

Since I spent a lot of time trying with all alternatives in this and other posts, without success, here the solution that works to me
database.yml
external_database:
adapter: postgresql
database: <%= ENV['DATABASE_NAME'] %>
username: <%= ENV['DATABASE_USERNAME'] %>
password: <%= ENV['DATABASE_PASSWORD'] %>
host: <%= ENV['DATABASE_HOST'] %>
port: <%= ENV['DATABASE_PORT'] || 5432 %>
models/yourmodel.rb
class YourModel < ActiveRecord::Base
establish_connection :external_database
end
your migration
class YourMigration < ActiveRecord::Migration
def self.up
YourModel.connection.enable_extension "plpgsql"
YourModel.connection.create_table :your_table_name, id: :uuid do |t|
t.text :title
...
end
end
def self.down
YourModel.connection.drop_table :your_table_name
end
end
Explanation:
Since the ActiveRecord::Base.establish_connection closes the connection, I'm using the connection from the model directly. Also, since I'm using UUID with the method uuid_generate_v4() as default, I enabled the extension using the same connection

=================================UPDATE=================================
I find a nice article talking about it: http://www.thegreatcodeadventure.com/managing-multiple-databases-in-a-single-rails-application/
=================================UPDATE=================================
I just put establish_connection out of a migrate class.
ActiveRecord::Base.establish_connection "users_#{Rails.env}"
class AddDeviseToUsers < ActiveRecord::Migration
def self.up
change_table(:users) do |t|
## Database authenticatable
t.string :email, :null => false, :default => ""
t.string :encrypted_password, :null => false, :default => ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, :default => 0, :null => false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, :default => 0, :null => false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
# Uncomment below if timestamps were not included in your original model.
# t.timestamps
end
add_index :users, :email, :unique => true
add_index :users, :reset_password_token, :unique => true
# add_index :users, :confirmation_token, :unique => true
# add_index :users, :unlock_token, :unique => true
end
def self.down
# By default, we don't want to make any assumption about how to roll back a migration when your
# model already existed. Please edit below which fields you would like to remove in this migration.
raise ActiveRecord::IrreversibleMigration
end
end

Create a method in your migration class
def self.connection
ActiveRecord::Base.establish_connection("#{Rails.env}_yourcustomenv").connection
end
took me some time to realize a method declared with self was needed. This worked on Rails 4.0

You were almost at the solution.
Just had to do User.connection instead of ActiveRecord::Base.establish_connection.
Take a look at the solution here:
https://stackoverflow.com/a/34292909/2499227
The ActiveRecord::Base.establish_connection way always gave me issues with schema_migrations and/or connection closing, but the Model connection runs smoothly.

In your config/database.yml :
development_custom_db:
database: almoxarifado
username: user
password: pass
host: localhost
Create a file contain this , in the path config/initializers/ :
module Custom
class Base < ActiveRecord::Base
self.abstract_class = true
connection_name = "#{Rails.env}_custom_db"
if Rails.application.config.database_configuration.has_key?(connection_name)
establish_connection connection_name.to_sym
end
end
class Migration < ActiveRecord::Migration
def connection
#_connection ||= Custom::Base.connection
end
end
end
Then inherit it at your migration file:
class AddDeviseToUsers < Custom::Migration
def up
change_table(:users) do |t|
end
end
end

Used a bit simpler approach and it worked for me on Rails 6.
class AddDimensionsToReportRecords < ActiveRecord::Migration[6.0]
def change
add_column :report_records, :sample_length, :decimal
add_column :report_records, :sample_height, :decimal
add_column :report_records, :sample_width, :decimal
end
def connection
ReportRecord.connection
end
end
And the model...
class ReportRecord < ApplicationRecord
connects_to database: { writing: :reports_db, reading: :reports_db_replica }
end

Related

Rails 4 SQLite3::SQLException error

I have a devise User model which I want to add a admin boolean field so I ran
rails generate migration add_admin_to_users admin:boolean which created the following migration
class AddAdminToUsers < ActiveRecord::Migration
def change
add_column :users, :admin, :boolean
end
end
However when I run rake db:migrate I keep getting the following error
SQLite3::SQLException: no such table: users: ALTER TABLE "users" ADD "admin" boolean/home/notebook/.rvm/gems/ruby-2.0.0-p353/gems/sqlite3-1.3.8/lib/sqlite3/database.rb:91:in `initialize'
I have tried to rake db:migrate VERSION=0 to rollback to the beginning and redone rake db:migrate again but I keep getting the same error
Here is my user model migration file
class DeviseCreateUsers < ActiveRecord::Migration
def change
create_table(:users) do |t|
## Database authenticatable
t.string :email, :null => false, :default => ""
t.string :encrypted_password, :null => false, :default => ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, :default => 0, :null => false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, :default => 0, :null => false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps
end
add_index :users, :email, :unique => true
add_index :users, :reset_password_token, :unique => true
add_index :users, :confirmation_token, :unique => true
# add_index :users, :unlock_token, :unique => true
end
end
My db/schema.rb file is as follows:
ActiveRecord::Schema.define(version: 20140217093954) do
create_table "entities", force: true do |t|
t.string "entity"
t.string "genre"
t.string "url"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "entities", ["entity"], name: "index_entities_on_entity", unique: true
end
It sounds like your development database is not in sync with the schema (or what you think the schema is). Running database migrations is intended for incremental changes and not intended to be be a way to rollback the database and run from the first migration. For this you want to use rake db:reset, which will drop the database and load the schema from db/schema. This post has a good overview of the different database rake commands.
Your database has not users table, you need to create users table first:
rails g migration create_users
And put that migration before the one you are trying to run now, i.e. changing its timestamp.
An easier option is to add in your current file:
class AddAdminToUsers < ActiveRecord::Migration
def change
create_table :users
add_column :users, :admin, :boolean
end
end
Whatever of the solutions you apply, you have a problem with the order of your migrations, search inside migrate folder for a migration that actually creates users table.

rails generate devise User error when migrating

I'm a bit new in Rails world and I try to add a new field in devise schema.
I found this :
rails generate model NAME [field[:type][:index] field[:type]
and tried to apply the command :
rails generate devise User linkedin:string
The process seemed correct :
invoke active_record
create db/migrate/20130902085306_add_devise_to_users.rb
insert app/models/user.rb
route devise_for :users
But when I launch a db:migrate it occures an error :
PG::Error: ERROR: column "email" of relation "users" already exists
What did I do wrong ? why does it say (and is it related) email is wrong while it was ok before ?
Thanks a lot !
Here is the migration file result :
class AddDeviseToUsers < ActiveRecord::Migration
def self.up
change_table(:users) do |t|
## Database authenticatable
t.string :email, :null => false, :default => ""
t.string :encrypted_password, :null => false, :default => ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, :default => 0
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
## Token authenticatable
# t.string :authentication_token
t.string :linkedin
# Uncomment below if timestamps were not included in your original model.
# t.timestamps
end
add_index :users, :email, :unique => true
add_index :users, :reset_password_token, :unique => true
# add_index :users, :confirmation_token, :unique => true
# add_index :users, :unlock_token, :unique => true
# add_index :users, :authentication_token, :unique => true
end
def self.down
# By default, we don't want to make any assumption about how to roll back a migration when your
# model already existed. Please edit below which fields you would like to remove in this migration.
raise ActiveRecord::IrreversibleMigration
end
end
Does your users table already have email field? It seems there is, you have to remove creating an email field again by removing it from migration and make sure it is not referenced twice in user.rb model
db/migrate/20130902085306_add_devise_to_users.rb
Ok!
I've found what was wrong : I created a new table instead of updating existing one.
So the good task was :
rails g migration add_columnLinkedin_to_users
Then adding in the new created migration file :
change_table :users do |t|
t.string :linkedin
end
And db:migrate was a success !
Thanks for your helps !

Gem devise set multiple user model system

In my app i need to do two way-login system:
1) User - only for user part of website, contain's information about user's, it's login data, etc...
2) Admin - another model for admin part of website.
But how to do this?
Now i have only first part, and my migration:
class DeviseCreateUsers < ActiveRecord::Migration
def self.up
create_table(:users) do |t|
## Database authenticatable
t.string :email, :null => false, :default => ""
t.string :encrypted_password, :null => false, :default => ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, :default => 0
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Encryptable
# t.string :password_salt
## Confirmable
#t.string :confirmation_token
#t.datetime :confirmed_at
#t.datetime :confirmation_sent_at
#t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
#t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts
#t.string :unlock_token # Only if unlock strategy is :email or :both
#t.datetime :locked_at
## Token authenticatable
t.string :authentication_token
t.timestamps
end
add_index :users, :email, :unique => true
add_index :users, :reset_password_token, :unique => true
#add_index :users, :confirmation_token, :unique => true
#add_index :users, :unlock_token, :unique => true
add_index :users, :authentication_token, :unique => true
end
def self.down
drop_table :users
end
end
And route:
devise_for :users
But how to add second admin-login part?
You have 3 options:
Add string field type to your table and derive from User model (the admin and user will have the same login route).
Add boolean field admin and use i.e. CanCan gem.
Run rails g devise admin and have separate admin model.
The way you choose depends on your app struct.

Rerunning a Migration - Devise Gem

I am developing a rails app and I added the Devise gem to authenticate users. I am now further along with the development and I want to add some of the modules Devise comes packaged with (specifically Confirmable, Lockable, and Token authenticatable). These modules were in the original migrate file commented out. I was wondering if it is possible to simply uncomment these modules and then run "rake db:migrate". Can you rerun a migration like this, or will it break something?
I would have tested this out, but given the work I have put into development, I do not want to break anything at this point. Here is the migration file as it stands:
class DeviseCreateUsers < ActiveRecord::Migration
def change
create_table(:users) do |t|
## Database authenticatable
t.string :email, :null => false, :default => ""
t.string :encrypted_password, :null => false, :default => ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, :default => 0
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
## Token authenticatable
# t.string :authentication_token
t.timestamps
end
add_index :users, :email, :unique => true
add_index :users, :reset_password_token, :unique => true
# add_index :users, :confirmation_token, :unique => true
# add_index :users, :unlock_token, :unique => true
# add_index :users, :authentication_token, :unique => true
end
end
Secondly, if I cannot rerun a migration, I realize that I will have to create new migrations and add the modules to the 'Users' model manually. For example:
rails generate migration AddConfirmationToUsers string:confirmation_token datetime:confirmed_at datetime:confirmation_sent_at string:unconfirmed_email
However, I will need to 'add_index' to this new migration just as with the original migration Devise created. Will I just place the 'add_index' at the bottom of the migration (ie, after the 'add_column' method)? Maybe something like this: ?
class AddConfirmableToUsers < ActiveRecord::Migration
def change
add_column :users, :price, :decimal
add_column :users, :confirmation_token, :string
add_column :users, :confirmed_at, :datetime
add_column :users, :confirmation_sent_at, :datetime
add_column :users, :unconfirmed_email, :string
add_index :users, :confirmation_token, :unique => true
end
end
As a recap:
Can I rerun migrations (edit a previous migration and then run
'rake db:migrate')?
If I cannot rerun migrations, where would I
place the 'add_index' line in the new migration?
You can't just edit the file and run db:migrate. Rails keeps track of which migrations have been run and which have not. Rails thinks that it has already run that migration.
The right thing to do is create a new migration just as you've suggested above.
It sound like you're worried that you're going to trash your database and lose a lot of work. I would suggest that you backup the database before you go forward with this. Database backups and git can help remove a lot of the fear that comes with making changes that you're not sure about.

Devise install from existing model/database

I am wondering how can i add devise to an existing database with a different user. Here I already have a customer model define and I want to change to allow devise to work on it.
I have created a new migration and inserted the code has follow
class AddDeviseToCustomer < ActiveRecord::Migration
def change
change_table :customers do |t|
#t.database_authenticatable
t.string :encrypted_password, :null => false, :default => '', :limit => 128
t.confirmable
t.recoverable
t.rememberable
t.trackable
t.token_authenticatable
t.timestamps
end
end
end
According to this it should work. https://github.com/plataformatec/devise/wiki/How-To:-change-an-already-existing-table-to-add-devise-required-columns. But when running rake db:migrate i get the following
undefined method `confirmable' for #<ActiveRecord::ConnectionAdapters::Table:0x9286a28>
I have run the following line
rails g devise:install
Any reason devise won't recognize it, do i need to do something to say customer is a devise??
Thanks in advance
It looks like that documentation is outdated.
Try using devise generator, it will create same migration, with correct parameters, it's ok if its an existing model:
rails g devise customer
it should create AddDeviseToCustomers migration
with something similar to this:
class AddDeviseToCustomers < ActiveRecord::Migration
def self.up
change_table(:customers) do |t|
## Database authenticatable
t.string :email, :null => false, :default => ""
t.string :encrypted_password, :null => false, :default => ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, :default => 0
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
## Token authenticatable
# t.string :authentication_token
# Uncomment below if timestamps were not included in your original model.
# t.timestamps
end
def self.down
# By default, we don't want to make any assumption about how to roll back a migration when your
# model already existed. Please edit below which fields you would like to remove in this migration.
raise ActiveRecord::IrreversibleMigration
end
end
note that there is no more t.confirmable

Resources