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 +4 -4
- data/README.md +27 -7
- data/lib/attribute-stats/tasks/unused_attribute_references.rake +15 -0
- data/lib/attribute-stats/version.rb +1 -1
- data/lib/entities/attribute_info.rb +11 -2
- data/lib/entities/table_data.rb +7 -3
- data/lib/formatters/hash_formatter.rb +18 -0
- data/lib/formatters/json_formatter.rb +4 -0
- data/lib/formatters/tabular_formatter.rb +26 -1
- data/lib/migration_generator/generate_migration.rb +1 -8
- data/lib/migration_generator/migration_template_contents.rb +2 -10
- data/lib/stats_generation/set_attribute_references.rb +105 -0
- data/lib/stats_generation/stats_generator.rb +6 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3eb42d4e6bfaa776c008a282bff30bc5a7c2d8b
|
4
|
+
data.tar.gz: fdfc70d6b97178fef60d2591f5306f9c9c8de366
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
34
|
+
|
35
|
+
irb:6> stats.migration
|
29
36
|
=> # sample migration up/down to remove your unused attributes
|
30
|
-
|
31
|
-
irb:7> stats.
|
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,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
|
data/lib/entities/table_data.rb
CHANGED
@@ -37,14 +37,18 @@ module AttributeStats
|
|
37
37
|
attribute
|
38
38
|
end
|
39
39
|
|
40
|
-
def
|
41
|
-
@
|
40
|
+
def unused_attribute_info
|
41
|
+
@unused_attribute_info ||= begin
|
42
42
|
attrs = []
|
43
43
|
@attributes.each do |attribute|
|
44
|
-
attrs << attribute
|
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|
|
@@ -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
|
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:
|
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:
|
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.
|
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-
|
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
|