arel_converter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +124 -0
  7. data/Rakefile +8 -0
  8. data/TODO.md +6 -0
  9. data/arel_converter.gemspec +28 -0
  10. data/bin/arel_convert +7 -0
  11. data/lib/arel_converter.rb +25 -0
  12. data/lib/arel_converter/active_record_finder.rb +19 -0
  13. data/lib/arel_converter/association.rb +21 -0
  14. data/lib/arel_converter/base.rb +76 -0
  15. data/lib/arel_converter/command.rb +50 -0
  16. data/lib/arel_converter/formatter.rb +46 -0
  17. data/lib/arel_converter/replacement.rb +21 -0
  18. data/lib/arel_converter/scope.rb +22 -0
  19. data/lib/arel_converter/translators/association.rb +71 -0
  20. data/lib/arel_converter/translators/base.rb +49 -0
  21. data/lib/arel_converter/translators/finder.rb +28 -0
  22. data/lib/arel_converter/translators/options.rb +172 -0
  23. data/lib/arel_converter/translators/scope.rb +28 -0
  24. data/lib/arel_converter/version.rb +3 -0
  25. data/spec/fixtures/grep_matching.rb +38 -0
  26. data/spec/fixtures/my/base_fixture.rb +0 -0
  27. data/spec/fixtures/my/files/controller.rb +0 -0
  28. data/spec/fixtures/my/files/model.rb +0 -0
  29. data/spec/fixtures/my/files/not_source.txt +0 -0
  30. data/spec/fixtures/my/files/source.rb +0 -0
  31. data/spec/lib/arel_converter/active_record_finder_spec.rb +26 -0
  32. data/spec/lib/arel_converter/association_spec.rb +36 -0
  33. data/spec/lib/arel_converter/base_spec.rb +130 -0
  34. data/spec/lib/arel_converter/command_spec.rb +7 -0
  35. data/spec/lib/arel_converter/replacement_spec.rb +22 -0
  36. data/spec/lib/arel_converter/scope_spec.rb +40 -0
  37. data/spec/lib/arel_converter/translators/association_spec.rb +110 -0
  38. data/spec/lib/arel_converter/translators/finder_spec.rb +88 -0
  39. data/spec/lib/arel_converter/translators/options_spec.rb +104 -0
  40. data/spec/lib/arel_converter/translators/scope_spec.rb +130 -0
  41. data/spec/spec_helper.rb +20 -0
  42. 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,3 @@
1
+ module ArelConverter
2
+ VERSION = "0.0.1"
3
+ 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