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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 47763feba6e20dbcb12a2980e34418cc44807028
4
+ data.tar.gz: 700c28866ae2ead78afe2cbea76814e3974cb568
5
+ SHA512:
6
+ metadata.gz: da48e77635fe41e943295a766e8ef381daf2b6f6f0bbe307f982abe1daf73a208a2bdda8ea84a8ea4846f28ce9c13424935a3dca7ff7f2081a33fe19cf97ea0b
7
+ data.tar.gz: c68ee8ea1d2fe292ed6192b82cfbeef192eede810115a4afd1ee6a768d0b25ff8b7f2147cabf3385c5abb430dbcc2b5967f6d68c0f25753cfd2e95ca73546184
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in arel_converter.gemspec
4
+ gemspec
@@ -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.
@@ -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.
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new('spec')
6
+
7
+ # If you want to make this the default task
8
+ task :default => :spec
data/TODO.md ADDED
@@ -0,0 +1,6 @@
1
+ # ArelConverter TODO
2
+
3
+ * add specs for the CLI command
4
+ * deal with multiline declarations
5
+ * add converter find_or_create_by_*
6
+ * convert in test files too?
@@ -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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+
5
+ require 'arel_converter'
6
+
7
+ ArelConverter::Command.new(*ARGV).run!
@@ -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
+