## What's it look like?
In config/environment.rb:
> config.active_record.connection.configure do |db|
> db.adapter = 'mysql'
> db.encoding = 'utf8'
> end
In config/development.rb:
> config.active_record.connection.configure do |db|
> db.database = 'test_app_development'
> db.socket = '/tmp/mysql.sock'
> db.username = 'root'
> db.password = ''
> end
So the short of it is that you configure global stuff in
environment.rb and environment-specific stuff in the respective files.
This generates the connection-specification hashes that
ActiveRecord::Base.establish_connection expects.
## Why change?
database.yml is workable, but its a little weird at this point. There
is no other instance of YAML configuration in one's app when it runs
in production. This approach also lets you significantly DRY up your
configuration. Its even possible to specify the entire configuration
in environment.rb.
Idatabase.yml is still loaded if you don't specify the database
connection in your environment files. I wouldn't up and take it away
from you like that. :)
## Securing your database credentials
We all know its a good idea to keep your database credentials
(username/password) out of source control. This patch supports that
raising an exception if you try to set your username or password in a
production setting. Instead, you specify a credentials file like so:
> config.active_record.configure do |db|
> db.database = 'test_app_production'
> db.socket = '/var/run/mysql.sock'
> db.credentials = "#{RAILS_ROOT}/config/credentials.rb"
> end
The preferred credential format is Ruby, like so:
> username = 'root'
> password = ''
YAML is also supported:
> username: root
> password:
## Other bits and bobs
I also patched the application generator to produce an app with no
database.yml. Database config bits are added to environment.rb. The
adapter-specific comments get added in environment.rb.
## Did I mention I'm looking for +1s
Yes, the pandering is strong with this one. Grab
dry_database_config.diff from http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/312
and give it a spin.
Thanks in advance!
--
~akk
http://therealadam.com
> What I like with database.yml is that I don't version it and
> therefore I don't keep production passwords in version control. Also
> I can have one database in production locally and another on the
> remote machine.
>
I've done that too. You could get that effect like so:
> config.active_record.connection.configure do |db|
> case `hostname`
> when 'my_laptop'
> db.host = 'localhost'
> when 'production'
> db.host = 'db1'
> end
> end
Or you can stick with database.yml :)
--
~akk
http://therealadam.com
Or you can stick with database.yml :)
I don't understand how a Ruby config encourages versioning passwords
more than using the yaml file. Either way you can svn/git ignore the
file with the passwords.
However, the main benefit of switching to a Ruby config file seems to be
"because database.yml was the only yaml config file", which doesn't seem
like a particularly great reason.
On the other hand, I like this just because I find the Ruby syntax more
clearly indicates what you are doing, whereas the yaml is pure data that
is sucked up into some mysterious place inside Rails. So I guess that's
more than a "just because it's not yaml" reason.
Ben
I don't think this encourages new users to version their passwords any
more than they are now. In fact, because there is no username/password
generated in the production config, I think it guides them towards
_not_ versioning their *production* credentials. If you try to set
username/password directly in your production configuration, the
application won't start.
Those taking the path of least resistance will generate their app, get
something small working and then check it in. When they get to the
point where they want to deploy, they will have to either add a
credentials file to source control (the bad route) or tweak their
deploy script to symlink the credentials (the good route).
Perhaps generated applications could have more verbiage encouraging
developers to store their credentials outside of source control and
link it into the application at deploy-time? Besides that, there's
only so much vinegar and hand-holding one can apply. ;)
--
~akk
http://therealadam.com
"No more YAML" is certainly the Reddit-friendly reason for this patch.
Ousting YAML+ERB tricks is just the humane thing to do. :)
However, the reason I really like this approach is that it cleans up
clever configurations. People have been using File.exists? to figure
out which MySQL socket to use within ERB blocks in database.yml for a
long time. This makes those sorts of idioms easier to read and write.
As the name of the patch implies, this approach is also considerably
DRYer. If you want, you can define everything in environment.rb and
move on.
--
~akk
http://therealadam.com
That sounds like a great reason to me. Why pull in a whole different
technology just to read a 6-line file?? (maybe that's what you were
meaning when you said YAML was a mysterious place inside Rails?)
Or, easily work around wandering mysql socket locations (excuse the junk code):
config.active_record.connection.configure do |db|
db.adapter = 'mysql'
sockets = %w(/var/run/mysqld/mysqld.sock
/var/lib/mysql/mysql.sock /tmp/mysql.sock)
db.socket = sockets.find { |f| File.exist?(f) }
end
Does anyone even use YAML anymore? My projects are mostly JSON, a
little XML, and zero YAML. database.yml is an an anachronism. Why
keep this legacy inside Rails?
Great work Adam. I'll be installing your patch as I move projects to
Rails 2.1 and I sure hope it moves upstream quickly.
- Scott
I'm not sure how you're repeating yourself by putting the database config in
a separate file. Where's the repetition?
If anything, this patch is anti-DRY because it locks up the database config
in a place that can only be read by Rails. If you've got anything other
than Rails that wants to talk to the database (cron jobs, other apps,
whatever) YAML is a far better way to store your credentials than a chunk of
Rails code (it's not even plain Ruby, because you need umpteen lines of
scaffolding to evaluate it and get the values out of it). Practically
anything can parse YAML, nothing except Rails can parse Rails.
I'm -1 on this patch because it'll almost certainly make my life harder
trying to host apps that use this convention.
- Matt
--
"I invented the term object-oriented, and I can tell you I did not have C++
in mind." -- Alan Kay
Or you can configure your MySQL installation properly, and put the socket
location in the global my.cnf file, as $DEITY intended.
- Matt
--
A friend is someone you can call to help you move. A best friend is someone
you can call to help you move a body.
-1
I agree. We have utilities which parse database.yml for rake tasks
and other batch jobs. An example (which we use on a daily basis) is a
utility to automatically pull and import a production database to the
local dev database, for easy testing against production data.
database.yml is metadata about an external resource. It makes sense
to put that metadata in a DRY, easily-parsable format which can be
used by things other than the Rails app itself.
As the previous poster mentioned, I can imagine this approach making
life harder for many rails hosting providers as well. I don't think
this is a good approach to encourage or propogate.
-- Chad
The beauty of this patch is that now the database config can be read
from anywhere.
If you still want to read your config from a YAML file, nothing's
stopping you. I presume you could do something like this:
require 'yaml'
db.merge(YAML::load(File.open('database.yml')))
And it's just as easy to store your database config in XML, CSV, LDAP,
or another database. Your deployment options are wide open. Really,
this patch frees the database config. Where's the downside?
- Scott
Because then it is nonstandard (again, making it hard for hosting
companies to provide standardized auto-deployment).
Convention over configuration.
-- Chad
It's different to every piece of documentation that is already out there,
and it breaks Rails' motto of convention over configuration.
- Matt
--
"The user-friendly computer is a red herring. The user-friendliness of a
book just makes it easier to turn pages. There's nothing user-friendly about
learning to read."
-- Alan Kay
>
>> Please, for the sake of sanity, do not change this from the old way
>> unless
>> you have a better reason than "It's DRYer"
>
Just for the sake of argument, yaml configs don't have to be repetitive:
defaults: &defaults
adapter: mysql
encoding: utf8
username: rota
password:
host: localhost
development:
<<: *defaults
database: rota_development
test:
<<: *defaults
database: rota_test
but this is quite underused (making the default generated database.yml
look like this was discussed a while back)
Fred
That's also a lot harder to read and write, especially for newbies. I
definitely support an all-ruby approach. Supporting some generic hash
merging, as Scott suggested, might not be a bad idea though.
In a similar vein: last night at the Baltimore ruby group, John
Trupiano suggested some way of merging the yml files from the
geminstaller gem with Rails' gem configurations.
--
Rick Olson
http://lighthouseapp.com
http://weblog.techno-weenie.net
http://mephistoblog.com
I've wanted to provide a non-YAML config option for GemInstaller for a
while, but it is down on the priority list. And, as pointed out, if
the config is in Ruby (as it is with config.gems), then it's still
easy to parse a YAML file (such as geminstaller.yml) to generate that
config. That's probably the best approach to support what John
wanted, and would be cool to have in Rails by default (patch
someone?!?)
After reading this thread, I'm convinced that both Ruby and YAML
config should be supported (so I retract my -1 if that will be the
case). All-ruby config is cool and nice, but for metadata describing
external dependencies (Gems, Databases), there should be a
STANDARDIZED, easily parsable, non-Ruby format supported as well.
So, I hope Rails still natively supports the old YAML format after
this patch, to support old tutorials, and to provide a standardized
option when it is needed, such as Rails parsing geminstaller.yml if it
exists, or a hosting company parsing geminstaller.yml or database.yml
to auto-configure whatever...
Also, I still don't think either option is DRY-er than the other,
because YAML supports reuse, as was just pointed out.
-- Chad
Something like this is ugly, but does the job:
config.active_record.connection.configure do |db|
YAML.load(ERB.new(File.read('config/database.yml').result))
[Rails.env].each_pair do |key, value|
db.send("#{key}=", value)
end
end
--
~akk
http://therealadam.com
To clarify, my patch tries to load database.yml if you don't set
anything in your environment.
> Also, I still don't think either option is DRY-er than the other,
> because YAML supports reuse, as was just pointed out.
I was perhaps overzealous in declaring Ruby DRYer than YAML. That
said, I think more people know how to DRY up Ruby than YAML. ;)
--
~akk
http://therealadam.com
Totally off topic at this point, sorry, but anyway...
John and I hacked on this a bit this morning. The conclusion is that
there is no reason to do what he said above. GemInstaller.autogem
already does everything you want if you desire to manage your gems
from YAML. See:
http://geminstaller.rubyforge.org/faq.html#rails_config_gems
http://geminstaller.rubyforge.org/documentation/tutorials.html#integrating_geminstaller_into_ruby_on_rails
Thanks!
-- Chad
I don't really mind dropping the YAML configuration, but is there any
reason you aren't just using a regular Hash? i.e.
## environment.rb
config.active_record.connection = {
:adapter => 'mysql',
....
}
## environments/development.rb
config.active_record.connection.merge {
:database => 'my_app_dev',
:username => 'dev',
:password => 'password'
}
(note the merge in specific environments)
I guess this boils down to - why generate the Hash, when just writing
the Hash is pretty much equally readable? Do we get some benefit using
the block/DSL-style syntax? If not, the implementation could perhaps
be simpler.
--
* J *
~
Running it through a proxy makes it easier to yell at the user when
they try to set username/password in production. Also, credentials is
handled differently from the other attributes. One could do all that
with a Hash, but why apply behavior to data when you can bundle the
data with the behavior?
--
~akk
http://therealadam.com
I have to chime in here and plead for you to leave the database.yml
alone. Or if you must use ruby config, please leave the db.yml
working. If the credentials/config is only in ruby and in the local
variable format you showed earlier, then other tools from other
languages are going to not be able to read this stuff in easily. Heck
in that format even ruby cannot read the credentials in without
loading rails and using eval hackery to make the local variables
available outside of the file they are in.
I see significant downsides to the pure ruby config:
1. cannot read from non ruby languages.
2. now we have to load the entire rails environment just to get all
the db config, this adds needless overhead to what is currently an
easy task
3. ruby config spread out over multiple environment files as well as a
credential file is going to hamper server automation in a serious way.
4. there are countless daemons and plugins that read the database.yml
to get the db config info. how will these daemons now get said info
without loading all of the rails environment?
I thought rails was all about not breaking backwards compatibility
unless there is a very good reason? What is the very good reason to
make deployment more difficult? What does this gain us must be weighed
against what we lose with the ruby config.
-1
Thanks
- Ezra Zygmuntowicz
-- Founder & Software Architect
-- ez...@engineyard.com
-- EngineYard.com
*The patch leaves database.yml alone.* If no database configuration is
done in the environment, Rails still looks in database.yml.
> I see significant downsides to the pure ruby config:
It seems there are a lot of deployment-time concerns here. Would it
help if there was a Rake task to dump database.yml for a given
environment?
> I thought rails was all about not breaking backwards compatibility
> unless there is a very good reason? What is the very good reason to
> make deployment more difficult? What does this gain us must be weighed
> against what we lose with the ruby config.
What part of deploying a typical Rails app is made more difficult? If
anything, I'd think the external credential file makes things easier
than it was before.
--
~akk
http://therealadam.com
-1
> I see significant downsides to the pure ruby config:It seems there are a lot of deployment-time concerns here. Would it
help if there was a Rake task to dump database.yml for a given
environment?
I think Duncan has a good, albeit snarky, point. If we do switch to the
ruby-based config we have to make sure that we dont slow other parts of
the framework.
On further thought, I think we should just stay with the yaml file.
I have to give a -1 to the possiblity of using both the ruby and yaml
methods. It isnt DRY.
I think DHH unintentionally did the right thing when he kept the db
config out of ruby.
If the database credentials reside in the ruby environment files then
we would be committing our database passwords into the repository.
Anyone with read access to the source code now has credentials to the
db. I'm skeptical of overly paranoid security measures, but this to me
this just seems blatantly a bad practice.
This ALMOST sounds like a good idea, but this really would be non-DRY,
in a dangerous way. Is the database.yml I dumped (or someone else
dumped) up-to-date? How do I know? When do I re-dump? Who re-dumps?
There's no reason to keep the database credentials in environment.rb.
Just throw them in a separate file, say database.rb, and ignore that
the way you currently do database.yml.
What's nice about this patch is that you can put the global configs in
environment.rb (UTF8, db host, database name) and leave just the
sensitive ones out of source control (username, password).
And, of course, many databases don't have any sensitive credentials to
worry about, like sqlite3.
- Scott
You do currently. Is that a problem?
> Best yet each rails project can come up with it's
> own conventions on what it should call this file and what should go in
> it.
Yep! Because each rails project best understands its own needs and
requirements.
I expect most Rails devs will continue to use database.yml the way
they currently do. There's no reason to break backward compatibility
but there's every reason to allow forward progress.
- Scott
This argument has been given a few times. Am I the only one who
considers the db host en database name to be just as security sensitive
as the username and password? Why would I tell anyone that the safe is
located behind the Van Gogh painting I've got hanging here?
Lawrence
On Thu, Jun 12, 2008 at 3:10 PM, matthew deiters <mdei...@gmail.com> wrote:
> If the database credentials reside in the ruby environment files then
> we would be committing our database passwords into the repository.
What's nice about this patch is that you can put the global configs in
environment.rb (UTF8, db host, database name) and leave just the
sensitive ones out of source control (username, password).
To keep this discussion on track, forget I said that abou the db host
and db name. :) But there's no reason to keep the adaptor, encoding,
or socket location secret.
(In theory, if your safe is secure, it doesn't matter if the thief
knows where it is. It doesn't even matter if he knows how it's built.
In practice, I agree that every little bit helps, even the most
incidental bit of obscurity.)
- Scott
I don't want you to know /anything/ about the databases I'm using.
Including the adapter and socket location(s) (if I have them at all,
which I won't tell you ;).
> (In theory, if your safe is secure, it doesn't matter if the thief
> knows where it is. It doesn't even matter if he knows how it's built.
> In practice, I agree that every little bit helps, even the most
> incidental bit of obscurity.)
>
It does matter, because you can damage the safe without actually
cracking the code making the safe inaccessible to the owner.
My point is that I don't want /any/ info wrt the production databases
under source control. I don't see how moving stuff into ruby code adds
any value. It's not like there's something dynamic going on for which
you need the power of ruby.
Lawrence
sure, I can do e.g.:
ActionMailer::Base.smtp_settings = YAML::load(ERB.new(IO.read(RAILS_ROOT
+ "/config/email.yml")).result)[env]
in my environment.rb, but then I can do that for database.yml as well if
that was what's needed.
Lawrence
I concur with Ezra on this. It's very nice to have the database config
in a single place that other applications in other languages can
easily reference.
If the behaviour you mean is simply to ensure that production
credentials are loaded from an external file, there are surely simpler
ways of doing this than building a Ruby DSL (loathe as I am to use
that word, but you get what I mean).
My concern is that this is *too much solution*, even for the benefits
that it claims, and keeping Rails lithe and slim should be a priority.
The problems with the current setup need to be much clearer -
"database.yml is workable, but its a little weird at this point" isn't
sufficient reason to change, IMHO. Weird how? I'm not suggesting that
it isn't weird, but we should have some explicit reasons or examples.
Likewise, "there is no other instance of YAML configuration in one's
app when it runs
in production" is a bit misleading: the database is the *only common
external dependency* in a Rails application. It is a description of
something outside of the Ruby process (unlike any ruby libraries or
frameworks you might be loading). If every Rails application had to
also interface with, say, Solr, we'd probably have a config/solr.yml
file too.
As Frederick Cheung points out, another solution to the "DRY" concern
is using YAML in a smarter way. Rick suggested that "[it's] a lot
harder to read and write, especially for newbies", but we could solve
that by providing comments in the file to educate. Making the change
to a ruby-based configuration will still confuse newbies, thanks to
Google and all the tutorial pages that won't be updated.
In a nutshell, lets be sure that we're solving genuine problems and/or
providing tangible benefits, before we make the framework that little
bit more complicated.