arel_converter 0.0.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.
- 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
|