climate_control 0.2.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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