climate_control 0.2.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 21d2f4ce1d74f8b2e4650cd7ab4efd3d40dac9d6
4
- data.tar.gz: d14c7f5bc3b4ae309682f529c0ada07a9a874d9b
2
+ SHA256:
3
+ metadata.gz: 919bb6d645214941bca1f5de4d30732891348bb76a29725e9db8ba00b6274dbc
4
+ data.tar.gz: 332e9222519c1c6a91d866783d4c3993c4372ffe29242f60759cf9948636d3ff
5
5
  SHA512:
6
- metadata.gz: 1c533460213a9e6a4285c3daa7fade227a4a5a5e4eb9a23e41d6bb06362c60468739476dd916be6b9ec3447b3e155dc1594e0fd89597ae58935acef27b850940
7
- data.tar.gz: 1a9aefaad845f0cdcb147b77cd176e81c0004d28fa17979aab312d27fe1e5251653902bc6fe05581ccd1ecab49075f122b7d029953c708af350e6ad0809f575d
6
+ metadata.gz: 1c5c9c97dcef22aa4923d284f196addeb1ddeb2f87c80785fd5d8a38e3f23ea7e66e11acbe265e225bf3663d36dbe500edc981d5baaf53cc872a19670dfa1878
7
+ data.tar.gz: c56fb5c40969ed979a485503765b33f7f4adc2d34830309502a2207dad363f4c9a0b04c5dda5cb4c56c001e80c317954a8d19b0b21fce53aa0fd84962d33c091
@@ -0,0 +1,23 @@
1
+ name: CI
2
+ on: [pull_request]
3
+ jobs:
4
+ tests:
5
+ name: Tests
6
+ runs-on: ubuntu-latest
7
+ strategy:
8
+ matrix:
9
+ ruby-version: [3.1, '3.0', 2.7, 2.6, 2.5]
10
+ steps:
11
+ - uses: actions/checkout@v2
12
+ - name: Set up Ruby ${{ matrix.ruby-version }}
13
+ uses: ruby/setup-ruby@v1
14
+ with:
15
+ ruby-version: ${{ matrix.ruby-version }}
16
+ - name: Install gems
17
+ run: |
18
+ bundle config path vendor/bundle
19
+ bundle install --jobs 4 --retry 3
20
+ - name: Run tests
21
+ run: bundle exec rake
22
+ - name: Run StandardRB
23
+ run: bundle exec standardrb
data/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## Unreleased
9
+
10
+ ## 1.1.0 / 2022-05-26
11
+
12
+ - Refactor to use `Monitor` instead of `Mutex`
13
+ - Add documentation about thread-safety
14
+ - Allow ClimateControl.modify to be called without environment variables
15
+ - Add test for concurrent access needed to be inside block
16
+ - Relax development dependencies
17
+
18
+ ## 1.0.1 / 2021-05-26
19
+
20
+ - Require minimum Ruby version of 2.5.0
21
+
22
+ # 1.0.0 / 2021-03-06
23
+
24
+ - Commit to supporting latest patch versions of Ruby 2.5+
25
+ - Improve documentation
26
+ - Format code with StandardRB
27
+ - Bump gem dependencies
28
+
29
+ # 0.2.0 / 2017-05-12
30
+
31
+ - Allow nested environment changes in the same thread
32
+
33
+ # 0.1.0 / 2017-01-07
34
+
35
+ - Remove ActiveSupport dependency
36
+
37
+ # 0.0.4 / 2017-01-06
38
+
39
+ - Improved thread safety
40
+ - Handle TypeErrors during assignment
41
+ - Improve documentation
42
+
43
+ # 0.0.1 / 2012-11-28
44
+
45
+ - Initial release
data/CODEOWNERS ADDED
@@ -0,0 +1 @@
1
+ * @dorianmariefr
@@ -0,0 +1,6 @@
1
+ # Code of Conduct
2
+
3
+ By participating in this project, you agree to abide by the
4
+ [thoughtbot code of conduct][1].
5
+
6
+ [1]: https://thoughtbot.com/open-source-code-of-conduct
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-2014 Joshua Clayton and thoughtbot, inc.
1
+ Copyright (c) 2012-2021 Joshua Clayton and thoughtbot, inc.
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -24,15 +24,31 @@ within a block:
24
24
  ```ruby
25
25
  ClimateControl.modify CONFIRMATION_INSTRUCTIONS_BCC: 'confirmation_bcc@example.com' do
26
26
  sign_up_as 'john@example.com'
27
+
27
28
  confirm_account_for_email 'john@example.com'
28
- current_email.should bcc_to('confirmation_bcc@example.com')
29
+
30
+ expect(current_email).to bcc_to('confirmation_bcc@example.com')
31
+ end
32
+ ```
33
+
34
+ To modify multiple environment variables:
35
+
36
+ ```ruby
37
+ ClimateControl.modify CONFIRMATION_INSTRUCTIONS_BCC: 'confirmation_bcc@example.com',
38
+ MAIL_FROM: 'us@example.com' do
39
+ sign_up_as 'john@example.com'
40
+
41
+ confirm_account_for_email 'john@example.com'
42
+
43
+ expect(current_email).to bcc_to('confirmation_bcc@example.com')
44
+ expect(current_email).to be_from('us@example.com')
29
45
  end
30
46
  ```
