Ruby
Contents of this page:
- GitHub Repos
- Tutorials and Guides
- Sinatra
- Deployment with Heroku
- Debugging
- Gems, packaging, versioning
- Testing
- Language Features
- Questions
- Monkey patching
- Troubleshooting
- Database stuff
GitHub Repos
Sadly by necessity some of my repos are private. Those that are private are clearly marked. For those that are, please don’t ask me to share the code, because I can’t. They’re listed here purely for my reference.
- https://github.com/claresudbery/clare-wiki-ably
- The code for this site, which is built in Jekyll, which is built in Ruby.
- https://github.com/claresudbery/clare-wiki-jekyll
- Skeleton Jekyll site which I created before I started using the Ably template (Jekyll is built in Ruby).
- Academy mob code base
- My Cards Against Humanity answer generator was written in Sinatra and Ruby.
- Sample gov uk front end rails app from Csaba
- Learn Enough Ruby - My code following along with examples in the book.
- See Sinatra below for various Sinatra repos.
Ruby katas
See below for a quick start guide if writing a kata from scratch
- Bowling kata - bowling kata implemented in Ruby
- Note that the bowling kata can sometimes be a bit opaque (because if you don’t play the game, the scoring is quite complex), but I’ve updated the readme on the Made Tech site to have a simpler explanation (in case that gets lost, I’ve also added it to the readme in my repo)
- bowling kata as a Ruby gem (
csud-bowl-kata
) - bowling kata designed to be released as a Ruby gem (might not actually be on RubyGems.org yet) - tic-tac-toe kata - tic-tac-toe kata implemented in Ruby
- wordwrap kata - wordwrap kata implemented in Ruby
- Mars Rover kata
Quick start Ruby kata guide
- Create dedicated folder
- Create git repo
- Check you have ruby installed -
which ruby
bundle init
bundle add rspec
- Or just add
gem 'rspec'
to end of gemfile - And then
bundle install –path ‘vendor/bundle’
- and
rspec --init
- Or just add
- Create a spec folder (might have already been done when you ran
rspec --init
) and a spec file, egfizzbuzz_spec.rb
- Now
bundle exec rspec
- Create something like
fizzbuzz.rb
to contain the code under test - can stick it in root- For first demo you might want to put executable code into spec file
- Then refactor to put code in
fizzbuzz.rb
- Test file will have
require 'rspec'
andrequire './fizzbuzz'
at the top- And
subject = Fizzbuzz.new
to call system under test
- And
- See this commit for an example
Tutorials and Guides
- Ruby cheatsheet
- Learn Enough Ruby to be Dangerous
- TDD and OOP and Ruby from Sandi Metz
- Learn TDD in Ruby
- Getting started with Ruby on Rails
- Gem Packaging Tutorial
- Your first Sinatra app
- Writing a command-line application in Ruby
- More detailed documentation for
OptionParser
- More detailed documentation for
- Tons of Ruby-related stuff in the Jekyll Troubleshooting page on this site.
- Ruby version stuff
- My original notes are here - written frequently in a state of confusion - quite chaotic
- Newer more organised notes here
Sinatra
- Your first Sinatra app
- Skeleton Sinatra app created using this tutorial
- This commit on the tic tac toe repo is a much simpler hello world Sinatra app, with just one
/
root route and no data storage. - Very simple Sinatra testing guide - less friction than the one below.
- Sample Sinatra app from rubymonsters/testing-for-beginners
- Handy Sinatra documentation
- Dockerising a Sinatra app
- Deploying a Docker container to Heroku
- See my sinatra-docker repo for an example created following the above two tutorials. It was deployed on Heroku here but I’ve now deleted the app to preserve free dyno hours.
- See my tic-tac-toe repo for another example of a Dockerised Sinatra app. It was deployed on Heroku here but I’ve now deleted the app to preserve free dyno hours.
- Views are held in *.erb template files (same is true of Rails) -
erb
stands for embedded ruby
- Have a look at the Shotgun gem if you don’t want to have to restart your server every time you make a change.
- More examples of sinatra apps I have created:
Using Sinatra to build a web api
- This word game repo of mine builds an API
- Check out the readme for some notes
Deployment with Heroku
Debugging
Debugging on command line
Debugging in VS Code using Ruby extension and ruby-debug-ide
Step by step guide to get started
- First, on the command line:
sudo gem install debase
gem install ruby-debug-ide
- Now, in the same folder:
rdebug-ide app.rb
(If necessary, replaceapp.rb
with the appropriate path and file name to start up the code you want to debug).- Open up VS Code.
- Click the Play button with a bug icon over on the left, then click “Create a launch.son file” (if you don’t already have one):
- When it asks you to Select Environment, select Ruby.
- Select “Listen for rdebug-ide”.
- Set a breakpoint in one of your files, eg in your default ‘/’ route in app.rb for a Sinatra web app.
- Click the green run triangle next to the dropdown top left.
- If what you’re running is a webapp, visit the app in the browser (eg http://localhost:4567 for a Sinatra app where you put your breakpoint in the ‘/’ route).
- You should now hit your breakpoint.
Once you’re up and running - an example
- I currently have this working in my bowling-kata-ruby repo:
- Checkout this commit
- Run
rdebug-ide src/bowling_cli.rb
on the command line - Select “Listen for rdebug-ide” in the dropdown top left
- Set a breakpoint, for instance in
bowling_cli.rb
- Click the green run triangle next to the dropdown top left
Debugging rspec tests
- If you want to debug Rspec tests, follow the guide above, then when click the Run Debug button, select Add configuration from the dropdown and add configs for
"RSpec - active spec file only"
and"RSpec - all"
More info
- Original article here: Debugging Ruby with breakpoints in VS Code (scroll down to where it talks about VS Code)
- !! Note that when it says to add a launch.json and “open the debugging tab”, you need to follow the instructions here (I suspect the UI has changed a little since that article was written)
- Then when you are told to add a configuration, you need to select Ruby and then select
Listen for rdebug-ide
. This will have the effect of adding a new entry into the"configurations"
section of yourlaunch.json
that looks something like this:
{
"name": "Listen for rdebug-ide",
"type": "Ruby",
"request": "attach",
"remoteHost": "127.0.0.1",
"remotePort": "1234",
"remoteWorkspaceRoot": "${workspaceRoot}"
}
- Note that you can do the same for Rspec by selecting Add configuration and adding in configs for
"RSpec - active spec file only"
and"RSpec - all"
- Also note that if you get any errors, you might have to restart the debug server with
rdebug-ide --host 0.0.0.0 --port 1234 --dispatcher-port 26162 /path/to/file.rb
- I got an
ECONNREFUSED
error on Windows but I think this was either because I hadn’t restarted all instances of VS Code or because I was passing the wrong file name to therdebug-ide
command - I also had another problem that I started debugging and it seemed like it hung, but this was because I was running code that was asking for command line input, which I wasn’t providing.
- I haven’t managed to work out how to pass command line arguments to code that you’re debugging.
Debugging in VS Code using ruby-debug extension
- Article here
- I didn’t get this working on Windows or Mac, but didn’t spend so long on Mac.
Gems, packaging, versioning
- My separate page on gems, versions and packaging
- Gem Packaging Tutorial
- bowling kata as a Ruby gem (
csud-bowl-kata
) - bowling kata designed to be released as a Ruby gem (might not actually be on RubyGems.org yet) - Require, gems, loading files: Understanding ruby load, require, gems, bundler and rails autoloading from the bottom up
Testing
- For Rspec see below
- Overview of unit testing using Test::Unit: https://en.wikibooks.org/wiki/Ruby_Programming/Unit_testing
- Running a single test in Rails:
https://stackoverflow.com/questions/1506780/how-to-run-a-single-test-from-a-rails-test-suite/38079221
- I didn’t manage to get this working, but that was probably something to do with how my ruby gems were installed - something to do with bundles and vendor and stuff
- Mocking in Ruby using Minitest: https://semaphoreci.com/community/tutorials/mocking-in-ruby-with-minitest
- Mocking stuff that is in a module instead of a class, using RR: https://github.com/btakita/rr/issues/98
Front end testing
- Helpful guide on using
Rack::Test
to test what is returned by your routes. - Examples of front end testing using Rack::Test with Sinatra:
- Using rspec to do front end testing on html and css (has loads of useful examples in the readme): rspec-html-matchers. DON’T FORGET TO ADD the following to
spec_helper.rb
andGemfile
:
In spec_helper.rb
:
require "rspec-html-matchers"
RSpec.configure do |config|
config.include RSpecHtmlMatchers
In Gemfile
:
gem "rspec-html-matchers"
Rspec
- To run tests: either
rspec
orbundle exec rspec
(depending whether you did a global bundle install or a vendor one, I think (eg if you ranbundle config set --local path 'vendor/bundle'
before you ranbundle install
)) - See quick-start section for a quick start guide if writing a kata from scratch (includes getting tests set up, and running tests / how to run tests)
- See debugging rspec for how to debug rspec tests
- See here for running individual tests or subsets of tests
- Using Rspec for Testing: Intro to Rspec
- Getting started with Ruby and TDD (using rspec)
- Rspec style guide
- All the different types of rspec expect statements
- Rspec syntax:
- built-in matchers (like
to eq
andnot_to eq
) - Other matchers (like
a_string_ending_with
,a_string_starting_with
, anda_string_including
)
- built-in matchers (like
- Rspec mocking (stubs and doubles)
- See also expecting messages for specifying what gets called on a test double
- … and matching arguments for specifying what gets passed to a test double
- See Front end testing for how to do front end testing with Rspec.
Test cases in rspec
You can handle test cases in rspec in the way shown below - and there are more examples here. Below we use a hash
to map inputs (“rolls”) to outputs “scores”, and then use an each
statement to loop through the list of elements in the hash.
expected_scores_with_a_strike_in_the_tenth_frame = {
"44 44 44 44 44 44 44 44 44 X 32" => (9*8) + (10+3+2),
"44 44 44 44 44 44 44 44 44 X X-" => (9*8) + (10+10+0),
"44 44 44 44 44 44 44 44 44 X -X" => (9*8) + (10+0+10),
"44 44 44 44 44 44 44 44 44 X XX" => (9*8) + (10+10+10),
"44 44 44 44 44 44 44 44 44 X 46" => (9*8) + (10+4+6),
"44 44 44 44 44 44 44 44 44 X 6-" => (9*8) + (10+6+0),
"44 44 44 44 44 44 44 44 44 X -3" => (9*8) + (10+0+3),
"44 44 44 44 44 44 44 44 44 X --" => (9*8) + (10+0+0),
"44 44 44 44 44 44 44 44 44 X 2-" => (9*8) + (10+2+0),
"44 44 44 44 44 44 44 44 44 X -5" => (9*8) + (10+0+5)
}
expected_scores_with_a_strike_in_the_tenth_frame.each do |rolls, score|
it "adds the final two rolls to the score twice, when a strike is rolled in the final frame: '#{rolls}'" do
bowling = Bowling.new
expect(bowling.score(rolls)).to eq(score)
end
end
Testing command-line inputs and outputs (stdin, stdout, stderr)
You can stub command-line inputs using standard rspec stubbing functionality and the fact that gets
and puts
are functions inherited from Object
by all classes. Note that you can also mimic several repeated inputs by giving a comma-separated list. Note that in the examples below, @communicator
and @mars_rover_app
are classes written by me, and I am specifying their inputs and outputs.
For the code below you will need the rspec
and rspec-html-matchers
gems in your Gemfile
(see example here).
allow(@communicator).to receive(:gets).and_return(INITIAL_INPUT, "f", "r", "f", "f", "l", "b", "")
You can test whether what you expected got sent to stdout
or stderr
using the to output
functionality:
expect{@mars_rover_app.start}.to output(a_string_ending_with(MarsRoverApp::BAD_INPUT_ERROR)).to_stdout
Note that you can use matchers such as a_string_ending_with
, a_string_starting_with
, and a_string_including
so that you are only checking a subset of the output rather than everything that has been sent to stdout
. More matchers listed here.
More examples of stdin
and stdout
testing in this file here.
Language Features
Misc Language Stuff
- General Ruby docs (they’re pretty good, once you’ve searched for what you’re interested in).
- Return values in Ruby functions are the last thing that was assigned
- the return statement is often not used
- IRB is the standard Ruby repl (run
irb
on command line)- If you run it using
irb -rpp
, you’ll get pretty-printing (passing-r
toirb
will automatically require a library when irb is loaded - in this case thepretty_print
library). - Enter
exit
to leave) - Enter
load './myfile.rb'
to load a Ruby file calledmyfile.rb
in the current folder (./
)- To reload, just enter
load './myfile.rb'
again.
- To reload, just enter
- An alternative to
IRB
is this handy online repl tool
- If you run it using
-
The puts statement is how you can output to console - useful for quick-and-dirty debug logging.
-
If you want to get the output from something in a Ruby script onto the command line and into a pipe: Use the puts keyword in the Ruby script
-
If Chef, you can use knife exec and then pipe the output to other commands
-
Otherwise just run the script with the script name, and pipe straight to something else
-
-
Boolean methods should be suffixed with a question mark. More here. However this is a convention and is not enforced. It’s possible to write a function suffixed with a question mark that doesn’t return a bool. In fact it’s really used to indicate that the function is asking a question - the answer might not be a bool in practice. More here.
-
Methods that end in
!
indicate that the method will modify the object it’s called on. Ruby calls these “dangerous methods” because they change state that someone else might have a reference to. -
Calling javascript code from Ruby: https://github.com/sstephenson/execjs
-
Frozen values: Frozen strings
-
Calling javascript code from Ruby: https://github.com/sstephenson/execjs
- Calling an API or a url - notes summarised from here
- You’ll need to require
json
andnet/http
. TheNet::HTTP
Ruby library will give you some methods to help you send an HTTP request and theJSON
library will help you to parse any JSON data that comes back - Use the following code to make a request to an API and parse the JSON that is returned. Note that
endpoint
will contain the url of the API, as a string:
- You’ll need to require
uri = URI.parse(URI.encode(endpoint))
api_response = Net::HTTP.get(uri)
JSON.parse(api_response)
- HEREDOC
- You can use
HEREDOC
for multi-line string literals, instead of concatenating individual lines. - Instead of this…
- You can use
populated_grid =
"-------------\n" +
"| | 360 |\n" +
"| | ^^^ |\n" +
"| | TST |\n" +
"-------------\n"
- … you can do this:
populated_grid =
<<~HEREDOC
-------------
| | 360 |
| | ^^^ |
| | TST |
-------------
HEREDOC
Blocks / anonymous functions, and the yield keyword
- Blocks of code, aka
anonymous
orunnamed functions
{ |i| puts 2**i }
is equivalent todo |i| puts 2**i end
- ie
do
andend
take the place of the opening and closing braces in defining a block of code. - Therefore
(1..5).each { |i| puts 2**i }
is equivalent to
(1..5).each do |i|
puts 2**i
end
- What we’re seeing in this example is that
each
is a method that takes ablock
of code as a parameter. - What’s not quite so obvious is that in this case, the block of code is an anonymous function that takes a parameter. The parameter is named
i
and is indicated by surrounding it with pipes:|i|
-
See
yield
below for more on blocks, and blocks that take parameters. - yield keyword:
yield
is a keyword in Ruby that calls a block that was given to a method.- Whenever you pass a block to a method (such as
each
,collect
,select
, and so on) this method can then call the block by using the keyword yield. - There are a couple of examples of its usage in my Mars Rover app code base, including this one.
- I did previously have a nested yield here (the
handle_exceptions
method usesyield
to forward a block toAppHelper.handle_mars_rover_exceptions
, which also usesyield
), but I refactored it out here because it represented unnecessary complexity.
- I did previously have a nested yield here (the
- So, in a
Sinatra
layout template, <%= yield %> marks the place where the other template (the one that is being wrapped) is supposed to be inserted. Example here. - Every Ruby method can take a
block
as a parameter- We can invoke that block using the
yield
keyword.- The block won’t get evaluated until the
yield
keyword appears.
- The block won’t get evaluated until the
- So, in the following example, although we don’t explicitly declare that the
sandwich
function takes ablock
as a parameter, we see that it can because of its use of theyield
keyword. First we define thesandwich
function and then we call it and pass ado .. end
block to it. The output is also shown.
- We can invoke that block using the
- Whenever you pass a block to a method (such as
# blocks.rb
def sandwich
puts "top bread"
yield
puts "bottom bread"
end
sandwich do
puts "mutton, lettuce, and tomato"
end
- So what about yielding a block with a parameter? Remember we can define a block with a param like this:
do |markup|
puts markup
end
- The code above defines a block that takes a parameter called
markup
, then just outputs that markup to the command line. - So let’s define a function that can take that block of code as a parameter and then call it using
yield
:
def tag(tagname, text)
html = "<#{tagname}>#{text}</#{tagname}>"
yield html
end
- The above code takes a tagname and text and uses string interpolation to turn it into a chunk of html. Then the statement
yield html
is saying “Call the block of code that has been passed in, and passhtml
to it as an argument.” - So finally we can call the
tag
function and pass in our original block of code so that thetag
function can * call* our original block:
tag("p", "Lorem ipsum dolor sit amet") do |markup|
puts markup
end
- The result of the above call to
tag
will be the following being output to the command line:- gcam “
<p>Lorem ipsum dolor sit amet</p>
- gcam “
Collections
Hashes
- Also known as associative arrays.
- Some examples of hashes:
my_hash = Hash.new my_hash["one"] = "First element" my_hash2 = {} my_hash2[:one] = "First element" my_hash3 = { "first_name" => "Pippi", "last_name" => "Longstocking" } my_hash4 = { :first_name => "Pippi", :last_name => "Longstocking" } # this is equivalent to the above: my_hash4 = { first_name: "Pippi", last_name: "Longstocking" }
:name
is a symbol (see below)- The
=>
operator is called a “hashrocket”. - when hashes have string keys, those strings are frozen
- Ruby documentation on hashes (it’s pretty good documentation)
- Using an array as a key in your hash
- we thought about creating an example in our wordwrap academy kata, but instead we used a nested hash as a key
Arrays
- Negative indices:
- To get last array element, use negative index: [-1]
- The second-to-last element has index -2, and so on.
- Square brackets are
built-in constructors
, but you can also use thenew
keyword if you want:a = Array.new
is equivalent toa = []
- Adding elements to arrays can be done in two ways:
a[0] = "A"
a << "A"
Strings
- gsub: Find and replace
- Frozen strings
- String interpolation. Do it like this: “search/#{type}”, where type is a variable
- !! It only works in double quotes!
- In fact it’s not recommended to use ‘ in ruby. Use “ instead.
- It doesn’t seem to work in a Ruby script run via knife exec -
instead, you can do this:
- This: query=”fqdn:”+ARGV[2]
- See below for how to do string interpolation with symbols
- Single-quoted strings
- Single-quoted strings are literal strings. You can’t do interpolation with them but you can include special characters without having to escape them - so they can be useful for that. More here.
?h
is the same as"h"
- Double and single quotes are
built-in constructors
, but you can also use thenew
keyword if you want:s = String.new("A man, a plan, a canal—Panama!")
Symbols
-
Symbols are things that look like this :symbol
-
The closest thing in C# is an enum
-
The symbol itself is the value - they are not variables and you don’t assign to them
-
Symbols do hold strings of characters, they are just immutable
-
This means that when you reference a symbol in string interpolation, what is printed out will be the name of the symbol
-
So #{:node} will give node as the output
-
-
A variable that has a string as its value will be mutable, but a symbol is immutable, and stored in a single place in memory
Dates and Times
Time
is a built-in class- you have to use
require 'time'
- you have to use
now = Time.new
will give you the current timenow = Time.now
is equivalent
- or you can initialise:
moon_landing = Time.new(1969, 7, 20, 20, 17, 40)
(= 1969-07-20 20:17:40) - By default, Time uses the local time zone, but this introduces weird location-dependence to the operations, so it’s a good practice to use UTC instead:
moon_landing = Time.utc(1969, 7, 20, 20, 17, 40)
- Other useful
Time
methods:now.year
(= 2020)now.month
(= 12)now.day
(= 31)now.hour
(= 19)now = Time.now.utc
()now.wday
(= 0 for Sunday)Date::DAYNAMES[Time.now.wday]
(= SUNDAY)- you have to use
require 'date'
- you have to use
Date.parse("10/10/2010")
Date.parse("September 3")
- More on date parsing here
Functions
- Functions are not attached to objects
Methods
- Methods are functions attached to objects
- This notation -
String#include?
indicates a method calledinclude?
which is aString
instance method.
Methods and functions
- You don’t need brackets when passing arguments to methods and functions (they’re optional) - you can use spaces instead.
Command Line Input
- you can use
gets
andputs
like this (example here) - or you can use
$stdin
- see article here - both
gets
and$stdin
can be used to pipe input directly from other sources (example here) (more here) - or you can create a CLI and pass data in via command line parameters like this using
OptionParser
(example here) (More detailed documentation forOptionParser
) - be aware that sometimes your input is requested before your output is output - you fix that by using
$stdout.sync = true
(example here) (more here.)
Inheritance
- great article here on inheritance in Ruby - includes an explanation of why instance variables are not defined by classes and are therefore also not inherited by subclasses.
Division and other Maths
- By default, the
/
operator does integer division - so 2/3 = 0- If you want floating point division, add
.0
to one of your integers:2/3.0
- If you want floating point division, add
- To get “to the power of”, use
**
instead of^
2**3 = 8
- More complex Maths operations are available via the
Math
moduleMath.log10(10)
means “10 to the power what equals 10?” and the answer is “1”.Maths.log(10)
is used forln
or the natural logarithm (log to the base e)Math::E
is used fore
Math::PI
ispi
and is an example of a module constant- Also available:
Math.sqrt(4)
(answer = 2)Math.cos(2*Math::PI)
(answer = 1)
Questions
- If RSpec is a Gem, why is it never required in your spec files? How do they get the code they need? And what does the
--
mean in front ofrequire spec_helper
in the.rspec
file?
Monkey patching
“It’s simply the dynamic replacement of attributes at runtime.
For instance, consider a class that has a method get_data. This method does an external lookup (on a database or web API, for example), and various other methods in the class call it. However, in a unit test, you don’t want to depend on the external data source - so you dynamically replace the get_data method with a stub that returns some fixed data.”
From here.
Caution: “In our experience, having monkey-patched gems is usually one of the hardest things to deal with. We have to spend hours updating monkey-patched gems to make them compatible with newer Rails APIs. So please keep that in mind before monkey patching Rails core libraries or gems that depend on specific Rails versions.” From here.
Troubleshooting
See the Ruby Gems page for most Ruby Troubleshooting stuff (including getting Ruby working on a 2022 Macbook)
Database stuff
- For Ruby, PostgreSQL, Sinatra/Rails and heroku see here