attribute-stats 0.2.0 → 0.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
2
  SHA1:
3
- metadata.gz: 2cc768178279a193172676127468cc10c3f9ee32
4
- data.tar.gz: bdef7d993c67edcf4e8b7ca07fa6f9ac13820eba
3
+ metadata.gz: d3eb42d4e6bfaa776c008a282bff30bc5a7c2d8b
4
+ data.tar.gz: fdfc70d6b97178fef60d2591f5306f9c9c8de366
5
5
  SHA512:
6
- metadata.gz: 542a32dfb641a8e486c4310249b46403f22c322d39cdbdaeabfdba468ef238e72b009275dd4f7bbe8fe04e7b777abb0612654ca3d2d0da4837b3800615c57c50
7
- data.tar.gz: 7d75586f3939d08b5939b6edbf7807c7eeccb3a98c275f3d6b0ef89a5cdf0692d710d452b81c00273cbe6ae2b050cc7c98403b77cd02606ee50599c39aa9d32d
6
+ metadata.gz: 808e7afbeb002cfc3f895f054f476a82bf20a653d0a324a4aab0cef53af9088e870264c82a7ca232344bd32291a6dcb1298a7cc274d4bc0cea4a8959a5c0dc97
7
+ data.tar.gz: 57253da7c993ea883cc6024b15c03f3e7e9d6f21689858dab24076df494ad6146650f8c19da7ef06587720c7a65c6db31a36aee013dc344c7f179be7458171f8
data/README.md CHANGED
@@ -18,17 +18,26 @@ Add `gem 'attribute-stats'` to your Gemfile, bundle, and follow the Usage instru
18
18
 
19
19
  You can use `AttributeStats` from within Rails (or a Rails console):
20
20
 
21
- irb:1> stats = AttributeStats::StatsGenerator.new()
21
+ irb:1> stats = AttributeStats::StatsGenerator.new
22
+
22
23
  irb:2> stats.attribute_usage
23
24
  => {...} # a list of your attributes and what % of records have a value set
25
+
24
26
  irb:3> stats.unused_attributes
25
27
  => {...} # a list of your attributes which have no value set in the database
26
- irb:4> stats.dormant_tables
28
+
29
+ irb:4> stats.unused_attribute_references
30
+ => [...] # a list counts of references to unused attributes in application code
31
+
32
+ irb:5> stats.dormant_tables
27
33
  => [...] # a list of your tables which have not been updated in awhile
28
- irb:5> stats.migration
34
+
35
+ irb:6> stats.migration
29
36
  => # sample migration up/down to remove your unused attributes
30
- irb:6> stats.set_formatter :json
31
- irb:7> stats.migration # returns json instead of a hash
37
+
38
+ irb:7> stats.set_formatter :json
39
+
40
+ irb:8> stats.migration # returns json instead of a hash
32
41
 
33
42
  ### Rake Tasks
34
43
 
@@ -37,6 +46,7 @@ Rake tasks are available once you've installed the gem in your Gemfile and bundl
37
46
  * rake db:stats:dormant_tables
38
47
  * rake db:stats:attribute_usage
39
48
  * rake db:stats:unused_attributes
49
+ * rake db:stats:unused_attribute_references
40
50
  * rake attribute-stats:generate_migration
41
51
 
42
52
  Each allows you to change the output to JSON if you'd like to pipe it into another application.
@@ -55,6 +65,18 @@ i.e. `rake db:stats:unused_attributes[true,json,false]`
55
65
 
56
66
  ---
57
67
 
68
+ #### rake db:stats:unused_attribute_references
69
+ For all unused attributes, outputs the number of times the attribute name is referenced in repo's app, views, config, and spec folders (except app/assets). This finds references which are symbols, wrapped in quotes, or accessed via dot notation. It is imprecise but gives you an idea of attributes may be used in the application code.
70
+
71
+ **Argument Options:**
72
+ 1. consider_defaults_unused: true or false (default: false). This option considers attributes set to the databse default value to be unused.
73
+ 2. format: tabular, json (default: tabular)
74
+ 3. verbose: true, false (default: true)
75
+
76
+ i.e. `rake db:stats:unused_attribute_references[true,json,false]`
77
+
78
+ ---
79
+
58
80
  #### rake db:stats:attribute_usage
