rails_best_practices 0.5.6 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +161 -0
- data/lib/rails_best_practices.rb +158 -20
- data/lib/rails_best_practices/checks/add_model_virtual_attribute_check.rb +108 -34
- data/lib/rails_best_practices/checks/always_add_db_index_check.rb +148 -29
- data/lib/rails_best_practices/checks/check.rb +178 -75
- data/lib/rails_best_practices/checks/dry_bundler_in_capistrano_check.rb +26 -5
- data/lib/rails_best_practices/checks/isolate_seed_data_check.rb +66 -15
- data/lib/rails_best_practices/checks/keep_finders_on_their_own_model_check.rb +53 -12
- data/lib/rails_best_practices/checks/law_of_demeter_check.rb +59 -30
- data/lib/rails_best_practices/checks/move_code_into_controller_check.rb +35 -15
- data/lib/rails_best_practices/checks/move_code_into_helper_check.rb +56 -12
- data/lib/rails_best_practices/checks/move_code_into_model_check.rb +30 -32
- data/lib/rails_best_practices/checks/move_finder_to_named_scope_check.rb +45 -15
- data/lib/rails_best_practices/checks/move_model_logic_into_model_check.rb +31 -27
- data/lib/rails_best_practices/checks/needless_deep_nesting_check.rb +99 -38
- data/lib/rails_best_practices/checks/not_use_default_route_check.rb +43 -12
- data/lib/rails_best_practices/checks/overuse_route_customizations_check.rb +140 -28
- data/lib/rails_best_practices/checks/replace_complex_creation_with_factory_method_check.rb +44 -30
- data/lib/rails_best_practices/checks/replace_instance_variable_with_local_variable_check.rb +18 -7
- data/lib/rails_best_practices/checks/use_before_filter_check.rb +88 -18
- data/lib/rails_best_practices/checks/use_model_association_check.rb +61 -22
- data/lib/rails_best_practices/checks/use_observer_check.rb +125 -23
- data/lib/rails_best_practices/checks/use_query_attribute_check.rb +75 -47
- data/lib/rails_best_practices/checks/use_say_with_time_in_migrations_check.rb +59 -10
- data/lib/rails_best_practices/checks/use_scope_access_check.rb +78 -23
- data/lib/rails_best_practices/command.rb +19 -34
- data/lib/rails_best_practices/core.rb +4 -2
- data/lib/rails_best_practices/core/checking_visitor.rb +49 -19
- data/lib/rails_best_practices/core/error.rb +5 -2
- data/lib/rails_best_practices/core/runner.rb +79 -55
- data/lib/rails_best_practices/core/visitable_sexp.rb +325 -55
- data/lib/rails_best_practices/{core/core_ext.rb → core_ext/enumerable.rb} +3 -6
- data/lib/rails_best_practices/core_ext/nil_class.rb +8 -0
- data/lib/rails_best_practices/version.rb +1 -1
- data/rails_best_practices.yml +2 -2
- metadata +8 -7
- data/README.textile +0 -150
data/README.md
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
rails_best_practices
|
2
|
+
====================
|
3
|
+
|
4
|
+
rails_best_practices is a code metric tool to check the quality of rails codes.
|
5
|
+
|
6
|
+
Donate
|
7
|
+
------
|
8
|
+
|
9
|
+
<a href='http://www.pledgie.com/campaigns/12057'><img alt='Click here to lend your support to: rails-bestpractices.com and make a donation at www.pledgie.com !' src='http://www.pledgie.com/campaigns/12057.png?skin_name=chrome' border='0' /></a>
|
10
|
+
|
11
|
+
Usage
|
12
|
+
-----
|
13
|
+
|
14
|
+
At the root directory of rails app
|
15
|
+
|
16
|
+
rails_best_practices .
|
17
|
+
|
18
|
+
By default rails_best_practices will do parse codes in vendor, spec, test and features directories. If you need, see the command options:
|
19
|
+
|
20
|
+
$ rails_best_practices -h
|
21
|
+
Usage: rails_best_practices [options]
|
22
|
+
-d, --debug Debug mode
|
23
|
+
--vendor include vendor files
|
24
|
+
--spec include spec files
|
25
|
+
--test include test files
|
26
|
+
--features include features files
|
27
|
+
-x, --exclude PATTERNS Don't analyze files matching a pattern
|
28
|
+
(comma-separated regexp list)
|
29
|
+
-g, --generate Generate configuration yaml
|
30
|
+
-v, --version Show this version
|
31
|
+
-h, --help Show this message
|
32
|
+
|
33
|
+
Resources
|
34
|
+
---------
|
35
|
+
|
36
|
+
Homepage: <http://rails-bestpractices.com>
|
37
|
+
|
38
|
+
Github: <http://github.com/flyerhzm/rails_best_practices>
|
39
|
+
|
40
|
+
RDoc: <http://rdoc.rails-bestpractices.com>
|
41
|
+
|
42
|
+
Team Blog <http://rails-bestpractices.com/blog/posts>
|
43
|
+
|
44
|
+
Google Group: <https://groups.google.com/group/rails_best_practices>
|
45
|
+
|
46
|
+
Wiki: <http://github.com/flyerhzm/rails_best_practices/wiki>
|
47
|
+
|
48
|
+
Issue Tracker: <http://github.com/flyerhzm/rails_best_practices/issues>
|
49
|
+
|
50
|
+
Install
|
51
|
+
-------
|
52
|
+
|
53
|
+
gem install rails_best_practices
|
54
|
+
|
55
|
+
Issue
|
56
|
+
-----
|
57
|
+
|
58
|
+
If you got NoMethodError or any syntax error, you should use debug mode to detect which file rails_best_practices is parsing and getting the error.
|
59
|
+
|
60
|
+
rails_best_practices -d .
|
61
|
+
|
62
|
+
Then give me the error stack and the source code of the file that rails_best_practices is parsing error.
|
63
|
+
|
64
|
+
Customize Configuration
|
65
|
+
-----------------------
|
66
|
+
|
67
|
+
First run
|
68
|
+
|
69
|
+
rails_best_practices -g
|
70
|
+
|
71
|
+
to generate <code>rails_best_practices.yml</code> file.
|
72
|
+
|
73
|
+
Now you can customize this configuration file, the default configuration is as follows:
|
74
|
+
|
75
|
+
MoveFinderToNamedScopeCheck: { }
|
76
|
+
UseModelAssociationCheck: { }
|
77
|
+
UseScopeAccessCheck: { }
|
78
|
+
AddModelVirtualAttributeCheck: { }
|
79
|
+
ReplaceComplexCreationWithFactoryMethodCheck: { attribute_assignment_count: 2 }
|
80
|
+
MoveModelLogicIntoModelCheck: { use_count: 4 }
|
81
|
+
OveruseRouteCustomizationsCheck: { customize_count: 3 }
|
82
|
+
NeedlessDeepNestingCheck: { nested_count: 2 }
|
83
|
+
NotUseDefaultRouteCheck: { }
|
84
|
+
KeepFindersOnTheirOwnModelCheck: { }
|
85
|
+
LawOfDemeterCheck: { }
|
86
|
+
UseObserverCheck: { }
|
87
|
+
IsolateSeedDataCheck: { }
|
88
|
+
AlwaysAddDbIndexCheck: { }
|
89
|
+
UseBeforeFilterCheck: { }
|
90
|
+
MoveCodeIntoControllerCheck: { }
|
91
|
+
MoveCodeIntoModelCheck: { use_count: 2 }
|
92
|
+
MoveCodeIntoHelperCheck: { array_count: 3 }
|
93
|
+
ReplaceInstanceVariableWithLocalVariableCheck: { }
|
94
|
+
DryBundlerInCapistranoCheck: { }
|
95
|
+
UseSayWithTimeInMigrationsCheck: { }
|
96
|
+
UseQueryAttributeCheck: { }
|
97
|
+
|
98
|
+
You can remove or comment one check to disable it, and you can change the options.
|
99
|
+
|
100
|
+
Implementation
|
101
|
+
--------------
|
102
|
+
|
103
|
+
Move code from Controller to Model
|
104
|
+
|
105
|
+
1. Move finder to named_scope (rails2 only)
|
106
|
+
2. Use model association
|
107
|
+
3. Use scope access
|
108
|
+
4. Add model virtual attribute
|
109
|
+
5. Replace Complex Creation with Factory Method
|
110
|
+
6. Move Model Logic into the Model
|
111
|
+
|
112
|
+
RESTful Conventions
|
113
|
+
|
114
|
+
1. Overuse route customizations
|
115
|
+
2. Needless deep nesting
|
116
|
+
3. Not use default route
|
117
|
+
|
118
|
+
Model
|
119
|
+
|
120
|
+
1. Keep Finders on Their Own Model (rails2 only)
|
121
|
+
2. the Law of Demeter
|
122
|
+
3. Use Observer
|
123
|
+
4. Use Query Attribute
|
124
|
+
|
125
|
+
Migration
|
126
|
+
|
127
|
+
1. Isolating Seed Data
|
128
|
+
2. Always add DB index
|
129
|
+
3. Use Say with Time in Migrations
|
130
|
+
|
131
|
+
Controller
|
132
|
+
|
133
|
+
1. Use before_filter
|
134
|
+
|
135
|
+
View
|
136
|
+
|
137
|
+
1. Move code into controller
|
138
|
+
2. Move code into model
|
139
|
+
3. Move code into helper
|
140
|
+
4. Replace instance variable with local variable
|
141
|
+
|
142
|
+
Deployment
|
143
|
+
|
144
|
+
1. Dry bundler in capistrano
|
145
|
+
|
146
|
+
Contribute
|
147
|
+
----------
|
148
|
+
|
149
|
+
If you want to add your rails best practices into the gem, please post your best practices on <http://rails-bestpractices.com>
|
150
|
+
|
151
|
+
Contact Us
|
152
|
+
----------
|
153
|
+
|
154
|
+
We provide rails consulting services, you can contact us by twitter or email.
|
155
|
+
|
156
|
+
Follow us on twitter: <http://twitter.com/railsbp>
|
157
|
+
|
158
|
+
Send us email: <team@rails-bestpractices.com>
|
159
|
+
|
160
|
+
|
161
|
+
Copyright © 2010 Richard Huang (flyerhzm@gmail.com), released under the MIT license
|
data/lib/rails_best_practices.rb
CHANGED
@@ -1,47 +1,169 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
|
3
|
+
#--
|
4
|
+
# Copyright (c) 2010 Richard Huang (flyerhzm@gmail.com)
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
#++
|
25
|
+
require 'rubygems'
|
26
|
+
require 'progressbar'
|
27
|
+
require 'colored'
|
2
28
|
require 'rails_best_practices/checks'
|
3
29
|
require 'rails_best_practices/core'
|
4
30
|
|
31
|
+
# RailsBestPractices helps you to analyze your rails code, according to best practices on http://rails-bestpractices.
|
32
|
+
# if it finds any violatioins to best practices, it will give you some readable suggestions.
|
33
|
+
#
|
34
|
+
# The analysis process is partitioned into two parts,
|
35
|
+
#
|
36
|
+
# 1. prepare process, it checks only model and mailer files, do some preparations, such as remember model names and associations.
|
37
|
+
# 2. review process, it checks all files, according to configuration, it really check if codes violate the best practices, if so, remember the violations.
|
38
|
+
#
|
39
|
+
# After analyzing, output the violations.
|
5
40
|
module RailsBestPractices
|
6
41
|
|
42
|
+
DEFAULT_CONFIG = File.join(File.dirname(__FILE__), "..", "rails_best_practices.yml")
|
43
|
+
|
7
44
|
class <<self
|
8
|
-
|
9
|
-
|
45
|
+
attr_writer :runner
|
46
|
+
|
47
|
+
# generate configuration yaml file.
|
48
|
+
#
|
49
|
+
# @param [String] path where to generate the configuration yaml file
|
50
|
+
def generate(path)
|
51
|
+
@path = path || '.'
|
52
|
+
FileUtils.cp DEFAULT_CONFIG, File.join(@path, 'config/rails_best_practices.yml')
|
10
53
|
end
|
11
54
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
55
|
+
# start checking rails codes.
|
56
|
+
#
|
57
|
+
# there are two steps to check rails codes,
|
58
|
+
#
|
59
|
+
# 1. prepare process, check all model and mailer files.
|
60
|
+
# 2. review process, check all files.
|
61
|
+
#
|
62
|
+
# if there are violations to rails best practices, output them.
|
63
|
+
#
|
64
|
+
# @param [String] path the directory of rails project
|
65
|
+
# @param [Hash] options
|
66
|
+
def start(path, options)
|
67
|
+
@path = path || '.'
|
68
|
+
@options = options
|
69
|
+
Core::Runner.base_path = @path
|
70
|
+
@runner = Core::Runner.new
|
71
|
+
@runner.debug = true if @options['debug']
|
72
|
+
|
73
|
+
if @runner.checks.find { |check| check.is_a? Checks::AlwaysAddDbIndexCheck } &&
|
74
|
+
!review_files.find { |file| file.index "db\/schema.rb" }
|
75
|
+
puts "AlwaysAddDbIndexCheck is disabled as there is no db/schema.rb file in your rails project.".blue
|
17
76
|
end
|
18
77
|
|
19
|
-
|
20
|
-
|
21
|
-
|
78
|
+
@bar = ProgressBar.new('Analyzing', prepare_files.size + review_files.size)
|
79
|
+
process("prepare")
|
80
|
+
process("review")
|
81
|
+
@bar.finish
|
82
|
+
|
83
|
+
output_errors
|
84
|
+
exit @runner.errors.size
|
85
|
+
end
|
86
|
+
|
87
|
+
# process prepare or reivew.
|
88
|
+
#
|
89
|
+
# get all files for the process, analyze each file,
|
90
|
+
# and increment progress bar unless debug.
|
91
|
+
#
|
92
|
+
# @param [String] process the process name, prepare or review.
|
93
|
+
def process(process)
|
94
|
+
files = send("#{process}_files")
|
95
|
+
files.each do |file|
|
96
|
+
@runner.send("#{process}_file", file)
|
97
|
+
@bar.inc unless @options['debug']
|
22
98
|
end
|
99
|
+
end
|
23
100
|
|
24
|
-
|
101
|
+
# get all files for prepare process.
|
102
|
+
#
|
103
|
+
# @return [Array] all files for prepare process
|
104
|
+
def prepare_files
|
105
|
+
@prepare_files ||= begin
|
106
|
+
files = []
|
107
|
+
['models', 'mailers'].each do |name|
|
108
|
+
files += expand_dirs_to_files(File.join(@path, 'app', name))
|
109
|
+
end
|
110
|
+
files.compact
|
111
|
+
end
|
25
112
|
end
|
26
113
|
|
114
|
+
# get all files for review process.
|
115
|
+
#
|
116
|
+
# @return [Array] all files for review process
|
117
|
+
def review_files
|
118
|
+
@review_files ||= begin
|
119
|
+
files = expand_dirs_to_files(@path)
|
120
|
+
files = file_sort(files)
|
121
|
+
['vendor', 'spec', 'test', 'features'].each do |pattern|
|
122
|
+
files = file_ignore(files, "#{pattern}/") unless @options[pattern]
|
123
|
+
end
|
124
|
+
|
125
|
+
# Exclude files based on exclude regexes if the option is set.
|
126
|
+
for pattern in @options[:exclude]
|
127
|
+
files = file_ignore(files, pattern)
|
128
|
+
end
|
129
|
+
|
130
|
+
files.compact
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# expand all files with extenstion rb, erb, haml and builder under the dirs
|
135
|
+
#
|
136
|
+
# @param [Array] dirs what directories to expand
|
137
|
+
# @return [Array] all files expanded
|
27
138
|
def expand_dirs_to_files *dirs
|
28
139
|
extensions = ['rb', 'erb', 'haml', 'builder']
|
29
140
|
|
30
|
-
dirs.flatten.map { |
|
31
|
-
|
32
|
-
|
141
|
+
dirs.flatten.map { |entry|
|
142
|
+
next unless File.exist? entry
|
143
|
+
if File.directory? entry
|
144
|
+
Dir[File.join(entry, '**', "*.{#{extensions.join(',')}}")]
|
33
145
|
else
|
34
|
-
|
146
|
+
entry
|
35
147
|
end
|
36
148
|
}.flatten
|
37
149
|
end
|
38
150
|
|
39
|
-
|
40
|
-
|
151
|
+
|
152
|
+
# sort files, models first, then mailers, and sort other files by characters.
|
153
|
+
#
|
154
|
+
# models and mailers first as for prepare process.
|
155
|
+
#
|
156
|
+
# @param [Array] files
|
157
|
+
# @return [Array] sorted files
|
158
|
+
def file_sort files
|
41
159
|
files.sort { |a, b|
|
42
|
-
if a =~
|
160
|
+
if a =~ Checks::Check::MODEL_FILES
|
43
161
|
-1
|
44
|
-
elsif b =~
|
162
|
+
elsif b =~ Checks::Check::MODEL_FILES
|
163
|
+
1
|
164
|
+
elsif a =~ Checks::Check::MAILER_FILES
|
165
|
+
-1
|
166
|
+
elsif b =~ Checks::Check::MAILER_FILES
|
45
167
|
1
|
46
168
|
else
|
47
169
|
a <=> b
|
@@ -49,8 +171,24 @@ module RailsBestPractices
|
|
49
171
|
}
|
50
172
|
end
|
51
173
|
|
52
|
-
|
174
|
+
# ignore specific files.
|
175
|
+
#
|
176
|
+
# @param [Array] files
|
177
|
+
# @param [Regexp] pattern files match the pattern will be ignored
|
178
|
+
# @return [Array] files that not match the pattern
|
179
|
+
def file_ignore files, pattern
|
53
180
|
files.reject { |file| file.index(pattern) }
|
54
181
|
end
|
182
|
+
|
183
|
+
# output errors if exist.
|
184
|
+
def output_errors
|
185
|
+
@runner.errors.each { |error| puts error.to_s.red }
|
186
|
+
puts "\nPlease go to http://rails-bestpractices.com to see more useful Rails Best Practices.".green
|
187
|
+
if @runner.errors.empty?
|
188
|
+
puts "\nNo error found. Cool!".green
|
189
|
+
else
|
190
|
+
puts "\nFound #{@runner.errors.size} errors.".red
|
191
|
+
end
|
192
|
+
end
|
55
193
|
end
|
56
194
|
end
|
@@ -3,22 +3,55 @@ require 'rails_best_practices/checks/check'
|
|
3
3
|
|
4
4
|
module RailsBestPractices
|
5
5
|
module Checks
|
6
|
-
#
|
6
|
+
# Make sure to add a model virual attribute to simplify model creation.
|
7
7
|
#
|
8
|
-
#
|
9
|
-
#
|
8
|
+
# See the best practice details here http://rails-bestpractices.com/posts/4-add-model-virtual-attribute
|
9
|
+
#
|
10
|
+
# Implementation:
|
11
|
+
#
|
12
|
+
# Prepare process:
|
13
|
+
# none
|
14
|
+
#
|
15
|
+
# Review process:
|
16
|
+
# check method define nodes in all controller files,
|
17
|
+
# if there are more than one [] method calls with the same subject and arguments,
|
18
|
+
# but assigned to one model's different attribute.
|
19
|
+
# and after these method calls, there is a save method call for that model, like
|
20
|
+
#
|
21
|
+
# def create
|
22
|
+
# @user = User.new(params[:user])
|
23
|
+
# @user.first_name = params[:full_name].split(' ', 2).first
|
24
|
+
# @user.last_name = params[:full_name].split(' ', 2).last
|
25
|
+
# @user.save
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# then the model needs to add a virtual attribute.
|
10
29
|
class AddModelVirtualAttributeCheck < Check
|
11
|
-
|
12
|
-
def
|
30
|
+
|
31
|
+
def interesting_review_nodes
|
13
32
|
[:defn]
|
14
33
|
end
|
15
|
-
|
16
|
-
def
|
34
|
+
|
35
|
+
def interesting_review_files
|
17
36
|
CONTROLLER_FILES
|
18
37
|
end
|
19
38
|
|
20
|
-
|
21
|
-
|
39
|
+
# check method define nodes to see if there are some attribute assignments that can use model virtual attribute instead in review process.
|
40
|
+
#
|
41
|
+
# it will check every attribute assignment nodes and call node of message :save or :save!, if
|
42
|
+
#
|
43
|
+
# 1. there are more than one arguments who contain call node with messages :[] in attribute assignment nodes, e.g.
|
44
|
+
# @user.first_name = params[:full_name].split(' ').first
|
45
|
+
# @user.last_name = params[:full_name].split(' ').last
|
46
|
+
# 2. the messages of attribute assignment nodes housld be different (:first_name= , :last_name=)
|
47
|
+
# 3. the argument of call nodes with message :[] should be same (:full_name)
|
48
|
+
# 4. there should be a call node with message :save or :save! after attribute assignment nodes
|
49
|
+
# @user.save
|
50
|
+
# 5. and the subject of save or save! call node should be the same with the subject of attribute assignment nodes
|
51
|
+
#
|
52
|
+
# then the attribute assignment nodes can add model virtual attribute instead.
|
53
|
+
def review_start_defn(node)
|
54
|
+
@attrasgns = {}
|
22
55
|
node.recursive_children do |child|
|
23
56
|
case child.node_type
|
24
57
|
when :attrasgn
|
@@ -28,36 +61,77 @@ module RailsBestPractices
|
|
28
61
|
else
|
29
62
|
end
|
30
63
|
end
|
31
|
-
@variables = nil
|
32
64
|
end
|
33
|
-
|
65
|
+
|
34
66
|
private
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
67
|
+
# check an attribute assignment node, if there is a :[] message of call node in the attribute assignment node,
|
68
|
+
# then remember this attribute assignment.
|
69
|
+
#
|
70
|
+
# s(:attrasgn, s(:ivar, :@user), :first_name=,
|
71
|
+
# s(:arglist,
|
72
|
+
# s(:call,
|
73
|
+
# s(:call,
|
74
|
+
# s(:call, s(:call, nil, :params, s(:arglist)), :[], s(:arglist, s(:lit, :full_name))),
|
75
|
+
# :split,
|
76
|
+
# s(:arglist, s(:str, " "), s(:lit, 2))
|
77
|
+
# ),
|
78
|
+
# :first,
|
79
|
+
# s(:arglist)
|
80
|
+
# )
|
81
|
+
# )
|
82
|
+
# )
|
83
|
+
#
|
84
|
+
# The remember attribute assignments (@attrasgns) are as follows
|
85
|
+
#
|
86
|
+
# {
|
87
|
+
# s(:ivar, :@user) =>
|
88
|
+
# [{
|
89
|
+
# :message=>:first_name=,
|
90
|
+
# :arguments=>s(:call, s(:call, nil, :params, s(:arglist)), :[], s(:arglist, s(:lit, :full_name)))
|
91
|
+
# }]
|
92
|
+
# }
|
93
|
+
def attribute_assignment(node)
|
94
|
+
subject = node.subject
|
95
|
+
arguments_node = node.arguments.grep_node(:message => :[])
|
96
|
+
return if subject.nil? or arguments_node.nil?
|
97
|
+
@attrasgns[subject] ||= []
|
98
|
+
@attrasgns[subject] << {:message => node.message, :arguments => arguments_node}
|
99
|
+
end
|
100
|
+
|
101
|
+
# check a call node with message :save or :save!,
|
102
|
+
# if there exists an attribute assignment for the subject of this call node,
|
103
|
+
# and if the arguments of this attribute assignments has duplicated entries (different message and same arguments),
|
104
|
+
# then this node needs to add a virtual attribute.
|
105
|
+
#
|
106
|
+
# e.g. this is @attrasgns
|
107
|
+
# {
|
108
|
+
# s(:ivar, :@user)=>
|
109
|
+
# [{
|
110
|
+
# :message=>:first_name=,
|
111
|
+
# :arguments=>s(:call, s(:call, nil, :params, s(:arglist)), :[], s(:arglist, s(:lit, :full_name)))
|
112
|
+
# }, {
|
113
|
+
# :message=>:last_name=,
|
114
|
+
# :arguments=>s(:call, s(:call, nil, :params, s(:arglist)), :[], s(:arglist, s(:lit, :full_name)))
|
115
|
+
# }]
|
116
|
+
# }
|
117
|
+
# and this is the call node
|
118
|
+
# s(:call, s(:ivar, :@user), :save, s(:arglist))
|
119
|
+
#
|
120
|
+
# The message of call node is :save,
|
121
|
+
# and the key of @attrasgns is the same as the subject of call node,
|
122
|
+
# and the value of @aatrasgns has different message and same arguments.
|
123
|
+
def call_assignment(node)
|
124
|
+
if [:save, :save!].include? node.message
|
125
|
+
subject = node.subject
|
126
|
+
add_error "add model virtual attribute (for #{subject})" if params_dup?(@attrasgns[subject].collect {|h| h[:arguments]})
|
43
127
|
end
|
44
128
|
end
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
def call_assignment(node)
|
51
|
-
if node.message == :save
|
52
|
-
variable = node.subject
|
53
|
-
add_error "add model virtual attribute (for #{variable})" if params_dup?(@variables[variable].collect {|h| h[:arguments]})
|
129
|
+
|
130
|
+
# if the nodes are duplicated.
|
131
|
+
def params_dup?(nodes)
|
132
|
+
return false if nodes.nil?
|
133
|
+
!nodes.dups.empty?
|
54
134
|
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def params_dup?(nodes)
|
58
|
-
return false if nodes.nil?
|
59
|
-
!nodes.dups.empty?
|
60
|
-
end
|
61
135
|
end
|
62
136
|
end
|
63
137
|
end
|