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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 47763feba6e20dbcb12a2980e34418cc44807028
|
4
|
+
data.tar.gz: 700c28866ae2ead78afe2cbea76814e3974cb568
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: da48e77635fe41e943295a766e8ef381daf2b6f6f0bbe307f982abe1daf73a208a2bdda8ea84a8ea4846f28ce9c13424935a3dca7ff7f2081a33fe19cf97ea0b
|
7
|
+
data.tar.gz: c68ee8ea1d2fe292ed6192b82cfbeef192eede810115a4afd1ee6a768d0b25ff8b7f2147cabf3385c5abb430dbcc2b5967f6d68c0f25753cfd2e95ca73546184
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Peer Allan
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# ArelConverter
|
2
|
+
|
3
|
+
This gem add the 'arel_convert' command to your system. When run against
|
4
|
+
a directory (or a single file) it will find and convert scopes,
|
5
|
+
associations and ActiveRecord finders into Rails 4 compatible Arel
|
6
|
+
syntax using Ruby 1.9 array syntax where appropriate. As you may expect
|
7
|
+
it works best under >=1.9 (development and testing was done under 2.0).
|
8
|
+
|
9
|
+
Here are some examples of what is converted,
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
scope :active, :conditions => {:active => true}, :order => 'created_at'
|
13
|
+
# becomes
|
14
|
+
scope :active, -> { where(active: true).order('created_at') }
|
15
|
+
|
16
|
+
Model.find(:all, :conditions => ['name = ?', params[:term]], :limit => 5)
|
17
|
+
# becomes
|
18
|
+
Model.where('name = ?', params[:term]).limit(5)
|
19
|
+
|
20
|
+
has_many :articles, :class_name => "Post", :order => 'updated_at DESC'
|
21
|
+
# becomes
|
22
|
+
has_many :articles, -> { order('updated_at DESC') }, class_name: "Post"
|
23
|
+
```
|
24
|
+
|
25
|
+
The converters use Ruby2Ruby and RubyParser to convert to and translate
|
26
|
+
s-expressions. This ensures the code is converter back into valid Ruby
|
27
|
+
code. String parsing just doesn't work as well.
|
28
|
+
|
29
|
+
There are 3 converters,
|
30
|
+
|
31
|
+
### Scope
|
32
|
+
|
33
|
+
Converts 'scope' statements into the updated Rails 4 syntax, i.e.
|
34
|
+
wrapped in a lambda, with pre-Arel options converted to Arel
|
35
|
+
|
36
|
+
### Associations
|
37
|
+
|
38
|
+
Updates associations to use the updated Rails 4 syntax, i.e. wrapped in
|
39
|
+
lambdas where necessary. Assocation types handled are,
|
40
|
+
|
41
|
+
* belongs_to
|
42
|
+
* has_one
|
43
|
+
* has_many
|
44
|
+
* has_and_belongs_to_many
|
45
|
+
|
46
|
+
### Finders
|
47
|
+
|
48
|
+
Updates old ActiveRecord (pre Rails 3) syntax into Arel syntax of
|
49
|
+
chained Relations. This handles the following, including chained calls,
|
50
|
+
|
51
|
+
* Object.find(:all
|
52
|
+
* Object.find(:first
|
53
|
+
* Object.find.*:conditions
|
54
|
+
* Object.all(
|
55
|
+
* Object.first(
|
56
|
+
|
57
|
+
|
58
|
+
## Installation
|
59
|
+
|
60
|
+
Add this line to your application's Gemfile:
|
61
|
+
|
62
|
+
gem 'arel_converter'
|
63
|
+
|
64
|
+
And then execute:
|
65
|
+
|
66
|
+
$ bundle
|
67
|
+
|
68
|
+
Or install it yourself as:
|
69
|
+
|
70
|
+
$ gem install arel_converter
|
71
|
+
|
72
|
+
## Usage
|
73
|
+
|
74
|
+
$ arel_convert [all,scope,association,finder] [PATH]
|
75
|
+
|
76
|
+
## Notes/Warnings/Recommendations
|
77
|
+
|
78
|
+
As with any software that could potentially update a wide swath of your
|
79
|
+
codebase in one (terrible) moment, it is recommended that you are
|
80
|
+
operating under some form of source control or have a reliable backup.
|
81
|
+
|
82
|
+
The converters doesn't play well with multiline code blocks. This means
|
83
|
+
that the parser will encounter a Exception (likely SyntaxError) when
|
84
|
+
processing the first line. This is because the matching code is a simple
|
85
|
+
grep of the file. Therefore, it would only grab the first line of a
|
86
|
+
multiline statement. The good news is that these exceptions will show up
|
87
|
+
in the results.
|
88
|
+
|
89
|
+
If your tests/specs are good then everything should still pass. If your
|
90
|
+
tests/specs are coupled to ActiveRecord finders then alot of them are
|
91
|
+
going to break. The converters make no attempt to update tests/specs at
|
92
|
+
this time. I've toyed with it and didn't get very far.
|
93
|
+
|
94
|
+
|
95
|
+
## Contributing
|
96
|
+
|
97
|
+
1. Fork it
|
98
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
99
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
100
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
101
|
+
5. Create new Pull Request
|
102
|
+
|
103
|
+
# License
|
104
|
+
|
105
|
+
### This code is free to use under the terms of the MIT license.
|
106
|
+
|
107
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
108
|
+
a copy of this software and associated documentation files (the
|
109
|
+
"Software"), to deal in the Software without restriction, including
|
110
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
111
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
112
|
+
permit persons to whom the Software is furnished to do so, subject to
|
113
|
+
the following conditions:
|
114
|
+
|
115
|
+
The above copyright notice and this permission notice shall be included
|
116
|
+
in all copies or substantial portions of the Software.
|
117
|
+
|
118
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
119
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
120
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
121
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
122
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
123
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
124
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/TODO.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'arel_converter/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "arel_converter"
|
8
|
+
spec.version = ArelConverter::VERSION
|
9
|
+
spec.authors = ["Peer Allan"]
|
10
|
+
spec.email = ["peer.allan@canadadrugs.com"]
|
11
|
+
spec.description = %q{Converts existing AR finder syntax to AREL}
|
12
|
+
spec.summary = %q{Converts AR finders, scopes and association arguments to AREL syntax}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency('ruby2ruby')
|
22
|
+
spec.add_dependency('ruby_parser')
|
23
|
+
spec.add_dependency('logging')
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rspec"
|
28
|
+
end
|
data/bin/arel_convert
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "arel_converter/version"
|
2
|
+
require 'ruby2ruby'
|
3
|
+
require 'ruby_parser'
|
4
|
+
require 'logging'
|
5
|
+
|
6
|
+
$:.unshift(File.dirname(__FILE__))
|
7
|
+
|
8
|
+
require File.join('arel_converter', 'base')
|
9
|
+
require File.join('arel_converter', 'command')
|
10
|
+
require File.join('arel_converter', 'formatter')
|
11
|
+
require File.join('arel_converter', 'active_record_finder')
|
12
|
+
require File.join('arel_converter', 'scope')
|
13
|
+
require File.join('arel_converter', 'association')
|
14
|
+
require File.join('arel_converter', 'replacement')
|
15
|
+
|
16
|
+
# Translators
|
17
|
+
require File.join('arel_converter', 'translators', 'base')
|
18
|
+
require File.join('arel_converter', 'translators', 'options')
|
19
|
+
require File.join('arel_converter', 'translators', 'scope')
|
20
|
+
require File.join('arel_converter', 'translators', 'finder')
|
21
|
+
require File.join('arel_converter', 'translators', 'association')
|
22
|
+
|
23
|
+
module ArelConverter
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ArelConverter
|
2
|
+
class ActiveRecordFinder < Base
|
3
|
+
|
4
|
+
def grep_matches_in_file(file)
|
5
|
+
raw_ar_finders = ''
|
6
|
+
["find(:all", "find(:first", "find.*:conditions =>", '\.all(', '\.first('].each do |v|
|
7
|
+
raw_ar_finders += `grep -hr '#{v}' #{file}`
|
8
|
+
end
|
9
|
+
|
10
|
+
raw_ar_finders.split("\n")
|
11
|
+
end
|
12
|
+
|
13
|
+
def process_line(finder)
|
14
|
+
ArelConverter::Translator::Finder.translate(finder)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ArelConverter
|
2
|
+
class Association < Base
|
3
|
+
|
4
|
+
def grep_matches_in_file(file)
|
5
|
+
raw_named_scopes = `grep -hr "^\s*has_\\|belongs_to" #{file}`
|
6
|
+
raw_named_scopes.split("\n")
|
7
|
+
end
|
8
|
+
|
9
|
+
def process_line(line)
|
10
|
+
ArelConverter::Translator::Association.translate(line)
|
11
|
+
end
|
12
|
+
|
13
|
+
def verify_line(line)
|
14
|
+
parser = RubyParser.new
|
15
|
+
sexp = parser.process(line)
|
16
|
+
sexp.shift == :call
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module ArelConverter
|
2
|
+
class Base
|
3
|
+
|
4
|
+
attr_accessor :options
|
5
|
+
|
6
|
+
def initialize(path, options = {})
|
7
|
+
@path = path
|
8
|
+
@options = {dry_run: false}.merge(options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def run!
|
12
|
+
File.directory?(@path) ? parse_directory(@path) : parse_file(@path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse_directory(path)
|
16
|
+
Dir[File.join(path, '**/*.rb')].each do |file|
|
17
|
+
begin
|
18
|
+
parse_file(file)
|
19
|
+
rescue => e
|
20
|
+
Formatter.alert(file, [])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_file(file)
|
26
|
+
|
27
|
+
lines_to_process = grep_matches_in_file(file)
|
28
|
+
|
29
|
+
return if lines_to_process.empty?
|
30
|
+
|
31
|
+
replacements = process_lines(lines_to_process)
|
32
|
+
|
33
|
+
unless (replacements.nil? || replacements.empty?)
|
34
|
+
Formatter.alert(file, replacements)
|
35
|
+
update_file(file, replacements) unless @options[:dry_run]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def process_lines(lines)
|
40
|
+
lines.map do |line|
|
41
|
+
r = Replacement.new(line)
|
42
|
+
begin
|
43
|
+
next unless verify_line(line)
|
44
|
+
r.new_content = process_line(line)
|
45
|
+
rescue SyntaxError => e
|
46
|
+
r.error = "SyntaxError when evaluating options for #{line}"
|
47
|
+
rescue Exception => e
|
48
|
+
r.error = "#{e.class} #{e.message} when evaluating options for \"#{line}\"\n#{e.backtrace.first}"
|
49
|
+
end
|
50
|
+
r
|
51
|
+
end.compact
|
52
|
+
end
|
53
|
+
|
54
|
+
def update_file(file, line_replacements)
|
55
|
+
contents = File.read(file)
|
56
|
+
line_replacements.each do |r|
|
57
|
+
contents.gsub!(r.old_content, r.new_content) if r.valid?
|
58
|
+
end
|
59
|
+
|
60
|
+
File.open(file, 'w') do |f|
|
61
|
+
f.puts contents
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def grep_matches_in_file(file)
|
66
|
+
[] # abstract method overriden by subclasses
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def verify_line(line)
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module ArelConverter
|
4
|
+
|
5
|
+
class Command
|
6
|
+
|
7
|
+
attr_accessor :options
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
args << '--help' if args.empty?
|
11
|
+
@translators = ['scope','finder','association']
|
12
|
+
@options = {}
|
13
|
+
parse_argv(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def run!
|
17
|
+
if @translators.include?('association')
|
18
|
+
puts "== Checking Associations"
|
19
|
+
ArelConverter::Association.new(options[:path], options).run!
|
20
|
+
end
|
21
|
+
|
22
|
+
if @translators.include?('scope')
|
23
|
+
puts "\n== Checking Scopes"
|
24
|
+
ArelConverter::Scope.new(options[:path], options).run!
|
25
|
+
end
|
26
|
+
|
27
|
+
if @translators.include?('finder')
|
28
|
+
puts "\n== Checking Finders"
|
29
|
+
ArelConverter::ActiveRecordFinder.new(options[:path], options).run!
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def parse_argv(*args)
|
36
|
+
OptionParser.new do |opts|
|
37
|
+
opts.banner = "Usage: arel_convert [options] [PATH]"
|
38
|
+
opts.on('-t', '--translators [scope,finder,association]', Array, 'Specify specific translators') { |list| @translators = list }
|
39
|
+
opts.on('--dry-run', 'Run a simulated update, files will not be updated' ) { |v| options[:dry_run] = true }
|
40
|
+
opts.on('-h', '--help', 'Display this screen' ) do
|
41
|
+
puts opts
|
42
|
+
exit 0
|
43
|
+
end
|
44
|
+
end.parse!(args)
|
45
|
+
options[:path] = args.empty? ? '.' : args.shift
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ArelConverter
|
2
|
+
class Formatter
|
3
|
+
|
4
|
+
# Terminal colors, borrowed from Thor
|
5
|
+
CLEAR = "\e[0m"
|
6
|
+
BOLD = "\e[1m"
|
7
|
+
RED = "\e[31m"
|
8
|
+
YELLOW = "\e[33m"
|
9
|
+
CYAN = "\e[36m"
|
10
|
+
WHITE = "\e[37m"
|
11
|
+
|
12
|
+
# Show an upgrade alert to the user
|
13
|
+
def self.alert(title, culprits, errors=nil)
|
14
|
+
if RbConfig::CONFIG['host_os'].downcase =~ /mswin|windows|mingw/
|
15
|
+
Formatter.basic_alert(title, culprits)
|
16
|
+
else
|
17
|
+
Formatter.color_alert(title, culprits)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Show an upgrade alert to the user. If we're on Windows, we can't
|
22
|
+
# use terminal colors, hence this method.
|
23
|
+
def self.basic_alert(title, culprits)
|
24
|
+
puts "** " + title
|
25
|
+
Array(culprits).each do |c|
|
26
|
+
puts c.valid? ? " FROM: #{c.old_content}\n TO: #{c.new_content}\n" :
|
27
|
+
"** ERROR - #{c.error}"
|
28
|
+
end
|
29
|
+
puts
|
30
|
+
end
|
31
|
+
|
32
|
+
# Show a colorful alert to the user
|
33
|
+
def self.color_alert(file, culprits )
|
34
|
+
puts "#{RED}#{BOLD}#{file}#{CLEAR}"
|
35
|
+
Array(culprits).each do |c|
|
36
|
+
puts c.valid? ? "#{YELLOW} FROM: #{c.old_content}\n TO: #{c.new_content}\n" :
|
37
|
+
"#{CYAN}#{BOLD} - #{c.error}#{CLEAR}"
|
38
|
+
|
39
|
+
end
|
40
|
+
ensure
|
41
|
+
puts "#{CLEAR}"
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|