Most companies have a method of storing passwords and configuration details in a configuration file. For programmers working on on DBIx::Class, this usually means writing your own functions to load a configuration file and supplying them to ->connect(). If you're working on something like a web framework, chances are you're writing something similar like Dancer's Plugin::DBIC or Catalyst's Model::DBIC::Schema.
DBIx::Class::Schema::Config can be added to any DBIC Schema to allow it to connect to a database with pre-configured credentials in any format that Config::Any recognizes, takes one additional line of code in a schema file, one change to your ->connect() statement, and takes less than a minute to configure after it's been installed.
We'll start with three assumptions:
I will use an example schema and example DBIx::Class for this demonstration. DBIx::Class::Schema::Loader was used to create the schema based on the following SQL in PostgreSQL.
schema.sql:
CREATE TABLE data(
key varchar PRIMARY KEY,
val varchar not null
);
INSERT INTO data( key, val) VALUES( 'Init', 'It works!' );
My/Schema.pm:
package My::Schema;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
use strict;
use warnings;
use base 'DBIx::Class::Schema';
__PACKAGE__->load_namespaces;
# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-10-02 19:07:53
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9pRJATkJbKVsD7JMXxShyA
# You can replace this text with custom code or comments.
1;
Now we'll make a test file to show that we can connect and interact with the database.
#!/usr/bin/perl
use warnings;
use strict;
use Test::More;
use My::Schema;
sub run_tests {
my ( $db ) = @_;
ok $db->resultset('Data')->create( { key => "Hello", val => "World" }),
"Adding a row to the database seems to work.";
is $db->resultset('Data')->find( { key => "Hello" } )->val, "World",
"Looking up a row from the database seems to work.";
ok $db->resultset('Data')->find( { key => "Hello" } )->delete,
"Deleting a row from the database seems to work.";
is $db->resultset('Data')->find( {key => "Hello"} ), undef,
"It seems that a deleted row is actually deleted.";
is $db->resultset('Data')->find( { key => "Init" } )->val, "It works!",
"It seems that the inital row from schema.sql exists.";
}
ok my $db = My::Schema->connect(
"dbi:Pg:database=dbic_test", # DSN Line
"symkat", # Username
'fP37e1Y^21!dV$$y' # Password
);
run_tests( $db );
done_testing;
Running the test shows that everything seems to be working correctly.
$ prove -l
t/01_connect.t .. ok
All tests successful.
Files=1, Tests=6, 0 wallclock secs ( 0.02 usr 0.02 sys + 0.15 cusr 0.02 csys = 0.21 CPU)
Result: PASS
Let's practice test driven development to make the changes. We'll modify our test to make a second DBIC instance, connecting to it as connections are made with DBIx::Class::Schema::Config. The following diff outlines the changes we've made to the test file.
--- 01_connect.t.old 2011-10-02 23:12:31.000000000 -0700
+++ 01_connect.t 2011-10-02 22:48:46.000000000 -0700
@@ -26,9 +26,12 @@
ok my $db = My::Schema->connect(
"dbi:Pg:database=dbic_test", # DSN Line
"symkat", # Username
'fP37e1Y^21!dV$$y' # Password
);
+ok my $other_db = My::Schema->connect('MY_DATABASE');
+
run_tests($db);
+run_tests($other_db);
done_testing;
Running the test fails because MY_DATABASE is not a valid DSN.
$ prove -l
t/01_connect.t .. 1/? DBIx::Class::ResultSet::create(): Your storage class (DBIx::Class::Storage::DBI) does not set sql_limit_dialect and you have not supplied an explicit limit_dialect in your connection_info. DBIC will attempt to use the GenericSubQ dialect, which works on most databases but can be (and often is) painfully slow. Please file an RT ticket against 'DBIx::Class::Storage::DBI' . at t/01_connect.t line 10
DBIx::Class::ResultSet::create(): DBI Connection failed: Can't connect to data source 'MY_DATABASE' because I can't work out what driver to use (it doesn't seem to contain a 'dbi:driver:' prefix and the DBI_DRIVER env var is not set) at /home/symkat/perl5/lib/perl5/DBIx/Class/Storage/DBI.pm line 1248 at t/01_connect.t line 10
# Tests were run but no plan was declared and done_testing() was not seen.
t/01_connect.t .. Dubious, test returned 255 (wstat 65280, 0xff00)
All 7 subtests passed
Test Summary Report
-------------------
t/01_connect.t (Wstat: 65280 Tests: 7 Failed: 0)
Non-zero exit status: 255
Parse errors: No plan found in TAP output
Files=1, Tests=7, 0 wallclock secs ( 0.02 usr 0.00 sys + 0.15 cusr 0.02 csys = 0.19 CPU)
Result: FAIL
So to make this work correctly, we'll update My/Schema.pm to use DBIx::Class::Schema::Config.
--- Schema.pm.old 2011-10-02 23:07:34.000000000 -0700
+++ Schema.pm 2011-10-02 23:10:16.000000000 -0700
@@ -14,7 +14,6 @@
# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-10-02 19:07:53
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9pRJATkJbKVsD7JMXxShyA
-
-# You can replace this text with custom code or comments.
+__PACKAGE__->load_components('Schema::Config');
1;
Now we'll make a configuration file in the current directory called dbic.conf. We could use any file format that Config::Any recognizes, and we could put the file in /home/$USER/dbic.conf or /etc/dbic.conf as well and it would be used.
dsn "dbi:Pg:database=dbic_test"
user "symkat"
password "fP37e1Y^21!dV$$y"
With the change to My/Schema.pm and the addition of dbic.conf, the test file should now pass if we did everything correctly.
$ prove -l
t/01_connect.t .. ok
All tests successful.
Files=1, Tests=12, 0 wallclock secs ( 0.02 usr 0.01 sys + 0.18 cusr 0.03 csys = 0.24 CPU)
Result: PASS
We'll notice that while we added a second DBIC instance that was made to work with DBIx::Class::Schema::Config, the initial DBIC instance still works as well. BIx::Class::Schema will always allow normal credentials to pass through unadulterated. This allows the component to be loaded when instances that are not configured to work with DBIx::Class::Schema::Config are in use.
We've created a new file, dbic.conf in the current directory, added a load_components statement to the DBIx::Class::Schema, and added a connect statement with the name of a hash in the configuration file. These changes took a matter of seconds to give us a central location to manage our database credentials.
First, check out the DBIx::Class::Schema::Config Documentation. Questions like "how do I change where it looks for configuration files?," and "can I make programatic changes to the credentials before they're loaded?," are answered.
If your question is not answered in the documentation, you might want to submit the question to the DBIx::Class Mailing List. The author is a member of the mailing list.