sentry-ruby 0.1.1 → 0.1.2
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 +4 -4
- data/.craft.yml +1 -0
- data/CHANGELOG.md +6 -0
- data/README.md +155 -21
- data/lib/sentry.rb +10 -1
- data/lib/sentry/client.rb +17 -34
- data/lib/sentry/configuration.rb +1 -4
- data/lib/sentry/event.rb +15 -19
- data/lib/sentry/transport.rb +4 -19
- data/lib/sentry/version.rb +1 -1
- metadata +2 -4
- data/lib/sentry/event/options.rb +0 -31
- data/lib/sentry/utils/deep_merge.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4cd10cdc1a28f396af641980c7440ab8fc74648a2ebe1364e81d137af7a8d832
|
4
|
+
data.tar.gz: d96bebca93ed6d16fc4bc1a7c6f14a53f33ebf84f4d6197ce79ee344bd2a0056
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bacf5d66a156684b3dcf3048806ffab6b53f3172a146626a4b28e4a80d1c933a729ee813a933052a425d86d3c58ce04b8409a55ba0b3fe76c45cce3defc8a6f0
|
7
|
+
data.tar.gz: 756550d44dd6765e478ae2922f69c2098121c3ae290dd99345805d5690281021683146b54e999b77ed82b82330f40b5ec7e57b172a39741e4023c911c9867cb0
|
data/.craft.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.1.2
|
4
|
+
|
5
|
+
- Fix: Fix async callback [1098](https://github.com/getsentry/sentry-ruby/pull/1098)
|
6
|
+
- Refactor: Some code cleanup [1100](https://github.com/getsentry/sentry-ruby/pull/1100)
|
7
|
+
- Refactor: Remove Event options [1101](https://github.com/getsentry/sentry-ruby/pull/1101)
|
8
|
+
|
3
9
|
## 0.1.1
|
4
10
|
|
5
11
|
- Feature: Allow passing custom scope to Hub#capture* helpers [1086](https://github.com/getsentry/sentry-ruby/pull/1086)
|
data/README.md
CHANGED
@@ -1,44 +1,178 @@
|
|
1
|
-
|
1
|
+
<p align="center">
|
2
|
+
<a href="https://sentry.io" target="_blank" align="center">
|
3
|
+
<img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" width="280">
|
4
|
+
</a>
|
5
|
+
<br>
|
6
|
+
</p>
|
2
7
|
|
3
|
-
|
8
|
+
# sentry-ruby, the Ruby Client for Sentry
|
4
9
|
|
5
|
-
|
10
|
+
---
|
6
11
|
|
7
|
-
## Installation
|
8
12
|
|
9
|
-
|
13
|
+
[](https://rubygems.org/gems/sentry-ruby)
|
14
|
+

|
15
|
+
[](https://codecov.io/gh/getsentry/sentry-ruby/branch/master)
|
16
|
+
[](https://rubygems.org/gems/sentry-ruby/)
|
17
|
+
[](https://dependabot.com/compatibility-score.html?dependency-name=sentry-ruby&package-manager=bundler&version-scheme=semver)
|
18
|
+
|
19
|
+
|
20
|
+
[Documentation](https://docs.sentry.io/clients/ruby/) | [Bug Tracker](https://github.com/getsentry/sentry-ruby/issues) | [Forum](https://forum.sentry.io/) | IRC: irc.freenode.net, #sentry
|
21
|
+
|
22
|
+
The official Ruby-language client and integration layer for the [Sentry](https://github.com/getsentry/sentry) error reporting API.
|
23
|
+
|
24
|
+
|
25
|
+
## Requirements
|
26
|
+
|
27
|
+
We test on Ruby 2.4, 2.5, 2.6 and 2.7 at the latest patchlevel/teeny version. We also support JRuby 9.0.
|
28
|
+
|
29
|
+
## Getting Started
|
30
|
+
|
31
|
+
### Install
|
10
32
|
|
11
33
|
```ruby
|
12
|
-
gem
|
34
|
+
gem "sentry-ruby"
|
13
35
|
```
|
14
36
|
|
15
|
-
|
37
|
+
and depends on the integrations you want to have, you might also want to install these:
|
16
38
|
|
17
|
-
|
39
|
+
```ruby
|
40
|
+
gem "sentry-rails"
|
41
|
+
gem "sentry-sidekiq"
|
42
|
+
# and mores to come in the future!
|
43
|
+
```
|
44
|
+
|
45
|
+
### Sentry only runs when Sentry DSN is set
|
46
|
+
|
47
|
+
Sentry will capture and send exceptions to the Sentry server whenever its DSN is set. This makes environment-based configuration easy - if you don't want to send errors in a certain environment, just don't set the DSN in that environment!
|
18
48
|
|
19
|
-
|
49
|
+
```bash
|
50
|
+
# Set your SENTRY_DSN environment variable.
|
51
|
+
export SENTRY_DSN=http://public@example.com/project-id
|
52
|
+
```
|
53
|
+
```ruby
|
54
|
+
# Or you can configure the client in the code.
|
55
|
+
Sentry.init do |config|
|
56
|
+
config.dsn = 'http://public@example.com/project-id'
|
57
|
+
end
|
58
|
+
```
|
20
59
|
|
21
|
-
|
60
|
+
### Sentry doesn't report some kinds of data by default
|
22
61
|
|
23
|
-
|
62
|
+
**Sentry ignores some exceptions by default** - most of these are related to 404s parameter parsing errors. [For a complete list, see the `IGNORE_DEFAULT` constant](https://github.com/getsentry/sentry-ruby/blob/master/sentry-ruby/lib/sentry/configuration.rb#L118) and the integration gems' `IGNORE_DEFAULT`, like [`sentry-rails`'s](https://github.com/getsentry/sentry-ruby/blob/update-readme/sentry-rails/lib/sentry/rails/configuration.rb#L12)
|
24
63
|
|
25
|
-
|
64
|
+
Sentry doesn't send personally identifiable information (pii) by default, such as request body, user ip or cookies. If you want those information to be sent, you can use the `send_default_pii` config option:
|
26
65
|
|
27
|
-
|
66
|
+
```ruby
|
67
|
+
Sentry.init do |config|
|
68
|
+
# other configs
|
69
|
+
config.send_default_pii = true
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
### Usage
|
74
|
+
|
75
|
+
`sentry-ruby` has a default integration with `Rack`, so you only need to use the middleware in your application like:
|
76
|
+
|
77
|
+
```
|
78
|
+
require 'rack'
|
79
|
+
require 'sentry'
|
80
|
+
|
81
|
+
use Sentry::Rack::CaptureException
|
82
|
+
|
83
|
+
run theapp
|
84
|
+
```
|
85
|
+
|
86
|
+
Otherwise, Sentry you can always use the capture helpers manually
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
Sentry.capture_message("hello world!")
|
90
|
+
|
91
|
+
begin
|
92
|
+
1 / 0
|
93
|
+
rescue ZeroDivisionError => exception
|
94
|
+
Sentry.capture_exception(exception)
|
95
|
+
end
|
96
|
+
```
|
28
97
|
|
29
|
-
|
98
|
+
We also provide integrations with popular frameworks/libraries with the related extensions:
|
30
99
|
|
31
|
-
|
100
|
+
- [sentry-rails](https://github.com/getsentry/sentry-ruby/tree/master/sentry-rails)
|
101
|
+
- [sentry-sidekiq](https://github.com/getsentry/sentry-ruby/tree/master/sentry-sidekiq)
|
32
102
|
|
33
|
-
|
103
|
+
### More configuration
|
34
104
|
|
35
|
-
|
105
|
+
You're all set - but there's a few more settings you may want to know about too!
|
36
106
|
|
107
|
+
#### async
|
37
108
|
|
38
|
-
|
109
|
+
When an error or message occurs, the notification is immediately sent to Sentry. Sentry can be configured to send asynchronously:
|
39
110
|
|
40
|
-
|
111
|
+
```ruby
|
112
|
+
config.async = lambda { |event|
|
113
|
+
Thread.new { Sentry.send_event(event) }
|
114
|
+
}
|
115
|
+
```
|
116
|
+
|
117
|
+
Using a thread to send events will be adequate for truly parallel Ruby platforms such as JRuby, though the benefit on MRI/CRuby will be limited. If the async callback raises an exception, Sentry will attempt to send synchronously.
|
118
|
+
|
119
|
+
Note that the naive example implementation has a major drawback - it can create an infinite number of threads. We recommend creating a background job, using your background job processor, that will send Sentry notifications in the background.
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
config.async = lambda { |event| SentryJob.perform_later(event) }
|
123
|
+
|
124
|
+
class SentryJob < ActiveJob::Base
|
125
|
+
queue_as :default
|
126
|
+
|
127
|
+
def perform(event)
|
128
|
+
Sentry.send_event(event)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
#### Contexts
|
134
|
+
|
135
|
+
In sentry-ruby, every event will inherit their contextual data from the current scope. So you can enrich the event's data by configuring the current scope like:
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
Sentry.configure_scope do |scope|
|
139
|
+
scope.set_user(id: 1, email: "test@example.com")
|
140
|
+
|
141
|
+
scope.set_tag(:tag, "foo")
|
142
|
+
scope.set_tags(tag_1: "foo", tag_2: "bar")
|
143
|
+
|
144
|
+
scope.set_extra(:order_number, 1234)
|
145
|
+
scope.set_extras(order_number: 1234, tickets_count: 4)
|
146
|
+
end
|
147
|
+
|
148
|
+
Sentry.capture_exception(exception) # the event will carry all those information now
|
149
|
+
```
|
150
|
+
|
151
|
+
Or build up a temporary scope for local information:
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
Sentry.configure_scope do |scope|
|
155
|
+
scope.set_tags(tag_1: "foo")
|
156
|
+
end
|
157
|
+
|
158
|
+
Sentry.with_scope do |scope|
|
159
|
+
scope.set_tags(tag_1: "bar", tag_2: "baz")
|
160
|
+
|
161
|
+
Sentry.capture_message("message") # this event will have 2 tags: tag_1 => "bar" and tag_2 => "baz"
|
162
|
+
end
|
163
|
+
|
164
|
+
Sentry.capture_message("another message") # this event will have 1 tag: tag_1 => "foo"
|
165
|
+
```
|
166
|
+
|
167
|
+
Of course, you can always assign the information on a per-event basis:
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
Sentry.capture_exception(exception, tags: {foo: "bar"})
|
171
|
+
```
|
41
172
|
|
42
|
-
##
|
173
|
+
## More Information
|
43
174
|
|
44
|
-
|
175
|
+
* [Documentation](https://docs.sentry.io/clients/ruby/)
|
176
|
+
* [Bug Tracker](https://github.com/getsentry/sentry-ruby/issues)
|
177
|
+
* [Forum](https://forum.sentry.io/)
|
178
|
+
- [Discord](https://discord.gg/ez5KZN7)
|
data/lib/sentry.rb
CHANGED
@@ -52,7 +52,12 @@ module Sentry
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def get_current_hub
|
55
|
-
|
55
|
+
# we need to assign a hub to the current thread if it doesn't have one yet
|
56
|
+
#
|
57
|
+
# ideally, we should do this proactively whenever a new thread is created
|
58
|
+
# but it's impossible for the SDK to keep track every new thread
|
59
|
+
# so we need to use this rather passive way to make sure the app doesn't crash
|
60
|
+
Thread.current[THREAD_LOCAL] || clone_hub_to_current_thread
|
56
61
|
end
|
57
62
|
|
58
63
|
def clone_hub_to_current_thread
|
@@ -71,6 +76,10 @@ module Sentry
|
|
71
76
|
get_current_hub.configure_scope(&block)
|
72
77
|
end
|
73
78
|
|
79
|
+
def send_event(event)
|
80
|
+
get_current_client.send_event(event)
|
81
|
+
end
|
82
|
+
|
74
83
|
def capture_event(event)
|
75
84
|
get_current_hub.capture_event(event)
|
76
85
|
end
|
data/lib/sentry/client.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require "sentry/transport"
|
2
|
-
require 'sentry/utils/deep_merge'
|
3
2
|
|
4
3
|
module Sentry
|
5
4
|
class Client
|
@@ -21,51 +20,35 @@ module Sentry
|
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
24
|
-
def capture_exception(exception, scope:, **options, &block)
|
25
|
-
event = event_from_exception(exception, **options)
|
26
|
-
return unless event
|
27
|
-
|
28
|
-
block.call(event) if block
|
29
|
-
capture_event(event, scope)
|
30
|
-
end
|
31
|
-
|
32
|
-
def capture_message(message, scope:, **options, &block)
|
33
|
-
event = event_from_message(message, **options)
|
34
|
-
block.call(event) if block
|
35
|
-
capture_event(event, scope)
|
36
|
-
end
|
37
|
-
|
38
23
|
def capture_event(event, scope)
|
39
24
|
scope.apply_to_event(event)
|
40
|
-
send_event(event)
|
41
|
-
event
|
42
|
-
end
|
43
25
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
26
|
+
if configuration.async?
|
27
|
+
begin
|
28
|
+
# We have to convert to a JSON-like hash, because background job
|
29
|
+
# processors (esp ActiveJob) may not like weird types in the event hash
|
30
|
+
configuration.async.call(event.to_json_compatible)
|
31
|
+
rescue => e
|
32
|
+
configuration.logger.error(LOGGER_PROGNAME) { "async event sending failed: #{e.message}" }
|
33
|
+
send_event(event)
|
52
34
|
end
|
35
|
+
else
|
36
|
+
send_event(event)
|
37
|
+
end
|
53
38
|
|
54
|
-
|
39
|
+
event
|
40
|
+
end
|
55
41
|
|
42
|
+
def event_from_exception(exception)
|
56
43
|
return unless @configuration.exception_class_allowed?(exception)
|
57
44
|
|
58
|
-
|
59
|
-
|
60
|
-
Event.new(configuration: configuration, options: options).tap do |event|
|
45
|
+
Event.new(configuration: configuration).tap do |event|
|
61
46
|
event.add_exception_interface(exception)
|
62
47
|
end
|
63
48
|
end
|
64
49
|
|
65
|
-
def event_from_message(message
|
66
|
-
|
67
|
-
options = Event::Options.new(options)
|
68
|
-
Event.new(configuration: configuration, options: options)
|
50
|
+
def event_from_message(message)
|
51
|
+
Event.new(configuration: configuration, message: message)
|
69
52
|
end
|
70
53
|
|
71
54
|
def send_event(event, hint = nil)
|
data/lib/sentry/configuration.rb
CHANGED
@@ -93,9 +93,6 @@ module Sentry
|
|
93
93
|
# Silences ready message when true.
|
94
94
|
attr_accessor :silence_ready
|
95
95
|
|
96
|
-
# Default tags for events. Hash.
|
97
|
-
attr_accessor :tags
|
98
|
-
|
99
96
|
attr_reader :transport
|
100
97
|
|
101
98
|
# Optional Proc, called before sending an event to the server/
|
@@ -157,7 +154,7 @@ module Sentry
|
|
157
154
|
self.dsn = ENV['SENTRY_DSN']
|
158
155
|
self.server_name = server_name_from_env
|
159
156
|
self.should_capture = false
|
160
|
-
|
157
|
+
|
161
158
|
@transport = Transport::Configuration.new
|
162
159
|
self.before_send = false
|
163
160
|
self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
|
data/lib/sentry/event.rb
CHANGED
@@ -2,10 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'socket'
|
4
4
|
require 'securerandom'
|
5
|
-
require 'sentry/event/options'
|
6
5
|
require 'sentry/interface'
|
7
6
|
require 'sentry/backtrace'
|
8
|
-
require 'sentry/utils/deep_merge'
|
9
7
|
require 'sentry/utils/real_ip'
|
10
8
|
|
11
9
|
module Sentry
|
@@ -23,7 +21,7 @@ module Sentry
|
|
23
21
|
|
24
22
|
alias event_id id
|
25
23
|
|
26
|
-
def initialize(
|
24
|
+
def initialize(configuration:, message: nil)
|
27
25
|
# this needs to go first because some setters rely on configuration
|
28
26
|
@configuration = configuration
|
29
27
|
|
@@ -33,27 +31,21 @@ module Sentry
|
|
33
31
|
@platform = :ruby
|
34
32
|
@sdk = Sentry.sdk_meta
|
35
33
|
|
36
|
-
@user =
|
37
|
-
@extra =
|
38
|
-
@contexts =
|
39
|
-
@tags =
|
34
|
+
@user = {}
|
35
|
+
@extra = {}
|
36
|
+
@contexts = {}
|
37
|
+
@tags = {}
|
40
38
|
|
41
|
-
@fingerprint =
|
39
|
+
@fingerprint = []
|
42
40
|
|
43
|
-
@server_name =
|
44
|
-
@environment =
|
45
|
-
@release =
|
41
|
+
@server_name = configuration.server_name
|
42
|
+
@environment = configuration.current_environment
|
43
|
+
@release = configuration.release
|
46
44
|
@modules = list_gem_specs if configuration.send_modules
|
47
45
|
|
48
|
-
@message =
|
46
|
+
@message = message || ""
|
49
47
|
|
50
|
-
self.level =
|
51
|
-
|
52
|
-
if !options.backtrace.empty?
|
53
|
-
@stacktrace = Sentry::StacktraceInterface.new.tap do |int|
|
54
|
-
int.frames = stacktrace_interface_from(options.backtrace)
|
55
|
-
end
|
56
|
-
end
|
48
|
+
self.level = :error
|
57
49
|
end
|
58
50
|
|
59
51
|
class << self
|
@@ -113,6 +105,10 @@ module Sentry
|
|
113
105
|
end
|
114
106
|
|
115
107
|
def add_exception_interface(exc)
|
108
|
+
if exc.respond_to?(:sentry_context)
|
109
|
+
@extra.merge!(exc.sentry_context)
|
110
|
+
end
|
111
|
+
|
116
112
|
@exception = Sentry::ExceptionInterface.new.tap do |exc_int|
|
117
113
|
exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exc).reverse
|
118
114
|
backtraces = Set.new
|
data/lib/sentry/transport.rb
CHANGED
@@ -26,27 +26,12 @@ module Sentry
|
|
26
26
|
|
27
27
|
return nil unless encoded_data
|
28
28
|
|
29
|
-
|
30
|
-
if configuration.async?
|
31
|
-
begin
|
32
|
-
# We have to convert to a JSON-like hash, because background job
|
33
|
-
# processors (esp ActiveJob) may not like weird types in the event hash
|
34
|
-
configuration.async.call(event.to_json_compatible)
|
35
|
-
rescue => e
|
36
|
-
configuration.logger.error(LOGGER_PROGNAME) { "async event sending failed: #{e.message}" }
|
37
|
-
send_data(encoded_data, content_type: content_type)
|
38
|
-
end
|
39
|
-
else
|
40
|
-
send_data(encoded_data, content_type: content_type)
|
41
|
-
end
|
42
|
-
|
43
|
-
state.success
|
44
|
-
rescue => e
|
45
|
-
failed_for_exception(e, event)
|
46
|
-
return
|
47
|
-
end
|
29
|
+
send_data(encoded_data, content_type: content_type)
|
48
30
|
|
31
|
+
state.success
|
49
32
|
event
|
33
|
+
rescue => e
|
34
|
+
failed_for_exception(e, event)
|
50
35
|
end
|
51
36
|
|
52
37
|
def generate_auth_header
|
data/lib/sentry/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sentry-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sentry Team
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-11-
|
11
|
+
date: 2020-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -55,7 +55,6 @@ files:
|
|
55
55
|
- lib/sentry/core_ext/object/duplicable.rb
|
56
56
|
- lib/sentry/dsn.rb
|
57
57
|
- lib/sentry/event.rb
|
58
|
-
- lib/sentry/event/options.rb
|
59
58
|
- lib/sentry/hub.rb
|
60
59
|
- lib/sentry/interface.rb
|
61
60
|
- lib/sentry/interfaces/exception.rb
|
@@ -73,7 +72,6 @@ files:
|
|
73
72
|
- lib/sentry/transport/dummy_transport.rb
|
74
73
|
- lib/sentry/transport/http_transport.rb
|
75
74
|
- lib/sentry/transport/state.rb
|
76
|
-
- lib/sentry/utils/deep_merge.rb
|
77
75
|
- lib/sentry/utils/exception_cause_chain.rb
|
78
76
|
- lib/sentry/utils/real_ip.rb
|
79
77
|
- lib/sentry/version.rb
|
data/lib/sentry/event/options.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
module Sentry
|
2
|
-
class Event
|
3
|
-
class Options
|
4
|
-
attr_reader :message,
|
5
|
-
:user, :extra, :tags, :contexts,
|
6
|
-
:backtrace, :level, :fingerprint,
|
7
|
-
:server_name, :release, :environment
|
8
|
-
|
9
|
-
def initialize(
|
10
|
-
message: "",
|
11
|
-
user: {}, extra: {}, tags: {}, contexts: {},
|
12
|
-
backtrace: [], level: :error, fingerprint: [],
|
13
|
-
# nilable attributes because we'll fallback to the configuration's values
|
14
|
-
server_name: nil, release: nil, environment: nil
|
15
|
-
)
|
16
|
-
@message = message || ""
|
17
|
-
@user = user || {}
|
18
|
-
@extra = extra || {}
|
19
|
-
@tags = tags || {}
|
20
|
-
@contexts = contexts || {}
|
21
|
-
@backtrace = backtrace || []
|
22
|
-
@fingerprint = fingerprint || []
|
23
|
-
@level = level || :error
|
24
|
-
@server_name = server_name
|
25
|
-
@environment = environment
|
26
|
-
@release = release
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module Sentry
|
2
|
-
module Utils
|
3
|
-
# ported from ActiveSupport
|
4
|
-
module DeepMergeHash
|
5
|
-
def self.deep_merge(hash, other_hash, &block)
|
6
|
-
deep_merge!(hash, other_hash, &block)
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.deep_merge!(hash, other_hash, &block)
|
10
|
-
hash.merge!(other_hash) do |key, this_val, other_val|
|
11
|
-
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
12
|
-
deep_merge(this_val, other_val, &block)
|
13
|
-
elsif block_given?
|
14
|
-
block.call(key, this_val, other_val)
|
15
|
-
else
|
16
|
-
other_val
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|