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,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
+