arca 2.1.3 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1becb49c860d2d307b0a29e91a2d93e0bfe86845
4
- data.tar.gz: b230fe2a8c7d72fc0d84767de6546d67ea707426
2
+ SHA256:
3
+ metadata.gz: 326dbd7961f9ccfe0b39d77572d3fcc6599179feaf88a81f3fdfdc64ce0bb8e2
4
+ data.tar.gz: e0298827379b3759f81eaac23fb9d626b8ce20ae4150a4c44b01379157c6263a
5
5
  SHA512:
6
- metadata.gz: fc86642a2abba3e45f437b4607ab54e775a009ea35611202907a3af1d7da8a59fc0fadc110467a5c66ce080f6133e9e409071c50c34254d1fa4d2611392f3777
7
- data.tar.gz: bd855300d7bc35f2d5029ec4749cf72572f626a675402f4af92d759b81bb5821cf8b3fca86b1675fa59cb3cfd6afc8d38e52a192a68031a11096867e160094b2
6
+ metadata.gz: 421d318fdbabf614179e883b5b009d3f7f8a560dbaed1c885ddcc09d14f656f774b162f3d111c576cf0b2fc5f63bfe913e7be09cb043c1c84a8fc18961a9a1c4
7
+ data.tar.gz: 6d9f740fb81d86ef2b2ede9b803ac04e77a3327b2053f48e4cbcbf551217293939cacba68b0c5ef87e47e8c90972eb7afe96ead015364011b7d34a21c0f6dae1
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ActiveRecord Callback Analyzer
2
2
 