31
47
 
32
48
  To use with RSpec, you could define this in your spec:
33
49
 
34
50
  ```ruby
35
- def with_modified_env(options, &block)
51
+ def with_modified_env(options = {}, &block)
36
52
  ClimateControl.modify(options, &block)
37
53
  end
38
54
  ```
@@ -89,6 +105,39 @@ manner becomes more difficult:
89
105
  Climate Control modifies environment variables only within the context of the
90
106
  block, ensuring values are managed properly and consistently.
91
107
 
108
+ ## Thread-safety
109
+
110
+ When using threads, for instance when running tests concurrently in the same
111
+ process, you may need to wrap your code inside `ClimateControl.modify` blocks,
112
+ e.g.:
113
+
114
+ ```ruby
115
+ first_thread = Thread.new do
116
+ ClimateControl.modify(SECRET: "1") do
117
+ p ENV["SECRET"] # => "1"
118
+ sleep 2
119
+ p ENV["SECRET"] # => "1"
120
+ end
121
+ end
122
+
123
+ second_thread = Thread.new do
124
+ ClimateControl.modify({}) do
125
+ sleep 1
126
+ p ENV["SECRET"] # => nil
127
+ sleep 1
128
+ p ENV["SECRET"] # => nil
129
+ end
130
+ end
131
+
132
+ first_thread.join
133
+ second_thread.join
134
+ ```
135
+
136
+ > The modification wraps ENV in a mutex. If there's contention (the env being used - including potentially mutating values), it blocks until the value is freed (we shift out of the Ruby block).
137
+ >
138
+ > <cite><a href="https://github.com/thoughtbot/climate_control/issues/32#issuecomment-800713686">Josh Clayton</a></cite>
139
+
140
+
92
141
  ## Contributing
93
142
 
94
143
  1. Fork it
@@ -97,6 +146,8 @@ block, ensuring values are managed properly and consistently.
97
146
  4. Push to the branch (`git push origin my-new-feature`)
98
147
  5. Create new Pull Request
99
148
 
