9
\$\begingroup\$

I have used code which looks a little like this in an app I'm working on:

module App
  module Settings

    class << self
      attr_accessor :console, :file
    end

    self.console ||= true
    self.file    ||= []

  end
end

I decided to use this method because I like the interface, I had the idea after reading the Singleton examples from Design Patterns in Ruby.

I can then require 'app/settings' from any file in my app and read/write the settings

p App::Settings.console
App::Settings.file << "afile.rb"
p App::Settings.file

etc...

Is this a reasonable way to keep my settings in a single file? I feel I am unnecessarily repeating myself. Any ideas for DRYing?

\$\endgroup\$
6
\$\begingroup\$

So much gratuitous and unnecessary metaprogramming in the answers. Do the simplest thing that could possibly work.

require 'ostruct'

module App
  Settings = OpenStruct.new({
    :console => true,
    :file => []
  })
end

App::Settings.console   # => true

App::Settings.widget = 'frobnosticated'
App::Settings.widget # => 'frobnosticated'

This will give you the behavior you're looking for with none of the metaprogramming cognitive overhead.

\$\endgroup\$
6
\$\begingroup\$

There is nothing really wrong with this but using a simple hash may be easier:

module App
    @@settings = {
        :console => true,
        :file => []
    }
    def self.settings
        @@settings
    end
end

Your following code would then become:

p App.settings[:console]
App.settings[:file] << "afile.rb"
p App.settings[:file]

There are several advantages to this approach:

  • Hashes are easy to serialise and store in files using yaml/json/etc.

    require 'yaml'
    p App.settings.to_yaml
    
  • You can reference App.settings in a local variable to avoid writing it out:

    s = App.settings
    #do stuff with s
    
  • Hash has many convenience methods for iterating over its values.

    App.settings.each do |key, value|
        #do something
    end
    
  • You can add to/update it succinctly using the merge! method.

    App.settings.merge! :console => false, :newsetting => 'value', :etc => '...'
    

To get functionality like this in your method would require you to write lots of methods yourself. It's better to just use the built in functionality that Ruby has instead of reinventing the wheel.

\$\endgroup\$
  • \$\begingroup\$ Why you use @@settings as a class variable instead of a instance variable? \$\endgroup\$ – LBg Feb 27 '11 at 14:37
  • 2
    \$\begingroup\$ Why would I use an instance variable? \$\endgroup\$ – david4dev Feb 27 '11 at 15:38
  • \$\begingroup\$ Thanks, this looks good to me. Re:instance variables and @LBg too. My understanding is that modules are included once only in the ruby class hierarchy. Is there then a need for class variables if I can use instance variables? Is the effect the same, as modules can not be instantiated? \$\endgroup\$ – bluekeys Feb 28 '11 at 7:10
3
\$\begingroup\$

Based on @david4dev solution, you can keep the old interface:

module App
    @settings = {
        :console => true,
        :file => []
    }
    def @settings.method_missing(name, *a)
        name.to_s =~ /(.*)=/ ? self[$1.to_sym] = a[0] : self[name]
    end
    def self.settings
        @settings
    end
end

p App.settings.console
App.settings.file << "afile.rb"
p App.settings.file

Additionally you can use Settings as a constant, as in your original code:

module App
    # ...
    def self.const_missing(name)
        name == :Settings ? @settings : super
    end
end

p App::Settings.console
App::Settings.file << "afile.rb"
p App::Settings.file

require 'yaml'
p App::Settings.to_yaml
\$\endgroup\$
  • \$\begingroup\$ please can you explain the purpose of App.settings.method_missing? \$\endgroup\$ – bluekeys Feb 28 '11 at 7:17
  • \$\begingroup\$ @dsjbirch, without this you can only access one of the settings with App.settings[:console]. Is just a wrapper to convert a undefined method (like console) to [:console]. I think it makes the interface better. \$\endgroup\$ – LBg Feb 28 '11 at 17:42
  • \$\begingroup\$ method_missing and const_missing here unnecessary and will only cause later debugging confusion. Use an OpenStruct instead. If you want a constant, make a constant. -1 \$\endgroup\$ – Rein Henrichs Apr 6 '11 at 18:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.