#+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 <http://gitlab.com/howardabrams>
  ;; 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 "<f17>"
      :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