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