Skip to content

Instantly share code, notes, and snippets.

@ianheggie
Last active April 25, 2024 20:27
Show Gist options
  • Save ianheggie/1038140bf2e850abc3252197bd26c26c to your computer and use it in GitHub Desktop.
Save ianheggie/1038140bf2e850abc3252197bd26c26c to your computer and use it in GitHub Desktop.
Experimental code to explore the use of synver-ruby on a rails 5.2, ruby 2.6.10 project and ChatGPT4
#!/usr/bin/env ruby
# frozen_string_literal: true
# Experimental code to explore the use of synver-ruby on a rails 5.2, ruby 2.6.10 project and ChatGPT4.
#
# I found that I needed to run synver-ruby using ruby 3.3 as the version installed under ruby 2.6.10 failed
# to run most snippets due to missing PRISM parser
# This checks the version of ruby being used in the project and the gem versions from Gemfile.local
# and sets an active flag accordingly
# My initial question was what snippets should I consider, eliminating snippets that are not applicable for this project
# with the current ruby and gem versions or that are dependencies of other snippets
# A significant amount of this code is based on code generated by ChatGPT upon being given appropriate input
# as part of further experimentation with ChatGPT. The Input Prompts I used are listed after __END__ in this file.
# This was a progressive development process, so the output was adjusted manually to make it more readable and
# conform to rubocop suggestions.
# LICENSE: In so far as it is permitted by law to copyright code mostly generated by an AI from prompts
# by an Australian citizen, I am releasing this code under the MIT license as follows
#
# Copyright (c) [2024] [Ian Heggie <[email protected]>]
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
require 'rubygems'
require 'bundler'
require 'open3'
require 'yaml'
require 'set'
class SynvertCommand
attr_reader :groups, :snippets, :ruby_version, :gem_versions
def initialize
@ruby_version = fetch_ruby_version
puts "INFO: excluding snippets that require ruby version > #{@ruby_version}."
@gem_versions = fetch_gem_versions
puts "INFO: excluding snippets that require gems / versions not in the #{@gem_versions.size} gems I found."
@groups, @snippets = fetch_snippets
puts "INFO: Found #{snippets.size} snippets in #{groups.size} groups."
end
def snippet_details(snippet)
@snippet_details ||= {}
@snippet_details[snippet] ||= fetch_snippet_details(snippet)
end
# Examines all snippets and returns a list of snippets that others depend on (Takes about a minute)
def dependent_snippets
@dependent_snippets ||=
snippets.each_with_object({}) do |snippet, deps|
snippet_details(snippet)[:dependencies].each { |dep| (deps[dep] ||= []) << snippet }
end
end
def active_head_snippets
all_dependents = dependent_snippets
snippets.select do |snippet|
is_dependent = all_dependents.key?(snippet)
snippet_details(snippet)[:active] && !is_dependent
end
end
private
def fetch_snippets
groups = []
snippets = []
# Variable to keep track of the current group
current_group = nil
# Process each line of the input
cmd = 'synvert-ruby --list'
input, status = Open3.capture2(cmd)
raise "#{cmd} returned #{status.inspect}: #{input}" unless status.success?
input.each_line do |line|
# Clean up any leading/trailing whitespace
cleaned_line = line.strip
# Check if the line is a group (no leading spaces)
if line[0] == ' '
# It's a snippet, add it to the current group
snippet = "#{current_group}/#{cleaned_line}"
snippets << snippet
else
current_group = cleaned_line
groups << current_group
end
end
[groups.sort, snippets.sort]
end
# Function to fetch and parse snippet details
def fetch_snippet_details(snippet)
cmd = "synvert-ruby --show #{snippet}"
puts "Running: #{cmd} ..."
snippet_details, status = Open3.capture2(cmd)
raise "#{cmd} returned #{status.inspect}: #{snippet_details}" unless status.success?
snippet_dependencies = []
min_ruby_version = nil
gem_dependencies = {}
snippet_details.each_line do |line|
if line.include?('add_snippet')
# Extract the snippet name included by this snippet
dep_group, dep_name = line.match(/add_snippet\s+'([^']+)',\s+'([^']+)'/).captures
snippet_dependencies << "#{dep_group}/#{dep_name}"
elsif line.include?('if_ruby')
# Extract the Ruby version requirement
version = line.match(/if_ruby\s+'([^']+)'/).captures.first
min_ruby_version = version if min_ruby_version.nil? || min_ruby_version < version
elsif line.include?('if_gem')
# Extract gem name and version requirement
gem_name, version = line.match(/if_gem\s+'([^']+)',\s+'([^']+)'/).captures
gem_dependencies[gem_name] = version
end
end
calculate_active(snippet, { dependencies: snippet_dependencies, ruby_version: min_ruby_version,
gem_dependencies: gem_dependencies, executable: true })
end
# Method to determine if the snippet is active based on Ruby and gem version requirements
def calculate_active(snippet, details)
active = true
# Check Ruby version compatibility, if specified
if details[:ruby_version]
required_ruby_version = details[:ruby_version]
active &&= Gem::Requirement.new(required_ruby_version).satisfied_by?(Gem::Version.new(ruby_version))
puts " #{snippet} FAILED IF_RUBY: #{required_ruby_version}, HAVE: #{ruby_version}!" unless active
end
# Check each gem's version compatibility, if specified
if active
details[:gem_dependencies]&.each do |gem_name, required_version|
if gem_versions[gem_name]
current_version = gem_versions[gem_name]
active &&= Gem::Requirement.new(required_version).satisfied_by?(Gem::Version.new(current_version))
puts " #{snippet} FAILED IF_GEM: #{gem_name} #{required_version}, HAVE: #{current_version}!" unless active
break
else
# Set active to false if the required gem is not installed
active = false
end
end
end
# Update the details hash with the active status
details.merge(active: active)
end
# Function to parse the .ruby-version file
def fetch_ruby_version
if File.exist?('.ruby-version')
File.read('.ruby-version').strip
elsif File.exist?('Gemfile.lock')
puts 'WARNING: No .ruby-version found - checking if ruby version is specified in Gemfile.lock!'
lockfile = Bundler::LockfileParser.new(Bundler.read_file('Gemfile.lock'))
(lockfile.ruby_version && lockfile.ruby_version.gem_version.to_s) || RUBY_VERSION
else
RUBY_VERSION
end
end
# Function to parse Gemfile.lock and return a hash of gems with their versions
def fetch_gem_versions
if File.exist?('Gemfile.lock')
parser = Bundler::LockfileParser.new(Bundler.read_file('Gemfile.lock'))
parser.specs.each_with_object({}) { |spec, hash| hash[spec.name] = spec.version.to_s }
else
puts 'WARNING: No Gemfile.lock found - using output of gem list instead!'
cmd = 'gem list'
gem_list_output, status = Open3.capture2(cmd)
puts "WARNING: #{cmd} returned #{status.inspect}: #{gem_list_output}" unless status.success?
gem_list_output.each_line.with_object({}) do |line, hash|
match = line.match(/(\S+) \(([^)]+)\)/)
next unless match
gem_name, versions = match.captures
# Process versions to remove any 'default: ' prefix and pick the maximum version
versions_array = versions.split(', ').map { |v| v.sub(/default: /, '') }
hash[gem_name] = versions_array.max_by { |version| Gem::Version.new(version) }
end
end
end
end
# Execute using chruby-exec 3.3 -- ruby FILE
# Check if at least one argument is provided
if ARGV.empty?
puts 'Usage: ruby run-synvert-ruby.rb <directory_path> [arguments for synvert-ruby]',
' - Runs diagnostic command if synvert-ruby arguments are not given'
' --help gives synvert-ruby help.'
exit 1
end
directory_path = ARGV.shift # Remove the first argument and use it as the directory path
# Change to the specified directory or issue a warning if it doesn't exist
if Dir.exist?(directory_path)
Dir.chdir(directory_path)
puts "Changed directory to #{directory_path}"
else
warn "Directory does not exist: #{directory_path}"
exit 1 # Exit if the directory is not valid
end
# Check if the synvert-ruby command is available
def synvert_ruby_installed?
command_check = RUBY_PLATFORM =~ /win32|win64|\.NET|windows|cygwin|mingw32/i ? 'where' : 'which'
system("#{command_check} synvert-ruby > /dev/null 2>&1")
end
unless synvert_ruby_installed?
puts "synvert-ruby is not installed. Attempting to install..."
system('gem install synvert-ruby')
# Check again after installation attempt
unless synvert_ruby_installed?
abort("Failed to install synvert-ruby. Please check your gem environment and permissions.")
end
end
if ARGV.empty?
cmd = SynvertCommand.new
puts 'RUBY VERSION:', cmd.ruby_version
puts 'GEM VERSIONS:', cmd.gem_versions.to_yaml
puts 'GROUPS:', cmd.groups
puts 'SNIPPETS', cmd.snippets.to_yaml
# Output the hash
puts 'SNIPPET_DETAILS:',
cmd.snippet_details(cmd.snippets.first).to_yaml
puts 'DEPENDENT SNIPPETS',
cmd.dependent_snippets.to_yaml
puts 'ACTIVE HEAD SNIPPETS',
cmd.active_head_snippets.to_yaml
else
if ARGV.include?('--run')
command = "synvert-ruby --show #{ARGV.last}"
puts "Executing command: #{command}"
system(command)
puts ''
end
# Construct the synvert-ruby command with any additional arguments
command = "synvert-ruby #{ARGV.join(' ')}"
puts "Executing command: #{command}"
system(command)
end
__END__
Prompts used with ChatGPT4 - 26 April 2024
-------------------------------------------------------------------------------------------
talking about synvert-ruby gem
-------------------------------------------------------------------------------------------
First `synvert-ruby --list` produces an indented list of snippets, with two parts, the group with no indentation and then the snippets under that group listed with space indentation. for example:
```
default
check_syntax
redis
deprecate_calling_redis_inside_multi
deprecate_calling_redis_inside_pipelined
rspec
collection_matcher
negative_error_expectation
boolean_matcher
```
Convert this to the proper names list, eg
```
default/check_syntax
redis/deprecate_calling_redis_inside_multi
redis/deprecate_calling_redis_inside_pipelined
rspec/collection_matcher
rspec/negative_error_expectation
rspec/boolean_matcher
```
` but not `rspec` by itself.
Produce a hash keyed by group with an array of full snippet names as the hash value in ruby.
-------------------------------------------------------------------------------------------
The command `synvert-ruby --show #{snippet_name}` shows the definition of the snippet.
Look for lines like `add_snippet 'ruby', 'deprecate_dir_exists'` in the output and change the structure generated
to an ordered tree based on add_snippet indicating that the listed snippet is included into this snippet.
Also check for `if_ruby '2.7.0'` and propegate this up the tree,
marking each node with the minimum ruby version specified.
-------------------------------------------------------------------------------------------
Also parse for gem dependencies of the form `if_gem 'rails', '~> 3.1.0'`
-------------------------------------------------------------------------------------------
check the .ruby-version file (if present) and match that version against any if_ruby and mark the dependency
entry with active: true (or false) accordingly. Also check if_gem entries against Gemfile.lock
for the gem versions that are locked in. set active to false if the gem is missing and set it to false
if the version in the Gemfile.lock doesn't match. Noting that if_gem can specify versions using
`>=` or `~>' qualifiers just like Gemfile and gem commands can.
-------------------------------------------------------------------------------------------
if Gemfile.lock doesn't exist then read from `gem list`
-------------------------------------------------------------------------------------------
Handle out from gem list like `fcntl (default: 1.0.0)`
-------------------------------------------------------------------------------------------
adjust finding the ruby version from Gemfile.lock if the ruby version is specified there when
.ruby-version doesn't exist
-------------------------------------------------------------------------------------------
Extract the code to calculate if a snippet is active for this ruby and gem versions into method
```
# set details[:active] to true if ruby and gems pass checks. return details hash
def calculate_active(details, ruby_version, gem_versions)
# code in here
end
```
-------------------------------------------------------------------------------------------
merge active into the hash rather than updating the hash passed
-------------------------------------------------------------------------------------------
User
Given the list of snippets in `@snippets` and the details in `@snippet_details`
list all the active snippets which are not dependencies of another snippet.
-------------------------------------------------------------------------------------------
error: cannot load such file -- gem
(ChatGpt had output code with: require 'gem' - I was curious how it would fix the error) - It responded:
It seems there was a mistake in the require statement in the previous example.
The Ruby Gem library should be loaded with require 'rubygems' for handling gem versions or simply use the
Ruby standard library without requiring anything if only using basic functionality.
However, if you're using the Gem::Requirement and Gem::Version classes, they are part of Ruby's standard library,
and no additional require statements should be necessary unless your environment is set up in an unusual way.
You can safely remove the require 'gem' line and the code should function correctly.
-------------------------------------------------------------------------------------------
uninitialized constant Set
it correctly determined I hadn't required set and suggested I do so
-------------------------------------------------------------------------------------------
What license should I quote for the code you have generated above?
It responded:
The code I generate for you doesn't require specific licensing or attribution—it's yours to use however you see fit!
This means you can integrate it into your projects, modify it, or distribute it under your own terms without needing
to quote a license from me or OpenAI.
......
For your projects, especially if they're shared publicly or used commercially, you might want to choose an
appropriate open-source license. Common choices include:
MIT License: Very permissive, allows for nearly unrestricted use while requiring only preservation of copyright
and license notices.
Apache License 2.0 and GNU General Public License (GPL) also mentioned
-------------------------------------------------------------------------------------------
If I pass an argument to this code, I wish to chdir to that directory, or issue a warning if it doesn't exist.
Additional prompt:
if I pass more than one argument, I want the rest to be passed to synvert-ruby after the chdir
Response: (this code insists on one argument being passed and runs the synvert-ruby even if only one argument is passed,
which isn't what I asked for, but I will go with it)
# Check if at least one argument is provided
if ARGV.empty?
puts "Usage: ruby script_name.rb <directory_path> [additional arguments for synvert-ruby]"
exit 1
end
directory_path = ARGV.shift # Remove the first argument and use it as the directory path
# Change to the specified directory or issue a warning if it doesn't exist
if Dir.exist?(directory_path)
Dir.chdir(directory_path)
puts "Changed directory to #{directory_path}"
else
warn "Directory does not exist: #{directory_path}"
exit 1 # Exit if the directory is not valid
end
# Construct the synvert-ruby command with any additional arguments
command = "synvert-ruby " + ARGV.join(' ')
puts "Executing command: #{command}"
system(command)
# I adjusted this to run my diagnostic command if no args where given, otherwise run the command
-------------------------------------------------------------------------------------------
check if `synvert-ruby` command is present and if not run `gem install synvert-ruby`
Code used above...
-------------------------------------------------------------------------------------------
if `ARGV.first == '--run'` also run command with `--show` and the following ARGS first
* BAD prompt, I clarified:
-------------------------------------------------------------------------------------------
the directory comes before --run
I didn't like the suggestion (it used index and removed '--run' from the ARGV list, prompted again:
-------------------------------------------------------------------------------------------
after the chdir, check if ARGV.first == '--run' and is so run the synvert-ruby command with --show and the
rest of the arguments, then fall through to the default to run it with --run
STILL not what I want:
# Check if the first argument is '--run'
if ARGV.first == '--run'
ARGV.shift # Remove '--run' from the argument list
# Execute synvert-ruby with `--show` first
show_command = "synvert-ruby --show " + ARGV.join(' ')
puts "Pre-executing show command: #{show_command}"
system(show_command)
end
# Execute the main synvert-ruby command
command = "synvert-ruby " + ARGV.join(' ')
puts "Executing main command: #{command}"
system(command)
-------------------------------------------------------------------------------------------
Still not what I wanted, so I prompted again- Ahh, the joys of learning prmpt engineering ;)
how can I join the second and subsequent elements of an array
-------------------------------------------------------------------------------------------
OUTPUT Running code above:
ACTIVE HEAD SNIPPETS
---
- bullet/rename_whitelist_to_safelist
- bundler/use_shortcut_git_source
- debug_me/remove_debug_me_calls
- default/check_syntax
- factory_bot/convert_factory_girl_to_factory_bot
- factory_bot/use_short_syntax
- factory_bot/use_string_as_class_name
- minitest/use_better_syntax
- rails/migrate-ujs-to-turbo
- rails/redirect_with_flash
- rails/upgrade_2_3_to_3_0
- rails/upgrade_3_0_to_3_1
- rails/upgrade_3_1_to_3_2
- rails/upgrade_3_2_to_4_0
- rails/upgrade_4_0_to_4_1
- rails/upgrade_4_1_to_4_2
- rails/upgrade_4_2_to_5_0
- rails/upgrade_5_0_to_5_1
- rails/upgrade_5_1_to_5_2
- rails/upgrade_5_2_to_6_0
- rails/upgrade_6_0_to_6_1
- rails/upgrade_6_1_to_7_0
- rails/upgrade_7_0_to_7_1
- redis/deprecate_calling_redis_inside_multi
- redis/deprecate_calling_redis_inside_pipelined
- rspec/use_new_syntax
- ruby/fast_syntax
- ruby/frozen_string_literal_comment
- ruby/nested_class_definition
- ruby/prefer_dig
- ruby/remove_debug_code
- ruby/upgrade_1_8_to_1_9
- ruby/upgrade_2_0_to_2_1
- ruby/upgrade_2_1_to_2_2
- ruby/upgrade_2_2_to_2_3
- ruby/upgrade_2_3_to_2_4
- ruby/upgrade_2_4_to_2_5
- ruby/upgrade_2_5_to_2_6
- ruby/upgrade_2_6_to_2_7
- ruby/upgrade_2_7_to_3_0
- ruby/upgrade_3_0_to_3_1
- ruby/upgrade_3_1_to_3_2
- ruby/upgrade_3_3_to_3_4
- shoulda/use_new_syntax
- will_paginate/use_new_syntax
-------------------------------------------------------------------------------------------------------
ianh@ben:.../gists/synvert-ruby$ ./run-synvert-ruby.rb$project_root --help
Changed directory to project_root
Executing command: synvert-ruby --help
Usage: synvert-ruby [project_path]
-l, --list list all available snippets
-q, --query QUERY query specified snippets
-s, --show SNIPPET_NAME show specified snippet description, SNIPPET_NAME is combined by group and name, e.g. ruby/new_hash_syntax
-o, --open SNIPPET_NAME Open a snippet
-g, --generate NEW_SNIPPET_NAME generate a new snippet
--sync sync snippets
--execute EXECUTE_COMMAND execute snippet
-r, --run SNIPPET_NAME run a snippet with snippet name, e.g. ruby/new_hash_syntax, or remote url, or local file path
-t, --test SNIPPET_NAME test a snippet with snippet name, e.g. ruby/new_hash_syntax, or remote url, or local file path
--show-run-process show processing files when running a snippet
--only-paths DIRECTORIES only specified files or directories, separated by comma, e.g. app/models,app/controllers
--skip-paths FILE_PATTERNS skip specified files or directories, separated by comma, e.g. vendor/,lib/**/*.rb
-f, --format FORMAT output format
--number-of-workers NUMBER_OF_WORKERS
set the number of workers, if it is greater than 1, it tests snippet in parallel
--double-quote prefer double quote, it uses single quote by default
--tab-width TAB_WIDTH prefer tab width, it uses 2 by default
--loose loose mode, it ignores ruby version and gem version check, it uses strict mode by default
--dont-respect-gitignore do not respect .gitignore, by default it ignores files and directories in .gitignore
--test-result TEST_RESULT result format of test, it can be actions or new_source, action is by default
--check-bundle-gems check code in all bundle gems
-v, --version show this version
-------------------------------------------------------------------------------------------
Results from running snippets: ./run-synvert-ruby.rb $project_root [--show-run-process] --run bullet/rename_whitelist_to_safelist (etc)
---------------------------------------------------
- bullet/rename_whitelist_to_safelist
No code changed, listed all the source files
---------------------------------------------------
- bundler/use_shortcut_git_source
-gem 'axlsx', git: 'https://github.com/randym/axlsx.git', ref: 'c8ac844'
+gem 'axlsx', github: 'randym/axlsx', ref: 'c8ac844'
---------------------------------------------------
- debug_me/remove_debug_me_calls
I don't use https://github.com/MadBomber/debug_me but it look instesting
---------------------------------------------------
- default/check_syntax
Worked for mne
---------------------------------------------------
- factory_bot/convert_factory_girl_to_factory_bot
---------------------------------------------------
- factory_bot/use_short_syntax
---------------------------------------------------
- factory_bot/use_string_as_class_name
---------------------------------------------------
- minitest/use_better_syntax
Updated a range of asserts presumably to get better output on failure, for example:
- assert_equal 0, Delayed::Job.count
+ assert_predicate Delayed::Job.count, :zero?
# Which issues errors that are more helpful like: Minitest::Assertion: Expected 4 to be zero?.
#
# It missed converting assert Delayed::Job.count.positive?
- assert File.exist?(@path)
+ assert_path_exists @path
# It does use refute, which rubocop/rubymine suggests shoudl be assert_not instead
#
# # It does convert
assert_equal false, expr
to
refute expr
Which is NOT what I intended (I wanted to test for false not nil, so I change to a two line test:
assert_not expr
assert_not_nil expr
- refute Table.where(id: table_id).empty?
+ refute_empty Table.where(id: table_id)
- assert output.to_s.include?('OK')
+ assert_includes output.to_s, 'OK'
add_snippet 'rails', 'convert_views_2_3_to_3_0'
is infinite looping on one view ... (cpu usage 10:45 so far at 100% cpu usage)
---------------------------------------------------
- rails/migrate-ujs-to-turbo
app/views/admins/index.html.slim
Error: undefined method `to_sym' for nil
---------------------------------------------------
- rails/redirect_with_flash
No changes
---------------------------------------------------
- rails/upgrade_2_3_to_3_0
add_snippet 'rails', 'convert_dynamic_finders_for_rails_3'
add_snippet 'rails', 'convert_mailers_2_3_to_3_0'
Both failed with Error: undefined method `name' for nil
add_snippet 'rails', 'convert_rails_root' changes the semantics as the replacement returns a Pathname not a string,
which may matter! for instance appending to a Pathname behaves differently than to a string
- File.join Rails.root, 'log', 'staging_image.log'
+ Rails.root.join('log', 'staging_image.log')
---------------------------------------------------
- rails/upgrade_3_0_to_3_1
---------------------------------------------------
- rails/upgrade_3_1_to_3_2
---------------------------------------------------
- rails/upgrade_3_2_to_4_0
---------------------------------------------------
- rails/upgrade_4_0_to_4_1
---------------------------------------------------
- rails/upgrade_4_1_to_4_2
---------------------------------------------------
- rails/upgrade_4_2_to_5_0
---------------------------------------------------
- rails/upgrade_5_0_to_5_1
---------------------------------------------------
- rails/upgrade_5_1_to_5_2
---------------------------------------------------
- rails/upgrade_5_2_to_6_0
---------------------------------------------------
- rails/upgrade_6_0_to_6_1
---------------------------------------------------
- rails/upgrade_6_1_to_7_0
---------------------------------------------------
- rails/upgrade_7_0_to_7_1
---------------------------------------------------
- redis/deprecate_calling_redis_inside_multi
---------------------------------------------------
- redis/deprecate_calling_redis_inside_pipelined
---------------------------------------------------
- rspec/use_new_syntax
---------------------------------------------------
- ruby/fast_syntax
---------------------------------------------------
- ruby/frozen_string_literal_comment
---------------------------------------------------
- ruby/nested_class_definition
---------------------------------------------------
- ruby/prefer_dig
---------------------------------------------------
- ruby/remove_debug_code
---------------------------------------------------
- ruby/upgrade_1_8_to_1_9
---------------------------------------------------
- ruby/upgrade_2_0_to_2_1
---------------------------------------------------
- ruby/upgrade_2_1_to_2_2
---------------------------------------------------
- ruby/upgrade_2_2_to_2_3
---------------------------------------------------
- ruby/upgrade_2_3_to_2_4
---------------------------------------------------
- ruby/upgrade_2_4_to_2_5
---------------------------------------------------
- ruby/upgrade_2_5_to_2_6
---------------------------------------------------
- ruby/upgrade_2_6_to_2_7
---------------------------------------------------
- ruby/upgrade_2_7_to_3_0
---------------------------------------------------
- ruby/upgrade_3_0_to_3_1
---------------------------------------------------
- ruby/upgrade_3_1_to_3_2
---------------------------------------------------
- ruby/upgrade_3_3_to_3_4
---------------------------------------------------
- shoulda/use_new_syntax
---------------------------------------------------
- will_paginate/use_new_syntax
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment