Suggestions for common/global data in app (cli)


#1

I am writing a (my first) client side ruby app. Many programs use the
same data such as db login, logger etc. Is there a standard ruby way of
doing this? Some suggestions would be useful.

  • I have used YAML for config data for single scripts earlier
  • I am aware of 'load’ing a ruby file which could have config data in
    it.
  • A module with global constants

Would these files (yaml, load) have to loaded in each program?

Have I missed any approach? I have heard of global vars but never used.

I would appreciate a link to any ruby application best practices, too.
Thanks.


#2

Nit K. wrote:

Have I missed any approach? I have heard of global vars but never used.

I would appreciate a link to any ruby application best practices, too.
Thanks.

One suggestion (based on yaml):

http://redshift.sourceforge.net/preferences/

It also provides a utility method to find the right user dir to store
prefs in, based on env vars.

It may be overkill for a simple program though.


#3

Joel VanderWerf wrote:

One suggestion (based on yaml):

http://redshift.sourceforge.net/preferences/

It also provides a utility method to find the right user dir to store
prefs in, based on env vars.

It may be overkill for a simple program though.
Thanks, looks good. My app should become pretty large. I like the idea
of config being saved too (easily), so next execution takes the new
config.

Btw, I am aware of Configatron, too.


#4

Nit K. wrote:

I am writing a (my first) client side ruby app. Many programs use the
same data such as db login, logger etc. Is there a standard ruby way of
doing this? Some suggestions would be useful.

  • I have used YAML for config data for single scripts earlier

This is very common.

  • I am aware of 'load’ing a ruby file which could have config data in
    it.
  • A module with global constants

That’s a possibility too.

Would these files (yaml, load) have to loaded in each program?

Yes, assuming these programs are separate (e.g. separate commands
invoked from the command line) as they will get a fresh Ruby interpreter
instance.

Have I missed any approach?

You may be able to combine this with the “dependency injection” pattern.
This sounds complicated but is actually very simple.

Jim W. wrote an excellent depinj.rb which was published on
onestepback.org; unfortunately he is in the process of reorganising his
blog and the download is not currently available.

You basically write Ruby code to configure each of your objects:

container = DI::Container.new
container.register(:db_host) { “localhost” }
container.register(:db_user) { “root” }
container.register(:db_pass) { “xyzzy” }
container.register(:db) { |c|
DataBaseAdapter.new(c.db_host, c.db_user, c.db_pass)
}

When you first reference container.db then the database connection is
instantiated for you. The nice thing is that all the interdependencies
between objects is sorted out for you.

I have heard of global vars but never used.

That would be just another way to refer to the loaded data. You could
write:

require “yaml”
$config = YAML.load("/etc/myapp.conf")

and then anywhere else in your program you could say

$config[“db_user”]

or whatever.

Remember that YAML allows nesting, so you could have

db:
host: localhost
user: root
pass: xyzzy
logger:
level: 2
file: /var/log/stuff

then you could refer to $config[“db”][“host”] etc.

B.


#5

Nit K. wrote:

Brian C. wrote:

Thanks for the tip on dependency injection. Did some reading up last
night - there’s needle.soureforge.net (based on Jim W), and also an
article on onestepback:
http://onestepback.org/index.cgi/Tech/Ruby/DependencyInjectionInRuby.rdoc
.

Perhaps, not what I need at present, but good to know for the future.


#6

Brian C. wrote:

Would these files (yaml, load) have to loaded in each program?

Yes, assuming these programs are separate (e.g. separate commands
invoked from the command line) as they will get a fresh Ruby interpreter
instance.

I have heard of global vars but never used.

That would be just another way to refer to the loaded data. You could
write:

require “yaml”
$config = YAML.load("/etc/myapp.conf")

and then anywhere else in your program you could say

$config[“db_user”]

Small question. The app is not separate programs loaded from the command
line. The main menu loads, then other classes available in other files
are required and instantiated.

So there are many separate files, but one ruby interpreter. Will
"$config[“XXX”] work through all the programs ? That seems fairly easy.
Will look into dep inj soon - thanks for the tip.


#7

On Oct 8, 2008, at 10:57 AM, Nit K. wrote:

Would these files (yaml, load) have to loaded in each program?

Have I missed any approach? I have heard of global vars but never
used.

I would appreciate a link to any ruby application best practices, too.
Thanks.

Posted via http://www.ruby-forum.com/.

gem install configuration

NAME
configuration.rb

SYNOPSIS
pure ruby scoped configuration files

DESCRIPTION
configuration.rb provides a mechanism for configuring ruby programs
with
ruby configuration files. a configuration.rb file, for example
‘config/app.rb’, can be written simply as

   Configuration.for('app'){
     key 'value'
     foo 'bar'
     port 42
   }

and loaded via the normal ruby require/load mechanism

   Kernel.load 'config/app.rb'

or with a slightly augmented loading mechnanism which simply
searches an
extra set of paths in addition to the standard ones

   Configuration.path = %w( config configuration )

   Configuration.load 'app'

configurations are completely open

   Configuration.for('app'){
     object_id 'very open'
   }