59
81
  Lists usage statistics for all attributes (count and percent which are unused). Note: this does a full scan of your tables, so it will be slow for tables with a lot of data.
60
82
 
@@ -96,8 +118,6 @@ The gem does not support:
96
118
 
97
119
  The gem is tested and works with Rails version 4.2 through 5.2, with `mysql2`, `sqlite3`, and `postgresql` database adapters. You can view all tested dependency sets in [Appraisals](Appraisals)
98
120
 
99
- *Due to changes in ActiveRecord between 4.1 and 4.2, the gem breaks in versions below 4.2. In a future version of `attribute-stats`, I intend to add support to previous versions of Rails (at least back to 4.0).*
100
-
101
121
  ## Testing the gem
102
122
 
103
123
  To test the gem against the current version of Rails (in [Gemfile.lock](Gemfile.lock)) and sqlite:
@@ -0,0 +1,15 @@
1
+ require 'attribute-stats'
2
+ namespace :db do
3
+ namespace :stats do
4
+ desc "Count source code references to unused attributes [options CONSIDER_DEFAULTS_UNUSED: false, FORMAT: json, tabular, VERBOSE: false]"
5
+ task :unused_attribute_references, [:consider_defaults_unused,:format,:verbose] => :environment do |task, args|
6
+ args.with_defaults(consider_defaults_unused: 'false', format: 'tabular', verbose: 'true')
7
+ options = {
8
+ consider_defaults_unused: args[:consider_defaults_unused].downcase != 'false',
9
+ formatter: args[:format],
10
+ verbose: args[:verbose].downcase != 'false',
11
+ source: :cli }
12
+ AttributeStats::StatsGenerator.new(options).unused_attribute_references
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module AttributeStats
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -1,9 +1,10 @@
1
1
  module AttributeStats
2
2
  class AttributeInfo
3
- attr_reader :count, :usage_percent, :name, :empty
3
+ attr_reader :count, :usage_percent, :name, :empty, :references
4
4
 
5
5
  def initialize(attribute_name)
6
6
  @name = attribute_name
7
+ @references = Hash.new(0)
7
8
  end
8
9
 
9
10
  def set_usage(record_count, table_total_record_count)
@@ -19,5 +20,13 @@ module AttributeStats
19
20
  def empty?
20
21
  @empty
21
22
  end
23
+
24
+ def set_reference(reference_type, count)
25
+ references[reference_type] += count
26
+ end
27
+
28
+ def total_references
29
+ references.values.sum
30
+ end
22
31
  end
23
- end
32
+ end
@@ -37,14 +37,18 @@ module AttributeStats
37
37
  attribute
38
38
  end
39
39
 
40
- def unused_attributes
41
- @unused_attributes ||= begin
40
+ def unused_attribute_info
41
+ @unused_attribute_info ||= begin
42
42
  attrs = []
43
43
  @attributes.each do |attribute|
44
- attrs << attribute.name if attribute.empty?
44
+ attrs << attribute if attribute.empty?
45
45
  end
46
46
  attrs
47
47
  end
48
48
  end
49
+
50
+ def unused_attributes
51
+ @unused_attributes ||= unused_attribute_info.map(&:name)
52
+ end
49
53
  end
50
54
  end
@@ -5,6 +5,24 @@ module AttributeStats
5
5
  @options, @table_info, @migration = options, table_info, migration
6
6
  end
7
7
 
8
+ def output_attribute_references
9
+ output = []
10
+ @table_info.each do |table_info|
11
+ table_info.attributes.each do |attribute|
12
+ next unless attribute.empty?
13
+ output << {
14
+ model: table_info.name,
15
+ attribute: attribute.name,
16
+ all_references: attribute.total_references,
17
+ code_references: attribute.references['app'],
18
+ spec_references: attribute.references['spec'],
19
+ view_references: attribute.references['views']
20
+ }
21
+ end
22
+ end
23
+ output.sort!{|a,b| a[:all_references] <=> b[:all_references]}
24
+ end
25
+
8
26
  def output_all_attributes
9
27
  output = []
10
28
  @table_info.each do |table_info|
@@ -1,6 +1,10 @@
1
1
  module AttributeStats
2
2
  class JSONFormatter < HashFormatter
3
3
 
4
+ def output_attribute_references
5
+ super.to_json
6
+ end
7
+
4
8
  def output_all_attributes
