arel_converter 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +124 -0
- data/Rakefile +8 -0
- data/TODO.md +6 -0
- data/arel_converter.gemspec +28 -0
- data/bin/arel_convert +7 -0
- data/lib/arel_converter.rb +25 -0
- data/lib/arel_converter/active_record_finder.rb +19 -0
- data/lib/arel_converter/association.rb +21 -0
- data/lib/arel_converter/base.rb +76 -0
- data/lib/arel_converter/command.rb +50 -0
- data/lib/arel_converter/formatter.rb +46 -0
- data/lib/arel_converter/replacement.rb +21 -0
- data/lib/arel_converter/scope.rb +22 -0
- data/lib/arel_converter/translators/association.rb +71 -0
- data/lib/arel_converter/translators/base.rb +49 -0
- data/lib/arel_converter/translators/finder.rb +28 -0
- data/lib/arel_converter/translators/options.rb +172 -0
- data/lib/arel_converter/translators/scope.rb +28 -0
- data/lib/arel_converter/version.rb +3 -0
- data/spec/fixtures/grep_matching.rb +38 -0
- data/spec/fixtures/my/base_fixture.rb +0 -0
- data/spec/fixtures/my/files/controller.rb +0 -0
- data/spec/fixtures/my/files/model.rb +0 -0
- data/spec/fixtures/my/files/not_source.txt +0 -0
- data/spec/fixtures/my/files/source.rb +0 -0
- data/spec/lib/arel_converter/active_record_finder_spec.rb +26 -0
- data/spec/lib/arel_converter/association_spec.rb +36 -0
- data/spec/lib/arel_converter/base_spec.rb +130 -0
- data/spec/lib/arel_converter/command_spec.rb +7 -0
- data/spec/lib/arel_converter/replacement_spec.rb +22 -0
- data/spec/lib/arel_converter/scope_spec.rb +40 -0
- data/spec/lib/arel_converter/translators/association_spec.rb +110 -0
- data/spec/lib/arel_converter/translators/finder_spec.rb +88 -0
- data/spec/lib/arel_converter/translators/options_spec.rb +104 -0
- data/spec/lib/arel_converter/translators/scope_spec.rb +130 -0
- data/spec/spec_helper.rb +20 -0
- metadata +186 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module ArelConverter
|
2
|
+
class Replacement
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
attr_accessor :old_content, :new_content, :error
|
6
|
+
|
7
|
+
def initialize(old_content=nil, new_content=nil)
|
8
|
+
@old_content = old_content
|
9
|
+
@new_content = new_content
|
10
|
+
end
|
11
|
+
|
12
|
+
def valid?
|
13
|
+
@error.nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
def <=>(other)
|
17
|
+
[self.old_content, self.new_content] <=> [other.old_content, other.new_content]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ArelConverter
|
2
|
+
class Scope < Base
|
3
|
+
|
4
|
+
def grep_matches_in_file(file)
|
5
|
+
raw_named_scopes = `grep -h -r "^\s*scope\s*:" #{file}`
|
6
|
+
raw_named_scopes.split("\n")
|
7
|
+
end
|
8
|
+
|
9
|
+
def process_line(line)
|
10
|
+
new_scope = ArelConverter::Translator::Scope.translate(line)
|
11
|
+
new_scope.gsub(/scope\((.*)\)$/, 'scope \1')
|
12
|
+
end
|
13
|
+
|
14
|
+
def verify_line(line)
|
15
|
+
parser = RubyParser.new
|
16
|
+
sexp = parser.process(line)
|
17
|
+
sexp[0] == :call && sexp[2] == :scope
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module ArelConverter
|
2
|
+
module Translator
|
3
|
+
class Association < Base
|
4
|
+
|
5
|
+
def process_call(exp)
|
6
|
+
@association_type ||= exp[1]
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def process_hash(exp) # :nodoc:
|
11
|
+
@options = []
|
12
|
+
scopes = [:hash]
|
13
|
+
|
14
|
+
until exp.empty?
|
15
|
+
lhs = exp.shift
|
16
|
+
rhs = exp.shift
|
17
|
+
if option_nodes.include?(lhs)
|
18
|
+
lhs = process(lhs)
|
19
|
+
t = rhs.first
|
20
|
+
rhs = process rhs
|
21
|
+
rhs = "(#{rhs})" unless [:lit, :str, :true, :false].include? t # TODO: verify better!
|
22
|
+
|
23
|
+
@options << format_for_hash(lhs,rhs)
|
24
|
+
else
|
25
|
+
scopes += [lhs, rhs]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
@options = nil if @options.empty?
|
29
|
+
@scopes = Options.translate(Sexp.from_array(scopes)) unless scopes == [:hash]
|
30
|
+
return ''
|
31
|
+
end
|
32
|
+
|
33
|
+
def post_processing(new_scope)
|
34
|
+
new_scope.gsub!(/has_(many|one|and_belongs_to_many)\((.*)\)$/, 'has_\1 \2')
|
35
|
+
new_scope.gsub!(/belongs_to\((.*)\)$/, 'belongs_to \1')
|
36
|
+
[new_scope, format_scope(@scopes), @options].compact.join(', ')
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def format_scope(scopes)
|
42
|
+
return nil if scopes.nil? || scopes.empty?
|
43
|
+
"-> { #{scopes.strip} }" unless scopes.nil? || scopes.empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
def option_nodes
|
47
|
+
[
|
48
|
+
s(:lit, :counter_cache),
|
49
|
+
s(:lit, :polymorphic),
|
50
|
+
s(:lit, :touch),
|
51
|
+
s(:lit, :as),
|
52
|
+
s(:lit, :autosave),
|
53
|
+
s(:lit, :class_name),
|
54
|
+
s(:lit, :dependent),
|
55
|
+
s(:lit, :foreign_key),
|
56
|
+
s(:lit, :inverse_of),
|
57
|
+
s(:lit, :primary_key),
|
58
|
+
s(:lit, :source),
|
59
|
+
s(:lit, :source_type),
|
60
|
+
s(:lit, :through),
|
61
|
+
s(:lit, :validate),
|
62
|
+
s(:lit, :association_foreign_key),
|
63
|
+
s(:lit, :autosave),
|
64
|
+
s(:lit, :join_table)
|
65
|
+
]
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ArelConverter
|
2
|
+
module Translator
|
3
|
+
class Base < Ruby2Ruby
|
4
|
+
|
5
|
+
LINE_LENGTH = 1_000
|
6
|
+
|
7
|
+
def self.translate(klass_or_str, method = nil)
|
8
|
+
sexp = klass_or_str.is_a?(String) ? self.parse(klass_or_str) : klass_or_str
|
9
|
+
processor = self.new
|
10
|
+
source = processor.process(sexp)
|
11
|
+
processor.post_processing(source)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.parse(code)
|
15
|
+
RubyParser.new.process(code)
|
16
|
+
end
|
17
|
+
|
18
|
+
def logger
|
19
|
+
@logger ||= setup_logger
|
20
|
+
end
|
21
|
+
|
22
|
+
def post_processing(source)
|
23
|
+
source
|
24
|
+
end
|
25
|
+
|
26
|
+
def format_for_hash(key, value)
|
27
|
+
key =~ /\A:/ ? "#{key.sub(':','')}: #{value}" : "#{key} => #{value}"
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def setup_logger(log_level = :info)
|
33
|
+
logging = Logging::Logger[self]
|
34
|
+
layout = Logging::Layouts::Pattern.new(:pattern => "[%d, %c, %5l] %m\n")
|
35
|
+
|
36
|
+
stdout = Logging::Appenders.stdout
|
37
|
+
stdout.level = log_level
|
38
|
+
|
39
|
+
#file = Logging::Appenders::File.new("./log/converters.log")
|
40
|
+
#file.layout = layout
|
41
|
+
#file.level = :debug
|
42
|
+
|
43
|
+
logging.add_appenders(stdout)
|
44
|
+
logging
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ArelConverter
|
2
|
+
module Translator
|
3
|
+
class Finder < Base
|
4
|
+
|
5
|
+
def process_call(exp)
|
6
|
+
case exp[1]
|
7
|
+
when :all, :first
|
8
|
+
parent = process(exp.shift)
|
9
|
+
method = (exp.shift == :first ? 'first' : 'all')
|
10
|
+
unless exp.empty?
|
11
|
+
options = Options.translate(exp.shift).strip
|
12
|
+
method = nil if method == 'all'
|
13
|
+
end
|
14
|
+
[parent, options, method].compact.join('.')
|
15
|
+
when :find
|
16
|
+
parent = process(exp.shift)
|
17
|
+
exp.shift # Replacing so we can discard the :find definition
|
18
|
+
first_or_all = process(exp.shift) == ':first' ? 'first' : nil
|
19
|
+
options = Options.translate(exp.shift).strip unless exp.empty?
|
20
|
+
[parent, options, first_or_all].compact.join('.')
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
module ArelConverter
|
2
|
+
module Translator
|
3
|
+
class Options < Base
|
4
|
+
|
5
|
+
LINE_LENGTH = 1_000
|
6
|
+
|
7
|
+
def logger
|
8
|
+
@logger ||= setup_logger
|
9
|
+
end
|
10
|
+
|
11
|
+
def process_hash(exp) # :nodoc:
|
12
|
+
@depth ||= 0
|
13
|
+
@depth += 1
|
14
|
+
|
15
|
+
result = []
|
16
|
+
|
17
|
+
until exp.empty?
|
18
|
+
lhs = process(exp.shift)
|
19
|
+
rhs = process(exp.shift)
|
20
|
+
result << (@depth > 1 ? format_for_hash(lhs,rhs) : hash_to_arel(lhs,rhs))
|
21
|
+
end
|
22
|
+
|
23
|
+
@depth -= 1
|
24
|
+
|
25
|
+
case @depth
|
26
|
+
when 0
|
27
|
+
result.empty? ? "" : result.join('.')
|
28
|
+
else
|
29
|
+
result.empty? ? "{}" : "{ #{result.join(', ')} }"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def process_call(exp)
|
34
|
+
if valid_arel_method?(exp[1])
|
35
|
+
@depth ||= 0
|
36
|
+
@depth += 1 if @depth == 0
|
37
|
+
end
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
def hash_to_arel(lhs, rhs)
|
42
|
+
key = lhs.sub(':','')
|
43
|
+
case key
|
44
|
+
when 'conditions'
|
45
|
+
key = 'where'
|
46
|
+
when 'include'
|
47
|
+
key = 'includes'
|
48
|
+
when 'none', 'reverse_order'
|
49
|
+
return key
|
50
|
+
end
|
51
|
+
rhs = rhs.gsub(/\A\[(.*)\]\z/, '\1').gsub(/\A\{(.*)\}\z/, '\1')
|
52
|
+
"#{key}(#{rhs})"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Have to override super class to get the overridden LINE_LENGTH
|
56
|
+
# constant to work
|
57
|
+
def process_iter(exp) # :nodoc:
|
58
|
+
iter = process exp.shift
|
59
|
+
args = exp.shift
|
60
|
+
body = exp.empty? ? nil : process(exp.shift)
|
61
|
+
|
62
|
+
args = case args
|
63
|
+
when 0 then
|
64
|
+
iter = '->' # no args? let's use a stubby
|
65
|
+
''
|
66
|
+
else
|
67
|
+
a = process(args)[1..-2]
|
68
|
+
a = " |#{a}|" unless a.empty?
|
69
|
+
a
|
70
|
+
end
|
71
|
+
|
72
|
+
b, e = if iter == "END" then
|
73
|
+
[ "{", "}" ]
|
74
|
+
else
|
75
|
+
[ "do", "end" ]
|
76
|
+
end
|
77
|
+
|
78
|
+
iter.sub!(/\(\)$/, '')
|
79
|
+
|
80
|
+
# REFACTOR: ugh
|
81
|
+
result = []
|
82
|
+
result << "#{iter} {"
|
83
|
+
result << args
|
84
|
+
if body then
|
85
|
+
result << " #{body.strip} "
|
86
|
+
else
|
87
|
+
result << ' '
|
88
|
+
end
|
89
|
+
result << "}"
|
90
|
+
result = result.join
|
91
|
+
return result if result !~ /\n/ and result.size < LINE_LENGTH
|
92
|
+
|
93
|
+
result = []
|
94
|
+
result << "#{iter} #{b}"
|
95
|
+
result << args
|
96
|
+
result << "\n"
|
97
|
+
if body then
|
98
|
+
result << indent(body.strip)
|
99
|
+
result << "\n"
|
100
|
+
end
|
101
|
+
result << e
|
102
|
+
result.join
|
103
|
+
end
|
104
|
+
|
105
|
+
def process_if(exp) # :nodoc:
|
106
|
+
expand = Ruby2Ruby::ASSIGN_NODES.include? exp.first.first
|
107
|
+
c = process exp.shift
|
108
|
+
t = process exp.shift
|
109
|
+
f = process exp.shift
|
110
|
+
|
111
|
+
c = "(#{c.chomp})" if c =~ /\n/
|
112
|
+
|
113
|
+
if t then
|
114
|
+
unless expand then
|
115
|
+
if f then
|
116
|
+
r = "#{c} ? (#{t}) : (#{f})"
|
117
|
+
r = nil if r =~ /return/ # HACK - need contextual awareness or something
|
118
|
+
else
|
119
|
+
r = "#{t} if #{c}"
|
120
|
+
end
|
121
|
+
return r if r and (@indent+r).size < LINE_LENGTH and r !~ /\n/
|
122
|
+
end
|
123
|
+
|
124
|
+
r = "if #{c} then\n#{indent(t)}\n"
|
125
|
+
r << "else\n#{indent(f)}\n" if f
|
126
|
+
r << "end"
|
127
|
+
|
128
|
+
r
|
129
|
+
elsif f
|
130
|
+
unless expand then
|
131
|
+
r = "#{f} unless #{c}"
|
132
|
+
return r if (@indent+r).size < LINE_LENGTH and r !~ /\n/
|
133
|
+
end
|
134
|
+
"unless #{c} then\n#{indent(f)}\nend"
|
135
|
+
else
|
136
|
+
# empty if statement, just do it in case of side effects from condition
|
137
|
+
"if #{c} then\n#{indent '# do nothing'}\nend"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def valid_arel_method?(m)
|
144
|
+
%w[bind
|
145
|
+
create_with
|
146
|
+
eager_load
|
147
|
+
extending
|
148
|
+
from
|
149
|
+
group
|
150
|
+
having
|
151
|
+
includes
|
152
|
+
joins
|
153
|
+
limit
|
154
|
+
lock
|
155
|
+
none
|
156
|
+
offset
|
157
|
+
order
|
158
|
+
preload
|
159
|
+
readonly
|
160
|
+
references
|
161
|
+
reorder
|
162
|
+
reverse_order
|
163
|
+
select
|
164
|
+
distinct
|
165
|
+
uniq
|
166
|
+
where].include?(m.to_s)
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ArelConverter
|
2
|
+
module Translator
|
3
|
+
class Scope < Base
|
4
|
+
|
5
|
+
def process_call(exp)
|
6
|
+
@options = Options.translate(exp.pop) if exp[1] == :scope
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def post_processing(new_scope)
|
11
|
+
new_scope.gsub!(/scope\((.*)\)$/, 'scope \1')
|
12
|
+
new_scope += format_options(@options)
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def format_options(options)
|
18
|
+
return if options.nil? || options.empty?
|
19
|
+
", " + (includes_lambda?(options) ? options : "-> { #{options.strip} }")
|
20
|
+
end
|
21
|
+
|
22
|
+
def includes_lambda?(source)
|
23
|
+
source.include?('lambda') || source.include?('->')
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class GrepMatching # < ActiveRecord::Base
|
2
|
+
|
3
|
+
# Associations
|
4
|
+
has_many :posts
|
5
|
+
has_and_belongs_to_many :articles
|
6
|
+
has_one :author
|
7
|
+
belongs_to :blog
|
8
|
+
|
9
|
+
def mystery_method
|
10
|
+
has_many = 'Test cases'
|
11
|
+
end
|
12
|
+
|
13
|
+
# Scopes
|
14
|
+
scope :active, :conditions => ['status NOT IN (?)', ['closed','cancelled']]
|
15
|
+
|
16
|
+
# this comment on scope should not show show up
|
17
|
+
def scoping
|
18
|
+
scope = 'My Scope'
|
19
|
+
end
|
20
|
+
|
21
|
+
# Finders
|
22
|
+
def finder_calls
|
23
|
+
Model.find(:all)
|
24
|
+
|
25
|
+
Model.find(:first)
|
26
|
+
|
27
|
+
Model.find(:all, :conditions => {:active => true})
|
28
|
+
|
29
|
+
Model.all(:conditions => {:active => false})
|
30
|
+
|
31
|
+
Model.first(:conditions => {:active => false})
|
32
|
+
|
33
|
+
# should not be found
|
34
|
+
Model.all
|
35
|
+
Model.first
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
File without changes
|
File without changes
|