support arbitrarily nested values

   Configuration.for('app'){
     a { b { c { d 42 } } }
   }

   c = Configuration.for 'app'

   p c.a.b.c.d #=> 42

allow POLS scoped lookup of vars

 Configuration.for('config'){
   outer 'bar'

   inner {
     value 42
   }
 }

 c = Configuration.for 'config'

 p c.outer       #=> 'bar'
 p c.inner.value #=> 42
 p c.inner.outer #=> 'bar'

and not a whole lot else - configuration.rb is s very small library
consisting of one file and < 150 loc

SAMPLES

<========< samples/a.rb >========>

~ > cat samples/a.rb

 #
 # basic usage is quite, simple, load the config and use it's

values. the
# config syntax is fairly obvious, i think, but note that it is
ruby and any
# ruby can be included. also note that each config is named,
allowing
# multiple configs to be places in one file
#
require ‘configuration’

   c = Configuration.load 'a'

   p c.a + c.b - c.c

~ > cat config/a.rb

 Configuration.for('a'){
   a 40
   b 4
   c 2
 }

~ > ruby samples/a.rb

 42

<========< samples/b.rb >========>

~ > cat samples/b.rb

 #
 # configuration.rb supports a very natural nesting syntax.  note

how values
# are scoped in a POLS fashion
#
require ‘configuration’

   c = Configuration.for 'b'

   p c.www.url
   p c.db.url
   p c.mail.url

~ > cat config/b.rb

 Configuration.for('b'){
   host "codeforpeople.com"

   www {
     port 80
     url "http://#{ host }:#{ port }"
   }

   db {
     port 5342
     url "db://#{ host }:#{ port }"
   }

   mail {
     host "gmail.com"
     port 25
     url "mail://#{ host }:#{ port }"
   }
 }

~ > ruby samples/b.rb

 "http://codeforpeople.com:80"
 "db://codeforpeople.com:5342"
 "mail://gmail.com:25"

<========< samples/c.rb >========>

~ > cat samples/c.rb

 #
 # configuration.rb let's you keep code very dry.
 #

   require 'configuration'

   Configuration.load 'c'

   p Configuration.for('development').db
   p Configuration.for('production').db
   p Configuration.for('testing').db

~ > cat config/c.rb

 %w( development production testing ).each do |environment|

   Configuration.for(environment){
     adapter "sqlite3"
     db "db/#{ environment }"
   }

 end

~ > ruby samples/c.rb

 "db/development"
 "db/production"
 "db/testing"

<========< samples/d.rb >========>

~ > cat samples/d.rb

 #
 # configuration.rb makes use of an external blank slate dsl, this

means that
# you Configuration objects do, in fact, have all built-in ruby
methods such
# as #inspect, etc, unless you configure over the top of them.
the effect
# is a configuration object that behaves like a nice ruby object,
but which
# allows any key to be configured
#
require ‘configuration’

   c = Configuration.for 'd'

   p c.object_id
   p c.inspect
   p c.p

~ > cat config/d.rb

 Configuration.for('d'){
   object_id 42
   inspect 'forty-two'
   p 42.0
 }

~ > ruby samples/d.rb

 42
 "forty-two"
 42.0

<========< samples/e.rb >========>

~ > cat samples/e.rb

 #
 # configuration.rb uses a totally clean slate dsl for the block.

if you need
# to access base Object methods you can do this
#

   require 'configuration'

   c = Configuration.for 'e'

   p c.foo
   p c.bar
   p c.foobar

~ > cat config/e.rb

 Configuration.for('e'){
   foo 42

   if Send('respond_to?', 'foo')
     bar 'forty-two'
   end

   respond_to = Method('bar')

   if respond_to.call('bar')
     foobar 42.0
   end
 }

~ > ruby samples/e.rb

 42
 "forty-two"
 42.0

AUTHOR
removed_email_address@domain.invalid

a @ http://codeforpeople.com/


#8

Nit K. wrote:

Small question. The app is not separate programs loaded from the command
line. The main menu loads, then other classes available in other files
are required and instantiated.

So there are many separate files, but one ruby interpreter. Will
"$config[“XXX”] work through all the programs ?

Yes. Your apps will be written in such a way that they’re somewhat tied
to the global data structure, but if there’s no intention of re-using
the component objects elsewhere that’s no problem.

Otherwise you could do something like this:

[config file]
thing1:
x: abc
y: def
thing2:
a: xyz
b: tuv

[library]
class Thing1
def initialize(params)
x = params[“x”]
y = params[“y”]
end
end

[initialisation]
$config = YAML.load("/etc/myapp.conf")
o1 = Thing1.new($config[“thing1”])

You can of course put object instances in global variables too. They can
be named:

$logger ||= Logger.new(…)

Or you can use the “object locator” pattern, which is a fancy way of
saying an object containing the other objects.

$locator ||= {}
$locator[:logger] ||= Logger.new(…)

Taking this one step further you get to dep inj, since creation of each
object can make use of other objects in the container.

Will look into dep inj soon - thanks for the tip.

I see you already found the page. I had a copy of the code lying around,
so I’ll attach it to this message.

Personally I’d load the $config and then use depinj to set up the
individual objects. This code is very easy to re-use, since any objects
in the container which you don’t access don’t get created.