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).
Related
I have a Rails 5 application that uses three different existing databases. No migrations were needed in this application. I want to build db/schema.rb to include all three databases, not just the main database. Executing rake db:schema:dump rebuilds the schema using the main database. I believe there is a way to do this but for some reason the way I've been searching I cannot find anything about this. All of the posts I have found discuss how to use models from different databases, not how to rebuild the schema to include all databases.
I started looking at this again about a month ago. After reviewing several options I found a solution created by #ostinelli that worked really well and easy to implement. This solution creates separate migrations for each database being maintained instead of putting them all in a single schema file. My goal mainly was to have a schema for each database which this accomplishes for me. There are some parts I did not implement because they were not needed.
http://www.ostinelli.net/setting-multiple-databases-rails-definitive-guide/
I created separate database yml files for each database like this:
config/database_stats.yml
development:
adapter: postgresql
encoding: utf8
host: localhost
pool: 10
database: myapp_stats_development
username: postgres
password:
test:
adapter: postgresql
encoding: utf8
host: localhost
pool: 10
database: myapp_stats_test
username: postgres
password:
production:
adapter: postgresql
encoding: utf8
url: <%= ENV["DATABASE_STATS_URL"] %>
pool: <%= ENV["DB_POOL"] || 5 %>
Then I created separate database rake tasks for each database:
lib/tasks/db_stats.rake
task spec: ["stats:db:test:prepare"]
namespace :stats do
namespace :db do |ns|
task :drop do
Rake::Task["db:drop"].invoke
end
task :create do
Rake::Task["db:create"].invoke
end
task :setup do
Rake::Task["db:setup"].invoke
end
task :migrate do
Rake::Task["db:migrate"].invoke
end
task :rollback do
Rake::Task["db:rollback"].invoke
end
task :seed do
Rake::Task["db:seed"].invoke
end
task :version do
Rake::Task["db:version"].invoke
end
namespace :schema do
task :load do
Rake::Task["db:schema:load"].invoke
end
task :dump do
Rake::Task["db:schema:dump"].invoke
end
end
namespace :test do
task :prepare do
Rake::Task["db:test:prepare"].invoke
end
end
# append and prepend proper tasks to all the tasks defined here above
ns.tasks.each do |task|
task.enhance ["stats:set_custom_config"] do
Rake::Task["stats:revert_to_original_config"].invoke
end
end
end
task :set_custom_config do
# save current vars
#original_config = {
env_schema: ENV['SCHEMA'],
config: Rails.application.config.dup
}
# set config variables for custom database
ENV['SCHEMA'] = "db_stats/schema.rb"
Rails.application.config.paths['db'] = ["db_stats"]
Rails.application.config.paths['db/migrate'] = ["db_stats/migrate"]
Rails.application.config.paths['db/seeds'] = ["db_stats/seeds.rb"]
Rails.application.config.paths['config/database'] = ["config/database_stats.yml"]
end
task :revert_to_original_config do
# reset config variables to original values
ENV['SCHEMA'] = #original_config[:env_schema]
Rails.application.config = #original_config[:config]
end
end
Then I created custom migration generators for the additional databases.
lib/generators/stats_migration_generator.rb
require 'rails/generators/active_record/migration/migration_generator'
class StatsMigrationGenerator < ActiveRecord::Generators::MigrationGenerator
source_root File.join(File.dirname(ActiveRecord::Generators::MigrationGenerator.instance_method(:create_migration_file).source_location.first), "templates")
def create_migration_file
set_local_assigns!
validate_file_name!
migration_template #migration_template, "db_stats/migrate/#{file_name}.rb"
end
end
I created database migrations for each database similar to:
rails g stats_migration create_clicks
create db_stats/migrate/20151201191642_create_clicks.rb
rake stats:db:create
rake stats:db:migrate
Then created separate database initializers for the additional databases then modified each model.
config/initializers/db_stats.rb
DB_STATS = YAML::load(ERB.new(File.read(Rails.root.join("config","database_stats.yml"))).result)[Rails.env]
class Click < ActiveRecord::Base
establish_connection DB_STATS
end
I had three databases in my application when I first wrote this question. I'm now adding another one database. I'm so grateful that #ostinelli wrote this. It made my life easier.
Rails collection ordering doesn't works as expected with UTF-8 string:
> University.order('abbr asc').map(&:abbr)
=> ["Б", "В", "А"]
It should be
> University.order('abbr asc').map(&:abbr)
=> ["А", "Б", "В"]
What am I missing?
Rails 4.1.8 with ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-darwin14.0]
This is most likely a collation settings issue with PostgreSQL:
The collation feature allows specifying the sort order and character classification behavior of data per-column, or even per-operation. This alleviates the restriction that the LC_COLLATE and LC_CTYPE settings of a database cannot be changed after its creation.
You can try and fix the collation of a column with a Rails migration. Something like this:
class FixCollationForAbbr < ActiveRecord::Migration
def up
execute 'ALTER TABLE universities ALTER COLUMN abbr TYPE varchar COLLATE "ru_RU";'
end
end
You should probably also add collation information to your database.yml:
defaults: &defaults
adapter: postgresql
encoding: utf8
collation: ru_RU.utf8
ctype: ru_RU.utf8
Here is how the database.yml settings affect table creation with PostgreSQL:
def create_database(name, options = {})
options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
option_string = options.inject("") do |memo, (key, value)|
memo += case key
...snip...
when :encoding
" ENCODING = '#{value}'"
when :collation
" LC_COLLATE = '#{value}'"
when :ctype
" LC_CTYPE = '#{value}'"
...snip...
end
end
execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
end
If you use the same locale when sorting, try out the ffi-icu gem. This part of the docs is most relevant to you.
There is also sort_alphabetical gem, but it relies on NFD decomposition so it may not work with all characters.
You can use "ru-RU-x-icu" collation in PostgreSQL, and either add it explicitly to every column, or monkey-patch ActiveRecord to add default collation if it's not defined:
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module ColumnMethods
def new_column_definition(name, type, **options) # :nodoc:
if integer_like_primary_key?(type, options)
type = integer_like_primary_key_type(type, options)
end
type = aliased_types(type.to_s, type)
options[:primary_key] ||= type == :primary_key
options[:null] = false if options[:primary_key]
# set default collation if it's not defined explicitly
options[:collation] = "ru-RU-x-icu" if options[:collation].blank? && (type == :string || type == :text)
create_column_definition(name, type, options)
end
end
end
end
end
In a rails application, I have this code in pure ruby :
class LinkCreator
attr_accessor :animal
def initialize(animal:)
#animal = animal
end
def call
"something#{link_id}"
end
private
def link_id
connection.execute(sql_request).first.first
end
def sql_request
"SELECT field FROM table WHERE field_id = '#{field_id}' LIMIT 1"
end
def field_id
animal.field_id
end
def connection
ActiveRecord::Base.establish_connection(
adapter: "mysql",
host: ENV["MYSQL_HOST"],
username: ENV["MYSQL_USERNAME"],
password: ENV["MYSQL_PASSWORD"],
database: ENV["MYSQL_DB_NAME"]
).connection
end
end
As you can see, this is not a model but only a simple class. The problem is than the connection of activerecord is changed and the other requests, later, are executed on the new connection.
Is it possible to establish a connection only in a block and go back to the old connection. I know I can establish another connection but this is very bad for performance.
It would be nice if you keep all database connections in database.yml
development:
adapter: mysql2
other stuff...
db_2:
adapter: mysql2
other stuff..
other_envs:
.....
Then create a class
class OtherDB < ActiveRecord::Base
establish_connection(:db_2)
end
From your controller you can access just like
OtherDB.table_name = "table_name"
OtherDB.first
Check my blog here http://imnithin.github.io/multiple-database.html
Have found most short example in Rails codebase at activerecord/test/support/connection_helper.rb and slightly adapted:
def with_another_db(another_db_config)
original_connection = ActiveRecord::Base.remove_connection
ActiveRecord::Base.establish_connection(another_db_config)
yield
ensure
ActiveRecord::Base.establish_connection(original_connection)
end
Usage (given that you have another_db: section in your database.yml):
with_another_db(ActiveRecord::Base.configurations['another_db']) do
ActiveRecord::Base.connection.execute("SELECT 'Look ma, I have changed DB!';")
end
You can perform some queries within a block. First, define some module which will extend ActiveRecord, as below. This is a part of code used in production to change db connection per each request as well as to temporarily switch db to perform some queries within another database.
# RAILS_ROOT/lib/connection_switch.rb
module ConnectionSwitch
def with_db(connection_spec_name)
current_conf = ActiveRecord::Base.connection_config
begin
ActiveRecord::Base.establish_connection(db_configurations[connection_spec_name]).tap do
Rails.logger.debug "\e[1;35m [ActiveRecord::Base switched database] \e[0m #{ActiveRecord::Base.connection.current_database}"
end if database_changed?(connection_spec_name)
yield
ensure
ActiveRecord::Base.establish_connection(current_conf).tap do
Rails.logger.debug "\e[1;35m [ActiveRecord::Base switched database] \e[0m #{ActiveRecord::Base.connection.current_database}"
end if database_changed?(connection_spec_name, current_conf)
end
end
private
def database_changed?(connection_spec_name, current_conf = nil)
current_conf = ActiveRecord::Base.connection_config unless current_conf
current_conf[:database] != db_configurations[connection_spec_name].try(:[], :database)
end
def db_configurations
#db_config ||= begin
file_name = "#{Rails.root}/config/database.yml"
if File.exists?(file_name) || File.symlink?(file_name)
config ||= HashWithIndifferentAccess.new(YAML.load(ERB.new(File.read(file_name)).result))
else
config ||= HashWithIndifferentAccess.new
end
config
end
end
end
ActiveRecord.send :extend, ConnectionSwitch
Now you can use it as below:
ActiveRecord.with_db("db_connection_name") do
# some queries to another db
end
If you want to connect to postgres sql, you can use pg ruby gem & add the below code inside the block.
postgres = PG.connect :host => <host_name>, :port => <port>, :dbname => <database_name>, :user => <user>, :password => <password>
tables = postgres.exec(query)
tables.num_tuples.times do |i|
print tables[i]
end
To connect to mysql db inside a block, use mysql2 ruby gem & add the below code inside the block.
db = Mysql2::Client.new ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(<database_url>).to_hash
data = db.query(<<-SQL)
select * from students
SQL
print data
I use environment variables taken from Heroku's DATABASE_URL to connect to different databases:
class Database
def self.development!
ActiveRecord::Base.establish_connection(:development)
end
def self.production!
ActiveRecord::Base.establish_connection(ENV['PRODUCTION_DATABASE'])
end
def self.staging!
ActiveRecord::Base.establish_connection(ENV['STAGING_DATABASE'])
end
end
e.g.:
Database.production!; puts User.all.map(&:name)
Database.staging!; puts User.all.map(&:name)
It might help to use an instance variable to store the connection. Something like this:
def connection
#connection ||= ActiveRecord::Base.establish_connection(
adapter: "mysql",
host: ENV["MYSQL_HOST"],
username: ENV["MYSQL_USERNAME"],
password: ENV["MYSQL_PASSWORD"],
database: ENV["MYSQL_DB_NAME"]
).connection
end
That way the existing connection is retrieved on future connection attempts, rather than establishing a new one.
Try active_record_slave gem:
Ruby gem: active_record_slave
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.
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]