Class: RailsBestPractices::Analyzer

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_best_practices/analyzer.rb

Overview

RailsBestPractices Analyzer helps you to analyze your rails code, according to best practices on rails-bestpractices. if it finds any violatioins to best practices, it will give you some readable suggestions.

The analysis process is partitioned into two parts,

  1. prepare process, it checks only model and mailer files, do some preparations, such as remember model names and associations.

  2. review process, it checks all files, according to configuration, it really check if codes violate the best practices, if so, remember the violations.

After analyzing, output the violations.

Constant Summary collapse

DEFAULT_CONFIG =
File.join(File.dirname(__FILE__), "..", "..", "rails_best_practices.yml")

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path, options = {}) ⇒ Analyzer

initialize

Parameters:

  • path (String)

    where to generate the configuration yaml file

  • options (Hash) (defaults to: {})


26
27
28
29
# File 'lib/rails_best_practices/analyzer.rb', line 26

def initialize(path, options={})
  @path = path || "."
  @options = options
end

Instance Attribute Details

#runnerObject

Returns the value of attribute runner.



18
19
20
# File 'lib/rails_best_practices/analyzer.rb', line 18

def runner
  @runner
end

Instance Method Details

#analyzeObject

Analyze rails codes.

there are two steps to check rails codes,

  1. prepare process, check all model and mailer files.

  2. review process, check all files.

if there are violations to rails best practices, output them.

Parameters:

  • path (String)

    the directory of rails project

  • options (Hash)


47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/rails_best_practices/analyzer.rb', line 47

def analyze
  @options["exclude"] ||= []
  @options["only"] ||= []
  @options["output-file"] ||= "rails_best_practices_output.html"

  Core::Runner.base_path = @path
  @runner = Core::Runner.new
  @runner.debug = true if @options["debug"]
  @runner.color = !@options["without-color"]

  @bar = ProgressBar.new('Source Codes', parse_files.size * 3) if display_bar?
  ["lexical", "prepare", "review"].each { |process| send(:process, process) }
  @bar.finish if display_bar?
end

#display_bar?Boolean

Returns:

  • (Boolean)


62
63
64
# File 'lib/rails_best_practices/analyzer.rb', line 62

def display_bar?
  !@options["debug"] && !@options["silent"]
end

#error_typesObject

unique error types.



252
253
254
# File 'lib/rails_best_practices/analyzer.rb', line 252

def error_types
  @runner.errors.map(&:type).uniq
end

#expand_dirs_to_files(*dirs) ⇒ Array

expand all files with extenstion rb, erb, haml, slim, builder and rxml under the dirs

Parameters:

  • dirs (Array)

    what directories to expand

Returns:

  • (Array)

    all files expanded



124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/rails_best_practices/analyzer.rb', line 124

def expand_dirs_to_files *dirs
  extensions = ['rb', 'erb', 'rake', 'rhtml', 'haml', 'slim', 'builder', 'rxml']

  dirs.flatten.map { |entry|
    next unless File.exist? entry
    if File.directory? entry
      Dir[File.join(entry, '**', "*.{#{extensions.join(',')}}")]
    else
      entry
    end
  }.flatten
end

#file_accept(files, patterns) ⇒ Object

accept specific files.

Parameters:

  • files (Array)
  • patterns, (Regexp)

    files match any pattern will be accepted



169
170
171
# File 'lib/rails_best_practices/analyzer.rb', line 169

def file_accept files, patterns
  files.reject { |file| !patterns.any? { |pattern| file =~ pattern } }
end

#file_ignore(files, pattern) ⇒ Array

ignore specific files.

Parameters:

  • files (Array)
  • pattern (Regexp)

    files match the pattern will be ignored

Returns:

  • (Array)

    files that not match the pattern



161
162
163
# File 'lib/rails_best_practices/analyzer.rb', line 161

def file_ignore files, pattern
  files.reject { |file| file.index(pattern) }
end

#file_sort(files) ⇒ Array

sort files, models first, mailers, helpers, and then sort other files by characters.

models and mailers first as for prepare process.

Parameters:

  • files (Array)

Returns:

  • (Array)

    sorted files



144
145
146
147
148
149
150
151
152
153
154
# File 'lib/rails_best_practices/analyzer.rb', line 144

def file_sort files
  models = files.find_all { |file| file =~ Core::Check::MODEL_FILES }
  mailers = files.find_all { |file| file =~ Core::Check::MAILER_FILES }
  helpers = files.find_all { |file| file =~ Core::Check::HELPER_FILES }
  others = files.find_all { |file| file !~ Core::Check::MAILER_FILES && file !~ Core::Check::MODEL_FILES && file !~ Core::Check::HELPER_FILES }
  models.sort
  mailers.sort
  helpers.sort
  others.sort
  return models + mailers + helpers + others
end

#generateObject

generate configuration yaml file.



32
33
34
# File 'lib/rails_best_practices/analyzer.rb', line 32

def generate
  FileUtils.cp DEFAULT_CONFIG, File.join(@path, 'config/rails_best_practices.yml')
end

#load_git_infoObject

load git commit and git username info.



