rails_best_practices 0.5.6 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|