appmap 0.43.0 → 0.44.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
2
  SHA256:
3
- metadata.gz: 4007f0eb67a3a25088e8b3677de8d0388fd1bfec4b326c43a51c503f640325b5
4
- data.tar.gz: 1b593251a7a072b7a1483e7af172b9157d6913208658fb324630bc52b0b4fe73
3
+ metadata.gz: 02dfa68caaa5e5413c68ae06e89664a676624c9dbb301e2ebbc2a7ae26359afd
4
+ data.tar.gz: af97e3fb4bb428a238b854ef30969f232c48d4f3a59956865a4cc95788ac35e8
5
5
  SHA512:
6
- metadata.gz: 6af64a17bf69655ae8bdc9b4b67b7e2c9bd1b87d07a23e266dcabd2a927311224a4b27a77355674559828a08ae1df2e7231e2a68a2734c0f620de8efc48ec6d4
7
- data.tar.gz: 7725cb0c79d8be13fddf7a3b688113f4fd31941c4f08f64ee44d4fbb3f5255dd3a308b08ef578413938052ee3b9eeecedc3e58bc092b12e23bfa872eac119eb7
6
+ metadata.gz: 9a0f2454acf5d10c48aaf1abb6e4b23d674acbbfe495770a25ff83093c38dd81cf0bb1c85dfbae4691773586ace8500c3f9b879b31749c4142851f460ed1a87c
7
+ data.tar.gz: a2860fb0258b96d95e5ca98f7d13b382c86b635ce1c2d97084c1f651a55973d2ffe31e360695fdec963292081e527b96876c6b42976a90573b9201e14032d1f5
data/.travis.yml CHANGED
@@ -13,11 +13,26 @@ services:
13
13
  # necessary.
14
14
  before_script:
15
15
  - unset RAILS_ENV
