Class: ActiveConfig

Inherits:
Object
  • Object
show all
Defined in:
lib/active_config.rb,
lib/active_config/suffixes.rb,
lib/active_config/hash_config.rb

Overview

… test_local.yaml: … hash_1:

foo: "foo"
bar: "baz"
zzz: "zzz"

irb> ActiveConfig.test
=> {"array_1"=>["a", "b", "c", "d"], "perform_caching"=>true,
"default"=>"yo!", "lazy"=>true, "hash_1"=>{"zzz"=>"zzz", "foo"=>"foo",
"bok"=>"bok", "bar"=>"baz"}, "secure_login"=>true, "test_mode"=>true}

--Notice that the hash produced is the result of merging the above
config files in a particular order

The overlay order of the config files is defined by ActiveConfig._get_file_suffixes:
* nil
* _local
* _config
* _local_config
* _{environment} (.i.e _development)
* _{environment}_local (.i.e _development_local)
* _{hostname} (.i.e _whiskey)
* _{hostname}_config_local (.i.e _whiskey_config_local)

------------------------------------------------------------------
irb> ActiveConfig.test_local
=> {"hash_1"=>{"zzz"=>"zzz", "foo"=>"foo", "bar"=>"baz"}, "test_mode"=>true}

Defined Under Namespace

Classes: DuplicateConfig, HashConfig, HashWithHooks, Suffixes

Constant Summary collapse

EMPTY_ARRAY =
[ ].freeze

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ ActiveConfig

ActiveConfig.new take options from a hash (or hash like) object. Valid keys are:

:path           :  Where it can find the config files, defaults to ENV['ACTIVE_CONFIG_PATH'], or RAILS_ROOT/etc
:root_file      :  Defines the file that holds "top level" configs. (ie active_config.key).  Defaults to "global"
:suffixes       :  Either a suffixes object, or an array of suffixes symbols with their priority.  See the ActiveConfig::Suffixes object
:config_refresh :  How often we should check for update config files

FIXME TODO



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/active_config.rb', line 75

def initialize opts={}
  opts = Hash[:path,opts] if opts.is_a?(String) or opts.is_a?(Array)
  if opts.is_a?(Array) 
    opts=Hash[:path,opts.join(':')]
  end 
  @config_path=opts[:path] || ENV['ACTIVE_CONFIG_PATH'] || (defined?(RAILS_ROOT) ? File.join(RAILS_ROOT,'etc') : nil)
  @opts=opts
  if opts[:one_file]
    @root_file=@config_path 
  else
  @root_file=opts[:root_file] || 'global' 
    if ActiveConfig::Suffixes===opts[:suffixes]
      @suffixes_obj = opts[:suffixes] 
    end
  end
  @suffixes_obj ||= Suffixes.new self, opts[:suffixes]
  @suffixes_obj.ac_instance=self
  @config_refresh = 
    (opts.has_key?(:config_refresh) ? opts[:config_refresh].to_i : 300)
  @on_load = { }
  self._flush_cache
  dups_h=Hash.new{|h,e|h[e]=[]}
  self._config_path.map{|e|
    if File.exists?(e) and File.directory?(e)
      Dir[e + '/*'].map{|f|
        if File.file?(f)
          dups_h[File.basename(f)] << f
        end
      }
    else
      STDERR.puts "WARNING:  Active Config Path NOT FOUND #{e}" unless opts[:quiet]
    end
  }
  dups = dups_h.to_a.select{|k,v|v.size>=2}
  raise ActiveConfig::DuplicateConfig.new(dups.map{|e|"Duplicate file #{e.first} found in \n#{e.last.map{|el|"\t"+el}.join("\n")}"}.join("\n")) if dups.size>0
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Object

Short-hand access to config file by its name.

Example:

ActiveConfig.global(:foo) => ActiveConfig.with_file(:global).foo
ActiveConfig.global.foo   => ActiveConfig.with_file(:global).foo


371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/active_config.rb', line 371

def method_missing(method, *args)
  return self[method.to_sym] if @opts[:one_file] 
  if method.to_s=~/^_(.*)/
    _flush_cache 
    return @suffixes.send($1, *args)
  else 
    if @root_file && rf=get_config_file(@root_file)      
      if rf.has_key?(method.to_sym) || rf.has_key?(method.to_s)
        return with_file(@root_file).send(method,*args)
      end
    end
    value = with_file(method, *args)
    value
  end
end

Instance Method Details

