Debugging ActiveRecord Migrations based on delayed_job - ruby-on-rails

I have been trying to run a migration based on the following database.yml
database.yml:
development:
adapter: postgresql
encoding: utf8
database: test_development
timeout: 5000
queue:
adapter: postgresql
encoding: utf8
database: test_queue_development
timeout: 5000
What I want to do is to create a table "delayed_jobs" in test_queue_development database.
Unfortuanately, the table gets created in test_development. The initializer for delayed_job.rb is the following.
config/initiallizers/delayed_job.rb:
db_queue_config = ActiveRecord::Base.configurations[Rails.env]['queue']
if db_queue_config
Delayed::Backend::ActiveRecord::Job.establish_connection(db_queue_config)
end
I am running the migration only in development
The migration should respect Delayed::Backend::ActiveRecord::Job.connection and create
the table in test_queue_development but it always creates the table in test_development.
db/migrate/create_delayed_jobs.rb
class CreateDelayedJobs < ActiveRecord::Migration
def self.connection
Delayed::Backend::ActiveRecord::Job.connection
end
def self.up
create_table :delayed_jobs do |table|
# Allows some jobs to jump to the front of the queue
table.integer :priority, :default => 0
# Provides for retries, but still fail eventually.
table.integer :attempts, :default => 0
# YAML-encoded string of the object that will do work
table.text :handler, :limit => 500.kilobytes
# reason for last failure (See Note below)
table.text :last_error
# The queue that this job is in
table.string :queue, :default => nil
# When to run.
# Could be Time.zone.now for immediately, or sometime in the future.
table.datetime :run_at
# Set when a client is working on this object
table.datetime :locked_at
# Set when all retries have failed
table.datetime :failed_at
# Who is working on this object (if locked)
table.string :locked_by
table.timestamps
end
add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
add_index :delayed_jobs, [:queue], :name => 'delayed_jobs_queue'
end
My question is: How can I introduce extra debugging into the migration so I can see what's going wrong? If i get into the rails console and type:
Delayed::Backend::ActiveRecord::Job.connection
i get info related to the queue database, which means self.connection runs fine and the initializer worked as expected. The code samples were take from canvas-lms
Thank you.

Related

Rails migration stopped, even though table does not exist

Rails 6
environment: local development on Mac OS
DB server: MySQL
I deleted all the tables from the DB, and the only tables left in the DB are:
schema_migrations
ar_internal_metadata
I made sure that schema_migrations has no data in it and looked into ar_internal_metadata, and that table has a single row in it, with the following values:
key: environment, value: development
I have several migrations, the most recent one, is devise_create_users.rb.
I am trying to run:
rake db:migrate
But I am getting the error message:
=> rake db:migrate
== 20200317184535 DeviseCreateUsers: migrating ================================
-- create_table(:users)
rake aborted!
StandardError: An error has occurred, all later migrations canceled:
Mysql2::Error: Table 'users' already exists
/Users/dev/rails/myapp/db/migrate/20200317184535_devise_create_users.rb:5:in `change'
Caused by:
ActiveRecord::StatementInvalid: Mysql2::Error: Table 'users' already exists
/Users/dev/rails/myapp/db/migrate/20200317184535_devise_create_users.rb:5:in `change'
Caused by:
Mysql2::Error: Table 'users' already exists
/Users/dev/rails/myapp/db/migrate/20200317184535_devise_create_users.rb:5:in `change'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)
Process finished with exit code 1
class DeviseCreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
t.string :reset_password_token
t.datetime :reset_password_sent_at
t.datetime :remember_created_at
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
t.timestamps null: false
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
When I check the DB after this, I still don't see a users table, and the schema_migrations table is still empty. In addition, the DeviseCreateUsers migration is the most recent on, so why is it running first.
Any ideas?
Edit:
Based on a comment to the question, I looked at my database.yml file:
default: &default
host: localhost
database: <%= ENV['RAILS_DB_NAME'] %>
username: <%= ENV['RAILS_DB_USER'] %>
password: <%= ENV['RAILS_DB_PWD'] %>
adapter: mysql2
encoding: utf8mb4
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
socket: /tmp/mysql.sock
development:
<<: *default
I made that change last night, and I forgot that my local ENV settings, were for a different project, so Rails was picking up the settings for that project, and was indeed correct, in that the users table is already there. The fix was for me to create project specific ENV settings, for my local development environment
When things like this happen it is needed to check the database.yml. As in most cases it happens because of some faulty configuration.
Good thing that just mentioning the database.yml in the comment section of the question helped to find the problem.
Using rails console to make sure there is not any users table, try create a new instance of User User.new. If the object was instanciated, your User table is hidden in somewhere. It is not a problem, see all atributtes previously generated and just migrate new ones. I can see in your devise_create_user migration that you can just replace it with something like addColumnstoUser.rb. This will just add the apropriated columns necessary to let Devise runing well.
Choosing this way, inside your migration:
add_column :table_name, :fieldname, :type, in your case, using just one column as an example, it must be:
def change
add_column :users, :reset_password_token, :string
end.
If you are using version controll, such as git, the migration might not know where you can rollback or go ahead. All the migration and rollbacks must stay on the same branch.
For example:
1) Adding a new branch to handle user migration: git checkout -b generate-user-model, generating user model rails generate model User name:string email:string
and running the migration rails db:migrate will create the users table and will update scheema.rb (check scheema file). Adding it to version controll git add . and `git commit -m "Migrating user model"
2) Moving to a new branch git checkout -b comments-controller, generating a new controller rails generate model Comment. And then migrating it rails db:migrate.
This second migration only teachs scheema.rb how to go ahead and how to rollback on this specifc migration. This last git branch doesn't know nothing about how to rollback the User model, unless you merge a branch inside another git merge generate-user-model.
After many migrations has been used, it's usefull keep using migrations to add or delete tables.
Hence, If I had a table called users and I wanted to set it to handle Devise, I just need to generate a new migration with the columns I'd needed.
Use https://sqlitebrowser.org/ to help you cheking your database tables and columns.