16
-
16
+
17
+ cache:
18
+ bundler: true
19
+ directories:
20
+ - $HOME/docker
21
+
22
+ # https://stackoverflow.com/a/41975912
23
+ before_cache:
24
+ # Save tagged docker images
25
+ - >
26
+ mkdir -p $HOME/docker && docker images -a --filter='dangling=false' --format '{{.Repository}}:{{.Tag}} {{.ID}}'
27
+ | xargs -n 2 -t sh -c 'test -e $HOME/docker/$1.tar.gz || docker save $0 | gzip -2 > $HOME/docker/$1.tar.gz'
28
+
29
+ before_install:
30
+ # Load cached docker images
31
+ - if [[ -d $HOME/docker ]]; then ls $HOME/docker/*.tar.gz | xargs -I {file} sh -c "zcat {file} | docker load"; fi
32
+
17
33
  jobs:
18
34
  include:
19
35
  - stage: test
20
36
  script:
21
37
  - mkdir tmp
22
38
  - bundle exec rake test
23
-
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ # v0.44.0
2
+
3
+ * Support recording and labeling of indivudal functions via `functions:` section in *appmap.yml*.
4
+ * Remove deprecated `exe/appmap`.
5
+ * Add `test_status` and `exception` fields to AppMap metadata.
6
+ * Write AppMap file atomically, by writing to a temp file first and then moving it into place.
7
+ * Remove printing of `Inventory.json` file.
8
+ * Remove source code from `classMap`.
9
+
1
10
  # v0.43.0
2
11
 
3
12
  * Record `name` and `class` of each entry in Hash-like parameters, messages, and return values.
data/README.md CHANGED
@@ -11,6 +11,7 @@
11
11
  - [Remote recording](#remote-recording)
12
12
  - [Server process recording](#server-process-recording)
13
13
  - [AppMap for VSCode](#appmap-for-vscode)
14
+ - [AppMap Swagger](#appmap-swagger)
14
15
  - [Uploading AppMaps](#uploading-appmaps)
15
16
  - [Development](#development)
16
17
  - [Running tests](#running-tests)
@@ -109,6 +110,9 @@ name: my_project
109
110
  packages:
110
111
  - path: app/controllers
111
112
  - path: app/models
113
+ # Exclude sub-paths within the package path
114
+ exclude:
115
+ - concerns/accessor
112
116
  - path: app/jobs
113
117
  - path: app/helpers
114
118
  # Include the gems that you want to see in the dependency maps.
@@ -117,15 +121,22 @@ packages:
117
121
  - gem: devise
118
122
  - gem: aws-sdk
119
123
  - gem: will_paginate
124
+ # Global exclusion of a class name
120
125
  exclude:
121
126
  - MyClass
122
127
  - MyClass#my_instance_method
123
128
  - MyClass.my_class_method
129
+ functions:
130
+ - packages: myapp
131
+ class: ControllerHelper
132
+ function: logged_in_user
133
+ labels: [ authentication ]
124
134
  ```
125
135
 
126
136
  * **name** Provides the project name (required)
127
137
  * **packages** A list of source code directories which should be recorded.
128
138
  * **exclude** A list of classes and/or methods to definitively exclude from recording.
139
+ * **functions** A list of specific functions, scoped by package and class, to record.
129
140
 
130
141
  **packages**
131
142
 
@@ -144,6 +155,11 @@ Each entry in the `packages` list is a YAML object which has the following keys:
144
155
 
145
156
  Optional list of fully qualified class and method names. Separate class and method names with period (`.`) for class methods and hash (`#`) for instance methods.
146
157
 
158
+ **functions**
159
+
160
+ Optional list of `class, function` pairs. The `package` name is used to place the function within the class map, and does not have to match
161
+ the folder or gem name. The primary use of `functions` is to apply specific labels to functions whose source code is not accessible (e.g., it's in a Gem).
162
+ For functions which are part of the application code, use `@label` or `@labels` in code comments to apply labels.
147
163
 
148
164
  # Labels
149
165
 
@@ -329,9 +345,20 @@ Run your Rails server with `APPMAP_RECORD=true`. When the server exits, an *appm
329
345
 
330
346
  Be sure and set `WEB_CONCURRENCY=1`, if you are using a webserver that can run multiple processes. You only want there to be one process while you are recording, otherwise they will both try and write *appmap.json* and one of them will clobber the other.
331
347
 
348
+
332
349
  # AppMap for VSCode
333
350
 
334
- The [AppMap extension for VSCode](https://marketplace.visualstudio.com/items?itemName=appland.appmap) is a great way to onboard developers to new code, and troubleshoot hard-to-understand bugs with visuals.
351
+ The [AppMap extension for VSCode](https://marketplace.visualstudio.com/items?itemName=appland.appmap) helps you navigate your code more efficiently with interactive, accurate software architecture diagrams right in your IDE. In less than two minutes you can go from installing the AppMap extension to exploring maps of your code's architecture. AppMap helps you:
352
+
353
+ * Onboard to code architecture, with no extra work for the team
354
+ * Conduct code and design reviews using live and accurate data
355
+ * Troubleshoot hard-to-understand bugs using a "top-down" approach.
356
+
357
+ Each interactive diagram links directly to the source code, and the information is easy to share.
358
+
359
+ # AppMap Swagger
360
+
361
+ [appmap_swagger](https://github.com/applandinc/appmap_swagger-ruby) is a tool to generate Swagger files from AppMap data. With `appmap_swagger`, you can add Swagger to your Ruby or Ruby on Rails project, with no need to write or modify code. Use the Swagger UI to interact with your web services API as you build it, and use diffs of Swagger to perform code review of web service changes.
335
362
 
336
363
  # Uploading AppMaps
337
364
 
data/appmap.gemspec CHANGED
@@ -20,8 +20,6 @@ Gem::Specification.new do |spec|
20
20
  ")
21
21
  spec.extensions << "ext/appmap/extconf.rb"
22
22
 
23
- spec.bindir = 'exe'
24
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
23
  spec.require_paths = ['lib']
26
24
 
27
25
  spec.add_dependency 'activesupport'
data/lib/appmap.rb CHANGED
@@ -43,6 +43,7 @@ module AppMap
43
43
  # Call this function before the program code is loaded by the Ruby VM, otherwise
44
44
  # the load events won't be seen and the hooks won't activate.
45
45
  def initialize(config_file_path = 'appmap.yml')
46
+ raise "AppMap configuration file #{config_file_path} does not exist" unless ::File.exists?(config_file_path)
46
47
  warn "Configuring AppMap from path #{config_file_path}"
47
48
  Config.load_from_file(config_file_path).tap do |configuration|
48
49
  self.configuration = configuration
@@ -50,11 +51,6 @@ module AppMap
50
51
  end
51
52
  end
52
53
 
53
- # Whether to include source and comments in all class maps.
54
- def include_source?
55
- ENV['APPMAP_SOURCE'] == 'true'
56
- end
57
-
58
54
  # Used to start tracing, stop tracing, and record events.
59
55
  def tracing
60
56
  @tracing ||= Trace::Tracing.new
@@ -88,8 +84,8 @@ module AppMap
88
84
  end
89
85
 
90
86
  # Builds a class map from a config and a list of Ruby methods.
91
- def class_map(methods, options = {})
92
- ClassMap.build_from_methods(methods, options)
87
+ def class_map(methods)
88
+ ClassMap.build_from_methods(methods)
93
89
  end
94
90
 
95
91
  # Returns default metadata detected from the Ruby system and from the
@@ -71,17 +71,17 @@ module AppMap
71
71
  end
72
72
 
73
73
  class << self
74
- def build_from_methods(methods, options = {})
74
+ def build_from_methods(methods)
75
75
  root = Types::Root.new
76
76
  methods.each do |method|
77
- add_function root, method, options
77
+ add_function root, method
78
78
  end
79
79
  root.children.map(&:to_h)
80
80
  end
81
81
 
82
82
  protected
83
83
 
84
- def add_function(root, method, include_source: true)
84
+ def add_function(root, method)
85
85
  package = method.package
86
86
  static = method.static
87
87
 
@@ -113,16 +113,13 @@ module AppMap
113
113
  [ method.defined_class, static ? '.' : '#', method.name ].join
114
114
  end
115
115
 
116
- source, comment = begin
117
- [ method.source, method.comment ]
116
+ comment = begin
117
+ method.comment
118
118
  rescue MethodSource::SourceNotFoundError
119
- [ nil, nil, ]
119
+ nil
120
120
  end
121
121
 
122
- if include_source
123
- function_info[:source] = source unless source.blank?
124
- function_info[:comment] = comment unless comment.blank?
125
- end
122
+ function_info[:comment] = comment unless comment.blank?
126
123
 
127
124
  function_info[:labels] = parse_labels(comment) + (package.labels || [])
128
125
  object_infos << function_info
@@ -27,7 +27,7 @@ module AppMap
27
27
  event_thread.join
28
28
  yield AppMap::APPMAP_FORMAT_VERSION,
29
29
  AppMap.detect_metadata,
30
- AppMap.class_map(tracer.event_methods, include_source: AppMap.include_source?),
30
+ AppMap.class_map(tracer.event_methods),
31
31
  events
32
32
  end
33
33
 
data/lib/appmap/config.rb CHANGED
@@ -49,44 +49,89 @@ module AppMap
49
49
  end
50
50
  end
51
51
 
52
- Hook = Struct.new(:method_names, :package) do
52
+ Function = Struct.new(:package, :cls, :labels, :function_names) do
53
+ def to_h
54
+ {
55
+ package: package,
56
+ class: cls,
57
+ labels: labels,
58
+ functions: function_names.map(&:to_sym)
59
+ }.compact
60
+ end
53
61
  end
54
62
 
55
- OPENSSL_PACKAGES = Package.build_from_path('openssl', package_name: 'openssl', labels: %w[security crypto])
63
+ class Hook
64
+ attr_reader :method_names, :package
65
+
66
+ def initialize(method_names, package)
67
+ @method_names = method_names
68
+ @package = package
69
+ end
70
+
71
+ def to_h
72
+ {
73
+ package: package.name,
74
+ method_names: method_names
75
+ }
76
+ end
77
+ end
78
+
79
+ OPENSSL_PACKAGES = ->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
56
80
 
57
81
  # Methods that should always be hooked, with their containing
58
82
  # package and labels that should be applied to them.
59
83
  HOOKED_METHODS = {
60
- 'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', labels: %w[provider.secure_compare])),
84
+ 'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', labels: %w[crypto.secure_compare])),
61
85
  'ActionView::Renderer' => Hook.new(:render, Package.build_from_path('action_view', labels: %w[mvc.view])),
62
- 'ActionDispatch::Cookies::CookieJar' => Hook.new(%i[[]= clear update delete recycle], Package.build_from_path('action_pack', labels: %w[provider.http.cookie])),
63
- 'ActionDispatch::Cookies::EncryptedCookieJar' => Hook.new(%i[[]=], Package.build_from_path('action_pack', labels: %w[provider.http.cookie crypto])),
64
- 'CanCan::ControllerAdditions' => Hook.new(%i[authorize! can? cannot?], Package.build_from_path('cancancan', labels: %w[provider.authorization])),
65
- 'CanCan::Ability' => Hook.new(%i[authorize!], Package.build_from_path('cancancan', labels: %w[provider.authorization])),
86
+ 'ActionDispatch::Request::Session' => Hook.new(%i[destroy [] dig values []= clear update delete fetch merge], Package.build_from_path('action_pack', labels: %w[http.session])),
87
+ 'ActionDispatch::Cookies::CookieJar' => Hook.new(%i[[]= clear update delete recycle], Package.build_from_path('action_pack', labels: %w[http.cookie])),
88
+ 'ActionDispatch::Cookies::EncryptedCookieJar' => Hook.new(%i[[]=], Package.build_from_path('action_pack', labels: %w[http.cookie crypto.encrypt])),
89
+ 'CanCan::ControllerAdditions' => Hook.new(%i[authorize! can? cannot?], Package.build_from_path('cancancan', labels: %w[security.authorization])),
90
+ 'CanCan::Ability' => Hook.new(%i[authorize!], Package.build_from_path('cancancan', labels: %w[security.authorization])),
91
+ 'ActionController::Instrumentation' => [
92
+ Hook.new(%i[process_action send_file send_data redirect_to], Package.build_from_path('action_view', labels: %w[mvc.controller])),
93
+ Hook.new(%i[render], Package.build_from_path('action_view', labels: %w[mvc.view])),
94
+ ]
66
95
  }.freeze
67
96
 
68
97
  BUILTIN_METHODS = {
69
- 'OpenSSL::PKey::PKey' => Hook.new(:sign, OPENSSL_PACKAGES),
70
- 'OpenSSL::X509::Request' => Hook.new(%i[sign verify], OPENSSL_PACKAGES),
71
- 'OpenSSL::PKCS5' => Hook.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES),
72
- 'OpenSSL::Cipher' => Hook.new(%i[encrypt decrypt final], OPENSSL_PACKAGES),
73
- 'OpenSSL::X509::Certificate' => Hook.new(:sign, OPENSSL_PACKAGES),
74
- 'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http io])),
75
- 'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.smtp protocol.email io])),
76
- 'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.pop protocol.email io])),
77
- 'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.imap protocol.email io])),
78
- 'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal provider.serialization])),
79
- 'Psych' => Hook.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml provider.serialization])),
80
- 'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
81
- 'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
98
+ 'OpenSSL::PKey::PKey' => Hook.new(:sign, OPENSSL_PACKAGES.(%w[crypto.pkey])),
99
+ 'OpenSSL::X509::Request' => Hook.new(%i[sign verify], OPENSSL_PACKAGES.(%w[crypto.x509])),
100
+ 'OpenSSL::PKCS5' => Hook.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES.(%w[crypto.pkcs5])),
101
+ 'OpenSSL::Cipher' => [
102
+ Hook.new(%i[encrypt], OPENSSL_PACKAGES.(%w[crypto.encrypt])),
103
+ Hook.new(%i[decrypt], OPENSSL_PACKAGES.(%w[crypto.decrypt]))
104
+ ],
105
+ 'ActiveSupport::Callbacks::CallbackSequence' => [
106
+ Hook.new(:invoke_before, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[mvc.before_action])),
107
+ Hook.new(:invoke_after, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[mvc.after_action])),
108
+ ],
109
+ 'OpenSSL::X509::Certificate' => Hook.new(:sign, OPENSSL_PACKAGES.(%w[crypto.x509])),
110
+ 'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http])),
111
+ 'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.email.smtp])),
112
+ 'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.email.pop])),
113
+ 'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.email.imap])),
114
+ 'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal])),
115
+ 'Psych' => Hook.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml])),
116
+ 'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
117
+ 'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
82
118
  }.freeze
83
119
 
84
- attr_reader :name, :packages, :exclude
120
+ attr_reader :name, :packages, :exclude, :builtin_methods
85
121
 
86
- def initialize(name, packages = [], exclude = [])
122
+ def initialize(name, packages, exclude: [], functions: [])
87
123
  @name = name
88
124
  @packages = packages
89
125
  @exclude = exclude
126
+ @builtin_methods = BUILTIN_METHODS
127
+ @functions = functions
128
+ @hooked_methods = HOOKED_METHODS.dup
129
+ functions.each do |func|
130
+ package_options = {}
131
+ package_options[:labels] = func.labels if func.labels
132
+ @hooked_methods[func.cls] ||= []
133
+ @hooked_methods[func.cls] << Hook.new(func.function_names, Package.build_from_path(func.package, package_options))
134
+ end
90
135
  end
91
136
 
92
137
  class << self
@@ -98,6 +143,16 @@ module AppMap
98
143
 
99
144
  # Loads configuration from a Hash.
100
145
  def load(config_data)
146
+ functions = (config_data['functions'] || []).map do |function_data|
147
+ package = function_data['package']
148
+ cls = function_data['class']
149
+ functions = function_data['function'] || function_data['functions']
150
+ raise 'AppMap class configuration should specify package, class and function(s)' unless package && cls && functions
151
+ functions = Array(functions).map(&:to_sym)
152
+ labels = function_data['label'] || function_data['labels']
153
+ labels = Array(labels).map(&:to_s) if labels
154
+ Function.new(package, cls, labels, functions)
155
+ end
101
156
  packages = (config_data['packages'] || []).map do |package|
102
157
  gem = package['gem']
103
158
  path = package['path']
@@ -112,7 +167,8 @@ module AppMap
112
167
  Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow'])
113
168
  end
114
169
  end.compact
115
- Config.new config_data['name'], packages, config_data['exclude'] || []
170
+ exclude = config_data['exclude'] || []
171
+ Config.new config_data['name'], packages, exclude: exclude, functions: functions
116
172
  end
117
173
  end
118
174
 
@@ -120,6 +176,7 @@ module AppMap
120
176
  {
121
177
  name: name,
122
178
  packages: packages.map(&:to_h),
179
+ functions: @functions.map(&:to_h),
123
180
  exclude: exclude
124
181
  }
125
182
  end
@@ -164,14 +221,17 @@ module AppMap
164
221
  end
165
222
 
166
223
  def find_package(defined_class, method_name)
167
- hook = find_hook(defined_class)
168
- return nil unless hook
224
+ hooks = find_hooks(defined_class)
225
+ return nil unless hooks
169
226
 
170
- Array(hook.method_names).include?(method_name) ? hook.package : nil
227
+ hook = Array(hooks).find do |hook|
228
+ Array(hook.method_names).include?(method_name)
229
+ end
230
+ hook ? hook.package : nil
171
231
  end
172
232
 
173
- def find_hook(defined_class)
174
- HOOKED_METHODS[defined_class] || BUILTIN_METHODS[defined_class]
233
+ def find_hooks(defined_class)
234
+ Array(@hooked_methods[defined_class] || @builtin_methods[defined_class])
175
235
  end
176
236
  end
177
237
  end
@@ -50,7 +50,7 @@ module AppMap
50
50
  appmap['metadata'] = update_metadata(scenario, appmap['metadata'])
51
51
  scenario_filename = AppMap::Util.scenario_filename(appmap['metadata']['name'])
52
52
 
53
- File.write(File.join(APPMAP_OUTPUT_DIR, scenario_filename), JSON.generate(appmap))
53
+ AppMap::Util.write_appmap(File.join(APPMAP_OUTPUT_DIR, scenario_filename), JSON.generate(appmap))
54
54
  end
55
55
 
56
56
  def enabled?
data/lib/appmap/hook.rb CHANGED
@@ -32,6 +32,7 @@ module AppMap
32
32
  end
33
33
 
34
34
  attr_reader :config
35
+
35
36
  def initialize(config)
36
37
  @config = config
37
38
  end
@@ -59,6 +60,10 @@ module AppMap
59
60
 
60
61
  hook = lambda do |hook_cls|
61
62
  lambda do |method_id|
63
+ # Don't try and trace the AppMap methods or there will be
64
+ # a stack overflow in the defined hook method.
65
+ return if (hook_cls&.name || '').split('::')[0] == AppMap.name
66
+
62
67
  method = begin
63
68
  hook_cls.public_instance_method(method_id)
64
69
  rescue NameError
@@ -78,11 +83,9 @@ module AppMap
78
83
  config.always_hook?(hook_cls, method.name) ||
79
84
  config.included_by_location?(method)
80
85
 
81
- hook_method = Hook::Method.new(config.package_for_method(method), hook_cls, method)
86
+ package = config.package_for_method(method)
82
87
 
83
- # Don't try and trace the AppMap methods or there will be
84
- # a stack overflow in the defined hook method.
85
- next if /\AAppMap[:\.]/.match?(hook_method.method_display_name)
88
+ hook_method = Hook::Method.new(package, hook_cls, method)
86
89
 
87
90
  hook_method.activate
88
91
  end
@@ -112,25 +115,27 @@ module AppMap
112
115
  end
113
116
  end
114
117
 
115
- Config::BUILTIN_METHODS.each do |class_name, hook|
116
- require hook.package.package_name if hook.package.package_name
117
- Array(hook.method_names).each do |method_name|
118
- method_name = method_name.to_sym
118
+ config.builtin_methods.each do |class_name, hooks|
119
+ Array(hooks).each do |hook|
120
+ require hook.package.package_name if hook.package.package_name
121
+ Array(hook.method_names).each do |method_name|
122
+ method_name = method_name.to_sym
119
123
 
120
- cls = class_from_string.(class_name)
121
- method = \
122
- begin
123
- cls.instance_method(method_name)
124
- rescue NameError
125
- cls.method(method_name) rescue nil
126
- end
124
+ cls = class_from_string.(class_name)
125
+ method = \
126
+ begin
127
+ cls.instance_method(method_name)
128
+ rescue NameError
129
+ cls.method(method_name) rescue nil
130
+ end
127
131
 
128
- next if config.never_hook?(method)
132
+ next if config.never_hook?(method)
129
133
 
130
- if method
131
- Hook::Method.new(hook.package, cls, method).activate
132
- else
133
- warn "Method #{method_name} not found on #{cls.name}"
134
+ if method
135
+ Hook::Method.new(hook.package, cls, method).activate
136
+ else
137
+ warn "Method #{method_name} not found on #{cls.name}"
138
+ end
134
139
  end
135
140
  end
136
141
  end