global 0.2.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile CHANGED
@@ -1,11 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rubygems'
2
4
  require 'bundler'
3
5
 
4
6
  Bundler.require
5
7
 
6
8
  require 'rspec/core/rake_task'
7
- require "bundler/gem_tasks"
9
+ require 'bundler/gem_tasks'
10
+ require 'rubocop/rake_task'
8
11
 
9
12
  RSpec::Core::RakeTask.new(:spec)
13
+ RuboCop::RakeTask.new(:rubocop) do |task|
14
+ task.options = ['-D'] # Display cop name
15
+ task.fail_on_error = true
16
+ end
10
17
 
11
- task :default => :spec
18
+ desc 'Run all tests'
19
+ task default: %i[rubocop spec]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'global'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/global.gemspec CHANGED
@@ -1,33 +1,31 @@
1
- $:.push File.expand_path("../lib", __FILE__)
2
- require "global/version"
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+ require 'global/version'
3
5
 
4
6
  Gem::Specification.new do |s|
5
- s.name = "global"
7
+ s.name = 'global'
6
8
  s.version = Global::VERSION
7
- s.authors = ["Railsware LLC"]
8
- s.email = "contact@railsware.com"
9
-
10
- s.rubyforge_project = "global"
9
+ s.authors = ['Railsware LLC']
10
+ s.email = 'contact@railsware.com'
11
11
 
12
- s.description = "Simple way to load your configs from yaml"
13
- s.summary = "Simple way to load your configs from yaml"
12
+ s.description = 'Simple way to load your configs from yaml/aws/gcp'
13
+ s.summary = 'Simple way to load your configs from yaml/aws/gcp'
14
14
 
15
15
  s.files = `git ls-files`.split("\n")
16
16
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
- s.require_paths = ["lib"]
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
18
+ s.require_paths = ['lib']
19
19
 
20
- s.homepage = "https://github.com/railsware/global"
21
- s.licenses = ["MIT"]
20
+ s.homepage = 'https://github.com/railsware/global'
21
+ s.licenses = ['MIT']
22
22
 
23
- s.add_development_dependency "rspec", ">= 3.0"
24
- s.add_development_dependency "simplecov", "~> 0.7.1"
25
- s.add_development_dependency "rake", "~> 10.1.0"
26
- if defined?(JRUBY_VERSION)
27
- s.add_development_dependency "therubyrhino", ">= 0"
28
- else
29
- s.add_development_dependency "therubyracer", ">= 0"
30
- end
23
+ s.add_development_dependency 'aws-sdk-ssm', '~> 1'
24
+ s.add_development_dependency 'google-cloud-secret_manager', '~> 0'
25
+ s.add_development_dependency 'rake', '~> 12.3.1'
26
+ s.add_development_dependency 'rspec', '>= 3.0'
27
+ s.add_development_dependency 'rubocop', '~> 0.81.0'
28
+ s.add_development_dependency 'simplecov', '~> 0.16.1'
31
29
 
32
- s.add_runtime_dependency "activesupport", ">= 2.0"
30
+ s.add_runtime_dependency 'activesupport', '>= 2.0'
33
31
  end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Global
4
+ module Backend
5
+ # Loads Global configuration from the AWS Systems Manager Parameter Store
6
+ # https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html
7
+ #
8
+ # This backend requires the `aws-sdk` or `aws-sdk-ssm` gem, so make sure to add it to your Gemfile.
9
+ #
10
+ # Available options:
11
+ # - `prefix` (required): the prefix in Parameter Store; all parameters within the prefix will be loaded;
12
+ # make sure to add a trailing slash, if you want it
13
+ # see https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-organize.html
14
+ # - `client`: pass you own Aws::SSM::Client instance, or alternatively set:
15
+ # - `aws_options`: credentials and other AWS configuration options that are passed to AWS::SSM::Client.new
16
+ # see https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/SSM/Client.html#initialize-instance_method
17
+ # If AWS access is already configured through environment variables,
18
+ # you don't need to pass the credentials explicitly.
19
+ #
20
+ # For Rails:
21
+ # - the `prefix` is optional and defaults to `/[Rails enviroment]/[Name of the app class]/`,
22
+ # for example: `/production/MyApp/`
23
+ # - to use a different app name, pass `app_name`,
24
+ # for example: `backend :aws_parameter_store, app_name: 'new_name_for_my_app'`
25
+ class AwsParameterStore
26
+
27
+ PATH_SEPARATOR = '/'
28
+
29
+ def initialize(options = {})
30
+ require_aws_gem
31
+ init_prefix(options)
32
+ init_client(options)
33
+ end
34
+
35
+ def load
36
+ build_configuration_from_parameters(load_all_parameters_from_ssm)
37
+ end
38
+
39
+ private
40
+
41
+ def require_aws_gem
42
+ require 'aws-sdk-ssm'
43
+ rescue LoadError
44
+ begin
45
+ require 'aws-sdk'
46
+ rescue LoadError
47
+ raise 'Either the `aws-sdk-ssm` or `aws-sdk` gem must be installed.'
48
+ end
49
+ end
50
+
51
+ def init_prefix(options)
52
+ @prefix = if defined?(Rails)
53
+ options.fetch(:prefix) do
54
+ environment = Rails.env.to_s
55
+ app_name = options.fetch(:app_name) { Rails.application.class.module_parent_name }
56
+ "/#{environment}/#{app_name}/"
57
+ end
58
+ else
59
+ options.fetch(:prefix)
60
+ end
61
+ end
62
+
63
+ def init_client(options)
64
+ if options.key?(:client)
65
+ @ssm = options[:client]
66
+ else
67
+ aws_options = options.fetch(:aws_options, {})
68
+ @ssm = Aws::SSM::Client.new(aws_options)
69
+ end
70
+ end
71
+
72
+ def load_all_parameters_from_ssm
73
+ response = load_parameters_from_ssm
74
+ all_parameters = response.parameters
75
+ loop do
76
+ break unless response.next_token
77
+
78
+ response = load_parameters_from_ssm(response.next_token)
79
+ all_parameters.concat(response.parameters)
80
+ end
81
+
82
+ all_parameters
83
+ end
84
+
85
+ def load_parameters_from_ssm(next_token = nil)
86
+ @ssm.get_parameters_by_path(
87
+ path: @prefix,
88
+ recursive: true,
89
+ with_decryption: true,
90
+ next_token: next_token
91
+ )
92
+ end
93
+
94
+ # builds a nested configuration hash from the array of parameters from SSM
95
+ def build_configuration_from_parameters(parameters)
96
+ configuration = {}
97
+ parameters.each do |parameter|
98
+ parameter_parts = parameter.name[@prefix.length..-1].split(PATH_SEPARATOR).map(&:to_sym)
99
+ param_container = parameter_parts[0..-2].reduce(configuration) do |container, part|
100
+ container[part] ||= {}
101
+ end
102
+ param_container[parameter_parts[-1]] = parameter.value
103
+ end
104
+
105
+ configuration
106
+ end
107
+
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Global
4
+ module Backend
5
+ # Loads Global configuration from the filesystem
6
+ #
7
+ # Available options:
8
+ # - `path` (required): the directory with config files
9
+ # - `environment` (required): the environment to load
10
+ # - `yaml_whitelist_classes`: the set of classes that are permitted to unmarshal from the configuration files
11
+ #
12
+ # For Rails:
13
+ # - the `path` is optional and defaults to `config/global`
14
+ # - the `environment` is optional and defaults to the current Rails environment
15
+ class Filesystem
16
+
17
+ FILE_ENV_SPLIT = '.'
18
+ YAML_EXT = '.yml'
19
+
20
+ def initialize(options = {})
21
+ if defined?(Rails)
22
+ @path = options.fetch(:path) { Rails.root.join('config', 'global').to_s }
23
+ @environment = options.fetch(:environment) { Rails.env.to_s }
24
+ else
25
+ @path = options.fetch(:path)
26
+ @environment = options.fetch(:environment)
27
+ end
28
+ @yaml_whitelist_classes = options.fetch(:yaml_whitelist_classes, [])
29
+ end
30
+
31
+ def load
32
+ load_from_path(@path)
33
+ end
34
+
35
+ private
36
+
37
+ def load_from_path(path)
38
+ load_from_file(path).deep_merge(load_from_directory(path))
39
+ end
40
+
41
+ def load_from_file(path)
42
+ config = {}
43
+
44
+ if File.exist?(file = "#{path}#{YAML_EXT}")
45
+ configurations = load_yml_file(file)
46
+ config = get_config_by_key(configurations, 'default')
47
+ config.deep_merge!(get_config_by_key(configurations, @environment))
48
+ if File.exist?(env_file = "#{path}#{FILE_ENV_SPLIT}#{@environment}#{YAML_EXT}")
49
+ config.deep_merge!(load_yml_file(env_file) || {})
50
+ end
51
+ end
52
+
53
+ config
54
+ end
55
+
56
+ def get_config_by_key(config, key)
57
+ return {} if config.empty?
58
+
59
+ config[key.to_sym] || config[key.to_s] || {}
60
+ end
61
+
62
+ def load_yml_file(file)
63
+ file_contents = ERB.new(IO.read(file)).result
64
+ permitted_classes = [Date, Time, DateTime, Symbol].concat(@yaml_whitelist_classes)
65
+
66
+ if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('4')
67
+ YAML.safe_load(file_contents, permitted_classes: permitted_classes, aliases: true)
68
+ else
69
+ YAML.safe_load(file_contents, permitted_classes, [], true)
70
+ end
71
+ end
72
+
73
+ def load_from_directory(path)
74
+ config = {}
75
+
76
+ if File.directory?(path)
77
+ Dir["#{path}/*"].each do |entry|
78
+ namespace = File.basename(entry, YAML_EXT)
79
+ next if namespace.include? FILE_ENV_SPLIT # skip files with dot(s) in name
80
+
81
+ file_with_path = File.join(File.dirname(entry), File.basename(entry, YAML_EXT))
82
+ config.deep_merge!(namespace => load_from_path(file_with_path))
83
+ end
84
+ end
85
+
86
+ config
87
+ end
88
+
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Global
4
+ module Backend
5
+ # Loads Global configuration from the Google Cloud Secret Manager
6
+ # https://cloud.google.com/secret-manager/docs
7
+ #
8
+ # This backend requires the `google-cloud-secret_manager` gem, so make sure to add it to your Gemfile.
9
+ #
10
+ # Available options:
11
+ # - `project_id` (required): Google Cloud project name
12
+ # - `prefix` (required): the prefix in Secret Manager; all parameters within the prefix will be loaded;
13
+ # make sure to add a undescore, if you want it
14
+ # see https://cloud.google.com/secret-manager/docs/overview
15
+ # - `client`: pass you own Google::Cloud::SecretManager instance, or alternatively set:
16
+ # - `gcp_options`: credentials and other Google cloud configuration options
17
+ # that are passed to Google::Cloud::SecretManager.configure
18
+ # see https://googleapis.dev/ruby/google-cloud-secret_manager/latest/index.html
19
+ # If Google Cloud access is already configured through environment variables,
20
+ # you don't need to pass the credentials explicitly.
21
+ #
22
+ # For Rails:
23
+ # - the `prefix` is optional and defaults to `[Rails enviroment]-[Name of the app class]-`,
24
+ # for example: `production-myapp-`
25
+ # - to use a different app name, pass `app_name`,
26
+ # for example: `backend :gcp_secret_manager, app_name: 'new_name_for_my_app'`
27
+ class GcpSecretManager
28
+
29
+ GCP_SEPARATOR = '/'
30
+ PATH_SEPARATOR = '-'
31
+
32
+ def initialize(options = {})
33
+ @project_id = options.fetch(:project_id)
34
+ require_gcp_gem
35
+ init_prefix(options)
36
+ init_client(options)
37
+ end
38
+
39
+ def load
40
+ pages = load_all_parameters_from_gcsm
41
+
42
+ configuration = {}
43
+ pages.each do |page|
44
+ configuration.deep_merge!(build_configuration_from_page(page))
45
+ end
46
+
47
+ configuration
48
+ end
49
+
50
+ private
51
+
52
+ def require_gcp_gem
53
+ require 'google/cloud/secret_manager'
54
+ rescue LoadError
55
+ raise 'The `google-cloud-secret_manager` gem must be installed.'
56
+ end
57
+
58
+ def init_prefix(options)
59
+ @prefix = if defined?(Rails)
60
+ options.fetch(:prefix) do
61
+ environment = Rails.env.to_s
62
+ app_name = options.fetch(:app_name) { Rails.application.class.module_parent_name }
63
+ "#{environment}-#{app_name}-"
64
+ end
65
+ else
66
+ options.fetch(:prefix)
67
+ end
68
+ end
69
+
70
+ def init_client(options)
71
+ if options.key?(:client)
72
+ @gcsm = options[:client]
73
+ else
74
+ gcp_options = options.fetch(:gcp_options, {})
75
+ @gcsm = Google::Cloud::SecretManager.secret_manager_service do |config|
76
+ config.credentials = gcp_options[:credentials] if gcp_options[:credentials]
77
+ config.timeout = gcp_options[:timeout] if gcp_options[:timeout]
78
+ end
79
+ end
80
+ end
81
+
82
+ def load_all_parameters_from_gcsm
83
+ response = load_parameters_from_gcsm
84
+ all_pages = [response]
85
+ loop do
86
+ break if response.next_page_token.empty?
87
+
88
+ response = load_parameters_from_gcsm(response.next_page_token)
89
+ all_pages << response
90
+ end
91
+
92
+ all_pages
93
+ end
94
+
95
+ def load_parameters_from_gcsm(next_token = nil)
96
+ @gcsm.list_secrets(
97
+ parent: @gcsm.project_path(project: @project_id),
98
+ page_size: 25_000,
99
+ page_token: next_token
100
+ )
101
+ end
102
+
103
+ # builds a nested configuration hash from the array of parameters from Secret Manager
104
+ def build_configuration_from_page(page)
105
+ configuration = {}
106
+
107
+ page.each do |parameter|
108
+ key_name = get_gcp_key_name(parameter)
109
+ next unless key_name.start_with?(@prefix)
110
+
111
+ parameter_parts = key_name[@prefix.length..-1].split(PATH_SEPARATOR).map(&:to_sym)
112
+ param_container = parameter_parts[0..-2].reduce(configuration) do |container, part|
113
+ container[part] ||= {}
114
+ end
115
+ param_container[parameter_parts[-1]] = get_latest_key_value(key_name)
116
+ end
117
+
118
+ configuration
119
+ end
120
+
121
+ def get_gcp_key_name(parameter)
122
+ parameter.name.split(GCP_SEPARATOR).last
123
+ end
124
+
125
+ def get_latest_key_value(key_name)
126
+ name = @gcsm.secret_version_path(
127
+ project: @project_id,
128
+ secret: key_name,
129
+ secret_version: 'latest'
130
+ )
131
+ version = @gcsm.access_secret_version(name: name)
132
+ version.payload.data
133
+ end
134
+
135
+ end
136
+ end
137
+ end
data/lib/global/base.rb CHANGED
@@ -1,20 +1,26 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'erb'
4
4
  require 'json'