How to access active_record database outside of rails project

I'm using active_record database without rails project like this.
require 'active_record'
ActiveRecord::Base.logger = Logger.new(STDERR)
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: "posts.db")
unless File.exists? "posts.db"
ActiveRecord::Schema.define do
create_table :posts do |t|
t.column :title, :string
t.column :content, :text
end
end
end
class Post < ActiveRecord::Base
def do_something
# do_something
create(title: title, content: content)
end
end
Now I'm using Firefox add-on SQL Lite Manager to access the database.
But if possible I want to access database like rails console.
Is there a way to use rails console even if only active_record is used?
You can install pry gem.
And then in your current project directory add .pryrc.
In your .pryrc you can add something like this:
current_dir = Dir.pwd
$LOAD_PATH.unshift(current_dir)
Now, every time you go to pry session just require manually your file such as:
pry(main)> require 'models/post'
This the simplest method, you can customize or extend from here.

PostgreSQL point type in Rails migration

I want to use the point type which there's in PostgreSQL. I've done:
rails g model Test point:point
The resulting migration is:
class CreateTests < ActiveRecord::Migration
def change
create_table :tests do |t|
t.point :point
t.timestamps
end
end
end
When I run:
rake db:migrate
The result is:
== CreateTests: migrating ====================================================
-- create_table(:tests)
rake aborted!
An error has occurred, this and all later migrations canceled:
undefined method `point' for #<ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::TableDefinition:0x000000038991a0>/home/i/Dropbox/programming/ruby/rails_pg_for_tests/db/migrate/20140306151700_create_tests.rb:4:in `block in change'
/home/i/.rvm/gems/ruby-1.9.3-p448/gems/activerecord-4.0.2/lib/active_record/connection_adapters/abstract/schema_statements.rb:184:in `create_table'
/home/i/.rvm/gems/ruby-1.9.3-p448/gems/activerecord-4.0.2/lib/active_record/migration.rb:625:in `block in method_missing'
/home/i/.rvm/gems/ruby-1.9.3-p448/gems/activerecord-4.0.2/lib/active_record/migration.rb:597:in `block in say_with_time'
...
May be I need to install PostGIS, but I don't understand what for, if I have point type in PostgreSQL. And I only need to store latitude and longitude without any other options.
How can I use this point type or what is better to use in this case?
Thank you.
You can specify the data type as a string
t.column 'point', 'point'
Rails have support for point for several years. The test cases have been there since rails 4.2 beta.
If you are using rails 4.2 or later. you should not get that error. I have not tested it on 4.2 beta yet. I only test it on rails 5.2 and it works without any additional gem.
https://github.com/rails/rails/blob/24adc20ace69ec0fcf317e0a8d91d112478ba015/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
Usage:
Migration
class CreateAddresses < ActiveRecord::Migration[5.2]
def change
create_table :addresses do |t|
t.point :coordinate
end
end
end
Create a new record
Address.create coordinate: ActiveRecord::Point.new(1, 1)
You receive undefined method 'point' for #<ActiveRecord:... error on migration code
t.point :point
because point method does not exist in ActiveRecord.
ActiveRecord natively supports following data types,
:primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp,
:time, :date, :binary, :boolean
See the official documentation of ActiveRecord for the same.
As suggested by #bridiver, you could choose to specify a type other than the supported datatypes of ActiveRecord(as listed above) with t.column :point, 'point' BUT as quoted in ActiveRecord documentation
this will not be database agnostic and should usually be avoided.
UPDATE
I would recommend utilizing the benefits of activerecord-postgis-adapter gem, as suggested by #euricovidal. It is a geospatial extension for pgsql and it acts as a wrapper around the PostGIS query functions and allows you to write geo-aware migrations.
It will allow you to create a point type and using this your current syntax should work.
Refer PostGIS ActiveRecord Adapter: Creating Spatial Tables for example. You would also need to update the adapter to postgis in the database.yml file for this to work.
if you are using activerecord-postgis-adapter you need change adapter on database.yml to postgis.
https://github.com/dazuma/activerecord-postgis-adapter#installation-and-configuration
You most likely need to edit your config\database.yml to reference postgis as your database adapter
default: &default
adapter: postgis
encoding: unicode
pool: 5
timeout: 5000
host: localhost
development:
<<: *default
database: learning_rails
username: brentpayne
password:
You might also want to edit your migration to specify the :point as geographic so you can calculate distances.
t.point :point, :geographic => true
But the error you are having is identical to mine when I left my adapter set to postgresql. Changing it to postgis fixed my issues.
Reference: https://github.com/rgeo/activerecord-postgis-adapter#installation-and-configuration
If you install georuby the point datatype becomes available.
Gemfile
gem 'pg'
gem 'georuby'
In a migration
def change
create_table :space_table do |t|
t.point :location
t.timestamps
end
end
You can save a point with model_object.location = [-33.233, 123.123]

Rails separate DB migration not working

I am trying to run a migration to a separate DB in Rails, using establish_connection. I have something like this in the migration file:
def change
ActiveRecord::Base.establish_connection("user_#{Rails.env}")
ActiveRecord::Base.initialize_schema_migrations_table
create_table "users", :force => true do |t|
t.string "email",
end
In the yml I have this config:
user_development:
adapter: postgresql
encoding: unicode
database: MyApp_user_development
pool: 5
host: localhost
username: xxx
password: xxx
Also I have defined the User Model like this:
class User < ActiveRecord::Base
establish_connection "user_#{Rails.env}"
[...]
end
Now, the DB exists but when running rake db:migrate I get this error:
== CreateUserOnSeparateDb: migrating =========================================
rake aborted!
An error has occurred, this and all later migrations canceled:
connection is closed
Does any of you have any idea what is going on?
I have also tried to move the establish_connection call into a connection method in the migration file. In this case the table is created on migrate but then I get the same error (so somehow failing on other migrations) and by the way it does not create the schema_migrations table...
Any help?
Resolved above issue by following below steps.
class AddCustomTbl < ActiveRecord::Migration
def connection
ActiveRecord::Base.establish_connection(Rails.env).connection
end
def up
# Connection with user_#{Rails.env}
oldEnv = Rails.env
Rails.env = "user_#{Rails.env}"
ActiveRecord::Base.establish_connection(Rails.env)
create_table "users", :force => true do |t|
t.string "email"
end
# Connection with Rails.env
Rails.env = oldEnv
ActiveRecord::Base.establish_connection ActiveRecord::Base.configurations[Rails.env]
end
def down
# Connection with user_#{Rails.env}
oldEnv = Rails.env
Rails.env = "user_#{Rails.env}"
ActiveRecord::Base.establish_connection(Rails.env).connection
drop_table :users
# Connection with Rails.env
Rails.env = oldEnv
ActiveRecord::Base.establish_connection ActiveRecord::Base.configurations[Rails.env]
end
end

Can't Rails 3 run in MySQL MyISAM mode, without InnoDB?

I have a MySQL server running with InnoDB disabled (for reasons of performance), with this setup I can't seem to be able to use Rails 3 (with the mysql2 adapter).
Here's my test migration:
class CreateTxts < ActiveRecord::Migration
def change
create_table(:txts, :options => 'ENGINE=MyISAM') do |t|
t.timestamps
end
end
end
And here's the error:
>rake db:migrate
rake aborted!
Mysql2::Error: Unknown storage engine 'InnoDB': CREATE TABLE `schema_migrations`
(`version` varchar(255) NOT NULL) ENGINE=InnoDB
Tried the workaround described here, but it doesn't seem to work either (I did modify MysqlAdapter to Mysql2Adapter to match my setup).
Sorry I'm a newb in Rails. Any help will be much appreciated :o
Going to answer my own question. Here's a patch for environment.rb I ended up with that works with the native mysql driver as well as JRuby/JDBC-mysql:
# Load the rails application
require File.expand_path('../application', __FILE__)
# Patch Mysql adapter to default to MyISAM instead of InnoDB
require 'active_record/connection_adapters/mysql_adapter'
module ActiveRecord
module ConnectionAdapters
class MysqlAdapter
def create_table(table_name, options = {}) #:nodoc:
super(table_name, options.reverse_merge(:options => "ENGINE=MyISAM"))
end
end
end
end
# Initialize the rails application
.....
rake db:migrate now succeeds and creates all tables including schema_migrations with TYPE=MyISAM.
Note: For mysql2 adapter, rename mysql_adapter to mysql2_adapter and MysqlAdapter to Mysql2Adapter.
Try creating the table without specifying the type of engine being used like this
class CreateTxts < ActiveRecord::Migration
def change
create_table(:txts) do |t|
t.timestamps
end
end
end
and then in mysql cli, type this
ALTER TABLE txts ENGINE = MYISAM
hope it helps
The error is coming from creating the schema_migrations table (which rails uses to track which migrations have been run) rather than your table. You could create that table yourself (with a single varchar(255) column called version with an index on it).
If you do end up overwriting the create_table method, you need to preserve the method's signature - you're ignoring the block that it yields. I'd try something like
def create_table(name, options={})
super(name, options.merge(...)) {|t| yield t}
end
#rustyx nice one! &here's my slightly tweaked monkey-patch so the engine can be set from within the /config/database.yml configuration:
&Rather than in /config/environment.rb, i put the codes into an initializer, Eg: /config/initializers/mysql2adapter_default_engine.rb.
Either this for brevity:
require 'active_record/connection_adapters/abstract_mysql_adapter'
class ActiveRecord::ConnectionAdapters::Mysql2Adapter < ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter
def create_table(table_name, options = {}) #:nodoc:
super(table_name, #config[:engine].nil? ? options : options.reverse_merge(:options => "ENGINE="+#config[:engine]))
end
end
or this for more clarity:
require 'active_record/connection_adapters/abstract_mysql_adapter'
module ActiveRecord
module ConnectionAdapters
class Mysql2Adapter < AbstractMysqlAdapter
def create_table(table_name, options = {}) #:nodoc:
super(table_name, #config[:engine].nil? ? options : options.reverse_merge(:options => "ENGINE="+#config[:engine]))
end
end
end
end
Then in /config/database.yml we can have something like this:
production:
adapter: mysql2
encoding: utf8
database: ooook
username: ooook
password: ooook
host: ooook.tld
port: 3306
pool: 5
timeout: 5000
engine: MyISAM
If no engine is specified then the mysql2 adapter will use the default (InnoDB or whatever).

Resources