Forum: Ruby Suggestions for common/global data in app (cli)

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
R. K. (Guest)
on 2008-10-08 21:00
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.
Joel VanderWerf (Guest)
on 2008-10-08 21:45
(Received via mailing list)
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.
R. K. (Guest)
on 2008-10-08 21:52
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.
Brian C. (Guest)
on 2008-10-08 22:54
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.
R. K. (Guest)
on 2008-10-08 23:06
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.
R. K. (Guest)
on 2008-10-09 07:42
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/Depende...
.

Perhaps, not what I need at present, but good to know for the future.
Ara H. (Guest)
on 2008-10-09 08:15
(Received via mailing list)
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/
Brian C. (Guest)
on 2008-10-09 12:09
Attachment: depinj.rb (0 Bytes)
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.
This topic is locked and can not be replied to.