5
5
 
6
6
  module Global
7
7
  module Base
8
- extend self
9
8
 
10
- attr_writer :environment, :config_directory, :namespace, :except, :only
9
+ extend self
11
10
 
12
11
  def configure
13
12
  yield self
14
13
  end
15
14
 
16
15
  def configuration
17
- @configuration ||= load_configuration(config_directory, environment)
16
+ raise 'Backend must be defined' unless @backends
17
+
18
+ @configuration ||= begin
19
+ configuration_hash = @backends.reduce({}) do |configuration, backend|
20
+ configuration.deep_merge(backend.load.with_indifferent_access)
21
+ end
22
+ Configuration.new(configuration_hash)
23
+ end
18
24
  end
19
25
 
20
26
  def reload!
@@ -22,72 +28,46 @@ module Global
22
28
  configuration
23
29
  end
24
30
 
25
- def environment
26
- @environment || raise("environment should be defined")
27
- end
28
-
29
- def config_directory
30
- @config_directory || raise("config_directory should be defined")
31
- end
32
-
33
- def namespace
34
- @namespace ||= 'Global'
35
- end
36
-
37
- def except
38
- @except ||= :all
39
- end
40
-
41
- def only
42
- @only ||= []
43
- end
44
-
45
- def generate_js(options = {})
46
- current_namespace = options[:namespace] || namespace
47
-
48
- js_options = { except: except, only: only }.merge(options)
49
- "window.#{current_namespace} = #{configuration.filter(js_options).to_json}"
31
+ # Add a backend to load configuration from.
32
+ #
33
+ # You can define several backends; they will all be loaded
34
+ # and the configuration hashes will be merged.
35
+ #
36
+ # Configure with either:
37
+ # Global.backend :filesystem, path: 'config', environment: Rails.env
38
+ # or:
39
+ # Global.backend YourConfigurationBackend.new
40
+ #
41
+ # backend configuration classes MUST have a `load` method that returns a configuration Hash
42
+ def backend(backend, options = {})
43
+ @backends ||= []
44
+ if backend.is_a?(Symbol)
45
+ require "global/backend/#{backend}"
46
+ backend_class = Global::Backend.const_get(camel_case(backend.to_s))
47
+ @backends.push backend_class.new(options)
48
+ elsif backend.respond_to?(:load)
49
+ @backends.push backend
50
+ else
51
+ raise 'Backend must be either a Global::Backend class or a symbol'
52
+ end
50
53
  end
