Ruby: Difference between revisions
(38 intermediate revisions by the same user not shown) | |||
Line 21: | Line 21: | ||
* Hashes | * Hashes | ||
* Arrays | * Arrays | ||
* Enumerable | |||
==Boolean== | ==Boolean== | ||
Boolean data type represents only one bit of information either true or false. | Boolean data type represents only one bit of information either true or false. | ||
==Numbers== | ==Numbers== | ||
Numbers are immutable in ruby<br> | |||
'''Fixnum''' - Normal number e.g. 1<br> | '''Fixnum''' - Normal number e.g. 1<br> | ||
'''Bignum''' - Big numbers e.g. 111111111111<br> | '''Bignum''' - Big numbers e.g. 111111111111<br> | ||
'''Float''' - Decimal number e.g. 3.142<br> | '''Float''' - Decimal number e.g. 3.142 (15 digits of precision)<br> | ||
'''Complex''' - Imaginary number e.g. 4 + 3i<br> | '''Complex''' - Imaginary number e.g. 4 + 3i<br> | ||
'''Rational''' - Fractional number e.g. 9/4<br> | '''Rational''' - Fractional number e.g. 9/4<br> | ||
'''BigDecimal''' - Precision number e.g. 6.0<br> | '''BigDecimal''' - Precision number e.g. 6.0<br> | ||
==Strings== | ==Strings== | ||
===Intro=== | |||
A string is a group of letters that represent a sentence or a word. Strings are defined by enclosing a text within single (') or double (") quote. | A string is a group of letters that represent a sentence or a word. Strings are defined by enclosing a text within single (') or double (") quote. | ||
===Escaping=== | |||
<syntaxhighlight> | |||
'Serenity' = Serenity | |||
'\'Serenity\'' = 'Serenity' | |||
'Serenity\\' = 'Serenity\' | |||
</syntaxhighlight> | |||
Or choose your own | |||
<syntaxhighlight> | |||
'Serenity' transport | |||
%q('Serenity' transport) | |||
%q['Serenity' transport] | |||
%q*'Serenity' transport* | |||
</syntaxhighlight> | |||
===Interpolation=== | |||
<syntaxhighlight lang="ruby"> | |||
put "Lander count: #{myVar}" | |||
</syntaxhighlight> | |||
===Heredoc=== | |||
Ruby support this | |||
<syntaxhighlight lang="ruby"> | |||
message = <<EOS | |||
There's no place like home | |||
There's no place like home | |||
EOS | |||
</syntaxhighlight> | |||
==Symbols== | ==Symbols== | ||
Symbols are like strings. A symbol is preceded by a colon (:). For example, | Symbols are like strings. A symbol is preceded by a colon (:). For example, | ||
Line 55: | Line 85: | ||
<syntaxhighlight lang="ruby"> | <syntaxhighlight lang="ruby"> | ||
["Akash", "Ankit", "Aman"] | ["Akash", "Ankit", "Aman"] | ||
</syntaxhighlight> | |||
These seem to work similar to python with the exception that these are inclusive | |||
<syntaxhighlight lang="ruby"> | |||
# slicing | |||
arr = [1,2,3,4,5] | |||
arr[1..3] | |||
=> [2,3,4] | |||
arr[1..-2] | |||
=> [2,3,4] | |||
</syntaxhighlight> | |||
==Enumerable== | |||
This provides functions well known to python | |||
===Map=== | |||
<syntaxhighlight lang="ruby"> | |||
[1,2,3].map { |v| v * 10} | |||
# => [10,20,30] | |||
</syntaxhighlight> | |||
===Reduce=== | |||
<syntaxhighlight lang="ruby"> | |||
[1,2,3].reduce(0) { |sum, v| sum + v} | |||
# => 6 | |||
</syntaxhighlight> | |||
===Sort=== | |||
<syntaxhighlight lang="ruby"> | |||
[3,1,3].sort | |||
# => [1,3,3] | |||
</syntaxhighlight> | |||
===Select=== | |||
<syntaxhighlight lang="ruby"> | |||
[1,2,3,4,5].select{ |n| n.even?} | |||
# => [2,4] | |||
</syntaxhighlight> | |||
===Each Cons=== | |||
<syntaxhighlight lang="ruby"> | |||
[1,2,3,4,5].each_cons(2) {|n| p v) | |||
# [1,2] | |||
# [2,3] | |||
# [4,5] | |||
#=> nil | |||
</syntaxhighlight> | |||
==Ranges== | |||
You can define ranges are inclusive of exclusive by using two or three dots. | |||
<syntaxhighlight lang="ruby"> | |||
1..5 # 1 to 5 | |||
1...5 # 1 to 4 | |||
</syntaxhighlight> | |||
They can be used in case statements | |||
<syntaxhighlight lang="ruby"> | |||
puts case sample_reading | |||
when 0..100 then "below normal" | |||
when 101..150 then "normal" | |||
else "excessive" | |||
end | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 86: | Line 172: | ||
and, or, &&, ||, ! and not are available. | and, or, &&, ||, ! and not are available. | ||
=Flow Control= | =Flow Control= | ||
==Branching== | |||
===if else=== | |||
Some examples and no surprises | |||
<syntaxhighlight lang="ruby"> | <syntaxhighlight lang="ruby"> | ||
a = "abc" | if a == "abc" | ||
doFoo1 | |||
elseif a == "def" | |||
doFoo2 | |||
else | |||
doFoo3 | |||
end | |||
myVar = if a == "123" then "123" else "not 123" end | |||
</syntaxhighlight> | |||
===unless=== | |||
Strange but OK I suppose | |||
<syntaxhighlight lang="ruby"> | |||
unless a == "abc" | |||
doFoo1 | |||
end | |||
myVar unless a == "123" | |||
</syntaxhighlight> | |||
===Elvis=== | |||
Thank you very much | |||
<syntaxhighlight lang="ruby"> | |||
iainisace == true ? doFoo1 : doFoo2 | |||
</syntaxhighlight> | |||
==Conditional Initializing== | |||
The ||= is used often in ruby. Here is an example with an expanded version | |||
<syntaxhighlight lang="ruby"> | |||
ship ||= Spaceship.new | |||
# | |||
ship = Spaceship.new unless ship | |||
</syntaxhighlight> | |||
==and and or vs && and ||== | |||
* and and or have much lower precedence than && and || | |||
* && has higher precedence than || | |||
* and and or have the same precedence | |||
and and or are used for flow control generally where && and || are used or if | |||
<syntaxhighlight lang="ruby"> | |||
# | |||
# and Continues when statement is returns true and not nil | |||
# | |||
lander = Lander.locat(lander_id) and lander.recall | |||
# same as | |||
lander = Lander.locate(lander_id) | |||
lander.recall if lander | |||
# | |||
# or Continues if the first statement returns false or nil | |||
# | |||
if engine.cut_out? | |||
engine.restart or enable_emergency_power | |||
end | |||
# same as | |||
if engine.cut_out? | |||
enable_emergency_power unless engine.restart | |||
end | |||
</syntaxhighlight> | |||
==Case Statement== | |||
Note case statements do not fall through. Below are some samples | |||
<syntaxhighlight lang="ruby"> | |||
case distance_to_dock | |||
when "far away" | |||
lander.maintain_thrust | |||
when "coasting time" | |||
lander.kill_thrust | |||
when "collision imminent" | |||
lander.reverse_thrust | |||
end | |||
</syntaxhighlight> | |||
Assign value | |||
<syntaxhighlight lang="ruby"> | |||
thrust_power = case distance_to_dock | |||
when "far away" then 100 | |||
when "coasting time" then 0 | |||
when "collision imminent" then -100 | |||
end | |||
</syntaxhighlight> | |||
Case on type | |||
<syntaxhighlight lang="ruby"> | |||
case unit | |||
when Lander | |||
lander.park | |||
when Probe | |||
probe.retrieve | |||
probe.save | |||
else | |||
do_something_else("Default") | |||
end | |||
</syntaxhighlight> | |||
==Looping Constructs== | |||
===While=== | |||
<syntaxhighlight lang="ruby"> | |||
while am_i_still_true? | |||
music_center.play | |||
end | |||
# Or | |||
while am_i_still_true? do music_center.play end | |||
# Or | |||
music_center.play end while am_i_still_true? | |||
</syntaxhighlight> | |||
===Unless=== | |||
<syntaxhighlight lang="ruby"> | |||
until am_i_still_true? | |||
music_center.play | |||
end | |||
# Or | |||
until am_i_still_true? do music_center.play end | |||
# Or | |||
music_center.play end until am_i_still_true? | |||
</syntaxhighlight> | |||
===Begin/End=== | |||
<syntaxhighlight lang="ruby"> | |||
begin | |||
music_center.intro | |||
music_center.play | |||
end while am_i_still_true? | |||
begin | |||
music_center.intro | |||
music_center.play | |||
end until am_i_still_true? | |||
</syntaxhighlight> | |||
===For Loop=== | |||
Not surprises | |||
<syntaxhighlight lang="ruby"> | |||
for i in [3,2,1] | |||
puts i | |||
end | |||
for i in(1..10) | |||
puts i | |||
end | |||
</syntaxhighlight> | |||
==Looping with Iterators== | |||
===Looping === | |||
Examples or loops | |||
<syntaxhighlight lang="ruby"> | |||
# Forward | |||
10.upto(20) { |i| puts i} | |||
# Reverse | |||
20.upto(10) { |i| puts i} | |||
# Print 3 times | |||
3.times {puts "This is great"} | |||
# Odd numbers 1-10 | |||
1.step(10,2) {|i| puts i} | |||
</syntaxhighlight> | |||
==Controlling Looping== | |||
===Next=== | |||
Sane as continue in C++ | |||
<syntaxhighlight lang="ruby"> | |||
while message = comms.get_message | |||
# next means continue to next item if true | |||
next if message.type == "sync" | |||
message.process | |||
end | |||
</syntaxhighlight> | |||
===Break=== | |||
Sane as break in C++ | |||
<syntaxhighlight lang="ruby"> | |||
while message = comms.get_message | |||
message.process | |||
# break means break out of loop | |||
break if message.type == "voice" | |||
end | |||
# Will return the message.text upon break | |||
text = while message = comms.get_message | |||
message.process | |||
# break means break out of loop | |||
break message.text if message.type == "voice" | |||
end | |||
</syntaxhighlight> | |||
===Redo=== | |||
Bit insane but here it is | |||
<syntaxhighlight lang="ruby"> | |||
i = 0 | |||
while i < 3 | |||
print "Please enter a positive number" | |||
input = gets.to_i | |||
redo if input <= 0 | |||
i += 1 | |||
end | |||
</syntaxhighlight> | |||
=Blocks, Procs and Lambdas= | |||
==Blocks== | |||
===Introduction=== | |||
Blocks are either single lines of code inside curly braces | |||
<syntaxhighlight lang="ruby"> | |||
ships = Spaceship.all | |||
ships.each{ |ship| put ship.name} | |||
end | |||
</syntaxhighlight> | |||
Or multi-line inside a do | |||
<syntaxhighlight lang="ruby"> | |||
[1,2,3].each do | |||
puts "This is a loop" | |||
end | |||
</syntaxhighlight> | |||
===Example=== | |||
When you pass a block the result is evaluated at the yield | |||
<syntaxhighlight lang="ruby"> | |||
class Spaceship | |||
def debug_only | |||
return nil unless @debug | |||
return nil unless block_given | |||
puts "Running debug code..." | |||
yield | |||
end | |||
end | |||
ship.debug_only # No output because block not given | |||
ship.debug_only do | |||
puts "This is debug output" | |||
end | |||
</syntaxhighlight> | |||
===Block arguments=== | |||
Here is an example of a do..end block | |||
<syntaxhighlight lang="ruby"> | |||
def produce | |||
yield :spaceship, :freighter, :yacht, size: :s, seats: :leather | |||
end | |||
produce do | what = :spaceship, *types, | |||
size: :m, engine_count: 2, **custom_components| | |||
puts "Producing #{what}" | |||
print "Types:" | |||
p types | |||
puts "Size: #{size}" | |||
puts "Engine count: #{engine_count}" | |||
print "Custom Components" | |||
p custom_components | |||
end | |||
#>Producing spaceship | |||
#>Types: [:freighter, : yacht] | |||
#>Size: s | |||
#>Engine count: 2 | |||
#>Custom components: {:seats=>:leather} | |||
</syntaxhighlight> | |||
===Block Context=== | |||
When you invoke a block the context which it was invoked in is present. That is to say the available parameters are still available in the yield. | |||
<syntaxhighlight lang="ruby"> | |||
$debug = true | |||
def debug_only | |||
yield if $debug && block_given | |||
end | |||
class Spaceship | |||
def initialize | |||
@some_attribute = {containment_status: :ok, core_temp: 350} | |||
end | |||
def launch | |||
debug_only { p @some_attribute} | |||
end | |||
end | |||
ship1 = Spaceship.new | |||
ship1.launch | |||
</syntaxhighlight> | |||
===Example Usage=== | |||
Here is an example usage of block where timings are acquired and perhaps makes more sense for the approach | |||
<syntaxhighlight lang="ruby"> | |||
def with_timing | |||
start = Time.now | |||
if block_given? | |||
yield | |||
puts "Time taken: #{Time.now - start} seconds" | |||
end | |||
end | |||
def run_operation_1 | |||
sleep(1) | |||
end | |||
def run_operation_2 | |||
with_timing do | |||
run_operation_1 | |||
run_operation_2 | |||
end | |||
# output: Time taken: 1.000057 seconds | |||
</syntaxhighlight> | |||
==Procs== | |||
Procs are named blocks. Adding an & to the argument will denote a block is a proc. You can create a proc from a block. | |||
<syntaxhighlight lang="ruby"> | |||
p = Proc.new {|bla|} puts "I am a proc that says #{bla}!" } | |||
p = proc {|bla|} puts "I am a proc that says #{bla}!"} | |||
# You can invoke them with | |||
p.call "Yay"! | |||
p.yield "Wow" | |||
p.("nothing") | |||
p["hello"] | |||
</syntaxhighlight> | |||
==Lamdas== | |||
Lambda can also be created from blocks. You must pass the right number of arguments to a lamba | |||
<syntaxhighlight lang="ruby"> | |||
lmb = lambda {|bla| "I am a proc that says #{bla}!" } | |||
# Stabby Lambdas | |||
also_lmb = ->(bla) {"I am a proc that says #{bla}!" } | |||
</syntaxhighlight> | |||
=Exceptions= | |||
==Handling Exceptions== | |||
Ruby provides many exception classes. The main 3 are | |||
* Exception | |||
* StandardError | |||
* RuntimeError | |||
<syntaxhighlight lang="ruby"> | |||
def launch | |||
batten_hatches | |||
light_seatbelt_sign | |||
true | |||
rescue LightError | |||
put "None Fatal so returning true" | |||
true | |||
rescue StandardError => e | |||
put e.message | |||
false | |||
end | |||
</syntaxhighlight> | |||
==Raising Exceptions== | |||
Raising can be done simply | |||
<syntaxhighlight lang="ruby"> | |||
def batten_hatches | |||
# | |||
raise "Stuffed me heaties" | |||
end | |||
</syntaxhighlight> | |||
==Ensure and Else (finally)== | |||
To make sure that things are tickerty-boo we after can add an else cause after the rescue to perform code when no exceptions and a ensure clause to manage resources on exception e.g. | |||
<syntaxhighlight lang="ruby"> | |||
def batten_hatches | |||
hatch_file = File.open("bat.txt") | |||
# | |||
raise HatchError, "Stuffed me heaties" if door.jammed? | |||
# | |||
true | |||
rescue SystemCallError => e | |||
false | |||
else | |||
puts "No Exception raised" | |||
ensure | |||
hatch_file.close if hatch_file | |||
end | |||
</syntaxhighlight> | |||
==Retrying== | |||
This does not seem like the right way to do this but | |||
<syntaxhighlight lang="ruby"> | |||
def batten_hatches | |||
hatch_list = API.request("/flamingos") | |||
rescue RuntimeError => e | |||
attempts ||= 0 | |||
attempts += 1 | |||
if attempts < 3 | |||
puts e.message + ". Retrying request." | |||
retry | |||
else | |||
puts "Request failed" | |||
raise | |||
end | |||
end | |||
</syntaxhighlight> | |||
==Rescue Modifier== | |||
This allows you to ignore errors on functions. If you want to make sure code reviews are working properly then use the in abundance. | |||
<syntaxhighlight lang="ruby"> | |||
# bad | |||
do_something rescue nil | |||
</syntaxhighlight> | |||
==Throw Catch== | |||
This is maybe a different syntax to c++. The arguments for the catch must match the throw. The syntax is as follows | |||
<syntaxhighlight lang="ruby"> | |||
catch :lable_name do | |||
# matching catch will be executed when the throw block encounter | |||
throw :lable_name condition | |||
# this block will not be executed | |||
end | |||
</syntaxhighlight> | |||
For example | |||
<syntaxhighlight lang="ruby"> | |||
gfg = catch(:divide) do | |||
# a code block of catch similar to begin | |||
100.times do | |||
100.times do | |||
100.times do | |||
number = rand(10_000) | |||
# comes out of all of the loops | |||
# and goes to catch statement | |||
throw :divide, 10 if number == 0 | |||
end | |||
end | |||
end | |||
number # set gfg = number if | |||
# no exception is thrown | |||
end | |||
puts gfg | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 228: | Line 728: | ||
* protected means "allow access for other objects of the same class" | * protected means "allow access for other objects of the same class" | ||
* private and protected are not used much | * private and protected are not used much | ||
==Monkey Patching== | |||
Gosh this looks awful, ruby replaces the function whilst running. | |||
<syntaxhighlight lang="ruby"> | |||
class SpaceShip | |||
def insane | |||
put "insane" | |||
end | |||
end | |||
spaceShip = SpaceShip.new | |||
class SpaceShip | |||
def insane | |||
put "insane 2" | |||
end | |||
end | |||
puts spaceShip.insane | |||
</syntaxhighlight> | |||
=Modules= | |||
==Namespaces== | |||
This is used to create namespaces for methods and classes and can be nested | |||
<syntaxhighlight lang="ruby"> | |||
module API | |||
def self.hatch_list | |||
# blah | |||
end | |||
end | |||
hatches = API.hatch_list | |||
module Spacestuff | |||
class Spaceship | |||
end | |||
end | |||
ship = SpaceStuff::Spaceship.new | |||
module A | |||
module B | |||
class Test | |||
end | |||
end | |||
end | |||
testy = A::B::Test.new | |||
</syntaxhighlight> | |||
==Mixins== | |||
==Instance Mixins== | |||
This is a way of combining functionality into different classes. Define a module and then include it in your class. | |||
<syntaxhighlight lang="ruby"> | |||
module AirControl | |||
def measure_oxygen | |||
# blah | |||
end | |||
end | |||
class Spaceship | |||
include AirControl | |||
# blah | |||
end | |||
ship = Spaceship.new | |||
ship.measure_oxygen | |||
</syntaxhighlight> | |||
==Class Method (static method) Mixins== | |||
Using extend can be used to mixin class methods | |||
<syntaxhighlight lang="ruby"> | |||
class Spaceship | |||
extend AirControl | |||
# blah | |||
end | |||
Spaceship.measure_oxygen | |||
</syntaxhighlight> | |||
==Including Static and Non Static Mixins== | |||
You can provide the hook method included in your module to reduce the need to include and extend. | |||
<syntaxhighlight lang="ruby"> | |||
module Docking | |||
module ClassMethods | |||
def get_default_config | |||
# | |||
end | |||
end | |||
def self.included(base) | |||
base.extend(ClassMethods) | |||
end | |||
def dock | |||
end | |||
end | |||
class Spaceship | |||
include Docking | |||
end | |||
</syntaxhighlight> | |||
=Methods= | |||
==Default parameters== | |||
<syntaxhighlight lang="ruby"> | |||
def make_cake(type = :type_of_cake, size = calculate_cake_size()) | |||
end | |||
def make_cake(type = :type_of_cake, size = (type == :type_of_cake ? :x : :m)) | |||
end | |||
</syntaxhighlight> | |||
==Variable parameters== | |||
Works similar to python | |||
<syntaxhighlight lang="ruby"> | |||
def produce_fleet(days_to_complete, *type) | |||
end | |||
product_fleet(10, :freighter, :freighter,, :explorer) | |||
# or | |||
types = [:freighter, :freighter, :explorer] | |||
product_fleet(10, types) | |||
</syntaxhighlight> | |||
==Keyword Arguments== | |||
Straight forward | |||
<syntaxhighlight lang="ruby"> | |||
def produce_fleet (days: days_to_complete, types: *type) | |||
end | |||
</syntaxhighlight> | |||
Like python double ** will be a dictionary | |||
<syntaxhighlight lang="ruby"> | |||
def produce_spaceship(type = :freighter sizxe: :m, **custom_components) | |||
components = {engine: :standard, | |||
seats: :standand, | |||
subwoofer: :none} | |||
components.merge!(custom_components) | |||
# | |||
end | |||
# engine and seats are custom | |||
produce_spaceship(:yacht, | |||
size: :s, | |||
engine: :rolls_royce, | |||
seats: :leather) | |||
</syntaxhighlight> | |||
==Operator Overloading== | ==Operator Overloading== | ||
===Introduction=== | |||
For the equals operator you can overload your class | For the equals operator you can overload your class | ||
<syntaxhighlight lang="ruby"> | <syntaxhighlight lang="ruby"> | ||
Line 244: | Line 891: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== | ===Further Examples=== | ||
Here we overload [], << and + | |||
<syntaxhighlight lang="ruby"> | |||
class Spacehsip | |||
attr_reader :name | |||
attr_reader :speed | |||
def initialize(name) | |||
@name = name | |||
@cargo = [] | |||
@speed = 0 | |||
@vessels = Hash.new { [] } | |||
def []=(type, vehicles) | |||
@vessels[type] = vehicles | |||
end | |||
def [](type) | |||
@vessels[type] | |||
end | |||
def <<(cargo) | |||
@cargo << cargo | |||
end | |||
def +@ | |||
@speed +10 | |||
end | |||
end | |||
ship1 = Spaceship.new("Serenity") | |||
# Example of [] | |||
class Landers; end | |||
ship1[:landers] = [Lander.new, Lander.new] | |||
puts "Landers: #{ship1[:landers].inspect}" | |||
# Example of << | |||
class CargoPod; end | |||
cargo_pod = CargoPod.new | |||
ship1 << cargo_pod | |||
p ship1 | |||
# Unary + Note the @ sign | |||
+ship1 | |||
put "Speed: #{ship1.speed}" | |||
</syntaxhighlight> | |||
==send and delegates== | |||
You can use send to call a method on a class eg. | |||
<syntaxhighlight lang="ruby"> | |||
myclassInstance.send(myclassMethod()) | |||
</syntaxhighlight> | |||
Given this you can use it as a delegate eg. | |||
<syntaxhighlight lang="ruby"> | <syntaxhighlight lang="ruby"> | ||
case input | |||
when :up_arrow then ship.tilt_up | |||
when :down_arrow then ship.tilt_down | |||
when :left_arrow then ship.tilt_left | |||
when :right_arrow then ship.tilt_right | |||
end | end | ||
# could be written | |||
handlers = { | |||
up_arrow: tilt_up | |||
down_arrow: tilt_down | |||
left_arrow: tilt_left | |||
right_arrow: tilt_right} | |||
ship.send(handlers[input]) | |||
</syntaxhighlight> | |||
==method_missing== | |||
When you send a message to an object, the object executes the first method it finds on its method lookup path with the same name as the message. If it fails to find any such method, it raises a NoMethodError exception - unless you have provided the object with a method called method_missing. The method_missing method is passed the symbol of the non-existent method, an array of the arguments that were passed in the original call and any block passed to the original method. | |||
method_missing is in part a safety net: It gives you a way to intercept unanswerable messages and handle them gracefully. See the example - p012zmm.rb below. | |||
class | <syntaxhighlight lang="ruby"> | ||
class Dummy | |||
def method_missing(m, *args, &block) | |||
puts "There's no method called #{m} here -- please try again." | |||
end | end | ||
end | |||
Dummy.new.anything | |||
</syntaxhighlight> | </syntaxhighlight> | ||
The output is: | |||
>ruby p012zmm.rb | |||
There's no method called anything here -- please try again. | |||
>Exit code: 0 | |||
You are also responsible for maintaining the method_missing signature. It's possible to write a hook that captures only a missing method's name while ignoring its arguments and associated block. | |||
=General= | |||
==Organizing Code== | |||
Here is a typical example | |||
product_deep_space | |||
- bin | |||
- doc | |||
- lib | |||
- deep_space | |||
- spaceship.rb | |||
- probe.rb | |||
- deep_space.rb | |||
- test | |||
- init.rb | |||
To include a file use __dir__ (current path) | |||
require '#{__dir__}/deep_space/spaceship' | |||
require_relative 'deep_space/spaceship' | |||
require 'json' | |||
You can look at the load path using $LOAD_PATH | |||
==Gems== | |||
===Introduction=== | |||
Gems are the equivalent of nuget packages. | |||
===Sites=== | |||
The main site is [[http://rubygems.org]] or [[https://www.ruby-toolbox.com]] | |||
===Usage=== | |||
Here is some examples | |||
gem install log4r | |||
gem list | |||
===Bundler=== | |||
This allows you to provide a list of dependency for your app. You put these in a Gemfile. | |||
source 'https://rubygems.org' | |||
gem 'pg', '0.13.2' | |||
gem 'haml', '3.1.6' | |||
To install you can then do | |||
bundle install | |||
==Testing== | |||
=== Test Driven Development (TDD)=== | |||
An example for this is mini test | |||
=== Behaviour Driven Development (BDD)=== | |||
An example for this is cucumber | |||
==Packaging== | |||
===Type of packaging=== | |||
* Source control + bundler | |||
* Package as a gem | |||
===Gem Example=== | |||
* Create a gemspec file | |||
* Run gem build <gemspec file> | |||
* Run gem install <package> | |||
* Run gem list | |||
==Resources== | |||
* [[rubydoc.info]] | |||
* [[rubygems.org]] | |||
* [[ruby-lang.org]] | |||
* [[ruby-toolbox.com]] | |||
* [[rspec.info]] | |||
* [[cukes.info]] |
Latest revision as of 03:41, 13 August 2020
Introduction
- Thoroughly object oriented
- Dynamic typing (Type errors are reported at runtime)
- Duck typing (Code requires that an object supports the operations that are used)
- Multi-paradigm (Supports procedural, functional and generic approaches)
- Reflection (Like RTTI in C++)
- Meta programming (Manipulating the underlying mechanics of the language)
- Byte code Interpreted (Like java but not like C++)
Interactive Shell
You can start this using. You can type exit to well exit
irb
This allows you to work with ruby similar to python.
- _ Provides the value of the last evaluated expression
- Up arrow to go through history
Data Types
The following are the built in data types
- Boolean
- Numbers
- Strings
- Symbols
- Hashes
- Arrays
- Enumerable
Boolean
Boolean data type represents only one bit of information either true or false.
Numbers
Numbers are immutable in ruby
Fixnum - Normal number e.g. 1
Bignum - Big numbers e.g. 111111111111
Float - Decimal number e.g. 3.142 (15 digits of precision)
Complex - Imaginary number e.g. 4 + 3i
Rational - Fractional number e.g. 9/4
BigDecimal - Precision number e.g. 6.0
Strings
Intro
A string is a group of letters that represent a sentence or a word. Strings are defined by enclosing a text within single (') or double (") quote.
Escaping
'Serenity' = Serenity
'\'Serenity\'' = 'Serenity'
'Serenity\\' = 'Serenity\'
Or choose your own
'Serenity' transport
%q('Serenity' transport)
%q['Serenity' transport]
%q*'Serenity' transport*
Interpolation
put "Lander count: #{myVar}"
Heredoc
Ruby support this
message = <<EOS
There's no place like home
There's no place like home
EOS
Symbols
Symbols are like strings. A symbol is preceded by a colon (:). For example,
:abcd
They do not contain spaces. Symbols containing multiple words are written with (_). One difference between string and symbol is that, if text is a data then it is a string but if it is a code it is a symbol.
Symbols are unique identifiers and represent static values, while string represent values that change.
Hash
A hash assign its values to its keys. They can be looked up by their keys. Value to a key is assigned by => sign. A key/value pair is separated with a comma between them and all the pairs are enclosed within curly braces. For example,
{"Akash" => "Physics", "Ankit" => "Chemistry", "Aman" => "Maths"}
Arrays
An array stroes data or list of data. It can contain all types of data. Data in an array are separated by comma in between them and are enclosed by square bracket. For example,
["Akash", "Ankit", "Aman"]
These seem to work similar to python with the exception that these are inclusive
# slicing
arr = [1,2,3,4,5]
arr[1..3]
=> [2,3,4]
arr[1..-2]
=> [2,3,4]
Enumerable
This provides functions well known to python
Map
[1,2,3].map { |v| v * 10}
# => [10,20,30]
Reduce
[1,2,3].reduce(0) { |sum, v| sum + v}
# => 6
Sort
[3,1,3].sort
# => [1,3,3]
Select
[1,2,3,4,5].select{ |n| n.even?}
# => [2,4]
Each Cons
[1,2,3,4,5].each_cons(2) {|n| p v)
# [1,2]
# [2,3]
# [4,5]
#=> nil
Ranges
You can define ranges are inclusive of exclusive by using two or three dots.
1..5 # 1 to 5
1...5 # 1 to 4
They can be used in case statements
puts case sample_reading
when 0..100 then "below normal"
when 101..150 then "normal"
else "excessive"
end
Operators
Arithmetic Operators
+ Addition
- Subtraction
* Multiplication
/ Division
% Modulus i.e. returns remainder
** Exponent e.g. 10**20 is 10 to the power of 20
Comparison Operators
== Check if values are equal
!= Check if values are not equal
> Check if value of left greater than value on right
< Check if value of left less than value on right
>= Check if value of left greater than or equal to value on right
<= Check if value of left less than or equal to value on right
<=> Combined comparison operator. Returns 0 if first operand equals second, 1 if first operand is greater than the second and -1 if first operand is less than the second.
=== Used to test equality within a when clause of a case statement. e.g. (1...10) === 5 returns true.
.eql? True if the receiver and argument have both the same type and equal values.e.g. 1 == 1.0 returns true, but 1.eql?(1.0) is false.
.equal?True if the receiver and argument have the same object id. e.g if aObj is duplicate of bObj then aObj == bObj is true, a.equal?bObj is false but a.equal?aObj is true.
Assignment Operators
These are =, +=, -=, *=. /=, %= and **=. None of which are surprising in how they work. There is a parallel operators as well which swaps variables
a, b = b, c
Bitwise Operators
These are &. |, ^, ~, <<, >> operators which behave like most languages
Logical Operators
and, or, &&, ||, ! and not are available.
Flow Control
Branching
if else
Some examples and no surprises
if a == "abc"
doFoo1
elseif a == "def"
doFoo2
else
doFoo3
end
myVar = if a == "123" then "123" else "not 123" end
unless
Strange but OK I suppose
unless a == "abc"
doFoo1
end
myVar unless a == "123"
Elvis
Thank you very much
iainisace == true ? doFoo1 : doFoo2
Conditional Initializing
The ||= is used often in ruby. Here is an example with an expanded version
ship ||= Spaceship.new
#
ship = Spaceship.new unless ship
and and or vs && and ||
- and and or have much lower precedence than && and ||
- && has higher precedence than ||
- and and or have the same precedence
and and or are used for flow control generally where && and || are used or if
#
# and Continues when statement is returns true and not nil
#
lander = Lander.locat(lander_id) and lander.recall
# same as
lander = Lander.locate(lander_id)
lander.recall if lander
#
# or Continues if the first statement returns false or nil
#
if engine.cut_out?
engine.restart or enable_emergency_power
end
# same as
if engine.cut_out?
enable_emergency_power unless engine.restart
end
Case Statement
Note case statements do not fall through. Below are some samples
case distance_to_dock
when "far away"
lander.maintain_thrust
when "coasting time"
lander.kill_thrust
when "collision imminent"
lander.reverse_thrust
end
Assign value
thrust_power = case distance_to_dock
when "far away" then 100
when "coasting time" then 0
when "collision imminent" then -100
end
Case on type
case unit
when Lander
lander.park
when Probe
probe.retrieve
probe.save
else
do_something_else("Default")
end
Looping Constructs
While
while am_i_still_true?
music_center.play
end
# Or
while am_i_still_true? do music_center.play end
# Or
music_center.play end while am_i_still_true?
Unless
until am_i_still_true?
music_center.play
end
# Or
until am_i_still_true? do music_center.play end
# Or
music_center.play end until am_i_still_true?
Begin/End
begin
music_center.intro
music_center.play
end while am_i_still_true?
begin
music_center.intro
music_center.play
end until am_i_still_true?
For Loop
Not surprises
for i in [3,2,1]
puts i
end
for i in(1..10)
puts i
end
Looping with Iterators
Looping
Examples or loops
# Forward
10.upto(20) { |i| puts i}
# Reverse
20.upto(10) { |i| puts i}
# Print 3 times
3.times {puts "This is great"}
# Odd numbers 1-10
1.step(10,2) {|i| puts i}
Controlling Looping
Next
Sane as continue in C++
while message = comms.get_message
# next means continue to next item if true
next if message.type == "sync"
message.process
end
Break
Sane as break in C++
while message = comms.get_message
message.process
# break means break out of loop
break if message.type == "voice"
end
# Will return the message.text upon break
text = while message = comms.get_message
message.process
# break means break out of loop
break message.text if message.type == "voice"
end
Redo
Bit insane but here it is
i = 0
while i < 3
print "Please enter a positive number"
input = gets.to_i
redo if input <= 0
i += 1
end
Blocks, Procs and Lambdas
Blocks
Introduction
Blocks are either single lines of code inside curly braces
ships = Spaceship.all
ships.each{ |ship| put ship.name}
end
Or multi-line inside a do
[1,2,3].each do
puts "This is a loop"
end
Example
When you pass a block the result is evaluated at the yield
class Spaceship
def debug_only
return nil unless @debug
return nil unless block_given
puts "Running debug code..."
yield
end
end
ship.debug_only # No output because block not given
ship.debug_only do
puts "This is debug output"
end
Block arguments
Here is an example of a do..end block
def produce
yield :spaceship, :freighter, :yacht, size: :s, seats: :leather
end
produce do | what = :spaceship, *types,
size: :m, engine_count: 2, **custom_components|
puts "Producing #{what}"
print "Types:"
p types
puts "Size: #{size}"
puts "Engine count: #{engine_count}"
print "Custom Components"
p custom_components
end
#>Producing spaceship
#>Types: [:freighter, : yacht]
#>Size: s
#>Engine count: 2
#>Custom components: {:seats=>:leather}
Block Context
When you invoke a block the context which it was invoked in is present. That is to say the available parameters are still available in the yield.
$debug = true
def debug_only
yield if $debug && block_given
end
class Spaceship
def initialize
@some_attribute = {containment_status: :ok, core_temp: 350}
end
def launch
debug_only { p @some_attribute}
end
end
ship1 = Spaceship.new
ship1.launch
Example Usage
Here is an example usage of block where timings are acquired and perhaps makes more sense for the approach
def with_timing
start = Time.now
if block_given?
yield
puts "Time taken: #{Time.now - start} seconds"
end
end
def run_operation_1
sleep(1)
end
def run_operation_2
with_timing do
run_operation_1
run_operation_2
end
# output: Time taken: 1.000057 seconds
Procs
Procs are named blocks. Adding an & to the argument will denote a block is a proc. You can create a proc from a block.
p = Proc.new {|bla|} puts "I am a proc that says #{bla}!" }
p = proc {|bla|} puts "I am a proc that says #{bla}!"}
# You can invoke them with
p.call "Yay"!
p.yield "Wow"
p.("nothing")
p["hello"]
Lamdas
Lambda can also be created from blocks. You must pass the right number of arguments to a lamba
lmb = lambda {|bla| "I am a proc that says #{bla}!" }
# Stabby Lambdas
also_lmb = ->(bla) {"I am a proc that says #{bla}!" }
Exceptions
Handling Exceptions
Ruby provides many exception classes. The main 3 are
- Exception
- StandardError
- RuntimeError
def launch
batten_hatches
light_seatbelt_sign
true
rescue LightError
put "None Fatal so returning true"
true
rescue StandardError => e
put e.message
false
end
Raising Exceptions
Raising can be done simply
def batten_hatches
#
raise "Stuffed me heaties"
end
Ensure and Else (finally)
To make sure that things are tickerty-boo we after can add an else cause after the rescue to perform code when no exceptions and a ensure clause to manage resources on exception e.g.
def batten_hatches
hatch_file = File.open("bat.txt")
#
raise HatchError, "Stuffed me heaties" if door.jammed?
#
true
rescue SystemCallError => e
false
else
puts "No Exception raised"
ensure
hatch_file.close if hatch_file
end
Retrying
This does not seem like the right way to do this but
def batten_hatches
hatch_list = API.request("/flamingos")
rescue RuntimeError => e
attempts ||= 0
attempts += 1
if attempts < 3
puts e.message + ". Retrying request."
retry
else
puts "Request failed"
raise
end
end
Rescue Modifier
This allows you to ignore errors on functions. If you want to make sure code reviews are working properly then use the in abundance.
# bad
do_something rescue nil
Throw Catch
This is maybe a different syntax to c++. The arguments for the catch must match the throw. The syntax is as follows
catch :lable_name do
# matching catch will be executed when the throw block encounter
throw :lable_name condition
# this block will not be executed
end
For example
gfg = catch(:divide) do
# a code block of catch similar to begin
100.times do
100.times do
100.times do
number = rand(10_000)
# comes out of all of the loops
# and goes to catch statement
throw :divide, 10 if number == 0
end
end
end
number # set gfg = number if
# no exception is thrown
end
puts gfg
Object vs Variables
In ruby variables are reference i.e.
a = "abc"
b = a;
b is a reference to a not a copy. i.e. if we go
a.upper!
b will be upper too. To get a copy you need to use the clone method. i.e. b = a.clone. Note this is not a deep copy.
Classes
Introduction
You can create a class like this
class Shapeship
# Multiple props
attr_accessor :prop1, :prop2
def use_prop
prop1 = "Hi I am local and broken" # Won't work.
self.prop1 = "Must use self like this!"
end
def launch(destination)
@destination = destination
end
# Function for Getter
def destination
@destination
end
# Read/Write accessor
attr_accessor :destination
# Readable accessor
attr_reader :destination
# Writable accessor
attr_writerr :destination
end
ship = Spaceship.new
ship.launch("Earth")
Initialize
Initialize is called when a new instance is created
class Shapeship
def initialize(name, cargo_module_cound)
@name = name
@cargo_hold = CargoHold.new(cargo_module_count)
@power_level = 100
end
end
ship = Spaceship.new("Dreadnought", 4)
Inheritance
Nothing strange here
class Probe
def deploy(deploy_time,return_time)
puts "Deploying"
end
end
class MineralProbe < Probe
def deploy(deploy_time)
puts "Preparing sample chamber"
super(deploy_time, Time.now + 2 * 60 * 60) # Call base function
end
end
Class Method (static)
class Shapeship
def self.thruster_count
2
end
end
==Class Variables (static)==
<syntaxhighlight lang="ruby">
class Shapeship
@@thruster_count = 2
end
Method Visibility
Standard Methods
Two approaches you can add the private word without the # or provide a list of private functions e.g. private:foo1, foo2
class Shapeship
def launch
batten_hatches
end
# private
def batten_hatches
put "batten_hatches"
end
private:batten_hatches
end
class SpritelyShapeship < Spaceship
def initialize
batten_hatches # Ok from derived class
end
end
Class Methods (statics)
Different for statics oops class methods
class SpaceShip
def self.disable_engine
# Blah, Blah, Blah
end
private :disable_engine # Does not work
private_class_method:disable_engine
end
Summary
- public is the default
- private mean "can't be called with an explicit receiver"
- private_class_method is private for class methods
- protected means "allow access for other objects of the same class"
- private and protected are not used much
Monkey Patching
Gosh this looks awful, ruby replaces the function whilst running.
class SpaceShip
def insane
put "insane"
end
end
spaceShip = SpaceShip.new
class SpaceShip
def insane
put "insane 2"
end
end
puts spaceShip.insane
Modules
Namespaces
This is used to create namespaces for methods and classes and can be nested
module API
def self.hatch_list
# blah
end
end
hatches = API.hatch_list
module Spacestuff
class Spaceship
end
end
ship = SpaceStuff::Spaceship.new
module A
module B
class Test
end
end
end
testy = A::B::Test.new
Mixins
Instance Mixins
This is a way of combining functionality into different classes. Define a module and then include it in your class.
module AirControl
def measure_oxygen
# blah
end
end
class Spaceship
include AirControl
# blah
end
ship = Spaceship.new
ship.measure_oxygen
Class Method (static method) Mixins
Using extend can be used to mixin class methods
class Spaceship
extend AirControl
# blah
end
Spaceship.measure_oxygen
Including Static and Non Static Mixins
You can provide the hook method included in your module to reduce the need to include and extend.
module Docking
module ClassMethods
def get_default_config
#
end
end
def self.included(base)
base.extend(ClassMethods)
end
def dock
end
end
class Spaceship
include Docking
end
Methods
Default parameters
def make_cake(type = :type_of_cake, size = calculate_cake_size())
end
def make_cake(type = :type_of_cake, size = (type == :type_of_cake ? :x : :m))
end
Variable parameters
Works similar to python
def produce_fleet(days_to_complete, *type)
end
product_fleet(10, :freighter, :freighter,, :explorer)
# or
types = [:freighter, :freighter, :explorer]
product_fleet(10, types)
Keyword Arguments
Straight forward
def produce_fleet (days: days_to_complete, types: *type)
end
Like python double ** will be a dictionary
def produce_spaceship(type = :freighter sizxe: :m, **custom_components)
components = {engine: :standard,
seats: :standand,
subwoofer: :none}
components.merge!(custom_components)
#
end
# engine and seats are custom
produce_spaceship(:yacht,
size: :s,
engine: :rolls_royce,
seats: :leather)
Operator Overloading
Introduction
For the equals operator you can overload your class
class SpaceShip
attr_reader :name
def initialize(name
@name = name
end
def ==(other)
name == other.name
end
end
Further Examples
Here we overload [], << and +
class Spacehsip
attr_reader :name
attr_reader :speed
def initialize(name)
@name = name
@cargo = []
@speed = 0
@vessels = Hash.new { [] }
def []=(type, vehicles)
@vessels[type] = vehicles
end
def [](type)
@vessels[type]
end
def <<(cargo)
@cargo << cargo
end
def +@
@speed +10
end
end
ship1 = Spaceship.new("Serenity")
# Example of []
class Landers; end
ship1[:landers] = [Lander.new, Lander.new]
puts "Landers: #{ship1[:landers].inspect}"
# Example of <<
class CargoPod; end
cargo_pod = CargoPod.new
ship1 << cargo_pod
p ship1
# Unary + Note the @ sign
+ship1
put "Speed: #{ship1.speed}"
send and delegates
You can use send to call a method on a class eg.
myclassInstance.send(myclassMethod())
Given this you can use it as a delegate eg.
case input
when :up_arrow then ship.tilt_up
when :down_arrow then ship.tilt_down
when :left_arrow then ship.tilt_left
when :right_arrow then ship.tilt_right
end
# could be written
handlers = {
up_arrow: tilt_up
down_arrow: tilt_down
left_arrow: tilt_left
right_arrow: tilt_right}
ship.send(handlers[input])
method_missing
When you send a message to an object, the object executes the first method it finds on its method lookup path with the same name as the message. If it fails to find any such method, it raises a NoMethodError exception - unless you have provided the object with a method called method_missing. The method_missing method is passed the symbol of the non-existent method, an array of the arguments that were passed in the original call and any block passed to the original method.
method_missing is in part a safety net: It gives you a way to intercept unanswerable messages and handle them gracefully. See the example - p012zmm.rb below.
class Dummy
def method_missing(m, *args, &block)
puts "There's no method called #{m} here -- please try again."
end
end
Dummy.new.anything
The output is:
>ruby p012zmm.rb There's no method called anything here -- please try again. >Exit code: 0
You are also responsible for maintaining the method_missing signature. It's possible to write a hook that captures only a missing method's name while ignoring its arguments and associated block.
General
Organizing Code
Here is a typical example
product_deep_space - bin - doc - lib - deep_space - spaceship.rb - probe.rb - deep_space.rb - test - init.rb
To include a file use __dir__ (current path)
require '#{__dir__}/deep_space/spaceship' require_relative 'deep_space/spaceship' require 'json'
You can look at the load path using $LOAD_PATH
Gems
Introduction
Gems are the equivalent of nuget packages.
Sites
The main site is [[1]] or [[2]]
Usage
Here is some examples
gem install log4r gem list
Bundler
This allows you to provide a list of dependency for your app. You put these in a Gemfile.
source 'https://rubygems.org'
gem 'pg', '0.13.2' gem 'haml', '3.1.6'
To install you can then do
bundle install
Testing
Test Driven Development (TDD)
An example for this is mini test
Behaviour Driven Development (BDD)
An example for this is cucumber
Packaging
Type of packaging
- Source control + bundler
- Package as a gem
Gem Example
- Create a gemspec file
- Run gem build <gemspec file>
- Run gem install <package>
- Run gem list