5
9
  super.to_json
6
10
  end
@@ -28,6 +28,30 @@ module AttributeStats
28
28
  @buffer
29
29
  end
30
30
 
31
+ def output_attribute_references
32
+ output = []
33
+ @table_info.each do |table_info|
34
+ table_info.attributes.each do |attribute|
35
+ next unless attribute.empty?
36
+ output << {
37
+ model: table_info.name,
38
+ attribute_name: attribute.name,
39
+ all_references: attribute.total_references,
40
+ code: attribute.references['app'],
41
+ specs: attribute.references['spec'],
42
+ views: attribute.references['views']
43
+ }
44
+ end
45
+ end
46
+ output.sort!{|a,b| a[:all_references] <=> b[:all_references]}
47
+ if output.empty?
48
+ puts "No unused attributes found"
49
+ else
50
+ print_table output, title: ['Attribute Code References', 'Mentions of the attribute name in app/ (except assets), lib/, config/, and spec/', '(symbol or dot notation, or wrapped in quotes)']
51
+ end
52
+ @buffer
53
+ end
54
+
31
55
  def output_dormant_tables
32
56
  output = []
33
57
  @table_info.each do |table_info|
@@ -106,7 +130,8 @@ module AttributeStats
106
130
 
107
131
  def header_order(column_names)
108
132
  [:model, :table_name, :attribute_name,
109
- :last_updated, :set_count, :set_percent] & column_names
133
+ :last_updated, :set_count, :set_percent,
134
+ :code, :views, :specs] & column_names
110
135
  end
111
136
 
112
137
  def formatted_headers(column_names)
@@ -1,8 +1,7 @@
1
1
  module AttributeStats
2
2
  class GenerateMigration
3
- def initialize(table_info: nil, options: {})
3
+ def initialize(table_info: [], options: {})
4
4
  @table_info, @options = table_info, options
5
- @table_info || set_table_info
6
5
  end
7
6
 
8
7
  def output_migration
@@ -13,12 +12,6 @@ module AttributeStats
13
12
  migration_file_path
14
13
  end
15
14
 
16
- def set_table_info
17
- stats_generator = AttributeStats::StatsGenerator.new(@options)
18
- stats_generator.send(:fetch_empty_attributes)
19
- @table_info = stats_generator.table_info
20
- end
21
-
22
15
  private
23
16
 
24
17
  def migration_file_path
@@ -1,9 +1,7 @@
1
1
  module AttributeStats
2
2
  class MigrationTemplateContents
3
- def initialize(table_info: nil, migration_class_suffix: nil)
4
- @table_info = table_info
5
- @table_info || set_table_info
6
- @migration_class_suffix = migration_class_suffix
3
+ def initialize(table_info: [], migration_class_suffix: nil)
4
+ @table_info, @migration_class_suffix = table_info, migration_class_suffix
7
5
  @migration_buffer = ''
8
6
  @table_info.each do |table_info|
9
7
  add_migrations_for_table_to_buffer(table_info)
@@ -24,12 +22,6 @@ end
24
22
  output
25
23
  end
26
24
 
27
- def set_table_info
28
- stats_generator = AttributeStats::StatsGenerator.new
29
- stats_generator.send(:fetch_empty_attributes)
30
- @table_info = stats_generator.table_info
31
- end
32
-
33
25
  private
34
26
 
35
27
  # This is a modified version of the ActiveRecord::SchemaDumper#table method.