#[](key, file = @root_file) ⇒ Object

Gets a value from the global config file



359
360
361
# File 'lib/active_config.rb', line 359

def [](key, file=@root_file)
  get_config_file(file)[key]
end

#_check_config_changed(iname = nil) ⇒ Object



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/active_config.rb', line 293

def _check_config_changed(iname=nil)
  iname=iname.nil? ?  @cache_hash.keys.dup : [*iname]
  ret=iname.map{ | name |
  # STDERR.puts "ActiveConfig: config changed? #{name.inspect} reload_disabled = #{@reload_disabled}" if @verbose
  if config_changed?(name) && ! @reload_disabled 
    STDERR.puts "ActiveConfig: config changed #{name.inspect}" if @verbose
    if @cache_hash[name]
      @cache_hash[name] = nil

      # force on_load triggers.
      name
    end
  end
  }.compact
  return nil if ret.empty?
  ret
end

#_config_files(name) ⇒ Object

Returns a list of all relavant config files as specified by the suffixes object.



230
231
232
233
234
235
236
237
# File 'lib/active_config.rb', line 230

def _config_files(name) 
  return [name] if File.exists?(name) and not File.directory?(name)
  _suffixes.for(name).inject([]) do | files,name_x |
    _config_path.reverse.inject(files) do |files, dir |
      files <<  File.join(dir, name_x.to_s + '.yml')
    end
  end
end

#_config_hash(name) ⇒ Object



239
240
241
242
243
244
245
246
247
# File 'lib/active_config.rb', line 239

def _config_hash(name)
  unless result = @cache_hash[name]
    result = @cache_hash[name] = 
      HashConfig._make_indifferent_and_freeze(
        _load_config_files(name).inject({ }) { | n, h | n.weave(h, false) })
  end
  #$stderr.puts result.inspect
  result
end

#_config_pathObject



111
112
113
114
115
116
117
118
# File 'lib/active_config.rb', line 111

def _config_path
  @config_path_ary ||=
    begin
      path_sep = (@config_path =~ /;/) ? /;/ : /:/ # Make Wesha happy
      path = @config_path.split(path_sep).reject{ | x | x.empty? }
      path.map!{|x| File.expand_path(x).freeze }.freeze
    end
end

#_fire_on_load(name) ⇒ Object

Do reload callbacks.



282
283
284
285
286
287
288
289
290
291
# File 'lib/active_config.rb', line 282

def _fire_on_load(name)
  callbacks = 
    (@on_load['ANY'] || EMPTY_ARRAY) + 
    (@on_load[name] || EMPTY_ARRAY)
  callbacks.uniq!
  STDERR.puts "_fire_on_load(#{name.inspect}): callbacks = #{callbacks.inspect}" if @verbose && ! callbacks.empty?
  callbacks.each do | cb |
    cb.call()
  end
end

#_flush_cache(*types) ⇒ Object

DON’T CALL THIS IN production.



121
122
123
124
125
126
127
128
129
130
131
# File 'lib/active_config.rb', line 121

def _flush_cache *types
  if types.size == 0 or types.include? :hash
    @cache_hash = { }
    @hash_times = Hash.new(0)
  end
  if types.size == 0 or types.include? :file
    @file_times = Hash.new(0)
    @file_cache = { }
  end
  self
end

#_load_config_files(name, force = false) ⇒ Object

Get each config file’s yaml hash for the given config name, to be merged later. Files will only be loaded if they have not been loaded before or the files have changed within the last five minutes, or force is explicitly set to true.

If file contains the comment:

# ACTIVE_CONFIG:ERB

It will be run through ERb before YAML parsing with the following object bound:

active_config.config_file => <<the name of the config.yml file>>
active_config.config_directory => <<the directory of the config.yml>>
active_config.config_name => <<the config name>>
active_config.config_files => <<Array of config files to be parsed>>


163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/active_config.rb', line 163

