arca 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +26 -0
- data/README.md +188 -0
- data/Rakefile +8 -0
- data/arca.gemspec +19 -0
- data/lib/arca.rb +64 -0
- data/lib/arca/callback_analysis.rb +134 -0
- data/lib/arca/collector.rb +95 -0
- data/lib/arca/model.rb +121 -0
- data/lib/arca/report.rb +80 -0
- data/test/fixtures/announcements.rb +11 -0
- data/test/fixtures/bar.rb +3 -0
- data/test/fixtures/foo.rb +33 -0
- data/test/fixtures/ticket.rb +23 -0
- data/test/lib/arca/callback_analysis_test.rb +133 -0
- data/test/lib/arca/collector_test.rb +66 -0
- data/test/lib/arca/model_test.rb +59 -0
- data/test/lib/arca/report_test.rb +27 -0
- data/test/lib/arca_test.rb +19 -0
- data/test/test_helper.rb +16 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0e3c9c827e804d9e159cd1287dd96d2f51a73b7c
|
4
|
+
data.tar.gz: 2258bba04de95cb191e8db9819ff6b170cd96ab0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a769d84288997f5dcb1557d3e8061b2c215a1bf492f63da1c9d84d4b37a6552a59b90a53f6531104e0b2ecfe0cdf20b88b34c6611d243b85385cfff04a24ae85
|
7
|
+
data.tar.gz: cbebb0af251cfbd5d8900d85aaaa9c973e8ca29a03cb9d73a94d0591f640b6cf6e7f5e03455859bea967346a01e2481bbae49949b6fb06ca8573c8dc3228a74b
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
arca (1.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
coderay (1.1.0)
|
10
|
+
method_source (0.8.2)
|
11
|
+
minitest (5.7.0)
|
12
|
+
pry (0.10.1)
|
13
|
+
coderay (~> 1.1.0)
|
14
|
+
method_source (~> 0.8.1)
|
15
|
+
slop (~> 3.4)
|
16
|
+
rake (10.4.2)
|
17
|
+
slop (3.5.0)
|
18
|
+
|
19
|
+
PLATFORMS
|
20
|
+
ruby
|
21
|
+
|
22
|
+
DEPENDENCIES
|
23
|
+
arca!
|
24
|
+
minitest (~> 5.7)
|
25
|
+
pry (~> 0.10.1)
|
26
|
+
rake (~> 10.4)
|
data/README.md
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
# ActiveRecord Callback Analyzer
|
2
|
+
|
3
|
+
Arca is a callback analyzer for ActiveRecord like models ideally suited for digging yourself out of callback hell. Arca helps you answer questions like:
|
4
|
+
|
5
|
+
* how spread out callbacks are for each model
|
6
|
+
* how many callbacks use conditionals (`:if` and `:unless`)
|
7
|
+
* how many possible permutations exist per callback type (`:commit`, `:create`, `:destroy`, `:find`, `:initialize`, `:rollback`, `:save`, `:touch`, `:update`, `:validation`) taking conditionals into consideration
|
8
|
+
|
9
|
+
The Arca library has two main components, the collector and the reporter. Include the collector module in each ActiveRecord model you want to analyze and then use the reporter to analyze and present the data.
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Add the gem to your Gemfile and run `bundle`.
|
14
|
+
|
15
|
+
```
|
16
|
+
gem 'arca'
|
17
|
+
```
|
18
|
+
|
19
|
+
Add an initializer to require the library and configure it (`config/initializers/arca.rb` for example). There's no magic here and Arca doesn't assume your root project path or the path to your `ActiveRecord` models so you have to specify those paths yourself in the initializer.
|
20
|
+
|
21
|
+
```
|
22
|
+
require "arca"
|
23
|
+
|
24
|
+
Arca.root_path = Rails.root
|
25
|
+
Arca.model_path = Rails.root.join("app", "models")
|
26
|
+
```
|
27
|
+
|
28
|
+
Include `Arca::Collector` in the models you want to analyze. It must be included before any callbacks so I recommend including it right after the class definition.
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
class Ticket < ActiveRecord::Base
|
32
|
+
include Arca::Collector
|
33
|
+
include Announcements
|
34
|
+
|
35
|
+
before_save :set_title, :set_body
|
36
|
+
before_save :upcase_title, :if => :title_is_a_shout?
|
37
|
+
|
38
|
+
def set_title
|
39
|
+
self.title ||= "Ticket id #{SecureRandom.hex(2)}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_body
|
43
|
+
self.body ||= "Everything is broken."
|
44
|
+
end
|
45
|
+
|
46
|
+
def upcase_title
|
47
|
+
self.title = title.upcase
|
48
|
+
end
|
49
|
+
|
50
|
+
def title_is_a_shout?
|
51
|
+
self.title.split(" ").size == 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
In this example the `Annoucements` module is included in `Ticket` and defines it's own callback.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
module Announcements
|
60
|
+
def self.included(base)
|
61
|
+
base.class_eval do
|
62
|
+
after_save :announce_save
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def announce_save
|
67
|
+
puts "saved #{self.class.name.downcase}!"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
Next use `Arca[Ticket].report` to analyze the callbacks for the `Ticket` class.
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
> Arca.root_path = `pwd`.chomp
|
76
|
+
=> "/Users/jonmagic/Projects/arca"
|
77
|
+
> Arca[Ticket].report
|
78
|
+
{
|
79
|
+
:model_name => "Ticket",
|
80
|
+
:model_file_path => "test/fixtures/ticket.rb",
|
81
|
+
:callbacks_count => 4,
|
82
|
+
:conditionals_count => 1,
|
83
|
+
:lines_between_count => 6,
|
84
|
+
:external_callbacks_count => 1,
|
85
|
+
:external_targets_count => 0,
|
86
|
+
:external_conditionals_count => 0,
|
87
|
+
:calculated_permutations => 2
|
88
|
+
}
|
89
|
+
> Arca[Ticket].analyzed_callbacks
|
90
|
+
{
|
91
|
+
:before_save => [
|
92
|
+
{
|
93
|
+
:callback => :before_save,
|
94
|
+
:callback_file_path => "test/fixtures/ticket.rb",
|
95
|
+
:callback_line_number => 5,
|
96
|
+
:external_callback => false,
|
97
|
+
:target => :set_title,
|
98
|
+
:target_file_path => "test/fixtures/ticket.rb",
|
99
|
+
:target_line_number => 8,
|
100
|
+
:external_target => false,
|
101
|
+
:lines_to_target => 3,
|
102
|
+
:conditional => nil,
|
103
|
+
:conditional_target => nil,
|
104
|
+
:conditional_target_file_path => nil,
|
105
|
+
:conditional_target_line_number => nil,
|
106
|
+
:external_conditional_target => nil,
|
107
|
+
:lines_to_conditional_target => nil
|
108
|
+
},
|
109
|
+
{
|
110
|
+
:callback => :before_save,
|
111
|
+
:callback_file_path => "test/fixtures/ticket.rb",
|
112
|
+
:callback_line_number => 5,
|
113
|
+
:external_callback => false,
|
114
|
+
:target => :set_body,
|
115
|
+
:target_file_path => "test/fixtures/ticket.rb",
|
116
|
+
:target_line_number => 12,
|
117
|
+
:external_target => false,
|
118
|
+
:lines_to_target => 7,
|
119
|
+
:conditional => nil,
|
120
|
+
:conditional_target => nil,
|
121
|
+
:conditional_target_file_path => nil,
|
122
|
+
:conditional_target_line_number => nil,
|
123
|
+
:external_conditional_target => nil,
|
124
|
+
:lines_to_conditional_target => nil
|
125
|
+
},
|
126
|
+
{
|
127
|
+
:callback => :before_save,
|
128
|
+
:callback_file_path => "test/fixtures/ticket.rb",
|
129
|
+
:callback_line_number => 6,
|
130
|
+
:external_callback => false,
|
131
|
+
:target => :upcase_title,
|
132
|
+
:target_file_path => "test/fixtures/ticket.rb",
|
133
|
+
:target_line_number => 16,
|
134
|
+
:external_target => false,
|
135
|
+
:lines_to_target => 10,
|
136
|
+
:conditional => :if,
|
137
|
+
:conditional_target => :title_is_a_shout?,
|
138
|
+
:conditional_target_file_path => "test/fixtures/ticket.rb",
|
139
|
+
:conditional_target_line_number => 20,
|
140
|
+
:external_conditional_target => false,
|
141
|
+
:lines_to_conditional_target => nil
|
142
|
+
}
|
143
|
+
],
|
144
|
+
:after_save => [
|
145
|
+
{
|
146
|
+
:callback => :after_save,
|
147
|
+
:callback_file_path => "test/fixtures/announcements.rb",
|
148
|
+
:callback_line_number => 4,
|
149
|
+
:external_callback => true,
|
150
|
+
:target => :announce_save,
|
151
|
+
:target_file_path => "test/fixtures/announcements.rb",
|
152
|
+
:target_line_number => 8,
|
153
|
+
:external_target => false,
|
154
|
+
:lines_to_target => 4,
|
155
|
+
:conditional => nil,
|
156
|
+
:conditional_target => nil,
|
157
|
+
:conditional_target_file_path => nil,
|
158
|
+
:conditional_target_line_number => nil,
|
159
|
+
:external_conditional_target => nil,
|
160
|
+
:lines_to_conditional_target => nil
|
161
|
+
}
|
162
|
+
]
|
163
|
+
}
|
164
|
+
```
|
165
|
+
|
166
|
+
## License
|
167
|
+
|
168
|
+
The MIT License (MIT)
|
169
|
+
|
170
|
+
Copyright (c) 2015 Jonathan Hoyt
|
171
|
+
|
172
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
173
|
+
of this software and associated documentation files (the "Software"), to deal
|
174
|
+
in the Software without restriction, including without limitation the rights
|
175
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
176
|
+
copies of the Software, and to permit persons to whom the Software is
|
177
|
+
furnished to do so, subject to the following conditions:
|
178
|
+
|
179
|
+
The above copyright notice and this permission notice shall be included in all
|
180
|
+
copies or substantial portions of the Software.
|
181
|
+
|
182
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
183
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
184
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
185
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
186
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
187
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
188
|
+
SOFTWARE.
|
data/Rakefile
ADDED
data/arca.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "arca"
|
3
|
+
spec.version = "1.0.0"
|
4
|
+
spec.date = "2015-07-11"
|
5
|
+
spec.summary = "ActiveRecord callback analyzer"
|
6
|
+
spec.description = "Arca is a callback analyzer for ActiveRecord ideally suited for digging yourself out of callback hell"
|
7
|
+
spec.authors = ["Jonathan Hoyt"]
|
8
|
+
spec.email = "jonmagic@gmail.com"
|
9
|
+
spec.require_paths = ["lib"]
|
10
|
+
spec.files = `git ls-files -z`.split("\x0")
|
11
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
12
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
13
|
+
spec.homepage = "https://github.com/jonmagic/arca"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.add_development_dependency "rake", "~> 10.4"
|
17
|
+
spec.add_development_dependency "minitest", "~> 5.7"
|
18
|
+
spec.add_development_dependency "pry", "~> 0.10"
|
19
|
+
end
|
data/lib/arca.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative "arca/collector"
|
2
|
+
require_relative "arca/model"
|
3
|
+
require_relative "arca/report"
|
4
|
+
require_relative "arca/callback_analysis"
|
5
|
+
|
6
|
+
module Arca
|
7
|
+
|
8
|
+
# Error raised if Arca[] is passed something other than a class constant.
|
9
|
+
class ClassRequired < StandardError; end
|
10
|
+
|
11
|
+
# Error raised if model does not respond to arca_callback_data
|
12
|
+
class CallbackDataMissing < StandardError; end
|
13
|
+
|
14
|
+
# Public: Reader method for accessing the Arca::Model for analysis and
|
15
|
+
# reporting.
|
16
|
+
def self.[](klass)
|
17
|
+
raise ClassRequired unless klass.kind_of?(Class)
|
18
|
+
raise CallbackDataMissing unless klass.respond_to?(:arca_callback_data)
|
19
|
+
|
20
|
+
Arca::Model.new(klass)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: (optional) Writer method for configuring the root path of the
|
24
|
+
# project where Arca is being used. Setting Arca.root_path will makes
|
25
|
+
# inspecting analyzed callbacks easier by shortening absolute paths to
|
26
|
+
# relative paths.
|
27
|
+
#
|
28
|
+
# path - Pathname or String representing the root path of the project.
|
29
|
+
def self.root_path=(path)
|
30
|
+
@root_path = path.to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: String representing the root path for the project.
|
34
|
+
def self.root_path
|
35
|
+
@root_path
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: (required) Writer method for configuring the root path to the models
|
39
|
+
# for the project where Arca is being used. This path is required by the
|
40
|
+
# Arca::Collector for finding the correct line in the caller Array.
|
41
|
+
def self.model_root_path=(path)
|
42
|
+
@model_root_path = path.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
# Public: String representing the path to the models for the project.
|
46
|
+
def self.model_root_path
|
47
|
+
@model_root_path
|
48
|
+
end
|
49
|
+
|
50
|
+
# Public: Helper method for turning absolute paths into relative paths.
|
51
|
+
#
|
52
|
+
# path - String absolute path.
|
53
|
+
#
|
54
|
+
# Returns a relative path String.
|
55
|
+
def self.relative_path(path)
|
56
|
+
return if path.nil?
|
57
|
+
|
58
|
+
if @root_path
|
59
|
+
path.sub(/^#{Regexp.escape(@root_path) || ""}\//, "")
|
60
|
+
else
|
61
|
+
path
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Arca
|
2
|
+
class CallbackAnalysis
|
3
|
+
|
4
|
+
# Arca::CallbackAnalysis takes an Arca::Model and data for a specific
|
5
|
+
# callback and then calculates and exposes a complete analysis for the
|
6
|
+
# callback including target methods, file paths, line numbers, booleans
|
7
|
+
# representing whether targets are in the same file they are called from,
|
8
|
+
# and finally the number of lines between callers and the target methods
|
9
|
+
# they call.
|
10
|
+
#
|
11
|
+
# model - Arca::Model instance.
|
12
|
+
# callback_data - Hash with callback data collected by Arca::Collector.
|
13
|
+
def initialize(model:, callback_data:)
|
14
|
+
@model = model
|
15
|
+
@callback_symbol = callback_data.fetch(:callback_symbol)
|
16
|
+
@callback_file_path = callback_data.fetch(:callback_file_path)
|
17
|
+
@callback_line_number = callback_data.fetch(:callback_line_number)
|
18
|
+
@target_symbol = callback_data.fetch(:target_symbol)
|
19
|
+
@conditional_symbol = callback_data[:conditional_symbol]
|
20
|
+
@conditional_target_symbol = callback_data[:conditional_target_symbol]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: Hash representation of the object for interactive consoles.
|
24
|
+
def inspect
|
25
|
+
to_hash.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: Hash of collected and analyzed callback data.
|
29
|
+
def to_hash
|
30
|
+
{
|
31
|
+
:callback => callback_symbol,
|
32
|
+
:callback_file_path => Arca.relative_path(callback_file_path),
|
33
|
+
:callback_line_number => callback_line_number,
|
34
|
+
:external_callback => external_callback?,
|
35
|
+
:target => target_symbol,
|
36
|
+
:target_file_path => Arca.relative_path(target_file_path),
|
37
|
+
:target_line_number => target_line_number,
|
38
|
+
:external_target => external_target?,
|
39
|
+
:lines_to_target => lines_to_target,
|
40
|
+
:conditional => conditional_symbol,
|
41
|
+
:conditional_target => conditional_target_symbol,
|
42
|
+
:conditional_target_file_path => Arca.relative_path(conditional_target_file_path),
|
43
|
+
:conditional_target_line_number => conditional_target_line_number,
|
44
|
+
:external_conditional_target => external_conditional_target?,
|
45
|
+
:lines_to_conditional_target => nil
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Public: Arca::Model this callback belongs to.
|
50
|
+
attr_reader :model
|
51
|
+
|
52
|
+
# Public: Symbol representing the callback method name.
|
53
|
+
attr_reader :callback_symbol
|
54
|
+
|
55
|
+
# Public: String path to the file where the callback is used.
|
56
|
+
attr_reader :callback_file_path
|
57
|
+
|
58
|
+
# Public: Integer line number where the callback is called.
|
59
|
+
attr_reader :callback_line_number
|
60
|
+
|
61
|
+
# Public: Boolean representing whether the callback is used in the same
|
62
|
+
# file where the ActiveRecord model is defined.
|
63
|
+
def external_callback?
|
64
|
+
callback_file_path != model.file_path
|
65
|
+
end
|
66
|
+
|
67
|
+
# Public: Symbol representing the callback target method name.
|
68
|
+
attr_reader :target_symbol
|
69
|
+
|
70
|
+
# Public: String path to the file where the callback target is located.
|
71
|
+
def target_file_path
|
72
|
+
model.source_location(target_symbol)[:file_path]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Public: Integer line number where the callback target is located.
|
76
|
+
def target_line_number
|
77
|
+
model.source_location(target_symbol)[:line_number]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Public: Boolean representing whether the callback target is located in the
|
81
|
+
# same file where the callback is defined.
|
82
|
+
def external_target?
|
83
|
+
target_file_path != callback_file_path
|
84
|
+
end
|
85
|
+
|
86
|
+
# Public: Integer representing the number of lines between where the
|
87
|
+
# callback is used and the callback target is located.
|
88
|
+
def lines_to_target
|
89
|
+
return if external_target?
|
90
|
+
|
91
|
+
(target_line_number - callback_line_number).abs
|
92
|
+
end
|
93
|
+
|
94
|
+
# Public: Symbol representing the conditional target method name.
|
95
|
+
attr_reader :conditional_symbol
|
96
|
+
attr_reader :conditional_target_symbol
|
97
|
+
|
98
|
+
# Public: String path to the file where the conditional target is located.
|
99
|
+
def conditional_target_file_path
|
100
|
+
return if conditional_target_symbol.nil?
|
101
|
+
|
102
|
+
model.source_location(conditional_target_symbol)[:file_path]
|
103
|
+
end
|
104
|
+
|
105
|
+
# Public: Integer line number where the conditional target is located.
|
106
|
+
def conditional_target_line_number
|
107
|
+
return if conditional_target_symbol.nil?
|
108
|
+
|
109
|
+
model.source_location(conditional_target_symbol)[:line_number]
|
110
|
+
end
|
111
|
+
|
112
|
+
# Public: Boolean representing whether the conditional target is located in
|
113
|
+
# the same file where the callback is defined.
|
114
|
+
def external_conditional_target?
|
115
|
+
return if conditional_target_symbol.nil?
|
116
|
+
|
117
|
+
callback_file_path != conditional_target_file_path
|
118
|
+
end
|
119
|
+
|
120
|
+
# Public: Integer representing the number of lines between where the
|
121
|
+
# callback is used and the conditional target is located.
|
122
|
+
def lines_to_conditional_target
|
123
|
+
return if conditional_target_symbol.nil? || external_conditional_target?
|
124
|
+
|
125
|
+
(conditional_target_line_number - callback_line_number).abs
|
126
|
+
end
|
127
|
+
|
128
|
+
# Public: Boolean representing whether the callback target is located in the
|
129
|
+
# ActiveRecord gem.
|
130
|
+
def target_file_path_active_record?
|
131
|
+
target_file_path =~ /gems\/activerecord/
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Arca
|
2
|
+
|
3
|
+
# Include Arca::Collector in an ActiveRecord class in order to collect data
|
4
|
+
# about how callbacks are being used.
|
5
|
+
module Collector
|
6
|
+
|
7
|
+
# Error raised if Arca.model_root_path is nil.
|
8
|
+
class ModelRootPathRequired < StandardError; end
|
9
|
+
|
10
|
+
# Internal: Regular expression used for extracting the file path and line
|
11
|
+
# number from a caller line.
|
12
|
+
ARCA_LINE_PARSER_REGEXP = /\A(.+)\:(\d+)\:in\s(.+)\z/
|
13
|
+
private_constant :ARCA_LINE_PARSER_REGEXP
|
14
|
+
|
15
|
+
# Internal: Array of conditional symbols.
|
16
|
+
ARCA_CONDITIONALS = [:if, :unless]
|
17
|
+
private_constant :ARCA_CONDITIONALS
|
18
|
+
|
19
|
+
# http://ruby-doc.org/core-2.2.1/Module.html#method-i-included
|
20
|
+
def self.included(base)
|
21
|
+
# Raise error if Arca.model_root_path is nil.
|
22
|
+
raise ModelRootPathRequired if Arca.model_root_path.nil?
|
23
|
+
|
24
|
+
# Get the file path to the model class that included the collector.
|
25
|
+
model_file_path, = caller[0].partition(":")
|
26
|
+
|
27
|
+
base.class_eval do
|
28
|
+
# Define :arca_callback_data for storing the data we collect.
|
29
|
+
define_singleton_method :arca_callback_data do
|
30
|
+
@callbacks ||= {}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Collect the model_file_path.
|
34
|
+
arca_callback_data[:model_file_path] = model_file_path
|
35
|
+
|
36
|
+
# Find the callback methods defined on this class.
|
37
|
+
callback_method_symbols = singleton_methods.grep /^(after|around|before)\_/
|
38
|
+
|
39
|
+
callback_method_symbols.each do |callback_symbol|
|
40
|
+
# Find the UnboundMethod for the callback.
|
41
|
+
callback_method = singleton_class.instance_method(callback_symbol)
|
42
|
+
|
43
|
+
# Redefine the callback method so that data can be collected each time
|
44
|
+
# the callback is used for this class.
|
45
|
+
define_singleton_method(callback_method.name) do |*args|
|
46
|
+
# Duplicate args before modifying.
|
47
|
+
args_copy = args.dup
|
48
|
+
|
49
|
+
# Get the options hash from the end of the args Array if it exists.
|
50
|
+
options = args_copy.pop if args[-1].is_a?(Hash)
|
51
|
+
|
52
|
+
# Iterate through the rest of the args. Each remaining arguement is
|
53
|
+
# a Symbol representing the callback target method.
|
54
|
+
args_copy.each do |target_symbol|
|
55
|
+
|
56
|
+
# Find the caller line where the callback is used.
|
57
|
+
line = caller.find {|line| line =~ /#{Regexp.escape(Arca.model_root_path)}/ }
|
58
|
+
|
59
|
+
# Parse the line in order to extract the file path and line number.
|
60
|
+
callback_line_matches = line.match(ARCA_LINE_PARSER_REGEXP)
|
61
|
+
|
62
|
+
# Find the conditional symbol if it exists in the options hash.
|
63
|
+
conditional_symbol = ARCA_CONDITIONALS.
|
64
|
+
find {|conditional| options && options.has_key?(conditional) }
|
65
|
+
|
66
|
+
# Find the conditional target symbol if there is a conditional.
|
67
|
+
conditional_target_symbol = if conditional_symbol
|
68
|
+
options[conditional_symbol]
|
69
|
+
end
|
70
|
+
|
71
|
+
# Set the collector hash for this callback_symbol to an empty
|
72
|
+
# Array if it has not already been set.
|
73
|
+
arca_callback_data[callback_symbol] ||= []
|
74
|
+
|
75
|
+
# Add the collected callback data to the collector Array for
|
76
|
+
# this callback_symbol.
|
77
|
+
arca_callback_data[callback_symbol] << {
|
78
|
+
:callback_symbol => callback_symbol,
|
79
|
+
:callback_file_path => callback_line_matches[1],
|
80
|
+
:callback_line_number => callback_line_matches[2].to_i,
|
81
|
+
:target_symbol => target_symbol,
|
82
|
+
:conditional_symbol => conditional_symbol,
|
83
|
+
:conditional_target_symbol => conditional_target_symbol
|
84
|
+
}
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
# Bind the callback method to self and call it with args.
|
89
|
+
callback_method.bind(self).call(*args)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/arca/model.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
module Arca
|
2
|
+
class Model
|
3
|
+
|
4
|
+
# Arca::Model wraps an ActiveRecord model class and provides an interface
|
5
|
+
# to the collected and analyzed callback data for that class and the file
|
6
|
+
# path to the model class.
|
7
|
+
def initialize(klass)
|
8
|
+
@klass = klass
|
9
|
+
@name = klass.name
|
10
|
+
@callbacks = klass.arca_callback_data.dup
|
11
|
+
@file_path = callbacks.delete(:model_file_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Array of ActiveRecord callback method symbols in a rough order of when
|
15
|
+
# they are used in the life cycle of an ActiveRecord model.
|
16
|
+
CALLBACKS = [
|
17
|
+
:after_initialize, :after_find, :after_touch, :before_validation, :after_validation,
|
18
|
+
:before_save, :around_save, :after_save, :before_create, :around_create,
|
19
|
+
:after_create, :before_update, :around_update, :after_update,
|
20
|
+
:before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
|
21
|
+
]
|
22
|
+
|
23
|
+
# Public: ActiveRecord model class.
|
24
|
+
attr_reader :klass
|
25
|
+
|
26
|
+
# Public: String model name.
|
27
|
+
attr_reader :name
|
28
|
+
|
29
|
+
# Public: String file path.
|
30
|
+
attr_reader :file_path
|
31
|
+
|
32
|
+
# Public: Hash of collected callback data.
|
33
|
+
attr_reader :callbacks
|
34
|
+
|
35
|
+
# Public: Arca::Report for this model.
|
36
|
+
def report
|
37
|
+
@report ||= Report.new(self)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Public: Helper method for finding the file path and line number where
|
41
|
+
# a method is located for the ActiveRecord model.
|
42
|
+
#
|
43
|
+
# method_symbol - Symbol representation of the method name.
|
44
|
+
def source_location(method_symbol)
|
45
|
+
source_location = klass.instance_method(method_symbol).source_location
|
46
|
+
{
|
47
|
+
:file_path => source_location[0],
|
48
|
+
:line_number => source_location[1]
|
49
|
+
}
|
50
|
+
rescue NameError
|
51
|
+
{
|
52
|
+
:file_path => nil,
|
53
|
+
:line_number => nil
|
54
|
+
}
|
55
|
+
rescue TypeError
|
56
|
+
{
|
57
|
+
:file_path => nil,
|
58
|
+
:line_number => nil
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
# Public: Hash of CallbackAnalysis objects for each callback type.
|
63
|
+
def analyzed_callbacks
|
64
|
+
@analyzed_callbacks ||= CALLBACKS.inject({}) do |result, callback_symbol|
|
65
|
+
Array(callbacks[callback_symbol]).each do |callback_data|
|
66
|
+
result[callback_symbol] ||= []
|
67
|
+
callback_analysis = CallbackAnalysis.new({
|
68
|
+
:model => self,
|
69
|
+
:callback_data => callback_data
|
70
|
+
})
|
71
|
+
|
72
|
+
unless callback_analysis.target_file_path_active_record?
|
73
|
+
result[callback_symbol] << callback_analysis
|
74
|
+
end
|
75
|
+
end
|
76
|
+
result
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Public: Array of all CallbackAnalysis objects for this model.
|
81
|
+
def analyzed_callbacks_array
|
82
|
+
@analyzed_callbacks_array ||= analyzed_callbacks.values.flatten
|
83
|
+
end
|
84
|
+
|
85
|
+
# Public: Integer representing the number of callbacks analyzed.
|
86
|
+
def analyzed_callbacks_count
|
87
|
+
analyzed_callbacks_array.size
|
88
|
+
end
|
89
|
+
|
90
|
+
# Public: Integer representing the total number of lines between callbacks
|
91
|
+
# called for this class from files other than the one where the class is
|
92
|
+
# defined.
|
93
|
+
def lines_between_count
|
94
|
+
lines_between = 0
|
95
|
+
line_numbers = analyzed_callbacks_array.map &:callback_line_number
|
96
|
+
sorted_line_numbers = line_numbers.sort {|a,b| b <=> a }
|
97
|
+
sorted_line_numbers.each_with_index do |line_number, index|
|
98
|
+
lines_between += line_number - (sorted_line_numbers[index + 1] || 0)
|
99
|
+
end
|
100
|
+
lines_between
|
101
|
+
end
|
102
|
+
|
103
|
+
# Public: Integer representing the number of callbacks called for this class
|
104
|
+
# from files other than this model.
|
105
|
+
def external_callbacks_count
|
106
|
+
analyzed_callbacks_array.select {|analysis| analysis.external_callback? }.size
|
107
|
+
end
|
108
|
+
|
109
|
+
# Public: Integer representing the number of callback targets that are
|
110
|
+
# defined in files other than this model.
|
111
|
+
def external_targets_count
|
112
|
+
analyzed_callbacks_array.select {|analysis| analysis.external_target? }.size
|
113
|
+
end
|
114
|
+
|
115
|
+
# Public: Integer representing the number of conditional callback targets
|
116
|
+
# that are defined in files other than this model.
|
117
|
+
def external_conditionals_count
|
118
|
+
analyzed_callbacks_array.select {|analysis| analysis.external_conditional_target? }.size
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
data/lib/arca/report.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
module Arca
|
2
|
+
class Report
|
3
|
+
|
4
|
+
# Arca::Report takes an Arca::Model and compiles the analyzed callback data
|
5
|
+
# into a short overview report for the model.
|
6
|
+
def initialize(model)
|
7
|
+
@model = model
|
8
|
+
end
|
9
|
+
|
10
|
+
# Public: Arca::Model representing the ActiveRecord class being reported.
|
11
|
+
attr_reader :model
|
12
|
+
|
13
|
+
# Public: Hash representation of the object for interactive consoles.
|
14
|
+
def inspect
|
15
|
+
to_hash.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: Hash of compiled report data.
|
19
|
+
def to_hash
|
20
|
+
{
|
21
|
+
:model_name => model_name,
|
22
|
+
:model_file_path => Arca.relative_path(model_file_path),
|
23
|
+
:callbacks_count => callbacks_count,
|
24
|
+
:conditionals_count => conditionals_count,
|
25
|
+
:lines_between_count => lines_between_count,
|
26
|
+
:external_callbacks_count => external_callbacks_count,
|
27
|
+
:external_targets_count => external_targets_count,
|
28
|
+
:external_conditionals_count => external_conditionals_count,
|
29
|
+
:calculated_permutations => calculated_permutations
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: String class name of model.
|
34
|
+
def model_name
|
35
|
+
model.name
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: String file path of model.
|
39
|
+
def model_file_path
|
40
|
+
model.file_path
|
41
|
+
end
|
42
|
+
|
43
|
+
# Public: Integer representing the number of callbacks used in this model.
|
44
|
+
def callbacks_count
|
45
|
+
model.analyzed_callbacks_count
|
46
|
+
end
|
47
|
+
|
48
|
+
# Public: Integer representing the number of conditionals used in callback
|
49
|
+
# for the model being reported.
|
50
|
+
def conditionals_count
|
51
|
+
number_of_unique_conditionals(model.analyzed_callbacks_array)
|
52
|
+
end
|
53
|
+
|
54
|
+
delegate :lines_between_count, :external_callbacks_count,
|
55
|
+
:external_targets_count, :external_conditionals_count, :to => :model
|
56
|
+
|
57
|
+
# Public: Integer representing the possible number of permutations stemming
|
58
|
+
# from conditionals for an instance of the model being reported during the
|
59
|
+
# lifecycle of the object.
|
60
|
+
def calculated_permutations
|
61
|
+
permutations = model.analyzed_callbacks.inject([]) do |results, (key, analyzed_callbacks)|
|
62
|
+
results << 2 ** number_of_unique_conditionals(analyzed_callbacks)
|
63
|
+
end.sum - number_of_unique_conditionals(model.analyzed_callbacks_array)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Internal: Integer representing the number of unique conditions for an
|
67
|
+
# Array of CallbackAnalysis objects.
|
68
|
+
#
|
69
|
+
# analyzed_callbacks - Array of CallbackAnalysis objects.
|
70
|
+
#
|
71
|
+
# Returns an Integer.
|
72
|
+
def number_of_unique_conditionals(analyzed_callbacks)
|
73
|
+
analyzed_callbacks.
|
74
|
+
select {|analysis| analysis.conditional_symbol }.
|
75
|
+
uniq {|analysis| analysis.conditional_target_symbol }.
|
76
|
+
size
|
77
|
+
end
|
78
|
+
private :number_of_unique_conditionals
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Foo
|
2
|
+
extend ActiveModel::Callbacks
|
3
|
+
define_model_callbacks :save
|
4
|
+
|
5
|
+
include Arca::Collector
|
6
|
+
before_save :bar, :baz, :if => :boop?
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@mission_complete = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def save
|
13
|
+
run_callbacks :save do
|
14
|
+
@mission_complete = true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
attr_reader :mission_complete
|
18
|
+
|
19
|
+
def bar
|
20
|
+
@bargo = "hello"
|
21
|
+
end
|
22
|
+
attr_reader :bargo
|
23
|
+
|
24
|
+
def baz
|
25
|
+
@bazinga = "world"
|
26
|
+
end
|
27
|
+
attr_reader :bazinga
|
28
|
+
|
29
|
+
def boop?
|
30
|
+
!!@boop
|
31
|
+
end
|
32
|
+
attr_writer :boop
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Ticket < ActiveRecord::Base
|
2
|
+
include Arca::Collector
|
3
|
+
include Announcements
|
4
|
+
|
5
|
+
before_save :set_title, :set_body
|
6
|
+
before_save :upcase_title, :if => :title_is_a_shout?
|
7
|
+
|
8
|
+
def set_title
|
9
|
+
self.title ||= "Ticket id #{SecureRandom.hex(2)}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def set_body
|
13
|
+
self.body ||= "Everything is broken."
|
14
|
+
end
|
15
|
+
|
16
|
+
def upcase_title
|
17
|
+
self.title = title.upcase
|
18
|
+
end
|
19
|
+
|
20
|
+
def title_is_a_shout?
|
21
|
+
self.title.split(" ").size == 1
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require_relative "../../test_helper"
|
2
|
+
|
3
|
+
class Arca::CallbackAnalysisTest < Minitest::Test
|
4
|
+
def model
|
5
|
+
@model ||= Arca::Model.new(Ticket)
|
6
|
+
end
|
7
|
+
|
8
|
+
def announce_save
|
9
|
+
@announce_save ||= Arca::CallbackAnalysis.new({
|
10
|
+
:model => model,
|
11
|
+
:callback_data => {
|
12
|
+
:callback_symbol => :after_save,
|
13
|
+
:callback_file_path => "/Users/jonmagic/Projects/arca/test/fixtures/announcements.rb",
|
14
|
+
:callback_line_number => 4,
|
15
|
+
:target_symbol => :announce_save,
|
16
|
+
:conditional_symbol => nil,
|
17
|
+
:conditional_target_symbol => nil
|
18
|
+
}
|
19
|
+
})
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_title
|
23
|
+
@set_title ||= Arca::CallbackAnalysis.new({
|
24
|
+
:model => model,
|
25
|
+
:callback_data => {
|
26
|
+
:callback_symbol=>:before_save,
|
27
|
+
:callback_file_path => "/Users/jonmagic/Projects/arca/test/fixtures/ticket.rb",
|
28
|
+
:callback_line_number => 5,
|
29
|
+
:target_symbol => :set_title,
|
30
|
+
:conditional_symbol => nil,
|
31
|
+
:conditional_target_symbol => nil
|
32
|
+
}
|
33
|
+
})
|
34
|
+
end
|
35
|
+
|
36
|
+
def upcase_title
|
37
|
+
@upcase_title ||= Arca::CallbackAnalysis.new({
|
38
|
+
:model => model,
|
39
|
+
:callback_data => {
|
40
|
+
:callback_symbol => :before_save,
|
41
|
+
:callback_file_path => "/Users/jonmagic/Projects/arca/test/fixtures/ticket.rb",
|
42
|
+
:callback_line_number => 6,
|
43
|
+
:target_symbol => :upcase_title,
|
44
|
+
:conditional_symbol => :if,
|
45
|
+
:conditional_target_symbol => :title_is_a_shout?
|
46
|
+
}
|
47
|
+
})
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_callback_symbol
|
51
|
+
assert_equal :after_save, announce_save.callback_symbol
|
52
|
+
assert_equal :before_save, set_title.callback_symbol
|
53
|
+
assert_equal :before_save, upcase_title.callback_symbol
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_callback_file_path
|
57
|
+
assert_match "test/fixtures/announcements.rb", announce_save.callback_file_path
|
58
|
+
assert_match "test/fixtures/ticket.rb", set_title.callback_file_path
|
59
|
+
assert_match "test/fixtures/ticket.rb", upcase_title.callback_file_path
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_callback_line_number
|
63
|
+
assert_equal 4, announce_save.callback_line_number
|
64
|
+
assert_equal 5, set_title.callback_line_number
|
65
|
+
assert_equal 6, upcase_title.callback_line_number
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_external_callback?
|
69
|
+
assert_predicate announce_save, :external_callback?
|
70
|
+
refute_predicate set_title, :external_callback?
|
71
|
+
refute_predicate upcase_title, :external_callback?
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_target_symbol
|
75
|
+
assert_equal :announce_save, announce_save.target_symbol
|
76
|
+
assert_equal :set_title, set_title.target_symbol
|
77
|
+
assert_equal :upcase_title, upcase_title.target_symbol
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_target_file_path
|
81
|
+
assert_match "test/fixtures/announcements.rb", announce_save.target_file_path
|
82
|
+
assert_match "test/fixtures/ticket.rb", set_title.target_file_path
|
83
|
+
assert_match "test/fixtures/ticket.rb", upcase_title.target_file_path
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_target_line_number
|
87
|
+
assert_equal 8, announce_save.target_line_number
|
88
|
+
assert_equal 8, set_title.target_line_number
|
89
|
+
assert_equal 16, upcase_title.target_line_number
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_external_target?
|
93
|
+
refute_predicate announce_save, :external_target?
|
94
|
+
refute_predicate set_title, :external_target?
|
95
|
+
refute_predicate upcase_title, :external_target?
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_lines_to_target
|
99
|
+
assert_equal 4, announce_save.lines_to_target
|
100
|
+
assert_equal 3, set_title.lines_to_target
|
101
|
+
assert_equal 10, upcase_title.lines_to_target
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_conditional_symbol
|
105
|
+
assert_nil announce_save.conditional_symbol
|
106
|
+
assert_nil set_title.conditional_symbol
|
107
|
+
assert_equal :if, upcase_title.conditional_symbol
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_conditional_target_symbol
|
111
|
+
assert_nil announce_save.conditional_target_symbol
|
112
|
+
assert_nil set_title.conditional_target_symbol
|
113
|
+
assert_equal :title_is_a_shout?, upcase_title.conditional_target_symbol
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_conditional_target_file_path
|
117
|
+
assert_nil announce_save.conditional_target_file_path
|
118
|
+
assert_nil set_title.conditional_target_file_path
|
119
|
+
assert_match "test/fixtures/ticket.rb", upcase_title.conditional_target_file_path
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_conditional_target_line_number
|
123
|
+
assert_nil announce_save.conditional_target_line_number
|
124
|
+
assert_nil set_title.conditional_target_line_number
|
125
|
+
assert_equal 20, upcase_title.conditional_target_line_number
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_lines_to_conditional_target
|
129
|
+
assert_nil announce_save.lines_to_conditional_target
|
130
|
+
assert_nil set_title.lines_to_conditional_target
|
131
|
+
assert_equal 14, upcase_title.lines_to_conditional_target
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require_relative "../../test_helper"
|
2
|
+
|
3
|
+
class Arca::CollectorTest < Minitest::Test
|
4
|
+
def test_collects_callback_data
|
5
|
+
callbacks = Ticket.arca_callback_data
|
6
|
+
|
7
|
+
callback = callbacks[:after_save][0]
|
8
|
+
assert_equal :after_save, callback[:callback_symbol]
|
9
|
+
assert_match "test/fixtures/announcements.rb", callback[:callback_file_path]
|
10
|
+
assert_equal 4, callback[:callback_line_number]
|
11
|
+
assert_equal :announce_save, callback[:target_symbol]
|
12
|
+
assert_nil callback[:conditional_symbol]
|
13
|
+
assert_nil callback[:conditional_target_symbol]
|
14
|
+
|
15
|
+
callback = callbacks[:before_save][0]
|
16
|
+
assert_equal :before_save, callback[:callback_symbol]
|
17
|
+
assert_match "test/fixtures/ticket.rb", callback[:callback_file_path]
|
18
|
+
assert_equal 5, callback[:callback_line_number]
|
19
|
+
assert_equal :set_title, callback[:target_symbol]
|
20
|
+
assert_nil callback[:conditional_symbol]
|
21
|
+
assert_nil callback[:conditional_target_symbol]
|
22
|
+
|
23
|
+
callback = callbacks[:before_save][1]
|
24
|
+
assert_equal :before_save, callback[:callback_symbol]
|
25
|
+
assert_match "test/fixtures/ticket.rb", callback[:callback_file_path]
|
26
|
+
assert_equal 5, callback[:callback_line_number]
|
27
|
+
assert_equal :set_body, callback[:target_symbol]
|
28
|
+
assert_nil callback[:conditional_symbol]
|
29
|
+
assert_nil callback[:conditional_target_symbol]
|
30
|
+
|
31
|
+
callback = callbacks[:before_save][2]
|
32
|
+
assert_equal :before_save, callback[:callback_symbol]
|
33
|
+
assert_match "test/fixtures/ticket.rb", callback[:callback_file_path]
|
34
|
+
assert_equal 6, callback[:callback_line_number]
|
35
|
+
assert_equal :upcase_title, callback[:target_symbol]
|
36
|
+
assert_equal :if, callback[:conditional_symbol]
|
37
|
+
assert_equal :title_is_a_shout?, callback[:conditional_target_symbol]
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_arca_model_root_path_is_required
|
41
|
+
model_root_path = Arca.model_root_path
|
42
|
+
Arca.instance_variable_set(:@model_root_path, nil)
|
43
|
+
|
44
|
+
assert_raises(Arca::Collector::ModelRootPathRequired) do
|
45
|
+
require_relative "../../fixtures/bar"
|
46
|
+
end
|
47
|
+
|
48
|
+
Arca.model_root_path = model_root_path
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_callback_is_reapplied_with_original_args
|
52
|
+
foo = Foo.new
|
53
|
+
refute foo.boop?
|
54
|
+
foo.save
|
55
|
+
assert foo.mission_complete
|
56
|
+
assert_nil foo.bargo
|
57
|
+
assert_nil foo.bazinga
|
58
|
+
|
59
|
+
foo.boop = true
|
60
|
+
assert foo.boop?
|
61
|
+
foo.save
|
62
|
+
assert foo.mission_complete
|
63
|
+
assert_equal "hello", foo.bargo
|
64
|
+
assert_equal "world", foo.bazinga
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative "../../test_helper"
|
2
|
+
|
3
|
+
class Arca::ModelTest < Minitest::Test
|
4
|
+
def model
|
5
|
+
@model ||= Arca::Model.new(Ticket)
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_report
|
9
|
+
assert model.report.instance_of?(Arca::Report)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_source_location
|
13
|
+
source_location = model.source_location(:set_title)
|
14
|
+
assert_match "test/fixtures/ticket.rb", source_location[:file_path]
|
15
|
+
assert_equal 8, source_location[:line_number]
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_source_location_with_method_symbol_with_no_associated_method
|
19
|
+
source_location = model.source_location(:foo_bar)
|
20
|
+
assert_nil source_location[:file_path]
|
21
|
+
assert_nil source_location[:line_number]
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_source_location_with_an_invalid_object
|
25
|
+
source_location = model.source_location(lambda {})
|
26
|
+
assert_nil source_location[:file_path]
|
27
|
+
assert_nil source_location[:line_number]
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_analyzed_callbacks
|
31
|
+
assert_equal 3, model.analyzed_callbacks[:before_save].size
|
32
|
+
assert_equal 1, model.analyzed_callbacks[:after_save].size
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_analyzed_callbacks_array
|
36
|
+
assert_equal 4, model.analyzed_callbacks_array.size
|
37
|
+
assert model.analyzed_callbacks_array[0].is_a?(Arca::CallbackAnalysis)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_analyzed_callbacks_count
|
41
|
+
assert_equal 4, model.analyzed_callbacks_count
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_lines_between_count
|
45
|
+
assert_equal 6, model.lines_between_count
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_external_callbacks_count
|
49
|
+
assert_equal 1, model.external_callbacks_count
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_external_targets_count
|
53
|
+
assert_equal 0, model.external_targets_count
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_external_conditionals_count
|
57
|
+
assert_equal 0, model.external_conditionals_count
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative "../../test_helper"
|
2
|
+
|
3
|
+
class Arca::ReportTest < Minitest::Test
|
4
|
+
def report
|
5
|
+
@report ||= Arca::Report.new(Arca::Model.new(Ticket))
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_model_class
|
9
|
+
assert_equal "Ticket", report.model_name
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_model_file_path
|
13
|
+
assert_match "test/fixtures/ticket.rb", report.model_file_path
|
14
|
+
end
|
15
|
+
|
16
|
+
def callbacks_count
|
17
|
+
assert_equal 4, report.callbacks_count
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_conditionals_count
|
21
|
+
assert_equal 1, report.conditionals_count
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_calculated_permutations
|
25
|
+
assert_equal 2, report.calculated_permutations
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative "../test_helper"
|
2
|
+
|
3
|
+
class ArcaTest < Minitest::Test
|
4
|
+
def test_returns_model
|
5
|
+
assert Arca[Ticket].instance_of?(Arca::Model)
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_requires_class
|
9
|
+
assert_raises(Arca::ClassRequired) do
|
10
|
+
Arca[:ticket]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_requires_callback_data
|
15
|
+
assert_raises(Arca::CallbackDataMissing) do
|
16
|
+
Arca[ArcaTest]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
2
|
+
require "active_record"
|
3
|
+
require "minitest/autorun"
|
4
|
+
|
5
|
+
require "arca"
|
6
|
+
Arca.root_path = `pwd`.chomp
|
7
|
+
Arca.model_root_path = Arca.root_path + "/test/fixtures"
|
8
|
+
|
9
|
+
require_relative "fixtures/announcements"
|
10
|
+
require_relative "fixtures/ticket"
|
11
|
+
require_relative "fixtures/foo"
|
12
|
+
|
13
|
+
if ENV["CONSOLE"]
|
14
|
+
require "pry"
|
15
|
+
binding.pry
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: arca
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jonathan Hoyt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-07-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '10.4'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '10.4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.7'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.10'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.10'
|
55
|
+
description: Arca is a callback analyzer for ActiveRecord ideally suited for digging
|
56
|
+
yourself out of callback hell
|
57
|
+
email: jonmagic@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- Gemfile
|
63
|
+
- Gemfile.lock
|
64
|
+
- README.md
|
65
|
+
- Rakefile
|
66
|
+
- arca.gemspec
|
67
|
+
- lib/arca.rb
|
68
|
+
- lib/arca/callback_analysis.rb
|
69
|
+
- lib/arca/collector.rb
|
70
|
+
- lib/arca/model.rb
|
71
|
+
- lib/arca/report.rb
|
72
|
+
- test/fixtures/announcements.rb
|
73
|
+
- test/fixtures/bar.rb
|
74
|
+
- test/fixtures/foo.rb
|
75
|
+
- test/fixtures/ticket.rb
|
76
|
+
- test/lib/arca/callback_analysis_test.rb
|
77
|
+
- test/lib/arca/collector_test.rb
|
78
|
+
- test/lib/arca/model_test.rb
|
79
|
+
- test/lib/arca/report_test.rb
|
80
|
+
- test/lib/arca_test.rb
|
81
|
+
- test/test_helper.rb
|
82
|
+
homepage: https://github.com/jonmagic/arca
|
83
|
+
licenses:
|
84
|
+
- MIT
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 2.2.2
|
103
|
+
signing_key:
|
104
|
+
specification_version: 4
|
105
|
+
summary: ActiveRecord callback analyzer
|
106
|
+
test_files:
|
107
|
+
- test/fixtures/announcements.rb
|
108
|
+
- test/fixtures/bar.rb
|
109
|
+
- test/fixtures/foo.rb
|
110
|
+
- test/fixtures/ticket.rb
|
111
|
+
- test/lib/arca/callback_analysis_test.rb
|
112
|
+
- test/lib/arca/collector_test.rb
|
113
|
+
- test/lib/arca/model_test.rb
|
114
|
+
- test/lib/arca/report_test.rb
|
115
|
+
- test/lib/arca_test.rb
|
116
|
+
- test/test_helper.rb
|