3
- Arca is a callback analyzer for ActiveRecord models ideally suited for digging yourself out of callback hell. At best it will help you move towards a [more maintainable design](http://adequate.io/culling-the-activerecord-lifecycle) and at worst it can be used in your test suite to give you feedback when callbacks change.
3
+ Arca is a callback analyzer for ActiveRecord models ideally suited for digging yourself out of callback hell. At best it will help you move towards a [more maintainable design](https://web.archive.org/web/20161016162603/http://adequate.io/culling-the-activerecord-lifecycle) and at worst it can be used in your test suite to give you feedback when callbacks change.
4
4
 
5
5
  Arca helps you answer questions like:
6
6
 
@@ -10,6 +10,8 @@ Arca helps you answer questions like:
10
10
 
11
11
  The Arca library has two main components, the collector and the reporter. Include the collector module in ActiveRecord::Base before your models are loaded.
12
12
 
13
+ At GitHub, we test callbacks by whitelisting existing callbacks, and adding a lint test to ensure new callbacks are not added without review. The [examples](examples) folder is a good starting point.
14
+
13
15
  ## Requirements
14
16
 
15
17
  ![travis-ci build status](https://travis-ci.org/jonmagic/arca.svg)
@@ -34,7 +36,9 @@ class ActiveRecord::Base
34
36
  include Arca::Collector
35
37
  end
36
38
 
37
- # load your app
39
+ # load your app. It's important to setup before loading your models because Arca
40
+ # works by wrapping itself around the callback method definitions (before_save,
41
+ # after_save, etc) and then records how and where those methods are used.
38
42
  ```
39
43
 
40
44
  In this example the `Annoucements` module is included in `Ticket` and defines it's own callback.
@@ -212,6 +216,12 @@ have to update this test to make it pass.
212
216
  ---------------------------------------------
213
217
  ```
214
218
 
219
+ ## Contributors
220
+
221
+ - [@jonmagic](https://github.com/jonmagic)
222
+ - [@jch](https://github.com/jch)
223
+ - [@bensheldon](https://github.com/bensheldon)
224
+
215
225
  ## License
216
226
 
217
227
  The MIT License (MIT)
data/arca.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "arca"
3
- spec.version = "2.1.3"
3
+ spec.version = "2.3.0"
4
4
  spec.date = "2015-08-07"
5
5
  spec.summary = "ActiveRecord callback analyzer"
6
6
  spec.description = "Arca is a callback analyzer for ActiveRecord ideally suited for digging yourself out of callback hell"
@@ -0,0 +1,42 @@
1
+ require "minitest"
2
+
3
+ class ActiveRecordLintTest < Minitest::Test
4
+ def test_callbacks_must_be_defined_in_the_base_model_file
5
+ whitelist_path = File.expand_path("../active_record_callback_whitelist.txt", __FILE__)
6
+ whitelisted_callbacks = if File.exists?(whitelist_path)
7
+ File.read(whitelist_path).split("\n")
8
+ else
9
+ ""
10
+ end
11
+
12
+ # git grep... list classes with a parent. Does not handle classes nested within a module
13
+ # xargs... arca analyze. We skip line numbers so we can diff the results
14
+ # grep... filter for callbacks defined in external files or callbacks without names
15
+ #
16
+ # To update the whitelist, save the output up to script/audit-callbacks.
17
+ callback_output = IO.popen(<<-CMD).read
18
+ git grep -h '^class.*< ' -- '*.rb' | cut -d' ' -f 2 | sort -u | \
19
+ xargs bundle exec audit-callbaks.rb --skip-line-num | \
20
+ grep -E 'external:true|block$'
21
+ CMD
22
+
23
+ actual_callbacks = callback_output.split("\n")
24
+
25
+ new_bad_callbacks = actual_callbacks - whitelisted_callbacks
26
+ unnecessary_whitelist_entries = whitelisted_callbacks - actual_callbacks
27
+
28
+ message = ""
29
+ if new_bad_callbacks.any?
30
+ message << "The following ActiveRecord callbacks must be defined in the base model file with a named method:\n\n"
31
+ message << new_bad_callbacks.join("\n")
32
+ end
33
+
34
+ if unnecessary_whitelist_entries.any?
35
+ message << "\n\n" unless message.empty?
36
+ message << "Hooray! You removed a bad ActiveRecord callback. Please update #{whitelist_path} and remove the following\n\n"
37
+ message << unnecessary_whitelist_entries.join("\n")
38
+ end
39
+
40
+ assert message.empty?, message
41
+ end
42
+ end
@@ -0,0 +1,75 @@
1
+ # Prints a list of Active Record callbacks for the given models
2
+ #
3
+ # Usage:
4
+ # bundle exec audit-callbacks.rb [--skip-line-num] [Model1 Model2...]
5
+ #
6
+ skip_line_num = false
7
+ if ARGV.first == "--skip-line-num"
8
+ ARGV.shift
9
+ skip_line_num = true
10
+ end
11
+
12
+ unless ARGV.size > 0
13
+ $stderr.puts "No models specified. Try script/audit-callbacks User Issue."
14
+ exit 1
15
+ end
16
+
17
+ # Arca must be required after ActiveRecord, but before the environment loads so
18
+ # we can install Arca::Collector.
19
+ require "active_record"
20
+ require "arca"
21
+ class ActiveRecord::Base
22
+ include Arca::Collector
23
+ end
24
+
25
+ require "config/environment"
26
+
27
+ # Returns a formatted string of a callback analysis.
28
+ #
29
+ # Examples:
30
+ #
31
+ # An unnamed (block) before_destroy callback defined in the same file as the model
32
+ # Ability before_destroy app/models/ability.rb external:false block
33
+ #
34
+ # A after_destroy callback named `dereference_asset` defined in a file outside of the model (external:true)
35
+ # Avatar after_destroy app/models/asset_uploadable.rb external:true dereference_asset
36
+ #
37
+ # A before_save callback defined by a callback class
38
+ # IssueComment before_save app/models/referrer.rb external:true Referrer::ReferenceMentionsCallback
39
+ #
40
+ def format_callback(callback, skip_line_num:)
41
+ path = Arca.relative_path(callback.callback_file_path)
42
+ path << ":#{callback.callback_line_number}" unless skip_line_num
43
+
44
+ # Arca outputs object ids which triggers a diff
45
+ # e.g. #<Referrer::ReferenceMentionsCallback:0x007f8bca3a1b48>
46
+ target = if match = /#<(.+):.+>/.match(callback.target_symbol.to_s)
47
+ match[1]
48
+ else
49
+ callback.target_symbol
50
+ end
51
+
52
+ [
53
+ callback.model.name,
54
+ callback.callback_symbol,
55
+ path,
56
+ "external:#{callback.external_callback?}",
57
+ target
58
+ ].map(&:to_s).join(" ")
59
+ end
60
+
61
+ ARGV.each do |model_name|
62
+ begin
63
+ model_class = model_name.constantize
64
+ rescue NameError # test classes
65
+ next
66
+ end
67
+
68
+ next unless model_class.ancestors.include?(ActiveRecord::Base)
69
+
70
+ Arca[model_class].analyzed_callbacks.each do |callback_symbol, callbacks|
71
+ callbacks.each do |callback|
72
+ puts format_callback(callback, skip_line_num: skip_line_num)
73
+ end
74
+ end
75
+ end
@@ -80,7 +80,7 @@ module Arca
80
80
  # Public: Boolean representing whether the callback target is located in the
81
81
  # same file where the callback is defined.
82
82
  def external_target?
83
- return false if target_symbol == :block
83
+ return false if target_symbol == :inline
84
84
  target_file_path != callback_file_path
85
85
  end
86
86
 
@@ -33,9 +33,21 @@ module Arca
33
33
  # Duplicate args before modifying.
34
34
  args_copy = args.dup
35
35
 
36
- # Add target_symbol :block to args_copy if a block was given.
36
+ # Add target_symbol :inline to args_copy if given a block or Proc and
37
+ # class name if a class or instance was given.
37
38
  if block
38
- args_copy.unshift(:block)
39
+ args_copy.unshift(:inline)
40
+ elsif args_copy.first.kind_of?(Proc)
41
+ args_copy.shift
42
+ args_copy.unshift(:inline)
43
+ elsif !args_copy.first.kind_of?(Symbol)
44
+ class_or_instance = args_copy.shift
45
+
46
+ if class_or_instance.class == Class
47
+ args_copy.unshift(class_or_instance.name.to_sym)
48
+ else
49
+ args_copy.unshift(class_or_instance.class.name.to_sym)
50
+ end
39
51
  end
40
52
 
41
53
  # Get the options hash from the end of the args Array if it exists.
@@ -78,7 +90,6 @@ module Arca
78
90
  :conditional_symbol => conditional_symbol,
79
91
  :conditional_target_symbol => conditional_target_symbol
80
92
  }
81
-
82
93
  end
83
94
 
84
95
  # Bind the callback method to self and call it with args.
data/lib/arca/model.rb CHANGED
@@ -15,9 +15,11 @@ module Arca
15
15
  # they are used in the life cycle of an ActiveRecord model.
16
16
  CALLBACKS = [
17
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
18
+ :before_save, :around_save, :after_save, :after_save_commit,
19
+ :before_create, :around_create, :after_create, :after_create_commit,
20
+ :before_update, :around_update, :after_update, :after_update_commit,
21
+ :before_destroy, :around_destroy, :after_destroy, :after_destroy_commit,
22
+ :after_commit, :after_rollback
21
23
  ]
22
24
 
23
25
  # Public: ActiveRecord model class.
@@ -1,8 +1,19 @@
1
+ class SomeCallbackClass
2
+ def after_destroy(record)
3
+ puts "after_destroy announcement callback"
4
+ end
5
+ end
6
+
1
7
  module Announcements
2
8
  def self.included(base)
3
9
  base.class_eval do
4
- before_save { puts "pre-save announcement callback" }
10
+ before_save { puts "before_save announcement callback" }
5
11
  after_save :announce_save
12
+
13
+ around_save lambda { puts "around_save announcement callback" }
14
+ before_destroy -> { puts "before_destroy announcement callback" }
15
+
16
+ after_destroy SomeCallbackClass.new
6
17
  end
7
18
  end
8
19
 
@@ -75,7 +75,7 @@ class Arca::CallbackAnalysisTest < Minitest::Test
75
75
  end
76
76
 
77
77
  def test_target_line_number
78
- assert_equal 9, announce_save.target_line_number
78
+ assert_equal 20, announce_save.target_line_number
79
79
  assert_equal 8, set_title.target_line_number
80
80
  assert_equal 16, upcase_title.target_line_number
81
81
  end
@@ -87,7 +87,7 @@ class Arca::CallbackAnalysisTest < Minitest::Test
87
87
  end
88
88
 
89
89
  def test_lines_to_target
90
- assert_equal 5, announce_save.lines_to_target
90
+ assert_equal 16, announce_save.lines_to_target
91
91
  assert_equal 3, set_title.lines_to_target
92
92
  assert_equal 10, upcase_title.lines_to_target
93
93
  end
@@ -6,11 +6,15 @@ class Arca::CollectorTest < Minitest::Test
6
6
  assert_equal 1, callbacks[:after_save].size
7
7
  assert_equal 4, callbacks[:before_save].size
8
8
  assert_equal 1, callbacks[:after_commit].size
9
+ assert_equal 1, callbacks[:around_save].size
10
+ assert_equal 1, callbacks[:before_destroy].size
11
+ assert_equal 1, callbacks[:after_destroy].size
12
+
9
13
 
10
14
  callback = callbacks[:after_save][0]
11
15
  assert_equal :after_save, callback[:callback_symbol]
12
16
  assert_match "test/fixtures/announcements.rb", callback[:callback_file_path]
13
- assert_equal 5, callback[:callback_line_number]
17
+ assert_equal 11, callback[:callback_line_number]
14
18
  assert_equal :announce_save, callback[:target_symbol]
15
19
  assert_nil callback[:conditional_symbol]
16
20
  assert_nil callback[:conditional_target_symbol]
@@ -18,8 +22,8 @@ class Arca::CollectorTest < Minitest::Test
18
22
  callback = callbacks[:before_save][0]
19
23
  assert_equal :before_save, callback[:callback_symbol]
20
24
  assert_match "test/fixtures/announcements.rb", callback[:callback_file_path]
21
- assert_equal 4, callback[:callback_line_number]
22
- assert_equal :block, callback[:target_symbol]
25
+ assert_equal 10, callback[:callback_line_number]
26
+ assert_equal :inline, callback[:target_symbol]
23
27
  assert_nil callback[:conditional_symbol]
24
28
  assert_nil callback[:conditional_target_symbol]
25
29
 
@@ -54,6 +58,30 @@ class Arca::CollectorTest < Minitest::Test
54
58
  assert_equal :update_timeline, callback[:target_symbol]
55
59
  assert_equal :on, callback[:conditional_symbol]
56
60
  assert_equal [:create, :destroy], callback[:conditional_target_symbol]
61
+
62
+ callback = callbacks[:around_save][0]
63
+ assert_equal :around_save, callback[:callback_symbol]
64
+ assert_match "test/fixtures/announcements.rb", callback[:callback_file_path]
65
+ assert_equal 13, callback[:callback_line_number]
66
+ assert_equal :inline, callback[:target_symbol]
67
+ assert_nil callback[:conditional_symbol]
68
+ assert_nil callback[:conditional_target_symbol]
69
+
70
+ callback = callbacks[:before_destroy][0]
71
+ assert_equal :before_destroy, callback[:callback_symbol]
72
+ assert_match "test/fixtures/announcements.rb", callback[:callback_file_path]
73
+ assert_equal 14, callback[:callback_line_number]
74
+ assert_equal :inline, callback[:target_symbol]
75
+ assert_nil callback[:conditional_symbol]
76
+ assert_nil callback[:conditional_target_symbol]
77
+
78
+ callback = callbacks[:after_destroy][0]
79
+ assert_equal :after_destroy, callback[:callback_symbol]
80
+ assert_match "test/fixtures/announcements.rb", callback[:callback_file_path]
81
+ assert_equal 16, callback[:callback_line_number]
82
+ assert_equal :SomeCallbackClass, callback[:target_symbol]
83
+ assert_nil callback[:conditional_symbol]
84
+ assert_nil callback[:conditional_target_symbol]
57
85
  end
58
86
 
59
87
  def test_callback_is_reapplied_with_original_args
@@ -33,24 +33,24 @@ class Arca::ModelTest < Minitest::Test
33
33
  end
34
34
 
35
35
  def test_analyzed_callbacks_array
36
- assert_equal 6, model.analyzed_callbacks_array.size
36
+ assert_equal 9, model.analyzed_callbacks_array.size
37
37
  assert model.analyzed_callbacks_array[0].is_a?(Arca::CallbackAnalysis)
38
38
  end
39
39
 
40
40
  def test_analyzed_callbacks_count
41
- assert_equal 6, model.analyzed_callbacks_count
41
+ assert_equal 9, model.analyzed_callbacks_count
42
42
  end
43
43
 
44
44
  def test_lines_between_count
45
- assert_equal 6, model.lines_between_count
45
+ assert_equal 16, model.lines_between_count
46
46
  end
47
47
 
48
48
  def test_external_callbacks_count
49
- assert_equal 2, model.external_callbacks_count
49
+ assert_equal 5, model.external_callbacks_count
50
50
  end
51
51
 
52
52
  def test_external_targets_count
53
- assert_equal 0, model.external_targets_count
53
+ assert_equal 1, model.external_targets_count
54
54
  end
55
55
 
56
56
  def test_external_conditionals_count
@@ -22,6 +22,6 @@ class Arca::ReportTest < Minitest::Test
22
22
  end
23
23
 
24
24
  def test_calculated_permutations
25
- assert_equal 3, report.calculated_permutations
25
+ assert_equal 6, report.calculated_permutations
26
26
  end
27
27
  end
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arca
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.3
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Hoyt
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
  date: 2015-08-07 00:00:00.000000000 Z
@@ -79,6 +79,8 @@ files:
79
79
  - README.md
80
80
  - Rakefile
81
81
  - arca.gemspec
82
+ - examples/active_record_lint_test.rb
83
+ - examples/audit-callbacks.rb
82
84
  - gemfiles/Gemfile_activerecord-3.2
83
85
  - gemfiles/Gemfile_activerecord-4.2
84
86
  - lib/arca.rb
@@ -99,7 +101,7 @@ homepage: https://github.com/jonmagic/arca
99
101
  licenses:
100
102
  - MIT
101
103
  metadata: {}
102
- post_install_message:
104
+ post_install_message:
103
105
  rdoc_options: []
104
106
  require_paths:
105
107
  - lib
@@ -114,9 +116,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
116
  - !ruby/object:Gem::Version
115
117
  version: '0'
116
118
  requirements: []
117
- rubyforge_project:
118
- rubygems_version: 2.2.2
119
- signing_key:
119
+ rubygems_version: 3.5.5
120
+ signing_key:
120
121
  specification_version: 4
121
122
  summary: ActiveRecord callback analyzer
122
123
  test_files: