#+TITLE: Programming in Ruby #+AUTHOR: Howard X. Abrams #+DATE: 2022-09-01 #+FILETAGS: :emacs: A literate programming file for configuring Emacs to support the Ruby programming language. #+begin_src emacs-lisp :exports none ;;; ha-programming-ruby --- Ruby configuration. -*- lexical-binding: t; -*- ;; ;; © 2022 Howard X. Abrams ;; Licensed under a Creative Commons Attribution 4.0 International License. ;; See http://creativecommons.org/licenses/by/4.0/ ;; ;; Author: Howard X. Abrams ;; Maintainer: Howard X. Abrams ;; Created: September 1, 2022 ;; ;; While obvious, GNU Emacs does not include this file or project. ;; ;; *NB:* Do not edit this file. Instead, edit the original literate file at: ;; /Users/howard.abrams/other/hamacs/ha-programming-ruby.org ;; And tangle the file to recreate this one. ;; ;;; Code: #+end_src * Getting Started Ruby is probably already installed on the system, and if not, we certainly can download it from [[https://www.ruby-lang.org/en/downloads/][ruby-lang.org]], but since I need to juggle different versions for each project, I use [[https://direnv.net/docs/ruby.html][direnv]] and [[https://www.ruby-lang.org/en/documentation/installation/#ruby-install][ruby-install]]: #+begin_src sh brew install ruby-install #+end_src And then install one or more versions: #+begin_src sh ruby-install -U ruby-install ruby 3 #+end_src ** Per Project While we /could/ use a large project templating system, I keep it simple. For each project, create the following directory structure: #+begin_example ├── Gemfile ├── Rakefile ├── lib │   └── hello_world.rb └── test └── hello_world_test.rb #+end_example For instance: #+begin_src sh mkdir ~/other/ruby-xp # Change me cd ~/other/ruby-xp mkdir lib test #+end_src Now, do the following steps. 1. Create a =.envrc= file with the Ruby you want to use: #+begin_src sh :tangle ~/other/ruby-xp/.envrc use ruby 2.6.8 #+end_src 2. Next, get Bundler: #+begin_src sh gem install bundle #+end_src 3. Create a minimal =Gemfile=: #+begin_src ruby :tangle ~/other/ruby-xp/Gemfile source 'https://rubygems.org' gem 'rake', group: :development gem 'rubocop', group: :development gem 'solargraph', group: :development #+end_src 4. Grab all the dependencies: #+begin_src sh bundle install #+end_src 5. Create a minimal =Rakefile=: #+begin_src ruby :tangle ~/other/ruby-xp/Rakefile task default: %w[test] task :run do ruby 'lib/hello_world.rb' end task :test do ruby 'test/hello_world_test.rb' end #+end_src 6. Create the first program: #+begin_src ruby :tangle ~/other/ruby-xp/lib/hello_world.rb class HelloWorld attr_reader :name def initialize(name = nil) @name = name end def greeting if @name "Hello there, #{@name}" else 'Hello World!' end end end puts HelloWorld.new.greeting #+end_src 7. Create the first test: #+begin_src ruby :tangle ~/other/ruby-xp/test/hello_world_test.rb require 'test/unit' require_relative '../lib/hello_world' class TestHelloWorld < Test::Unit::TestCase def test_default assert_equal 'Hello World!', HelloWorld.new.greeting end def test_name assert_equal 'Hello there, Bob', HelloWorld.new('Bob').greeting end end #+end_src Or something like that. * Configuration Ruby-specific commands are attached to the =ha-ruby-leader=, bound to ~SPC m~: #+begin_src emacs-lisp (general-create-definer ha-ruby-leader :states '(normal visual motion) :keymaps 'ruby-mode-map :prefix "SPC m" :global-prefix "" :non-normal-prefix "S-SPC") #+end_src While Emacs supplies a Ruby editing environment, we’ll still use =use-package= to grab the latest: #+begin_src emacs-lisp (use-package ruby-mode :after projectile :mode (rx ".rb" eos) :mode (rx "Rakefile" eos) :mode (rx "Gemfile" eos) :mode (rx "Berksfile" eos) :mode (rx "Vagrantfile" eos) :interpreter "ruby" :init (setq ruby-indent-level 2 ruby-indent-tabs-mode nil) :hook (ruby-mode . superword-mode)) #+end_src ** Ruby REPL I am not sure I can learn a new language without a REPL connected to my editor, and for Ruby, this is [[https://github.com/nonsequitur/inf-ruby][inf-ruby]]: #+BEGIN_SRC elisp (use-package inf-ruby :config (ha-ruby-leader "R" '("REPL" . inf-ruby))) #+END_SRC ** Electric Ruby The [[https://melpa.org/#/ruby-electric][ruby-electric]] project is a minor mode that aims to add the /extra syntax/ when typing Ruby code. #+begin_src emacs-lisp (use-package ruby-electric :hook (ruby-mode . ruby-electric-mode)) #+end_src ** Testing The [[https://github.com/r0man/ruby-test-mode][ruby-test-mode]] project aims a running Ruby test from Emacs seemless: #+begin_src emacs-lisp (use-package ruby-test-mode :hook (ruby-mode . ruby-test-mode) :config (ha-ruby-leader "t" '(:ignore t :which-key "test") "t t" '("test one" . ruby-test-run-at-point) "t g" '("toggle code/test" . ruby-test-toggle-implementation-and-specification) "t A" '("test all" . ruby-test-run) "t a" '("retest" . ruby-test-rerun))) #+end_src ** Rubocop? The lint-like style checker of choice for Ruby is [[https://github.com/bbatsov/rubocop][Rubocop]]. The [[https://github.com/bbatsov/rubocop-emacs][rubocop.el]] mode should work with [[https://github.com/flycheck/flycheck][Flycheck]]. First install it with: #+begin_src sh gem install rubocop #+end_src And then we may or may not need to enable the =rubocop-mode=: #+BEGIN_SRC elisp :tangle no (use-package rubocop :hook (ruby-mode . rubocop-mode)) #+END_SRC * LSP Need to install [[https://github.com/castwide/solargraph][Solargraph]] for the LSP server experience: #+begin_src sh gem install solargraph #+end_src Or add it to your =Gemfile=: #+begin_src ruby gem 'solargraph', group: :development #+end_src * Technical Artifacts :noexport: Let's =provide= a name so we can =require= this file: #+begin_src emacs-lisp :exports none (provide 'ha-programming-ruby) ;;; ha-programming-ruby.el ends here #+end_src #+DESCRIPTION: configuring Emacs to support the Ruby programming language. #+PROPERTY: header-args:sh :tangle no #+PROPERTY: header-args:emacs-lisp :tangle yes #+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes #+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil #+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil #+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js