@@ -0,0 +1,105 @@
1
+ require_relative 'terminal'
2
+ module AttributeStats
3
+ class SetAttributeReferences
4
+ include Terminal
5
+ attr_reader :table_info
6
+
7
+ def initialize(table_info: [], options: {})
8
+ @table_info, @options = table_info, options
9
+ end
10
+
11
+ def execute
12
+ @executed ||= begin
13
+ lookup_and_set_attribute_counts
14
+ true
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def lookup_and_set_attribute_counts
21
+ @table_info.each_with_index do |t,index|
22
+ @table_count = index
23
+ @current_table = t
24
+ fetch_table_stats
25
+ end
26
+ erase_line if @options[:verbose]
27
+ true
28
+ end
29
+
30
+ private
31
+
32
+ def fetch_table_stats
33
+ print("Finding Code References #{in_color(@current_table.name,@table_count)} ") if @options[:verbose]
34
+ set_reference_counts
35
+ erase_line if @options[:verbose]
36
+ end
37
+
38
+ def set_reference_counts
39
+ @current_table.unused_attribute_info.each do |attribute_info|
40
+ count = set_attribute_reference_count(attribute_info)
41
+ next unless @options[:verbose]
42
+ print count.zero? ? green('✓') : red('x')
43
+ end
44
+ end
45
+
46
+ def set_attribute_reference_count(attribute_info)
47
+ files = `grep -rcE "#{regexes(attribute_info).join('|')}" --exclude-dir={#{regex_exclude_paths.join(',')}} #{regex_target_paths.join(' ')}`
48
+ attribute_total = 0
49
+ files.scan(/(.*):(\d+)(?:\n|\z)/).each do |matches|
50
+ next if matches[0].nil?
51
+ filename, count = matches[0], matches[1].to_i
52
+ next if count == 0
53
+ attribute_total += count
54
+ attribute_info.set_reference section(filename), count
55
+ end
56
+ attribute_total
57
+ end
58
+
59
+ def regexes(attribute_info)
60
+ [
61
+ "\\\"#{attribute_info.name}\\\"", # "my_attribute_name"
62
+ "'#{attribute_info.name}'", # 'my_attribute_name'
63
+ "\\:#{attribute_info.name}[^A-Za-z_]?", # :my_attribute_name
64
+ "\\.#{attribute_info.name}[^A-Za-z_]?", # .my_attribute_name
65
+ # "[^A-Za-z_]#{attribute_info.name}[^A-Za-z_]", # my_attribute_name (standalone word)
66
+ ]
67
+ end
68
+
69
+ def regex_target_paths
70
+ directories = @options[:include_directories] || %w(app lib config spec)
71
+ if regex_base_path
72
+ directories.map do |directory|
73
+ File.join regex_base_path, directory
74
+ end
75
+ else
76
+ directories
77
+ end
78
+ end
79
+
80
+ def regex_exclude_paths
81
+ directories = @options[:exclude_directories] || %w(app/assets db public)
82
+ if regex_base_path
83
+ directories.map do |directory|
84
+ File.join regex_base_path, directory
85
+ end
86
+ else
87
+ directories
88
+ end
89
+ end
90
+
91
+ def section(filename)
92
+ if filename =~ /\A#{regex_base_path}\/spec\//
93
+ 'spec'
94
+ elsif filename =~ /\A#{regex_base_path}\/app\/views\//
95
+ 'views'
96
+ else
97
+ 'app'
98
+ end
99
+ end
100
+
101
+ def regex_base_path
102
+ @options[:rails_root]
103
+ end
104
+ end
105
+ end
@@ -34,6 +34,12 @@ module AttributeStats
34
34
  GenerateMigration.new(table_info: table_info, options: options).output_migration
35
35
  end
36
36
 
37
+ def unused_attribute_references
38
+ fetch_empty_attributes
39
+ SetAttributeReferences.new(table_info: table_info, options: options).execute
40
+ output formatter.output_attribute_references
41
+ end
42
+
37
43
  def set_formatter(formatter_type)
38
44
  formatter_was = options[:formatter]
39
45
  case formatter_type.to_s.downcase
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attribute-stats
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Hodges
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-10-01 00:00:00.000000000 Z
11
+ date: 2018-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -136,6 +136,7 @@ files:
136
136
  - lib/attribute-stats/tasks/dormant.rake
137
137
  - lib/attribute-stats/tasks/list_models.rake
138
138
  - lib/attribute-stats/tasks/migration.rake
139
+ - lib/attribute-stats/tasks/unused_attribute_references.rake
139
140
  - lib/attribute-stats/tasks/unused_attributes.rake
140
141
  - lib/attribute-stats/version.rb
141
142
  - lib/entities/attribute_info.rb
@@ -145,6 +146,7 @@ files:
145
146
  - lib/formatters/tabular_formatter.rb
146
147
  - lib/migration_generator/generate_migration.rb
147
148
  - lib/migration_generator/migration_template_contents.rb
149
+ - lib/stats_generation/set_attribute_references.rb
148
150
  - lib/stats_generation/set_attribute_stats.rb
149
151
  - lib/stats_generation/set_dormant_tables.rb
150
152
  - lib/stats_generation/stats_generator.rb