Ruby NotesRussell Bateman |
I'm not super-serious about learning Ruby at this point, but my ability to Chef stands greatly impaired until I do learn enough of this language. These are some short notes I'm keeping separate from a Ruby training exercise I tackled. To read these is to begin to see Ruby through the eyes of this old C and now Java programmer.
These notes are very rough right now and mostly motivated by the training I followed. Most of the examples are just slapped from there onto this page without changing them to be more to the point. This said, however, they can be pasted into irb or files, then executed to see their output.
I need a place early on where I can deposit notes on this language until I become more at ease. This will help me avoiding re-looking things up. Consequently, they will be augmented and polished very dynamically over the next while.
-
zenspider.com: Ruby QuickRef, it's got everything!
-
tutorialspoint: Simply Easy Learning: Ruby Programming, it's got
everything too!
-
Google.com (Ben, voyons !)
I use the following HTML/CSS constructs to frame my examples. Inside the console output should be what you'd see if you scraped the file contents or source code boxes into a file and executed them using Ruby.
Source code |
File contents |
Console output |
Here and there are mentions of best practice that I noted while completing the Ruby training exercises. Some of these are interesting, some are not and some may not appear interesting at first, but may become so with more experience. Some of this advice smells like an absence of TDD. It all seemed worth noting until more experience comes. This said, here are best practices for...
Well, odd at first until you get used to it. Some of this is by convention, some is imposed by Ruby. Also, remember that Ruby is, above all, a scripting language.
@ | sigil* denoting a variable holding an object/instance of a class |
@@ | sigil denoting a class variable |
$ | sigil denoting a global variable |
% | combined with other stuff (see below) |
=> | called "hashrocket", used to assign values to tuples in hashes |
self | sort of like this in Java |
nil | sole instance of NilClass |
true | sole instance of TrueClass |
false | sole instance of FalseClass |
__FILE__ | current source-file name |
__LINE__ | current source-file line |
$DEBUG | status of the -d command-line option |
$FILENAME | current input file from ARGF ARGF.filename |
$LOAD_PATH | load path for scripts and binary modules by load or require |
$stderr | current standard error output |
$stdout | current standard output |
$stdin | current standard input |
$VERBOSE | verbose flag, set by -v command-line option |
# | single evaluation: the sum of x and y is #{ x+y } |
%q, %Q | to initialize string arrays |
%w, %W | to initialize string arrays |
: | symbol; guaranteed unique, no need to initialize |
See more stuff like this at Ruby QuickRef. |
* a variable without a sigil is a local variable and scoped to the enclosing method, block or module.
+ | plus |
- | minus |
/ | slash |
* | asterisk |
% | percent |
< | less-than |
> | greater-than |
<= | less-than-equal |
>= | greater-than-equal |
(Not sure where to put this.)
A symbol is like a variable, but it's constant, guaranteed unique and there's no need to initialize it to be "something". Here is an exercise, using object_id (sort of like Java hashCode()):
puts "Oregon".object_id # (string) puts "Oregon".object_id # (different string!) puts :state.object_id # symbol puts :state.object_id # (yup, it's the same thing) string = "Oregon" # here's the string "Oregon" puts string.to_sym # make it a symbol puts string.to_sym.object_id # get its object id puts string.object_id # and its string object id |
4621300 4621120 163048 163048 Oregon 455788 16883940 |
When to use a string or a symbol?
When the contents of the object are important, use a string, "Oregon". If only the identity of the object is significant, use a symbol, :state.
So, a Ruby symbol is a sort of constant variable that need not be predefined because...
In fact, Ruby symbols aren't constants, instead they are "a consistent name within code".
A symbol object is created by prefixing a Ruby operator, string, variable, constant, method, class or module name with a colon.
Symbols are particularly useful in creating hash maps in which there is to be a careful distinction between keys and values. Thus, the two bits of code below have the same effect, the second and third using symbols. (The third example also uses syntax new to Ruby 1.9.)
french_hashmap = { 'dog' => "chien", 'cat' => "chat", 'ant' => "fourmie" } puts french_hashmap[ 'dog' ] |
chien |
french_hashmap = { :dog => "chien", :cat => "chat", :ant => "fourmie" } puts french_hashmap[ :cat ] |
chat |
french_hashmap = { dog: "chien", cat: "chat", :ant: "fourmie" } puts french_hashmap[ :ant ] |
fourmie |
Reference:
Ruby Symbols.
Example using hash maps:
Ruby Hashes.
args.rb: first, second, third = ARGV puts "The script is called: #{$0}" puts "Your first variable is: #{first}" puts "Your second variable is: #{second}" puts "Your third variable is: #{third}" |
$ ruby args.rb 1st 2nd 3rd
The script is called: args.rb
Your first variable is: 1st
Your second variable is: 2nd
Your third variable is: 3rd
|
methods-and-args.rb: # this one is like your scripts with argv def puts_two( *args ) arg1, arg2 = args puts "arg1: #{arg1}, arg2: #{arg2}" end # ok, that *args is actually pointless, we can just do this def puts_two_again( arg1, arg2 ) puts "arg1: #{arg1}, arg2: #{arg2}" end # this just takes one argument def puts_one( arg1 ) puts "arg1: #{arg1}" end # this one takes no arguments def puts_none() puts "I got nothin'." end puts_two( "Zed","Shaw" ) puts_two_again( "Zed","Shaw" ) puts_one( "First!" ) puts_none() |
$ ruby methods-and-args.rb
arg1: Zed, arg2: Shaw
arg1: Zed, arg2: Shaw
arg1: First!
I got nothin'.
|
two-returns.rb: def return_two_things return true, false end two_things = return_two_things puts two_things[ 0 ] puts two_things[ 1 ] |
$ ruby methods-and-args.rb
true
false
|
The relational operators and other elements of logic in Ruby are:
Ruby's array include? method returns true/false that some value is an element of (in) the array. Here's some playing around to illustrate that:
~/dev/ruby $ irb irb(main):001:0> array = [ 'cat', 'dog', 'ant' ] => ["cat", "dog", "ant"] irb(main):002:0> array.include? "dog" => true irb(main):003:0> array = [ 1, 2, 3 ] => [1, 2, 3] irb(main):004:0> array.include?( 3 ) => true irb(main):005:0> array.include? 2 => true
Ruby conditionals | |
---|---|
if | if expression true-part end |
if | if expression true-part elsif expression true-part end |
if | if expression true-part else false-part end |
people = 20
cats = 30
dogs = 15
if people < cats
puts "Too many cats! The world is doomed!"
end
if people > cats
puts "Not many cats! The world is saved!"
end
if people < dogs
puts "The world is drooled on!"
end
if people > dogs
puts "The world is dry!"
end
dogs += 5
if people >= dogs
puts "People are greater than or equal to dogs."
end
if people <= dogs
puts "People are less than or equal to dogs."
end
if people == dogs
puts "People are dogs."
end
if( people == dogs ) # added parens just to see if it works.)
puts "My parens work."
end
|
Too many cats! The world is doomed! The world is dry! People are greater than or equal to dogs. People are less than or equal to dogs. People are dogs. My parens work. |
people = 30 cars = 40 buses = 15 if cars > people puts "We should take the cars." elsif cars < people puts "We should not take the cars." else puts "We can't decide." end if buses > cars puts "That's too many buses." elsif buses < cars puts "Maybe we could take the buses." else puts "We still can't decide." end if people > buses puts "Alright, let's just take the buses." else puts "Fine, let's stay home then." end |
We should take the cars. Maybe we could take the buses. Alright, let's just take the buses. |
It's useful in some settings, such as using irb, to keep parts of full statements or several statements on a single line. In Ruby, the semicolon is the statement separator. As you can see, these are conveniently identical:
found = false if found puts "Found!" else puts "Not found!" end |
irb(main):001:0> if found irb(main):002:1> puts "Found!" irb(main):003:1> else irb(main):004:1* puts "Not found!" irb(main):005:1> end Not found! => nil |
found = false if found ; puts "Found!"; else; puts "Not found!"; end |
irb(main):006:0> if found ; puts "Found!" ; else ; puts "Not found!" ; end Not found! => nil |
Ruby control statements (loops) | |
---|---|
for | for variable in collection ... end |
while | while expression ... end |
the_count = [ 1, 2, 3, 4, 5 ] fruits = [ 'apples', 'oranges', 'pears', 'apricots' ] change = [ 1, 'pennies', 2, 'dimes', 3, 'quarters' ] # this first kind of for-loop goes through an array for number in the_count puts "This is count #{number}" end # same as above, but using a block instead fruits.each do |fruit| puts "A fruit of type: #{fruit}" end # also we can go through mixed arrays too for i in change puts "I got #{i}" end # we can also build arrays, first start with an empty one elements = [] # then use a range object to do 0 to 5 counts for i in ( 0..5 ) puts "Adding #{i} to the list." # push is a function that arrays understand elements.push( i ) end # now we can puts them out too for i in elements puts "Element was: #{i}" end |
This is count 1 This is count 2 This is count 3 This is count 4 This is count 5 A fruit of type: apples A fruit of type: oranges A fruit of type: pears A fruit of type: apricots I got 1 I got pennies I got 2 I got dimes I got 3 I got quarters Adding 0 to the list. Adding 1 to the list. Adding 2 to the list. Adding 3 to the list. Adding 4 to the list. Adding 5 to the list. Element was: 0 Element was: 1 Element was: 2 Element was: 3 Element was: 4 Element was: 5 |
i = 0 numbers = [] while i < 6 puts "At the top i is #{i}" numbers.push( i ) i = i + 1 puts "Numbers now: #{numbers}" puts "At the bottom i is #{i}" end puts "The numbers: " for num in numbers puts num end |
At the top i is 0 Numbers now: [0] At the bottom i is 1 At the top i is 1 Numbers now: [0, 1] At the bottom i is 2 At the top i is 2 Numbers now: [0, 1, 2] At the bottom i is 3 At the top i is 3 Numbers now: [0, 1, 2, 3] At the bottom i is 4 At the top i is 4 Numbers now: [0, 1, 2, 3, 4] At the bottom i is 5 At the top i is 5 Numbers now: [0, 1, 2, 3, 4, 5] At the bottom i is 6 The numbers: 0 1 2 3 4 5 |
You can have arrays of anything.
There are some cool things about arrays in Ruby. Check out Array for methods. There is no lack of functionality. Ellipese are used to shorten the output below.
animals = [ 'bear', 'tiger', 'penguin', 'zebra' ] colors = [ "red", "yellow", "blue", "green" ] digits = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] hexdigits = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' ] hexnumbers = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f ] puts animals puts colors puts digits puts hexdigits puts hexnumbers |
bear tiger penguin zebra red yellow blue green 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 ... e f 0 1 2 3 4 ... 14 15 |
%w( foo bar ) is a shortcut for [ "foo", "bar" ] in Ruby for writing an array of strings separated by spaces instead of commas and without the need to quote.
In Ruby, a hash is what is more properly termed a map or, if the notion of the underlying indexing strategy is to be included, a hash map.
Hash maps do not have order. If you print one out, it comes out in whatever order they're stored in, which from my observation is, at least at first, the order in which they're built.
This construct is what imparts the greatest amount of obfuscation to reading Ruby when coming from another language. It's the "hash rocket" that does it.
# create a mapping of state to abbreviation states = { 'Oregon' => 'OR', 'Florida' => 'FL', 'California' => 'CA', 'New York' => 'NY', 'Michigan' => 'MI' } # create a basic set of states and some cities in them cities = { 'CA' => 'San Francisco', 'MI' => 'Detroit', 'FL' => 'Jacksonville' } # add some more cities cities['NY'] = 'New York' cities['OR'] = 'Portland' # puts out some cities puts '-' * 10 puts "NY State has: ", cities[ 'NY' ] puts "OR State has: ", cities[ 'OR' ] # puts some states puts '-' * 10 puts "Michigan's abbreviation is: ", states[ 'Michigan' ] puts "Florida's abbreviation is: ", states[ 'Florida' ] # do it by using the state then cities dictionary puts '-' * 10 puts "Michigan has: ", cities[ states[ 'Michigan' ] ] puts "Florida has: ", cities[ states[ 'Florida' ] ] # puts every state abbreviation puts '-' * 10 for state, abbrev in states puts "%s is abbreviated %s" % [ state, abbrev ] end # puts every city in state puts '-' * 10 for abbrev, city in cities puts "%s has the city %s" % [ abbrev, city ] end # now do both at the same time puts '-' * 10 for state, abbrev in states puts "%s state is abbreviated %s and has city %s" % [ state, abbrev, cities[ abbrev ] ] end puts '-' * 10 # if it's not there you get nil state = states[ 'Texas' ] if not state puts "Sorry, no Texas." end # get a city with a default value city = cities[ 'TX' ] || 'Does Not Exist' puts "The city for the state 'TX' is: %s" % city |
---------- NY State has: New York OR State has: Portland ---------- Michigan's abbreviation is: MI Florida's abbreviation is: FL ---------- Michigan has: Detroit Florida has: Jacksonville ---------- Oregon is abbreviated OR Florida is abbreviated FL California is abbreviated CA New York is abbreviated NY Michigan is abbreviated MI ---------- CA has the city San Francisco MI has the city Detroit FL has the city Jacksonville NY has the city New York OR has the city Portland ---------- Oregon state is abbreviated OR and has city Portland Florida state is abbreviated FL and has city Jacksonville California state is abbreviated CA and has city San Francisco New York state is abbreviated NY and has city New York Michigan state is abbreviated MI and has city Detroit ---------- Sorry, no Texas. The city for the state 'TX' is: Does Not Exist |
Beginning in Ruby 1.9, the standard "hashrocket" syntax was made optional. The first two statements are equivalent, but the third one is not (as I understand it).
languages = { 'French' => 'fr', 'German' => 'de' } languages = { French: 'fr', 'German': => 'de' } languages = { "French" => 'fr', German => 'de' }
A missing key in the hash behaves as following (look for primary in the replica sets below). The expression, if replica_1[ :primary ], will not match when replica_1[ :primary ] is set to false or nil or is missing altogether (as here).
require 'pp' replica_1 = { :hostname => "16.86.193.100", :port => 27017 } replica_2 = { :hostname => "16.86.193.102", :port => 27019, :primary => true } puts "replica_1=", replica_1 if replica_1[ :primary ] puts "replica_1[ :primary ] is set" else puts "replica_1[ :primary ] is not set/true" end puts "replica_2=", replica_2 if replica_2[ :primary ] puts "replica_2[ :primary ] is set/true" else puts "replica_2[ :primary ] is not set" end |
replica_1={:hostname=>"16.86.193.100", :port=>27017} replica_1[ :primary ] is not set/true replica_2={:hostname=>"16.86.193.102", :port=>27019, :primary=>true} replica_2[ :primary ] is set/true |
CAVEAT: It seems to me that half the time, this doesn't work in running code. It's always "true" or "non-nil" with the result that what I'm trying not to do get done anyway. After hair-pulling and abuse by a couple of members of the stackoverflow.com Ruby community, I have resolved that it's much better to set and check for something specific. Don't rely on the example above working. I advise instead something like:
require 'pp' replica_1 = { :hostname => "16.86.193.100", :port => 27017 } replica_2 = { :hostname => "16.86.193.102", :port => 27019, :primary => true } puts "replica_1=", replica_1 if replica_1[ :primary ] == true puts "replica_1[ :primary ] is set" else puts "replica_1[ :primary ] is not set/true" end puts "replica_2=", replica_2 if replica_2[ :primary ] == true puts "replica_2[ :primary ] is set/true" else puts "replica_2[ :primary ] is not set" end |
replica_1={:hostname=>"16.86.193.100", :port=>27017} replica_1[ :primary ] is not set/true replica_2={:hostname=>"16.86.193.102", :port=>27019, :primary=>true} replica_2[ :primary ] is set/true |
These are here to be waded through and compared.
class Song def initialize( lyrics ) @lyrics = lyrics end def sing_me_a_song() for line in @lyrics puts line end end end happy_bday = Song.new( [ "Happy birthday to you", "I don't want to get sued", "So I'll stop right there" ] ) bulls_on_parade = Song.new( [ "They rally around the family", "With pockets full of shells" ] ) happy_bday.sing_me_a_song() bulls_on_parade.sing_me_a_song() |
Happy birthday to you I don't want to get sued So I'll stop right there They rally around the family With pockets full of shells |
class Parent def implicit() puts "PARENT implicit()" end end class Child < Parent end dad = Parent.new() son = Child.new() dad.implicit() son.implicit() |
PARENT implicit() PARENT implicit() |
class Parent def override() puts "PARENT override()" end end class Child < Parent def override() puts "CHILD override()" end end dad = Parent.new() son = Child.new() dad.override() son.override() |
PARENT override() CHILD override() |
class Parent def altered() puts "PARENT altered()" end end class Child < Parent def altered() puts "CHILD, BEFORE PARENT altered()" super() puts "CHILD, AFTER PARENT altered()" end end dad = Parent.new() son = Child.new() dad.altered() son.altered() |
PARENT altered() CHILD, BEFORE PARENT altered() PARENT altered() CHILD, AFTER PARENT altered() |
class Parent def override() puts "PARENT override()" end def implicit() puts "PARENT implicit()" end def altered() puts "PARENT altered()" end end class Child < Parent def override() puts "CHILD override()" end def altered() puts "CHILD, BEFORE PARENT altered()" super() puts "CHILD, AFTER PARENT altered()" end end dad = Parent.new() son = Child.new() dad.implicit() son.implicit() dad.override() son.override() dad.altered() son.altered() |
PARENT implicit() PARENT implicit() PARENT override() CHILD override() PARENT altered() CHILD, BEFORE PARENT altered() PARENT altered() CHILD, AFTER PARENT altered() |
class Other def override() puts "OTHER override()" end def implicit() puts "OTHER implicit()" end def altered() puts "OTHER altered()" end end class Child def initialize() @other = Other.new() end def implicit() @other.implicit() end def override() puts "CHILD override()" end def altered() puts "CHILD, BEFORE OTHER altered()" @other.altered() puts "CHILD, AFTER OTHER altered()" end end son = Child.new() son.implicit() son.override() son.altered() |
OTHER implicit() CHILD override() CHILD, BEFORE OTHER altered() OTHER altered() CHILD, AFTER OTHER altered() |
Any object can be nil or empty (or contain something that's not empty). The distinction between nil and empty is an important one.
irb(main):001:0> node = Hash.new { | h,k | h[ k ] = Hash.new( &h.default_proc ) } => {} irb(main):002:0> node[ :crap ] = "fun" => "fun" irb(main):003:0> puts node[ :crap ] fun => nil irb(main):004:0> puts node[ :poop ] {} => nil irb(main):005:0> thing = node[ :poop ] => {} irb(main):006:0> puts thing {} => nil irb(main):007:0> if thing.nil? irb(main):008:1> puts "thing is nil" irb(main):009:1> else irb(main):010:1* puts "thing isn't nil" irb(main):011:1> end thing isn't nil => nil irb(main):012:0> thing.nil? => false irb(main):013:0> thing.empty? => true irb(main):014:0> if thing.empty? irb(main):015:1> puts "thing is empty" irb(main):016:1> else irb(main):017:1* puts "thing isn't empty" irb(main):018:1> end thing is empty => nil
node = Hash.new { | h,k | h[ k ] = Hash.new( &h.default_proc ) } node[ :crap ] = "fun" puts node[ :crap ] puts node[ :poop ] thing = node[ :poop ] puts thing if thing.nil? puts "thing is nil" else puts "thing isn't nil" end thing.nil? thing.empty? if thing.empty? puts "thing is empty" else puts "thing isn't empty" end
irb(main):005:0> Integer( 3 ) => 3 irb(main):006:0> Integer( 3.2 ) => 3 irb(main):007:0> Integer( cat ) NameError: undefined local variable or method `cat' for main:Object from (irb):7 from /usr/bin/irb:12:in `<main>' irb(main):008:0> Integer( "cat" ) ArgumentError: invalid value for Integer(): "cat" from (irb):8:in `Integer' from (irb):8 from /usr/bin/irb:12:in `<main>'
def convert_number( number ) begin Integer( number ) rescue ArgumentError puts "ArgumentError--not a number" nil end end
raise ParserError.new( "Some error statement." )
Ruby | Java |
---|---|
begin ... rescue exception ... end |
try { ... } catch( exception ) { ... } |
raise exception |
throw exception |
projects/lexicon/lib/lexicon.rb: class Lexicon attr_accessor :lexicon, :dictionary def initialize() @dictionary = { :north => :direction, :south => :direction, :east => :direction, :west => :direction, :go => :verb, :kill => :verb, :eat => :verb, :bear => :noun, :princess => :noun, :the => :stop, :in => :stop, :of => :stop, } end Pair = Struct.new( :qualifier, :value ) def scan(list) pairs = [] list.split.map do | value | puts value if value.match( /^[0-9]+$/ ) pairs.push( Pair.new( :number, value.to_i ) ) elsif qualifier = @dictionary[ value.intern ] pairs.push( Pair.new( qualifier, value ) ) else pairs.push( Pair.new( :error, value.to_s ) ) end end pairs end end projects/lexicon/test/test_lexicon.rb: require 'test/unit' require_relative "../lib/lexicon/lexicon" class LexiconTests < Test::Unit::TestCase Pair = Lexicon::Pair @@lexicon = Lexicon.new() def test_directions() assert_equal( [ Pair.new( :direction, 'north' ) ], @@lexicon.scan( "north" ) ) result = @@lexicon.scan( "north south east" ) assert_equal( result, [ Pair.new( :direction, 'north' ), Pair.new( :direction, 'south' ), Pair.new( :direction, 'east' ) ] ) end def test_verbs() assert_equal( @@lexicon.scan("go"), [Pair.new(:verb, 'go' ) ] ) result = @@lexicon.scan( "go kill eat" ) assert_equal( result, [ Pair.new( :verb, 'go' ), Pair.new( :verb, 'kill' ), Pair.new( :verb, 'eat' ) ] ) end def test_stops() assert_equal( @@lexicon.scan( "the" ), [Pair.new(:stop, 'the' ) ] ) result = @@lexicon.scan( "the in of" ) assert_equal( result, [ Pair.new( :stop, 'the' ), Pair.new( :stop, 'in' ), Pair.new( :stop, 'of' ) ] ) end def test_nouns() assert_equal( @@lexicon.scan( "bear" ), [Pair.new(:noun, 'bear' ) ] ) result = @@lexicon.scan( "bear princess" ) assert_equal( result, [ Pair.new( :noun, 'bear' ), Pair.new( :noun, 'princess' ) ] ) end def test_numbers() assert_equal( @@lexicon.scan( "1234" ), [ Pair.new( :number, 1234 ) ] ) result = @@lexicon.scan( "3 91234 " ) assert_equal( result, [ Pair.new( :number, 3 ), Pair.new( :number, 91234 ) ] ) end def test_errors() assert_equal( @@lexicon.scan( "ASDFADFASDF" ), [ Pair.new( :error, 'ASDFADFASDF' ) ] ) result = @@lexicon.scan( "bear IAS princess" ) assert_equal( result, [ Pair.new( :noun, 'bear' ), Pair.new( :error, 'IAS' ), Pair.new( :noun, 'princess' ) ] ) end end |
$ ruby test_lexicon.rb
Run options:
# Running tests:
north
north
south
east
.ASDFADFASDF
bear
IAS
princess
.bear
bear
princess
.1234
3
91234
.the
the
in
of
.go
go
kill
eat
.
Finished tests in 0.000920s, 6519.4432 tests/s, 13038.8863 assertions/s.
6 tests, 12 assertions, 0 failures, 0 errors, 0 skips
|
A module is a way to package up methods so that they don't conflict with anything else in Ruby or your Ruby code. In essence, the Ruby module wraps all the methods with a name. You consume module methods merely by stating the module name, adding the dot operator and then the name of the method you want to use.
Parser.parse_verb( word_list )
formatter = "%s %s %s %s" puts formatter % [ 1, 2, 3, 4 ] puts formatter % [ "one", "two", "three", "four" ] puts formatter % [ true, false, false, true ] puts formatter % [ formatter, formatter, formatter, formatter ] puts formatter % [ "I had this thing.", "That you could type up right.", "But it didn't sing.", "So I said goodnight." ] |
1 2 3 4 one two three four true false false true %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s I had this thing. That you could type up right. But it didn't sing. So I said goodnight. |
Now observe the artificial insertion of newlines into a string. Observe also the special PARAGRAPH-delimiting operator and the redirection operator (<<) used to "redirect" the paragraph to the puts method.
days = "Mon Tue Wed Thu Fri Sat Sun" months = "Jan\nFeb\nMar\nApr\nMay\nJun\nJul\nAug" puts "Here are the days: ", days puts "Here are the months: ", months puts <<PARAGRAPH There's something going on here. With the PARAGRAPH thing We'll be able to type as much as we like. Even 4 lines if we want, or 5, or 6. PARAGRAPH |
Here are the days: Mon Tue Wed Thu Fri Sat Sun Here are the months: Jan Feb Mar Apr May Jun Jul Aug There's something going on here. With the PARAGRAPH thing We'll be able to type as much as we like. Even 4 lines if we want, or 5, or 6. |
Ruby's gets method, its name perhaps inspired (just as puts) by the C Standard Library functions, fetches a string from stdin. The exercises will more difficult to document from here on since they'll require interaction. This is, after all, the beginning of script-writing.
The chomp() method truncates while strip() trims.
print "How old are you? " age = gets.chomp() print "How tall are you? " height = gets.chomp() print "How much do you weigh? " weight = gets.chomp() puts "So, you're #{age} years old, #{height} tall and #{weight} heavy." |
How old are you? 65 How tall are you? 72 How much do you weigh? 568 So, you're 65 years old, 72 tall and 568 heavy. |
user = ARGV.first prompt = '> ' puts "Hi #{user}, I'm the #{$0} script." puts "I'd like to ask you a few questions." puts "Do you like me #{user}?" print prompt likes = STDIN.gets.chomp() puts "Where do you live #{user}?" print prompt lives = STDIN.gets.chomp() puts "What kind of computer do you have?" print prompt computer = STDIN.gets.chomp() puts <<MESSAGE Alright, so you said #{likes} about liking me. You live in #{lives}. Not sure where that is. And you have a #{computer} computer. Nice. MESSAGE |
$ ruby prompt.rb Jack in the Bean Stalk Hi Jack, I'm the prompt.rb script. I'd like to ask you a few questions. Do you like me ? > Yes Where do you live ? > Here What kind of computer do you have? > Hewlett-Packard Alright, so you said Yes about liking me. You live in Here. Not sure where that is. And you have a Hewlett-Packard computer. Nice. |
filename = ARGV.first prompt = "> " txt = File.open( filename ) puts "Here's your file: #{filename}" puts txt.read() close() puts "I'll also ask you to type it again:" print prompt file_again = STDIN.gets.chomp() txt_again = File.open( file_again ) puts txt_again.read() again.close()
Ruby file I/O | |
---|---|
open( filename, action ) | Opens filename for action {r|w|a}. |
close() | Closes the file. |
read() | Reads the file's contents. |
readline() | Reads a single line from file. |
truncate() | Empties the open file. |
write() | Writes to the file if opened with w. |
exists | Returns true/false that the file exists. |
filename = ARGV.first script = $0 puts "We're going to erase #{filename}." puts "If you don't want that, hit CTRL-C (^C)." puts "If you do want that, hit RETURN." print "? " STDIN.gets puts "Opening the file..." target = File.open(filename, 'w') puts "Truncating the file. Goodbye!" target.truncate(target.size) puts "Now I'm going to ask you for three lines." print "line 1: "; line1 = STDIN.gets.chomp() print "line 2: "; line2 = STDIN.gets.chomp() print "line 3: "; line3 = STDIN.gets.chomp() puts "I'm going to write these to the file." target.write(line1) target.write("\n") target.write(line2) target.write("\n") target.write(line3) target.write("\n") puts "And finally, we close it." target.close()
from_file, to_file = ARGV script = $0 puts "Copying from #{from_file} to #{to_file}" # we could do these two on one line too, how? input = File.open(from_file) indata = input.read() puts "The input file is #{indata.length} bytes long" puts "Does the output file exist? #{File.exists? to_file}" puts "Ready, hit RETURN to continue, CTRL-C to abort." STDIN.gets output = File.open(to_file, 'w') output.write(indata) puts "Alright, all done." output.close() input.close()
try-library.rb: require 'open-uri' open("http://www.ruby-lang.org/en") do |f| f.each_line {|line| p line} puts f.base_uri # <URI::HTTP:0x40e6ef2 URL:http://www.ruby-lang.org/en/> puts f.content_type # "text/html" puts f.charset # "iso-8859-1" puts f.content_encoding # [] puts f.last_modified # Thu Dec 05 02:45:02 UTC 2002 end |
If you're behind a firewall, this won't work. $ export http_proxy="http://web-proxy.austin.hp.com:8080" $ ruby try-library.rb "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n" " \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n" "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" " <head>\n" " <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />\n" . . . |
You can create a skeleton like this one, then clone it to start a new Ruby project.
$ tree projects projects +-- skeleton + bin + lib | + NAME | | ` version.rb | ` NAME.rb + NAME.gemspec ` test ` test_NAME.rb 5 directories, 4 files
(Note that NAME everywhere is to be replaced by the name of your project.)
# -*- encoding: utf-8 -*-
$:.push File.expand_path( "../lib", __FILE__ )
require "NAME/version"
Gem::Specification.new do | s |
s.name = "NAME"
s.version = NAME::VERSION
s.authors = [ "Russ Bateman" ]
s.email = [ "[email protected]" ]
s.homepage = ""
s.summary = %q{TODO: Write a gem summary}
s.description = %q{TODO: Write a gem description}
s.rubyforge_project = "NAME"
s.files = `git ls-files`.split( "\n" )
s.test_files = `git ls-files -- { test, spec,features }/*`.split( "\n" )
s.executables = `git ls-files -- bin/*`.split( "\n" ).map{ | f | File.basename( f ) }
s.require_paths = [ "lib" ]
end
Steps to use this skeleton.
This is just like JUnit testing...
projects/fun/lib/fun.rb: class Room attr_accessor :name, :description, :paths def initialize( name, description ) @name = name @description = description @paths = {} end def go( direction ) @paths[ direction ] end def add_paths( paths ) @paths.update( paths ) end end projects/fun/test/test_fun.rb: require 'test/unit' require_relative '../lib/fun' class MyUnitTests < Test::Unit::TestCase def test_room() gold = Room.new( "GoldRoom", """This room has gold in it you can grab. There's a door to the north.""" ) assert_equal( gold.name, "GoldRoom") assert_equal( gold.paths, {} ) end def test_room_paths() center = Room.new( "Center", "Test room in the center." ) north = Room.new( "North", "Test room in the north." ) south = Room.new( "South", "Test room in the south." ) center.add_paths( { :north => north, :south => south } ) assert_equal( center.go( :north ), north ) assert_equal( center.go( :south ), south ) end def test_map() start = Room.new( "Start", "You can go west and down a hole." ) west = Room.new( "Trees", "There are trees here, you can go east." ) down = Room.new( "Dungeon", "It's dark down here, you can go up." ) start.add_paths( { :west => west, :down => down } ) west.add_paths( { :east => start } ) down.add_paths( { :up => start } ) assert_equal( start.go( :west ), west ) assert_equal( start.go( :west ).go( :east ), start ) assert_equal( start.go( :down ).go( :up ), start ) end end |
$ ruby test_fun.rb
Run options:
# Running tests:
...
Finished tests in 0.000536s, 5599.6790 tests/s, 13065.9176 assertions/s.
3 tests, 7 assertions, 0 failures, 0 errors, 0 skips
|
Useful links to articles about the gemspec file.
There are myriad ways, some better than others. From Linux, this works well because Ruby implements the backtick.
run-ruby-file.rb:#!/usr/bin/ruby puts "Run a selected Ruby file..." output = `ruby ./hello-world.rb` puts output hello-world.rb:#!/usr/bin/ruby # Some Ruby code to run... print "Hello, World!" |
$ ruby test-test.rb
Hello, World!
|
Watch and learn. This is the answer to some questions. Ruby has its own package manager named Gem. (This is like Perl and PHP which have their own package managers.) You'll want to get root on your system and approximate the following in order to equip your host with Ruby developer software.
~/dev/ruby # dpkg --list | grep ruby ii libruby1.8 1.8.7.352-2ubuntu1.3 Libraries necessary to run Ruby 1.8 ii libruby1.9.1 1.9.3.0-1ubuntu2.7 Libraries necessary to run Ruby 1.9.1 ii ruby1.9.1 1.9.3.0-1ubuntu2.7 Interpreter of object-oriented scripting language Ruby ~/dev/ruby # aptitude install ruby1.9.1-dev The following NEW packages will be installed: ruby1.9.1-dev 0 packages upgraded, 1 newly installed, 0 to remove and 14 not upgraded. Need to get 1,212 kB of archives. After unpacking 4,072 kB will be used. Get: 1 http://archive.ubuntu.com/ubuntu/ precise-updates/main ruby1.9.1-dev amd64 1.9.3.0-1ubuntu2.7 [1,212 kB] Fetched 1,212 kB in 4s (276 kB/s) Selecting previously unselected package ruby1.9.1-dev. (Reading database ... 183671 files and directories currently installed.) Unpacking ruby1.9.1-dev (from .../ruby1.9.1-dev_1.9.3.0-1ubuntu2.7_amd64.deb) ... Setting up ruby1.9.1-dev (1.9.3.0-1ubuntu2.7) ... ~/dev/ruby # gem install rdoc-data Building native extensions. This could take a while... Fetching: rdoc-4.0.1.gem (100%) Depending on your version of ruby, you may need to install ruby rdoc/ri data: <= 1.8.6 : unsupported = 1.8.7 : gem install rdoc-data; rdoc-data --install = 1.9.1 : gem install rdoc-data; rdoc-data --install >= 1.9.2 : nothing to do! Yay! Fetching: rdoc-data-4.0.1.gem (100%) rdoc-data is only required for C ruby 1.8.7 or 1.9.1. rdoc-data is required for JRuby. To install ri data for RDoc 4.0+ run: rdoc-data --install Successfully installed json-1.8.0 Successfully installed rdoc-4.0.1 Successfully installed rdoc-data-4.0.1 3 gems installed Installing ri documentation for json-1.8.0... Installing ri documentation for rdoc-4.0.1... Installing ri documentation for rdoc-data-4.0.1... Installing RDoc documentation for json-1.8.0... Installing RDoc documentation for rdoc-4.0.1... Installing RDoc documentation for rdoc-data-4.0.1... ~/dev/ruby # rdoc-data --install
Now that it's installed, it should work, right? Well, no, it doesn't...
You get stuff like the following:
~/dev/ruby $ ri File.open
Nothing known about File
...or, after solving some missing Gem package management...
~/dev/ruby $ ri File.open /var/lib/gems/1.9.1/gems/rdoc-4.0.1/lib/rdoc/store.rb:617:in `initialize': store at \ /usr/share/ri/1.9.1/system missing file /usr/share/ri/1.9.1/system/Kernel/open-c.ri \ for Kernel::open (RDoc::Store::MissingFileError) from /var/lib/gems/1.9.1/gems/rdoc-4.0.1/lib/rdoc/store.rb:617:in `open' from /var/lib/gems/1.9.1/gems/rdoc-4.0.1/lib/rdoc/store.rb:617:in `load_method' from /var/lib/gems/1.9.1/gems/rdoc-4.0.1/lib/rdoc/ri/driver.rb:1185:in `load_method' . . . from /var/lib/gems/1.9.1/gems/rdoc-4.0.1/lib/rdoc/ri/driver.rb:356:in `run' from /var/lib/gems/1.9.1/gems/rdoc-4.0.1/bin/ri:12:in `' from /usr/local/bin/ri:19:in `load' from /usr/local/bin/ri:19:in ` '
This is because the Ruby community doesn't have it all together. I found this suggestion on stackoverflow.com for a different syntax (using the #). And yet, ri File.read does work. Go figure.
~/dev/ruby $ ri File#open
= File#open
(from ruby core)
=== Implementation from Kernel
------------------------------------------------------------------------------
open(path [, mode_enc [, perm]] [, opt]) -> io or nil
open(path [, mode_enc [, perm]] [, opt]) {|io| block } -> obj
------------------------------------------------------------------------------
Creates an IO object connected to the given stream, file, or subprocess.
If path does not start with a pipe character (``|''), treat it as the
name of a file to open using the specified mode (defaulting to ``r'').
The mode_enc is either a string or an integer. If it is an integer, it must
.
.
.
Open a subprocess using a block to receive the I/O object:
open("|-") do |f|
if f == nil
puts "in Child"
else
puts "Got: #{f.gets}"
end
end
produces:
Got: in Child
(END)
...which is wonderful and just what Exercise 15 above was looking for.
I tried these to find out about the pop() method:
$ ri String —yes, but doesn't list method pop() $ ri String.pop —no $ ri String.pop() —no $ ri String#pop —no $ ri String#pop() —no $ ri pop —yes, but as applies to all objects
Yes, there is! And it's totally ripped off from dbx/gdb, but hey, it works! See How do I debug Ruby scripts?.
$ ruby -rdebug ruby-file
Debugger help v.-0.002b Commands b[reak] [file:|class:]<line|method> b[reak] [class.]<line|method> set breakpoint to some position wat[ch] <expression> set watchpoint to some expression cat[ch] (<exception>|off) set catchpoint to an exception b[reak] list breakpoints cat[ch] show catchpoint del[ete][ nnn] delete some or all breakpoints disp[lay] <expression> add expression into display expression list undisp[lay][ nnn] delete one particular or all display expressions c[ont] run until program ends or hit breakpoint s[tep][ nnn] step (into methods) one line or till line nnn (and press Enter to repeat) n[ext][ nnn] go over one line or till line nnn (and press Enter to repeat) w[here] display frames f[rame] alias for where l[ist][ (-|nn-mm)] list program, - lists backwards nn-mm lists given lines up[ nn] move to higher frame down[ nn] move to lower frame fin[ish] return to outer frame tr[ace] (on|off) set trace mode of current thread tr[ace] (on|off) all set trace mode of all threads q[uit] exit from debugger v[ar] g[lobal] show global variables v[ar] l[ocal] show local variables v[ar] i[nstance] <object> show instance variables of object v[ar] c[onst] <object> show constants of object m[ethod] i[nstance] <obj> show methods of object m[ethod] <class|module> show instance methods of class or module th[read] l[ist] list all threads th[read] c[ur[rent]] show current thread th[read] [sw[itch]] <nnn> switch thread context to nnn th[read] stop <nnn> stop thread nnn th[read] resume <nnn> resume thread nnn p expression evaluate expression and print its value h[elp] print this help <everything else> evaluate
Gemfile is a file at the root of whatever Ruby project you're creating, including a Chef cookbook, that acts sort of like Maven's .pom in that it lists Ruby software and versions for the bundler to get on which the project depends. For example: |
Running bundle with this Gemfile produces something like the following. It can take a long time to run. |
|
source "https://rubygems.org" gem 'net-ssh', "~> 2.2.2" gem 'net-ssh-gateway' gem 'chef', "~> 11.0.0" gem 'chefspec', '>=1.0.0' gem 'librarian', ">=0.0.26" gem 'vagrant', "~> 1.0.7" gem 'knife-block' gem 'thor' gem 'rbvmomi' gem 'icontrol' |
~/ruby-fun $ bundle
Fetching gem metadata from https://rubygems.org/......
Fetching gem metadata from https://rubygems.org/..
Installing archive-tar-minitar (0.5.2)
Installing bindata (1.5.0)
Installing builder (3.2.2)
Installing erubis (2.7.0)
Installing highline (1.6.19)
Installing json (1.6.1)
Using mixlib-log (1.6.0)
Using mixlib-authentication (1.3.0)
Using mixlib-cli (1.3.0)
Using mixlib-config (1.1.2)
Installing mixlib-shellout (1.2.0)
Installing net-ssh (2.2.2)
Installing net-ssh-gateway (1.1.0)
Installing net-ssh-multi (1.1)
Installing ipaddress (0.8.0)
Using systemu (2.5.2)
Installing yajl-ruby (1.1.0)
Installing ohai (6.18.0)
Installing mime-types (1.23)
Installing rest-client (1.6.7)
Installing chef (11.0.0)
Installing multi_json (1.7.7)
Installing multi_xml (0.5.4)
Installing httparty (0.11.0)
Installing fauxhai (1.1.1)
Installing ci_reporter (1.9.0)
Installing minitest (4.7.5)
Installing minitest-chef-handler (1.0.1)
Installing rspec-core (2.14.4)
Installing diff-lcs (1.2.4)
Installing rspec-expectations (2.14.0)
Installing rspec-mocks (2.14.1)
Installing rspec (2.14.1)
Installing chefspec (1.3.1)
Installing ffi (1.9.0)
Installing childprocess (0.3.9)
Installing crack (0.1.8)
Installing gyoku (1.0.0)
Installing rack (1.5.2)
Installing httpi (0.7.9)
Installing i18n (0.6.4)
Installing savon (0.8.6)
Installing icontrol (0.3.9)
Installing knife-block (0.0.6)
Installing thor (0.18.1)
Installing librarian (0.1.0)
Installing log4r (1.1.10)
Installing mini_portile (0.5.1)
Installing net-scp (1.0.4)
Installing nokogiri (1.6.0)
Installing trollop (2.0)
Installing rbvmomi (1.6.0)
Installing vagrant (1.0.7)
Using bundler (1.3.5)
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
Post-install message from httparty:
When you HTTParty, you must party hard!
|