arca 2.1.3 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 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: