I have a Rails project which has a Postgres database for the actual application but which needs to pull a heck of a lot of data out of an Oracle database.
database.yml looks like
development:
adapter: postgresql
database: blah blah
...
oracle_db:
adapter: oracle
database: blah blah
My models which descend from data on the Oracle DB look something like
class LegacyDataClass < ActiveRecord::Base
establish_connection "oracle_db"
set_primary_key :legacy_data_class_id
has_one :other_legacy_class, :foreign key => :other_legacy_class_id_with_funny_column_name
...
end
Now, by habit I often do a lot of my early development (and this is early development) by coding for a bit and then playing in the Rails console. For example, after defining all the associations for LegacyDataClass I'll start trying things like a = LegacyDataClass.find(:first); puts a.some_association.name. Unexpectedly, this dies with LegacyDataClass not being already loaded.
I can then require 'LegacyDataClass' which fixes the problem until I either need to reload!, which won't actually reload it, or until I open a new instance of the console.
Thus the questions:
Why does this happen? Clearly there is some Rails magic I am not understanding.
What is the convenient Rails workaround?
I believe this might have to do with your model name, rather than your connection. The Rails convention is that model class names are CamelCase, while the files they reside in are lowercase+underscore.
The "LegacyModel" class should therefore be in models/legacy_model.rb. Your statement about "require 'LegacyDataClass'" indicates that this is not the case, and therefore Rails doesn't know how to automagically load that model.
I wrote something for an app at work that handles connections to other databases' at runtime, it might be able to help.
http://github.com/cherring/connection_ninja
Related
I am trying to connect to an existing Sybase db using Rails and populate a few selection lists. Here's what I have done so far:
1. Installed and configured FreeTDS
2. Installed TinyTDS gem
if I execute command tsql -S serverName -U userName, I'm able to query the data. I have my config/database.yml configured as such:
development:
adapter: sybase
host: <sybase_host>
port: <port_no>
username: <user>
password: <password>
database: <db>
I then tried generating a model via rails generate model sybase_db --skip-migration and editing the created app/models/sybase_db.rb file as follows:
class SybaseDb < ActiveRecord::Base
set_table_name "my_sybase_table"
end
When I try to run SybaseDb.new command in rails console, it doesn't seem to work. I'm pretty new to Rails so what am I doing wrong?
Thanks!
Everything looks right up to the part where you generate a model called sybase_db. A model in Rails is typically linked to a specific table in a database, not the whole database. Rails uses naming conventions to simplify the linking of tables and columns to models and attributes.
For example, if you have a model User with attributes name and email that is linked to a table in your database called users and having columns name and email then all sorts of wonderful Rails magic just works. You could start the rails console and execute User.all to produce a collection of all users in the database. Or you might do something like
> u = User.find_by_email 'joe#example.com'
=> #<User id: 123, email: "joe#example.com", name: "Joe"...>
> u.name
=> "Joe"
If you have an existing database, however, chances are you're going to have to explain in more detail to Rails how to map the names in Sybase to those in your Rails system. You have one case of this in set_table_name -- if your Sybase table was named t_user with a primary key of user_id but it had columns name and email then you could create a Rails model like
class User < ActiveRecord::Base
set_table_name "t_user"
set_primary_key "user_id"
end
Here's a discussion of this topic on Quora with a couple good links that you might use to go further.
Depending on how extensive your existing system is, you may find that all of the magic of Rails goes away. Rails helps us avoid all of this mapping this to that, and follows strong naming conventions to give us all sorts of wonderful coolness. If your existing system has strong and predictable naming conventions, and isn't terribly far off from Rails' way, you might be able to use Rails successfully.
I need to query a 3rd party database which is entirely separate from the Rails 3.2 application I'm building (belongs to a different application which my company uses internally).
Ultimately, I'll be setting up a cron to load new rows from the "other" database which my Rails application will be processing.
I have the access to otherdb set up, and I'm wondering where to go from hereādo I create a new entry in config/database.yml? If so, how do I then specify when a query is to be directed to otherdb, instead of my default Rails development or production db?
There's a few ways to implement this requirement, the easiest of which would be to use config/database.yml and custom namespaced model(s).
Setting up something similar to the below, using a suffix of Rails.env to follow the naming convention will provide the functionality you outlined.
First, create new entries for the external database each of your existing environments. It'll help you to be able to test the functionality.
# database.yml
development:
# add configuration as required
otherdb_development:
# add configuration as required
Second, add a model for each of the specific tables you need to access in the otherdb database. I'd recommend adding a namespace directory for these models (otherdb in the example below) to avoid confusion and potential clobbering:
# /app/models/otherdb
class Otherdb::Foo < ActiveRecord::Base
establish_connection "otherdb_#{Rails.env}"
set_table_name "foo" # customize this if the table name will be different from the classname and is required
end
You can then use (as an example) methods on Otherdb::Foo and use the resulting data as required.
I had the same problem yesterday. Since you are using Rails 3.2, all your models that connect to the external database will have to be subclasses of a single, abstract class that establishes the connection. In earlier versions of Rails, #Sasha's answer would have worked. But in 3.2, that answer will lead you to getting various confusing database errors. (What errors you get depends on what DB you use.)
In Rails 3.2, this is the only way I have found that will work:
Make a common base class for all models that need to talk to a
non-default database.
Tell ActiveRecord that this base class is abstract by calling self.abstract_class = true.
Call establish_connection in the base class.
Here is an example with students and courses from an external table:
# database.yml:
development:
# default configuration goes here
other_development:
# external db configuration goes here
class OtherTable < ActiveRecord::Base
self.abstract_class = true
establish_connection "other_#{Rails.env}"
end
class Student < OtherTable
end
class Course < OtherTable
end
If you'd like more detail, see the blog post I wrote titled Establishing a Connection to a Non-Default Database in Rails 3.2.
I have some weird issues going on (for a very weird use case as I'll explain). I'm setting up a multi-tenant application using postgres schemas for data multi-tenancy.
Each company in my system will get its own schema. The way I accomplish this is with an after_commit on the model, on create, that then goes and creates a new postgres schema, and loads schema.rb into it. (copied from rake db:schema:load code) using ruby load.
You can see the gem here
Anyway, all this works (in the console). Creating a company creates the new schema and i can switch to it etc... my problem lies in my integration tests. I have an rspec test that creates to companies like so:
before do
#c1 = Factory :company
#c2 = Factory :company
end
What's odd is that I start to get the logs about the db schema loading, but they're truncated. Almost as if they're happening in parallel. Here's a sample output:
>> create: database: unique_name1
-- create_table("first_table_in_schema_rb", {:force=>true})
>> create: database: unique_name2
create: database is my log line, the -- create_table is from schema.rb itself.
As you can see, the second create: database seems to happen while I'm loading schema.rb from the first company creation.
Does anyone know if load is somehow asynchronous? I know ruby doesn't have real threads, but could it be using fibres or something? This is really messing me up because when my test comes around, the postgres schema that was meant to be created doesn't seem to exist.
Rails 3.0.8
Ruby 1.9.2
Im not 100% sure this is your problem because im sure of what happens with require but not with load, the things this happen to me once with require because require is not atomic, so loading code from a file with require will cause a race condition. Maybe that is what is happening with load but i was not able to find any info about load been atomic or not.
nevermind... issue had nothing to do with load it was the fact that i was already connected to the wrong schema when importing the schema.rb
There was in fact an exception being thrown that was silently caught somewhere
I'm refactoring some features of a legacy php application which uses multiple databases with the same structure, one per language. When an user login choose his language and then all the following connections are made with the db for that app with some code like this:
$db_name = 'db_appname_' . $_SESSION['language'];
mysql_connect(...);
mysql_select_db($db_name);
I'd like to refactor also the database, but currently it's not an option because other pieces of software should remain in production with the old structure while the new app is developed, and for some time after it's been developed.
I saw this question, but both the question and the suggested gems are pretty old and it seems that they are not working with Rails 3.
Which is the best method to achieve this behavior in my new rails 3 app? Is there any other choice that avoid me to alter the db structure and fits my needs?
Last detail: in the php app even the login information are kept in separate tables, i.e. every db has its own users table and when the user logs in it also passes a language param in the login form. I'd like to use devise for auth in the new app which likely won't work with this approach, so I'm thinking to duplicate (I know, this is not DRY) login information in a separate User model with a language attribute and a separate db shared among languages to use devise features with my app. Will this cause any issue?
EDIT:
For completeness, I ended with this yml configuration file
production: &production
adapter: mysql
host: localhost
username: user
password: secret
timeout: 5000
production_italian:
<<: *production
database: db_app_ita
production_english:
<<: *production
database: db_app_eng
and with this config in base model (actually not exactly this but this is for keeping things clear)
MyModel < AR::Base
establish_connection "production_#{session[:language]}"
...
end
use establish_connection in your models:
MyModel < AR::Base
establish_connection "db_appname_#{session[:language]}"
...
end
Use MultiConfig gem I created to make this easy.
You could specify the configs in separate file like database_italian.yml etc and then call:
ActiveRecord::Base.config_file = 'database_italian'
This way it will be much easier to maintain and cleaner looking. Just add more language db config files as you wish
I run a Rails app, and we're in the process of splitting out our signup process to a separate app. The signup app has its own separate database (for CMS and collecting prospects), but it also needs to have access to the main database. This works really well using ActiveRecord::Base.establish_connection.
However, I'd like to be able to write some specs. The trouble is, how can I write specs/tests without clearing out my development database every time my tests run? If I go into the console in test mode, it's obvious the the test mode is hooked into the development database from my main app.
Here's what my database.yml file looks like:
development:
database: signup_dev
test:
database: signup_test
main_app_dev:
database: main_app_dev
main_app_test:
database: main_app_test
Based on this file, I'd like establish_connection to connect to connect to the database my_app_dev in development mode, and my_app_test in test mode. Any ideas?
We have a gem that is basically a collection of ActiveRecord models that connect to our legacy system. In our case we have all those models contained in a module from which all models related to the legacy database connects.
module Legacy
class Base < ActiveRecord::Base
establish_connection :legacy
end
class User < Base
end
end
With this setup it makes it really easy to switch out the database connection. If you really go for that automated detection you can put logic in your base class to determine which database to use:
module Legacy
class Base < ActiveRecord::Base
if Rails.env == 'test'
establish_connection :legacy_test
else
establish_connection :legacy
end
end
Or simply tell your module which connection to use in your spec helper:
# spec/spec_helper.rb
Legacy::Base.establish_connection(ActiveRecord::Base.configurations['legacy_test'])
Personally I would recommend the second option. Of course both solutions depend on namespaced models.
Peer
Ryan, we were also in the process of migrating from one datastore to another. We needed to develop against two databases and maintain separate migrations and fixtures for each.
I created a gem called Secondbase to help with this. Essentially, it allows you to manage two databases seamlessly in a single Rails app. Perhaps it will solve your issue as well: https://github.com/karledurante/secondbase
Here's what I came up with as a mixin:
# lib/establish_connection_to_master_database.rb
module EstablishConnectionToMasterDatabase
def establish_connection_to_master_database
case RAILS_ENV
when "development"
establish_connection :master_dev
when "test"
establish_connection :master_test
when "production"
establish_connection :master
end
end
end
ActiveRecord::Base.send(:extend, EstablishConnectionToMasterDatabase)
# models/subscription.rb
class Subscription < ActiveRecord::Base
establish_connection_to_master_database
end
# config/initializers/config.rb
require 'establish_connection_to_master_database'
In order for this to work with RSpec, this needs to be loaded in an initializer - apparently loading it in the environment file causes it to be loaded it too late, and it won't work.
We just used interpolation for this:
class ServiceModel < ActiveRecord::Base
establish_connection :"main_app_#{Rails.env}"
end
The funny :"main_app_" syntax makes a symbol from a string. This could be also written "main_app_#{Rails.env}".to_sym. In either case with Rails 4.1 this must be a symbol (under 3.2 we had just used a string).