51
54
 
52
55
  protected
53
56
 
54
- def load_configuration(dir, env)
55
- config = load_from_file(dir, env)
56
- config.deep_merge!(load_from_directory(dir, env))
57
- Configuration.new(config)
57
+ def respond_to_missing?(method, include_private = false)
58
+ configuration.key?(method) || super
58
59
  end
59
60
 
60
- def load_from_file(dir, env)
61
- config = {}
62
-
63
- if File.exists?(file = "#{dir}.yml")
64
- configurations = YAML::load(ERB.new(IO.read(file)).result)
65
- config = configurations[:default] || configurations["default"] || {}
66
- config.deep_merge!(configurations[env] || {})
67
- end
68
-
69
- config
61
+ def method_missing(method, *args, &block)
62
+ configuration.key?(method) ? configuration.get_configuration_value(method) : super
70
63
  end
71
64
 
72
- def load_from_directory(dir, env)
73
- config = {}
65
+ # from Bundler::Thor::Util.camel_case
66
+ def camel_case(str)
67
+ return str if str !~ /_/ && str =~ /[A-Z]+.*/
74
68
 
75
- if File.directory?(dir)
76
- Dir["#{dir}/*"].each do |entry|
77
- namespace = entry.gsub(/^#{dir}\/?/, '').gsub(/\.yml$/, '')
78
- config.deep_merge!(namespace => load_configuration(entry.gsub(/\.yml$/, ''), env))
79
- end
80
- end
81
-
82
- config
83
- end
84
-
85
- def respond_to_missing?(method, include_private=false)
86
- configuration.key?(method) || super
69
+ str.split('_').map(&:capitalize).join
87
70
  end
88
71
 
89
- def method_missing(method, *args, &block)
90
- configuration.key?(method) ? configuration[method] : super
91
- end
92
72
  end
93
73
  end
@@ -1,15 +1,17 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'forwardable'
4
4
 
5
5
  module Global
6
6
  class Configuration
7
+
7
8
  extend Forwardable
8
9
 
9
10
  attr_reader :hash
10
11
 
11
- def_delegators :hash, :key?, :has_key?, :include?, :member?, :[], :[]=, :to_hash, :to_json, :inspect
12
-
12
+ def_delegators :hash, :key?, :has_key?, :include?,
13
+ :member?, :[], :[]=, :to_hash, :to_json,
14
+ :inspect, :fetch
13
15
 
14
16
  def initialize(hash)
15
17
  @hash = hash.respond_to?(:with_indifferent_access) ? hash.with_indifferent_access : hash
@@ -17,46 +19,51 @@ module Global
17
19
 
18
20
  def filter(options = {})
19
21
  keys = filtered_keys_list(options)
20
- hash.select{|key, _| keys.include?(key)}
22
+ hash.select { |key, _| keys.include?(key) }
23
+ end
24
+
25
+ def get_configuration_value(key)
26
+ return nil unless key?(key)
27
+
28
+ value = hash[key]
29
+ value.is_a?(Hash) ? Global::Configuration.new(value) : value
21
30
  end
22
31
 
23
32
  private
24
33
 
25
34
  def filtered_keys_list(options)
26
- if options[:except].is_a?(Array)
27
- return hash.keys - options[:except].map(&:to_s)
28
- end
29
-
30
- if options[:only].is_a?(Array)
31
- return hash.keys & options[:only].map(&:to_s)
32
- end
35
+ return hash.keys - options[:except].map(&:to_s) if options[:except].is_a?(Array)
36
+ return hash.keys & options[:only].map(&:to_s) if options[:only].is_a?(Array)
33
37
 
34
38
  return hash.keys if options[:only] == :all
35
39
  return [] if options[:except] == :all
36
- return []
40
+
41
+ []
37
42
  end
38
43
 
39
44
  protected
40
45
 
41
- def respond_to_missing?(method_name, include_private=false)
46
+ def respond_to_missing?(method_name, include_private = false)
42
47
  method = normalize_key_by_method(method_name)
43
- key?(method) || super
48
+ key?(method) || boolean_method?(method) || super
44
49
  end
45
50
 
46
51
  def method_missing(method, *args, &block)
47
52
  method = normalize_key_by_method(method)
48
53
  if key?(method)
49
- value = hash[method]
50
- value.kind_of?(Hash) ? Global::Configuration.new(value) : value
54
+ get_configuration_value(method)
51
55
  else
52
56
  super
53
57
  end
54
58
  end
55
59
 
56
- def normalize_key_by_method(method)
57
- '?' == method.to_s[-1] ? method.to_s[0..-2] : method
60
+ def boolean_method?(method)
61
+ '?' == method.to_s[-1]
58
62
  end
59
63
 
64
+ def normalize_key_by_method(method)
65
+ boolean_method?(method) ? method.to_s[0..-2].to_sym : method
66
+ end
60
67
 
61
68
  end
62
69
  end
@@ -1,5 +1,7 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Global
4
- VERSION = "0.2.2"
4
+
5
+ VERSION = '2.1.0'
6
+
5
7
  end
data/lib/global.rb CHANGED
@@ -1,4 +1,6 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
2
4
  require 'yaml'
3
5
 
4
6
  require 'active_support/core_ext/hash/indifferent_access'
@@ -6,9 +8,10 @@ require 'active_support/core_ext/hash/deep_merge'
6
8
 
7
9
  require 'global/configuration'
8
10
  require 'global/base'
9
- require 'global/engine' if defined?(Rails)
10
11
  require 'global/version'
11
12
 
12
13
  module Global
14
+
13
15
  extend Base
16
+
14
17
  end