def _load_config_files(name, force=false)
  name = name.to_s
  now = Time.now

  # Get array of all the existing files file the config name.
  config_files = _config_files(name)
  
  #$stderr.puts config_files.inspect
  # Get all the data from all yaml files into as hashes
  _fire_on_load(name)
  hashes = config_files.collect do |f|
    filename=f
    val=nil
    mod_time=nil
    next unless File.exists?(filename)
    next(@file_cache[filename]) unless (mod_time=File.stat(filename).mtime) != @file_times[filename]
    begin
    File.open( filename ) { | yf |
      val = yf.read
    }
    # If file has a # ACTIVE_CONFIG:ERB comment,
    # Process it as an ERb first.
    if /^\s*#\s*ACTIVE_CONFIG\s*:\s*ERB/i.match(val)
      # Prepare a object visible from ERb to
      # allow basic substitutions into YAMLs.
      active_config = HashConfig.new({
        :config_file => filename,
        :config_directory => File.dirname(filename),
        :config_name => name,
        :config_files => config_files,
      })
      val = ERB.new(val).result(binding)
    end
    # Read file data as YAML.
    val = YAML::load(val)
    # STDERR.puts "ActiveConfig: loaded #{filename.inspect} => #{val.inspect}"
    (@config_file_loaded ||= { })[name] = config_files
    rescue Exception => e
      raise
    end
    @file_cache[filename]=val
    @file_times[filename]=mod_time
    @file_cache[filename]
  end
  hashes.compact
end

#_reload_delay=(x) ⇒ Object



137
138
139
# File 'lib/active_config.rb', line 137

def _reload_delay=(x)
  @config_refresh = x || 300
end

#_reload_disabled=(x) ⇒ Object



133
134
135
# File 'lib/active_config.rb', line 133

def _reload_disabled=(x)
  @reload_disabled = x.nil? ? false : x
end

#_suffixesObject



63
64
65
# File 'lib/active_config.rb', line 63

def _suffixes
  @suffixes_obj
end

#_verbose=(x) ⇒ Object



141
142
143
# File 'lib/active_config.rb', line 141

def _verbose=(x)
  @verbose = x.nil? ? false : x;
end

#disable_reload(&block) ⇒ Object

Disables any reloading of config, executes &block, calls check_config_changed, returns result of block



341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/active_config.rb', line 341

def disable_reload(&block)
  # This should increment @reload_disabled on entry, decrement on exit.
  # -- kurt 2007/06/12
  result = nil
  reload_disabled_save = @reload_disabled
  begin
    @reload_disabled = true
    result = yield
  ensure
    @reload_disabled = reload_disabled_save
    _check_config_changed unless @reload_disabled
  end
  result
end

#get_config_file(name) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/active_config.rb', line 211

def get_config_file(name)
  # STDERR.puts "get_config_file(#{name.inspect})"
  name = name.to_s # if name.is_a?(Symbol)
  now = Time.now
  return @cache_hash[name.to_sym] if 
    (now.to_i - @hash_times[name.to_sym]  < @config_refresh) 
  # return cached if we have something cached and no reload_disabled flag
  return @cache_hash[name.to_sym] if @cache_hash[name.to_sym] and @reload_disabled
  # $stderr.puts "NOT USING CACHED AND RELOAD DISABLED" if @reload_disabled
  @cache_hash[name.to_sym]=begin
    x = _config_hash(name)
    @hash_times[name.to_sym]=now.to_i
    x
  end
end

#on_load(*args, &blk) ⇒ Object

Register a callback when a config has been reloaded.

The config :ANY will register a callback for any config file change.

Example:

class MyClass 
  @my_config = { }
  ActiveConfig.on_load(:global) do 
    @my_config = { } 
  end
  def my_config
    @my_config ||= something_expensive_thing_on_config(ACTIVEConfig.global.foobar)
  end
end


267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/active_config.rb', line 267

def on_load(*args, &blk)
  args << :ANY if args.empty?
  proc = blk.to_proc

  # Call proc on registration.
  proc.call()

  # Register callback proc.
  args.each do | name |
    name = name.to_s
    (@on_load[name] ||= [ ]) << proc
  end
end

#reload(force = false) ⇒ Object

If you are using this in production code, you fail.



328
329
330
331
332
333
# File 'lib/active_config.rb', line 328

def reload(force = false)
  if force || ! @reload_disabled
    _flush_cache
  end
  nil
end

#with_file(name, *args) ⇒ Object



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/active_config.rb', line 311

def with_file(name, *args)
  # STDERR.puts "with_file(#{name.inspect}, #{args.inspect})"; result = 
  args.inject(get_config_file(name)) { | v, i | 
    # STDERR.puts "v = #{v.inspect}, i = #{i.inspect}"
    case v
    when Hash
      v[i.to_s]
    when Array
      i.is_a?(Integer) ? v[i] : nil
    else
      nil
    end
  }
  # STDERR.puts "with_file(#{name.inspect}, #{args.inspect}) => #{result.inspect}"; result
end