200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/rails_best_practices/analyzer.rb', line 200

def load_git_info
  git_progressbar = ProgressBar.new('Git Info', @runner.errors.size) if display_bar?
  start = @runner.class.base_path =~ /\/$/ ? @runner.class.base_path.size : @runner.class.base_path.size + 1
  @runner.errors.each do |error|
    git_info = `cd #{@runner.class.base_path}; git blame -L #{error.line_number.split(',').first},+1 #{error.filename[start..-1]}`
    unless git_info == ""
      git_commit, git_username = git_info.split(/\d{4}-\d{2}-\d{2}/).first.split("(")
      error.git_commit = git_commit.split(" ").first.strip
      error.git_username = git_username.strip
    end
    git_progressbar.inc if display_bar?
  end
  git_progressbar.finish if display_bar?
end

#load_hg_infoObject

load hg commit and hg username info.



185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/rails_best_practices/analyzer.rb', line 185

def load_hg_info
  hg_progressbar = ProgressBar.new('Hg Info', @runner.errors.size) if display_bar?
  @runner.errors.each do |error|
    hg_info = `cd #{@runner.class.base_path}; hg blame -lvcu #{error.filename[@runner.class.base_path.size..-1].gsub(/^\//, "")} | sed -n /:#{error.line_number.split(',').first}:/p`
    unless hg_info == ""
      hg_commit_username = hg_info.split(':')[0].strip
      error.hg_username = hg_commit_username.split(/\ /)[0..-2].join(' ')
      error.hg_commit = hg_commit_username.split(/\ /)[-1]
    end
    hg_progressbar.inc if display_bar?
  end
  hg_progressbar.finish if display_bar?
end

#outputObject

Output the analyze result.



67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/rails_best_practices/analyzer.rb', line 67

def output
  if @options["format"] == 'html'
    if @options["with-hg"]
      load_hg_info
    elsif @options["with-git"]
      load_git_info
    end
    output_html_errors
  else
    output_terminal_errors
  end
end

#output_html_errorsObject

output errors with html format.



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/rails_best_practices/analyzer.rb', line 216

def output_html_errors
  require 'erubis'
  template = @options["template"] ? File.read(File.expand_path(@options["template"])) : File.read(File.join(File.dirname(__FILE__), "..", "..", "assets", "result.html.erb"))

  if @options["with-github"]
    last_commit_id = @options["last-commit-id"] ? @options["last-commit-id"] : `cd #{@runner.class.base_path}; git rev-parse HEAD`.chomp
  end
  File.open(@options["output-file"], "w+") do |file|
    eruby = Erubis::Eruby.new(template)
    file.puts eruby.evaluate(
      :errors => @runner.errors,
      :error_types => error_types,
      :textmate => @options["with-textmate"],
      :mvim => @options["with-mvim"],
      :github => @options["with-github"],
      :github_name => @options["github-name"],
      :last_commit_id => last_commit_id,
      :git => @options["with-git"],
      :hg => @options["with-hg"]
    )
  end
end

#output_terminal_errorsObject

output errors on terminal.



174
175
176
177
178
179
180
181
182
# File 'lib/rails_best_practices/analyzer.rb', line 174

def output_terminal_errors
  @runner.errors.each { |error| plain_output(error.to_s, 'red') }
  plain_output("\nPlease go to http://rails-bestpractices.com to see more useful Rails Best Practices.", 'green')
  if @runner.errors.empty?
    plain_output("\nNo warning found. Cool!", 'green')
  else
    plain_output("\nFound #{@runner.errors.size} warnings.", 'red')
  end
end

#parse_filesArray

get all files for parsing.

Returns:

  • (Array)

    all files for parsing



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/rails_best_practices/analyzer.rb', line 97

def parse_files
  @parse_files ||= begin
    files = expand_dirs_to_files(@path)
    files = file_sort(files)

    if @options["only"].present?
      files = file_accept(files, @options["only"])
    end

    # By default, tmp, vender, spec, test, features are ignored.
    ["vendor", "spec", "test", "features", "tmp"].each do |pattern|
      files = file_ignore(files, "#{pattern}/") unless @options[pattern]
    end

    # Exclude files based on exclude regexes if the option is set.
    @options["exclude"].each do |pattern|
      files = file_ignore(files, pattern)
    end

    files.compact
  end
end

#plain_output(message, color) ⇒ Object

plain output with color.

Parameters:

  • message (String)

    to output

  • color (String)


243
244
245
246
247
248
249
# File 'lib/rails_best_practices/analyzer.rb', line 243

def plain_output(message, color)
  if @options["without-color"]
    puts message
  else
    puts message.send(color)
  end
end

#process(process) ⇒ Object

process lexical, prepare or reivew.

get all files for the process, analyze each file, and increment progress bar unless debug.

Parameters:

  • process (String)

    the process name, lexical, prepare or review.



86
87
88
89
90
91
92
# File 'lib/rails_best_practices/analyzer.rb', line 86

def process(process)
  parse_files.each do |file|
    @runner.send("#{process}_file", file)
    @bar.inc if display_bar?
  end
  @runner.send("after_#{process}")
end