Class: Setting

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/setting.rb

Defined Under Namespace

Classes: AlreadyLoaded, FileError, NotFound

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeSetting

Instance Methods



84
85
86
# File 'lib/setting.rb', line 84

def initialize
  @available_settings ||= new_available_settings
end

Instance Attribute Details

#available_settingsObject (readonly)

Returns the value of attribute available_settings.



13
14
15
# File 'lib/setting.rb', line 13

def available_settings
  @available_settings
end

Class Method Details

.[](value) ⇒ Object

In [] invocation syntax, we return settings value 'as is' without Hash conversions.

For example, if the YML data is: tax:

default: 0.0
california: 7.5

Then calling Setting returns

{ 'default' => "0.0", 'california' => "7.5"}


71
72
73
# File 'lib/setting.rb', line 71

def self.[](value)
  self.instance.value_for(value)
end

.available_settingsObject

DEPRECATED: Please use method accessors instead.



76
77
78
# File 'lib/setting.rb', line 76

def self.available_settings
  self.instance.available_settings
end

.load(args = {}) ⇒ Object

This method can be called only once.

Parameter hash looks like this:

{  :files => [ "file1.yml", "file2.yml", ...],
   :path  => "/var/www/apps/my-app/current/config/settings",
   :local => true }

If :local => true is set, we will load all *.yml files under :path/local directory after all files in :files have been loaded. “Local” settings thus take precedence by design. See README for more details.

Raises:



27
28
29
30
# File 'lib/setting.rb', line 27

def self.load(args = {})
  raise AlreadyLoaded.new('Settings already loaded') if self.instance.loaded?
  self.instance.load(args)
end

.method_missing(method, *args, &block) ⇒ Object

In Method invocation syntax we collapse Hash values and return a single value if 'default' is found among keys or Hash has only one key/value pair.

For example, if the YML data is: tax:

default: 0.0
california: 7.5

Then calling Setting.tax returns “0.0”“

This is the preferred method of using settings class.



49
50
51
52
53
# File 'lib/setting.rb', line 49

def self.method_missing(method, *args, &block)
  self.instance.value_for(method, args) do |v, args|
    self.instance.collapse_hashes(v, args)
  end
end

.reload(args = {}) ⇒ Object



32
33
34
# File 'lib/setting.rb', line 32

def self.reload(args = {})
  self.instance.load(args)
end

.respond_to?(method_name, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


55
56
57
58
# File 'lib/setting.rb', line 55

def self.respond_to?(method_name, include_private = false)
  self.instance.available_settings.has_key?(method_name.to_s.sub(/\?\z/, '')) ||
    super
end

Instance Method Details

#collapse_hashes(v, args) ⇒ Object

This method performs collapsing of the Hash settings values if the Hash contains 'default' value, or just 1 element.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/setting.rb', line 118

def collapse_hashes(v, args)
  out = if v.is_a?(Hash)
    if args.empty?
      if v.has_key?("default")
        v['default'].nil? ? "" : v['default']
      elsif v.keys.size == 1
        v.values.first
      else
        v
      end
    else
      v[args.shift.to_s]
    end
  else
    v
  end
  if out.is_a?(Hash) && !args.empty?
      collapse_hashes(out, args)
  elsif out.is_a?(Hash) && out.has_key?('default')
    out['default']
  else
    out
  end
end

#has_key?(key) ⇒ Boolean

Returns:

  • (Boolean)


88
89
90
91
# File 'lib/setting.rb', line 88

def has_key?(key)
  @available_settings.has_key?(key) ||
    (key[-1,1] == '?' && @available_settings.has_key?(key.chop))
end

#load(params) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/setting.rb', line 147

def load(params)
  # reset settings hash
  @available_settings = new_available_settings
  @loaded = false

  files = []
  path  = params[:path] || Dir.pwd
  params[:files].each do |file|
    files << File.join(path, file)
  end

  if params[:local]
    files << Dir.glob(File.join(path, 'local', '*.yml')).sort
  end

  files.flatten.each do |file|
    begin
      # Ruby versions before 3.0.3 include Psych < 3.3.2, which does not include `unsafe_load`. In those versions,
      # `load` is the behavior we want (in later versions, `load` uses `safe_load`, which doesn't support aliases and
      # requires allowlisting classes used in files.
      if Psych::VERSION < '3.3.2'
        @available_settings.deep_merge!(YAML::load(ERB.new(IO.read(file)).result) || {}) if File.exist?(file)
      else
        @available_settings.deep_merge!(YAML::unsafe_load(ERB.new(IO.read(file)).result) || {}) if File.exist?(file)
      end
    rescue Exception => e
      raise FileError.new("Error parsing file #{file}, with: #{e.message}")
    end
  end

  @loaded = true
  @available_settings
end

#loaded?Boolean

Returns:

  • (Boolean)


143
144
145
# File 'lib/setting.rb', line 143

def loaded?
  @loaded
end

#value_for(key, args = []) ⇒ Object

Raises:



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/setting.rb', line 93

def value_for(key, args = [])
  name = key.to_s
  raise NotFound.new("#{name} was not found") unless has_key?(name)
  bool = false
  if name[-1,1] == '?'
    name.chop!
    bool = true
  end

  v = @available_settings[name]
  if block_given?
    v = yield(v, args)
  end


  if v.is_a?(Integer) && bool
    v.to_i > 0
  else
    v
  end
end