149
+ This project uses [StandardRB](https://github.com/testdouble/standard) to ensure formatting.
150
+
100
151
  ## License
101
152
 
102
- climate_control is copyright 2012-2017 Joshua Clayton and thoughtbot, inc. It is free software and may be redistributed under the terms specified in the [LICENSE.txt](https://github.com/thoughtbot/climate_control/blob/master/LICENSE.txt) file.
153
+ climate_control is copyright 2012-2021 Joshua Clayton and thoughtbot, inc. It is free software and may be redistributed under the terms specified in the [LICENSE](https://github.com/thoughtbot/climate_control/blob/main/LICENSE) file.
@@ -1,23 +1,25 @@
1
- # -*- encoding: utf-8 -*-
2
1
  lib = File.expand_path("../lib", __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require "climate_control/version"
5
4
 
6
5
  Gem::Specification.new do |gem|
7
- gem.name = "climate_control"
8
- gem.version = ClimateControl::VERSION
9
- gem.authors = ["Joshua Clayton"]
10
- gem.email = ["joshua.clayton@gmail.com"]
11
- gem.description = %q{Modify your ENV}
12
- gem.summary = %q{Modify your ENV easily with ClimateControl}
13
- gem.homepage = "https://github.com/thoughtbot/climate_control"
14
- gem.license = "MIT"
6
+ gem.name = "climate_control"
7
+ gem.version = ClimateControl::VERSION
8
+ gem.authors = ["Joshua Clayton", "Dorian Marié"]
9
+ gem.email = ["joshua.clayton@gmail.com", "dorian@dorianmarie.fr"]
10
+ gem.description = "Modify your ENV"
11
+ gem.summary = "Modify your ENV easily with ClimateControl"
12
+ gem.homepage = "https://github.com/thoughtbot/climate_control"
13
+ gem.license = "MIT"
15
14
 
16
- gem.files = `git ls-files`.split($/)
17
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
17
  gem.require_paths = ["lib"]
19
18
 
20
- gem.add_development_dependency "rspec", "~> 3.1.0"
21
- gem.add_development_dependency "rake", "~> 10.3.2"
22
- gem.add_development_dependency "simplecov", "~> 0.9.1"
19
+ gem.required_ruby_version = ">= 2.5.0"
20
+
21
+ gem.add_development_dependency "rspec"
22
+ gem.add_development_dependency "rake"
23
+ gem.add_development_dependency "simplecov"
24
+ gem.add_development_dependency "standard"
23
25
  end
@@ -1,3 +1,3 @@
1
1
  module ClimateControl
2
- VERSION = "0.2.0".freeze
2
+ VERSION = "1.1.0".freeze
3
3
  end
@@ -1,16 +1,44 @@
1
- require "climate_control/environment"
2
1
  require "climate_control/errors"
3
- require "climate_control/modifier"
4
2
  require "climate_control/version"
3
+ require "monitor"
5
4
 
6
5
  module ClimateControl
7
- @@env = ClimateControl::Environment.new
6
+ extend self
8
7
 
9
- def self.modify(environment_overrides, &block)
10
- Modifier.new(env, environment_overrides, &block).process
8
+ SEMAPHORE = Monitor.new
9
+ private_constant :SEMAPHORE
10
+
11
+ def modify(environment_overrides = {}, &block)
12
+ environment_overrides = environment_overrides.transform_keys(&:to_s)
13
+
14
+ SEMAPHORE.synchronize do
15
+ previous = ENV.to_hash
16
+ middle = {}
17
+ copy environment_overrides
18
+ middle = ENV.to_hash
19
+ block.call
20
+ ensure
21
+ after = ENV
22
+ (previous.keys | middle.keys | after.keys).each do |key|
23
+ if previous[key] != after[key] && middle[key] == after[key]
24
+ ENV[key] = previous[key]
25
+ end
26
+ end
27
+ end
11
28
  end
12
29
 
13
- def self.env
14
- @@env
30
+ def env
31
+ ENV
32
+ end
33
+
34
+ private
35
+
36
+ def copy(overrides)
37
+ overrides.each do |key, value|
38
+ ENV[key] = value
39
+ rescue TypeError => e
40
+ raise UnassignableValueError,
41
+ "attempted to assign #{value} to #{key} but failed (#{e.message})"
42
+ end
15
43
  end
16
44
  end
@@ -1,5 +1,7 @@
1
1
  require "spec_helper"
2
2
 
3
+ Thing = Class.new
4
+
3
5
  describe "Climate control" do
4
6
  it "allows modification of the environment" do
5
7
  block_run = false
@@ -87,22 +89,22 @@ describe "Climate control" do
87
89
  # 0.25s passes
88
90
  # [other_thread] thread resolves, FOO is removed, BAZ is copied back to ENV
89
91
 
90
- thread_removing_env = Thread.new do
92
+ thread_removing_env = Thread.new {
91
93
  with_modified_env BAZ: "buzz" do
92
94
  sleep 0.5
93
95
  end
94
96
 
95
97
  expect(ENV["BAZ"]).to be_nil
96
- end
98
+ }
97
99
 
98
- other_thread = Thread.new do
100
+ other_thread = Thread.new {
99
101
  sleep 0.25
100
102
  with_modified_env FOO: "bar" do
101
103
  sleep 0.5
102
104
  end
103
105
 
104
106
  expect(ENV["FOO"]).to be_nil
105
- end
107
+ }
106
108
 
107
109
  thread_removing_env.join
108
110
  other_thread.join
@@ -111,12 +113,45 @@ describe "Climate control" do
111
113
  expect(ENV["BAZ"]).to be_nil
112
114
  end
113
115
 
116
+ it "handles threads accessing the same key wrapped in a block" do
117
+ first_thread = Thread.new {
118
+ with_modified_env do
119
+ with_modified_env CONFLICTING_KEY: "1" do
120
+ sleep 0.5
121
+ expect(ENV["CONFLICTING_KEY"]).to eq("1")
122
+ end
123
+
124
+ expect(ENV["CONFLICTING_KEY"]).to be_nil
125
+ end
126
+ }
127
+
128
+ second_thread = Thread.new {
129
+ with_modified_env do
130
+ sleep 0.25
131
+ expect(ENV["CONFLICTING_KEY"]).to be_nil
132
+
133
+ with_modified_env CONFLICTING_KEY: "2" do
134
+ expect(ENV["CONFLICTING_KEY"]).to eq("2")
135
+ sleep 0.5
136
+ expect(ENV["CONFLICTING_KEY"]).to eq("2")
137
+ end
138
+
139
+ expect(ENV["CONFLICTING_KEY"]).to be_nil
140
+ end
141
+ }
142
+
143
+ first_thread.join
144
+ second_thread.join
145
+
146
+ expect(ENV["CONFLICTING_KEY"]).to be_nil
147
+ end
148
+
114
149
  it "is re-entrant" do
115
- ret = with_modified_env(FOO: "foo") do
150
+ ret = with_modified_env(FOO: "foo") {
116
151
  with_modified_env(BAR: "bar") do
117
152
  "bar"
118
153
  end
119
- end
154
+ }
120
155
 
121
156
  expect(ret).to eq("bar")
122
157
 
@@ -125,15 +160,14 @@ describe "Climate control" do
125
160
  end
126
161
 
127
162
  it "raises when the value cannot be assigned properly" do
128
- Thing = Class.new
129
163
  message = generate_type_error_for_object(Thing.new)
130
164
 
131
- expect do
165
+ expect {
132
166
  with_modified_env(FOO: Thing.new)
133
- end.to raise_error ClimateControl::UnassignableValueError, /attempted to assign .*Thing.* to FOO but failed \(#{message}\)$/
167
+ }.to raise_error ClimateControl::UnassignableValueError, /attempted to assign .*Thing.* to FOO but failed \(#{message}\)$/
134
168
  end
135
169
 
136
- def with_modified_env(options, &block)
170
+ def with_modified_env(options = {}, &block)
137
171
  ClimateControl.modify(options, &block)
138
172
  end
139
173
 
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  begin
2
- require 'simplecov'
2
+ require "simplecov"
3
3
  SimpleCov.start
4
4
  rescue LoadError
5
5
  warn "warning: simplecov gem not found; skipping coverage"
metadata CHANGED
@@ -1,76 +1,92 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: climate_control
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Clayton
8
- autorequire:
8
+ - Dorian Marié
9
+ autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2017-05-12 00:00:00.000000000 Z
12
+ date: 2022-05-26 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: rspec
15
16
  requirement: !ruby/object:Gem::Requirement
16
17
  requirements:
17
- - - "~>"
18
+ - - ">="
18
19
  - !ruby/object:Gem::Version
19
- version: 3.1.0
20
+ version: '0'
20
21
  type: :development
21
22
  prerelease: false
22
23
  version_requirements: !ruby/object:Gem::Requirement
23
24
  requirements:
24
- - - "~>"
25
+ - - ">="
25
26
  - !ruby/object:Gem::Version
26
- version: 3.1.0
27
+ version: '0'
27
28
  - !ruby/object:Gem::Dependency
28
29
  name: rake
29
30
  requirement: !ruby/object:Gem::Requirement
30
31
  requirements:
31
- - - "~>"
32
+ - - ">="
32
33
  - !ruby/object:Gem::Version
33
- version: 10.3.2
34
+ version: '0'
34
35
  type: :development
35
36
  prerelease: false
36
37
  version_requirements: !ruby/object:Gem::Requirement
37
38
  requirements:
38
- - - "~>"
39
+ - - ">="
39
40
  - !ruby/object:Gem::Version
40
- version: 10.3.2
41
+ version: '0'
41
42
  - !ruby/object:Gem::Dependency
42
43
  name: simplecov
43
44
  requirement: !ruby/object:Gem::Requirement
44
45
  requirements:
45
- - - "~>"
46
+ - - ">="
46
47
  - !ruby/object:Gem::Version
47
- version: 0.9.1
48
+ version: '0'
48
49
  type: :development
49
50
  prerelease: false
50
51
  version_requirements: !ruby/object:Gem::Requirement
51
52
  requirements:
52
- - - "~>"
53
+ - - ">="
53
54
  - !ruby/object:Gem::Version
54
- version: 0.9.1
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: standard
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
55
70
  description: Modify your ENV
56
71
  email:
57
72
  - joshua.clayton@gmail.com
73
+ - dorian@dorianmarie.fr
58
74
  executables: []
59
75
  extensions: []
60
76
  extra_rdoc_files: []
61
77
  files:
78
+ - ".github/workflows/ci.yml"
62
79
  - ".gitignore"
63
- - ".travis.yml"
80
+ - CHANGELOG.md
81
+ - CODEOWNERS
82
+ - CODE_OF_CONDUCT.md
64
83
  - Gemfile
65
- - LICENSE.txt
66
- - NEWS
84
+ - LICENSE
67
85
  - README.md
68
86
  - Rakefile
69
87
  - climate_control.gemspec
70
88
  - lib/climate_control.rb
71
- - lib/climate_control/environment.rb
72
89
  - lib/climate_control/errors.rb
73
- - lib/climate_control/modifier.rb
74
90
  - lib/climate_control/version.rb
75
91
  - spec/acceptance/climate_control_spec.rb
76
92
  - spec/spec_helper.rb
@@ -78,7 +94,7 @@ homepage: https://github.com/thoughtbot/climate_control
78
94
  licenses:
79
95
  - MIT
80
96
  metadata: {}
81
- post_install_message:
97
+ post_install_message:
82
98
  rdoc_options: []
83
99
  require_paths:
84
100
  - lib
@@ -86,16 +102,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
86
102
  requirements:
87
103
  - - ">="
88
104
  - !ruby/object:Gem::Version
89
- version: '0'
105
+ version: 2.5.0
90
106
  required_rubygems_version: !ruby/object:Gem::Requirement
91
107
  requirements:
92
108
  - - ">="
93
109
  - !ruby/object:Gem::Version
94
110
  version: '0'
95
111
  requirements: []
96
- rubyforge_project:
97
- rubygems_version: 2.5.2
98
- signing_key:
112
+ rubygems_version: 3.3.13
113
+ signing_key:
99
114
  specification_version: 4
100
115
  summary: Modify your ENV easily with ClimateControl
101
116
  test_files:
data/.travis.yml DELETED
@@ -1,11 +0,0 @@
1
- rvm:
2
- - 2.2.6
3
- - 2.3.3
4
- - 2.4.0
5
- before_install:
6
- - gem update --system
7
- - gem update bundler
8
- install: "bundle install"
9
- branches:
10
- only:
11
- - master
data/NEWS DELETED
@@ -1,13 +0,0 @@
1
- 0.2.0 (May 12, 2017)
2
- Allow nested environment changes in the same thread
3
-
4
- 0.1.0 (January 7, 2017)
5
- Remove ActiveSupport dependency
6
-
7
- 0.0.4 (January 6, 2017)
8
- Improved thread safety
9
- Handle TypeErrors during assignment
10
- Improve documentation
11
-
12
- 0.0.1 (November 28, 2012)
13
- Initial release
@@ -1,36 +0,0 @@
1
- require "thread"
2
- require "forwardable"
3
-
4
- module ClimateControl
5
- class Environment
6
- extend Forwardable
7
-
8
- def initialize
9
- @semaphore = Mutex.new
10
- @owner = nil
11
- end
12
-
13
- def_delegators :env, :[]=, :to_hash, :[], :delete
14
-
15
- def synchronize
16
- if @owner == Thread.current
17
- return yield if block_given?
18
- end
19
-
20
- @semaphore.synchronize do
21
- begin
22
- @owner = Thread.current
23
- yield if block_given?
24
- ensure
25
- @owner = nil
26
- end
27
- end
28
- end
29
-
30
- private
31
-
32
- def env
33
- ENV
34
- end
35
- end
36
- end
@@ -1,96 +0,0 @@
1
- module ClimateControl
2
- class Modifier
3
- def initialize(env, environment_overrides = {}, &block)
4
- @environment_overrides = stringify_keys(environment_overrides)
5
- @block = block
6
- @env = env
7
- end
8
-
9
- def process
10
- @env.synchronize do
11
- begin
12
- prepare_environment_for_block
13
- run_block
14
- ensure
15
- cache_environment_after_block
16
- delete_keys_that_do_not_belong
17
- revert_changed_keys
18
- end
19
- end
20
- end
21
-
22
- private
23
-
24
- def prepare_environment_for_block
25
- @original_env = clone_environment
26
- copy_overrides_to_environment
27
- @env_with_overrides_before_block = clone_environment
28
- end
29
-
30
- def run_block
31
- @block.call
32
- end
33
-
34
- def copy_overrides_to_environment
35
- @environment_overrides.each do |key, value|
36
- begin
37
- @env[key] = value
38
- rescue TypeError => e
39
- raise UnassignableValueError,
40
- "attempted to assign #{value} to #{key} but failed (#{e.message})"
41
- end
42
- end
43
- end
44
-
45
- def keys_to_remove
46
- @environment_overrides.keys
47
- end
48
-
49
- def keys_changed_by_block
50
- @keys_changed_by_block ||= OverlappingKeysWithChangedValues.new(@env_with_overrides_before_block, @env_after_block).keys
51
- end
52
-
53
- def cache_environment_after_block
54
- @env_after_block = clone_environment
55
- end
56
-
57
- def delete_keys_that_do_not_belong
58
- (keys_to_remove - keys_changed_by_block).each {|key| @env.delete(key) }
59
- end
60
-
61
- def revert_changed_keys
62
- (@original_env.keys - keys_changed_by_block).each do |key|
63
- @env[key] = @original_env[key]
64
- end
65
- end
66
-
67
- def clone_environment
68
- @env.to_hash
69
- end
70
-
71
- def stringify_keys(env)
72
- env.each_with_object({}) do |(key, value), hash|
73
- hash[key.to_s] = value
74
- end
75
- end
76
-
77
- class OverlappingKeysWithChangedValues
78
- def initialize(hash_1, hash_2)
79
- @hash_1 = hash_1 || {}
80
- @hash_2 = hash_2
81
- end
82
-
83
- def keys
84
- overlapping_keys.select do |overlapping_key|
85
- @hash_1[overlapping_key] != @hash_2[overlapping_key]
86
- end
87
- end
88
-
89
- private
90
-
91
- def overlapping_keys
92
- @hash_2.keys & @hash_1.keys
93
- end
94
- end
95
- end
96
- end