JETZT ONLINE BESTELLEN
First Edition September 2006
ISBN 978-0-596-52369-5
906 Seiten
EUR41.00
Weitere Informationen zu diesem Buch
Inhaltsverzeichnis |
Rezensionen |
Inhaltsverzeichnis
- Chapter 1: Strings
- InhaltsvorschauRuby is a programmer-friendly language. If you are already familiar with object oriented programming, Ruby should quickly become second nature. If you've struggled with learning object-oriented programming or are not familiar with it, Ruby should make more sense to you than other object-oriented languages because Ruby's methods are consistently named, concise, and generally act the way you expect.Throughout this book, we demonstrate concepts through interactive Ruby sessions. Strings are a good place to start because not only are they a useful data type, they're easy to create and use. They provide a simple introduction to Ruby, a point of comparison between Ruby and other languages you might know, and an approachable way to introduce important Ruby concepts like duck typing (see Recipe 1.12), open classes (demonstrated in Recipe 1.10), symbols (Recipe 1.7), and even Ruby gems (Recipe 1.20).If you use Mac OS X or a Unix environment with Ruby installed, go to your command line right now and type
irb. If you're using Windows, you can download and install the One-Click Installer from http://rubyforge.org/projects/rubyinstaller/, and do the same from a command prompt (you can also run thefxriprogram, if that's more comfortable for you). You've now entered an interactive Ruby shell, and you can follow along with the code samples in most of this book's recipes.Strings in Ruby are much like strings in other dynamic languages like Perl, Python and PHP. They're not too much different from strings in Java and C. Ruby strings are dynamic, mutable, and flexible. Get started with strings by typing this line into your interactive Ruby session:string = "My first string"
You should see some output that looks like this:=> "My first string"
You typed in a Ruby expression that created a string "My first string", and assigned it to the variablestring. The value of that expression is just the new value ofstring, which is what your interactive Ruby session printed out on the right side of the arrow. Throughout this book, we'll represent this kind of interaction in the following form:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Building a String from Parts
- InhaltsvorschauYou want to iterate over a data structure, building a string from it as you do.There are two efficient solutions. The simplest solution is to start with an empty string, and repeatedly append substrings onto it with the
<<operator:hash = { "key1" => "val1", "key2" => "val2" } string = "" hash.each { |k,v| string << "#{k} is #{v}\n" } puts string # key1 is val1 # key2 is val2This variant of the simple solution is slightly more efficient, but harder to read:string = "" hash.each { |k,v| string << k << " is " << v << "\n" }If your data structure is an array, or easily transformed into an array, it's usually more efficient to useArray#join:puts hash.keys.join("\n") + "\n" # key1 # key2In languages like Python and Java, it's very inefficient to build a string by starting with an empty string and adding each substring onto the end. In those languages, strings are immutable, so adding one string to another builds an entirely new string. Doing this multiple times creates a huge number of intermediary strings, each of which is only used as a stepping stone to the next string. This wastes time and memory.In those languages, the most efficient way to build a string is always to put the substrings into an array or another mutable data structure, one that expands dynamically rather than by implicitly creating entirely new objects. Once you're done processing the substrings, you get a single string with the equivalent of Ruby'sArray#join. In Java, this is the purpose of theStringBufferclass.In Ruby, though, strings are just as mutable as arrays. Just like arrays, they can expand as needed, without using much time or memory. The fastest solution to this problem in Ruby is usually to forgo a holding array and tack the substrings directly onto a base string. Sometimes usingArray#joinis faster, but it's usually pretty close, and the<<construction is generally easier to understand.If efficiency is important to you, don't build a new string when you can append items onto an existing string. Constructs likestr << 'a' + 'b'Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Substituting Variables into Strings
- InhaltsvorschauYou want to create a string that contains a representation of a Ruby variable or expression.Within the string, enclose the variable or expression in curly brackets and prefix it with a hash character.
number = 5 "The number is #{number}." # => "The number is 5." "The number is #{5}." # => "The number is 5." "The number after #{number} is #{number.next}." # => "The number after 5 is 6." "The number prior to #{number} is #{number-1}." # => "The number prior to 5 is 4." "We're ##{number}!" # => "We're #5!"When you define a string by putting it in double quotes, Ruby scans it for special substitution codes. The most common case, so common that you might not even think about it, is that Ruby substitutes a single newline character every time a string contains slash followed by the letter n ("\n").Ruby supports more complex string substitutions as well. Any text kept within the brackets of the special marker #{} (that is, #{text in here}) is interpreted as a Ruby expression. The result of that expression is substituted into the string that gets created. If the result of the expression is not a string, Ruby calls itsto_smethod and uses that instead.Once such a string is created, it is indistinguishable from a string created without using the string interpolation feature:"#{number}" == '5' # => trueYou can use string interpolation to run even large chunks of Ruby code inside a string. This extreme example defines a class within a string; its result is the return value of a method defined in the class. You should never have any reason to do this, but it shows the power of this feature.%{Here is #{class InstantClass def bar "some text" end end InstantClass.new.bar }.} # => "Here is some text."The code run in string interpolations runs in the same context as any other Ruby code in the same location. To take the example above, theInstantClassclass has now been defined like any other class, and can be used outside the string that defines it.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Substituting Variables into an Existing String
- InhaltsvorschauYou want to create a string that contains Ruby expressions or variable substitutions, without actually performing the substitutions. You plan to substitute values into the string later, possibly multiple times with different values each time.There are two good solutions:
printf-style strings, and ERB templates.Ruby supports aprintf-style string format like C's and Python's. Putprintfdirectives into a string and it becomes a template. You can interpolate values into it later using the modulus operator:template = 'Oceania has always been at war with %s.' template % 'Eurasia' # => "Oceania has always been at war with Eurasia." template % 'Eastasia' # => "Oceania has always been at war with Eastasia." 'To 2 decimal places: %.2f' % Math::PI # => "To 2 decimal places: 3.14" 'Zero-padded: %.5d' % Math::PI # => "Zero-padded: 00003"
An ERB template looks something like JSP or PHP code. Most of it is treated as a normal string, but certain control sequences are executed as Ruby code. The control sequence is replaced with either the output of the Ruby code, or the value of its last expression:require 'erb' template = ERB.new %q{Chunky <%= food %>!} food = "bacon" template.result(binding) # => "Chunky bacon!" food = "peanut butter" template.result(binding) # => "Chunky peanut butter!"You can omit the call toKernel#bindingif you're not in anirbsession:puts template.result # Chunky peanut butter!
You may recognize this format from the .rhtmlfiles used by Rails views: they use ERB behind the scenes.An ERB template can reference variables likefoodbefore they're defined. When you callERB#result, orERB#run, the template is executed according to the current values of those variables.Like JSP and PHP code, ERB templates can contain loops and conditionals. Here's a more sophisticated template:template = %q{ <% if problems.empty? %> Looks like your code is clean! <% else %> I found the following possible problems with your code: <% problems.each do |problem, line| %> * <%= problem %> on line <%= line %> <% end %> <% end %>}.gsub(/^\s+/, '') template = ERB.new(template, nil, '<>') problems = [["Use of is_a? instead of duck typing", 23], ["eval() is usually dangerous", 44]] template.run(binding) # I found the following possible problems with your code: # * Use of is_a? instead of duck typing on line 23 # * eval() is usually dangerous on line 44 problems = [] template.run(binding) # Looks like your code is clean!Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Reversing a String by Words or Characters
- InhaltsvorschauThe letters (or words) of your string are in the wrong order.To create a new string that contains a reversed version of your original string, use the
reversemethod. To reverse a string in place, use thereverse! method.s = ".sdrawkcab si gnirts sihT" s.reverse # => "This string is backwards." s # => ".sdrawkcab si gnirts sihT" s. reverse! # => "This string is backwards." s # => "This string is backwards."
To reverse the order of the words in a string, split the string into a list of whitespaceseparated words, then join the list back into a string.s = "order. wrong the in are words These" s.split(/(\s+)/). reverse!.join('') # => "These words are in the wrong order." s.split(/\b/).reverse!.join('') # => "These words are in the wrong. order"TheString#splitmethod takes a regular expression to use as a separator. Each time the separator matches part of the string, the portion of the string before the separator goes into a list.splitthen resumes scanning the rest of the string. The result is a list of strings found between instances of the separator. The regular expression/(\s+)/matches one or more whitespace characters; this splits the string on word boundaries, which works for us because we want to reverse the order of the words.The regular expression\bmatches a word boundary. This is not the same as matching whitespace, because it also matches punctuation. Note the difference in punctuation between the two final examples in the Solution.Because the regular expression/(\s+)/includes a set of parentheses, the separator strings themselves are included in the returned list. Therefore, when we join the strings back together, we've preserved whitespace. This example shows the difference between including the parentheses and omitting them:"Three little words".split(/\s+/) # => ["Three", "little", "words"] "Three little words".split(/(\s+)/) # => ["Three", " ", "little", " ", "words"]
- Recipe 1.9, "Processing a String One Word at a Time," has some regular expressions for alternative definitions of "word"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Representing Unprintable Characters
- InhaltsvorschauYou need to make reference to a control character, a strange UTF-8 character, or some other character that's not on your keyboard.Ruby gives you a number of escaping mechanisms to refer to unprintable characters. By using one of these mechanisms within a double-quoted string, you can put any binary character into the string.You can reference any any binary character by encoding its octal representation into the format "\000", or its hexadecimal representation into the format "\x00".
octal = "\000\001\010\020" octal.each_byte { |x| puts x } # 0 # 1 # 8 # 16 hexadecimal = "\x00\x01\x10\x20" hexadecimal.each_byte { |x| puts x } # 0 # 1 # 16 # 32This makes it possible to represent UTF-8 characters even when you can't type them or display them in your terminal. Try running this program, and then opening the generated file smiley.html in your web browser:open('smiley.html', 'wb') do |f| f << '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">' f << "\xe2\x98\xBA" endThe most common unprintable characters (such as newline) have special mneumonic aliases consisting of a backslash and a letter."\a" == "\x07" # => true # ASCII 0x07 = BEL (Sound system bell) "\b" == "\x08" # => true # ASCII 0x08 = BS (Backspace) "\e" == "\x1b" # => true # ASCII 0x1B = ESC (Escape) "\f" == "\x0c" # => true # ASCII 0x0C = FF (Form feed) "\n" == "\x0a" # => true # ASCII 0x0A = LF (Newline/line feed) "\r" == "\x0d" # => true # ASCII 0x0D = CR (Carriage return) "\t" == "\x09" # => true # ASCII 0x09 = HT (Tab/horizontal tab) "\v" == "\x0b" # => true # ASCII 0x0B = VT (Vertical tab)
Ruby stores a string as a sequence of bytes. It makes no difference whether those bytes are printable ASCII characters, binary characters, or a mix of the two.When Ruby prints out a human-readable string representation of a binary character, it uses the character's\xxxoctal representation. Characters with special\xmneumonics are printed as the mneumonic. Printable characters are output as their printable representation, even if another representation was used to create the string.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Converting Between Characters and Values
- InhaltsvorschauYou want to see the ASCII code for a character, or transform an ASCII code into a string.To see the ASCII code for a specific character as an integer, use the
?operator:?a # => 97 ?! # => 33 ?\n # => 10
To see the integer value of a particular in a string, access it as though it were an element of an array:'a'[0] # => 97 'bad sound'[1] # => 97
To see the ASCII character corresponding to a given number, call its#chrmethod. This returns a string containing only one character:97.chr # => "a" 33.chr # => "!" 10.chr # => "\n" 0.chr # => "\000" 256.chr # RangeError: 256 out of char range
Though not technically an array, a string acts a lot like like an array ofFixnumobjects: oneFixnumfor each byte in the string. Accessing a single element of the "array" yields aFixnumfor the corresponding byte: for textual strings, this is an ASCII code. CallingString#each_bytelets you iterate over theFixnumobjects that make up a string.- Recipe 1.8, "Processing a String One Character at a Time"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Converting Between Strings and Symbols
- InhaltsvorschauYou want to get a string containing the label of a Ruby symbol, or get the Ruby symbol that corresponds to a given string.To turn a symbol into a string, use
Symbol#to_s, orSymbol#id2name, for whichto_sis an alias.:a_ symbol.to_s # => "a_symbol" :AnotherSymbol.id2name # => "AnotherSymbol" :"Yet another symbol!".to_s # => "Yet another symbol!"
You usually reference a symbol by just typing its name. If you're given a string in code and need to get the corresponding symbol, you can useString.intern::dodecahedron.object_id # => 4565262 symbol_name = "dodecahedron" symbol_name.intern # => :dodecahedron symbol_name.intern.object_id # => 4565262
ASymbolis about the most basic Ruby object you can create. It's just a name and an internal ID. Symbols are useful becase a given symbol name refers to the same object throughout a Ruby program.Symbols are often more efficient than strings. Two strings with the same contents are two different objects (one of the strings might be modified later on, and become different), but for any given name there is only oneSymbolobject. This can save both time and memory."string".object_id # => 1503030 "string".object_id # => 1500330 :symbol.object_id # => 4569358 :symbol.object_id # => 4569358
If you have n references to a name, you can keep all those references with only one symbol, using only one object's worth of memory. With strings, the same code would use n different objects, all containing the same data. It's also faster to compare two symbols than to compare two strings, because Ruby only has to check the object IDs."string1" == "string2" # => false :symbol1 == :symbol2 # => false
Finally, to quote Ruby hacker Jim Weirich on when to use a string versus a symbol:- If the contents (the sequence of characters) of the object are important, use a string.
- If the identity of the object is important, use a symbol.
- See Recipe 5.1, "Using Symbols as Hash Keys" for one use of symbols
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Processing a String One Character at a Time
- InhaltsvorschauYou want to process each character of a string individually.If you're processing an ASCII document, then each byte corresponds to one character. Use
String#each_byteto yield each byte of a string as a number, which you can turn into a one-character string:'foobar'.each_byte { |x| puts "#{x} = #{x.chr}" } # 102 = f # 111 = o # 111 = o # 98 = b # 97 = a # 114 = rUseString#scanto yield each character of a string as a new one-character string:'foobar'.scan( /./ ) { |c| puts c } # f # o # o # b # a # rSince a string is a sequence of bytes, you might think that theString#eachmethod would iterate over the sequence, the wayArray#eachdoes. ButString#eachis actually used to split a string on a given record separator (by default, the newline):"foo\nbar".each { |x| puts x } # foo # barThe string equivalent ofArray#eachmethod is actuallyeach_byte. A string stores its characters as a sequence of Fixnum objects, andeach_bytesyields that sequence.String#each_byteis faster thanString#scan, so if you're processing an ASCII file, you might want to useString#each_byteand convert to a string every number passed into the code block (as seen in the Solution).String#scanworks by applying a given regular expression to a string, and yielding each match to the code block you provide. The regular expression/./matches every character in the string, in turn.If you have the$KCODEvariable set correctly, then thescantechnique will work on UTF-8 strings as well. This is the simplest way to sneak a notion of "character" into Ruby's byte-based strings.Here's a Ruby string containing the UTF-8 encoding of the French phrase "ça va":french = "\xc3\xa7a va"
Even if your terminal can't properly display the character "ç", you can see how the behavior ofString#scanchanges when you make the regular expression Unicodeaware, or set$KCODEso that Ruby handles all strings as UTF-8:french.scan(/./) { |c| puts c } # # # a # # v # a french.scan(/./u) { |c| puts c } # ç # a # # v # a $KCODE = 'u' french.scan(/./) { |c| puts c } # ç # a # # v # aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Processing a String One Word at a Time
- InhaltsvorschauYou want to split a piece of text into words, and operate on each word.First decide what you mean by "word." What separates one word from another? Only whitespace? Whitespace or punctuation? Is "johnny-come-lately" one word or three? Build a regular expression that matches a single word according to whatever definition you need (there are some samples are in the Discussion).Then pass that regular expression into
String#scan. Every word it finds, it will yield to a code block. Theword_countmethod defined below takes a piece of text and creates a histogram of word frequencies. Its regular expression considers a "word" to be a string of Ruby identifier characters: letters, numbers, and underscores.class String def word_count frequencies = Hash.new(0) downcase.scan(/\w+/) { |word| frequencies[word] += 1 } return frequencies end end %{Dogs dogs dog dog dogs.}.word_count # => {"dogs"=>3, "dog"=>2} %{"I have no shame," I said.}.word_count # => {"no"=>1, "shame"=>1, "have"=>1, "said"=>1, "i"=>2}The regular expression/\w+/is nice and simple, but you can probably do better for your application's definition of "word." You probably don't consider two words separated by an underscore to be a single word. Some English words, like "pan-fried" and "fo'c'sle", contain embedded punctuation. Here are a few more definitions of "word" in regular expression form:# Just like /\w+/, but doesn't consider underscore part of a word. /[0-9A-Za-z]/ # Anything that's not whitespace is a word. /[^\S]+/ # Accept dashes and apostrophes as parts of words. /[-'\w]+/ # A pretty good heuristic for matching English words. /(\w+([-'.]\w+)*/
The last one deserves some explanation. It matches embedded punctuation within a word, but not at the edges. "Work-in-progress" is recognized as a single word, and "—-never—-" is recognized as the word "never" surrounded by punctuation. This regular expression can even pick out abbreviations and acronyms such as "Ph.D" and "U.N.C.L.E.", though it can't distinguish between the final period of an acronym and the period that ends a sentence. This means that "E.F.F." will be recognized as the word "E.F.F" and then a nonword period.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Changing the Case of a String
- InhaltsvorschauYour string is in the wrong case, or no particular case at all.The String class provides a variety of case-shifting methods:
s = 'HELLO, I am not here. I WENT to tHe MaRKEt.' s. upcase # => "HELLO, I AM NOT HERE. I WENT TO THE MARKET." s. downcase # => "hello, i am not here. i went to the market." s.swapcase # => "hello, i AM NOT HERE. i went TO ThE mArkeT." s.capitalize # => "Hello, i am not here. i went to the market."
Theupcaseanddowncasemethods force all letters in the string to upper-or lowercase, respectively. Theswapcasemethod transforms uppercase letters into lowercase letters and vice versa. Thecapitalizemethod makes the first character of the string uppercase, if it's a letter, and makes all other letters in the string lowercase.All four methods have corresponding methods that modify a string in place rather than creating a new one:upcase!, downcase!, swapcase!, andcapitalize!. Assuming you don't need the original string, these methods will save memory, especially if the string is large.un_banged = 'Hello world.' un_banged.upcase # => "HELLO WORLD." un_banged # => "Hello world." banged = 'Hello world.' banged.upcase! # => "HELLO WORLD." banged # => "HELLO WORLD."
To capitalize a string without lowercasing the rest of the string (for instance, because the string contains proper nouns), you can modify the first character of the string in place. This corresponds to thecapitalize! method. If you want something more likecapitalize, you can create a new string out of the old one.class String def capitalize_first_letter self[0].chr.capitalize + self[1, size] end def capitalize_first_letter! unless self[0] == (c = self[0,1].upcase[0]) self[0] = c self end # Return nil if no change was made, like upcase! et al. end end s = 'i told Alice. She remembers now.' s.capitalize_first_letter # => "I told Alice. She remembers now." s # => "i told Alice. She remembers now." s.capitalize_first_letter! s # => "I told Alice. She remembers now."
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Managing Whitespace
- InhaltsvorschauYour string contains too much whitespace, not enough whitespace, or the wrong kind of whitespace.Use
stripto remove whitespace from the beginning and end of a string:" \tWhitespace at beginning and end. \t\n\n". strip
Add whitespace to one or both ends of a string withljust, rjust, andcenter:s = "Some text." s. center(15) s. ljust(15) s. rjust(15)
Use thegsubmethod with a string or regular expression to make more complex changes, such as to replace one type of whitespace with another.#Normalize Ruby source code by replacing tabs with spaces rubyCode.gsub("\t", " ") #Transform Windows-style newlines to Unix-style newlines "Line one\n\rLine two\n\r".gsub(\n\r", "\n") # => "Line one\nLine two\n" #Transform all runs of whitespace into a single space character "\n\rThis string\t\t\tuses\n all\tsorts\nof whitespace.".gsub(/\s+/," ") # => " This string uses all sorts of whitespace."What counts as whitespace? Any of these five characters: space, tab (\t), newline (\n), linefeed (\r), and form feed (\f). The regular expression/\s/matches any one character from that set. Thestripmethod strips any combination of those characters from the beginning or end of a string.In rare cases you may need to handle oddball "space" characters like backspace (\bor\010) and vertical tab (\vor\012). These are not part of the\scharacter group in a regular expression, so use a custom character group to catch these characters." \bIt's whitespace, Jim,\vbut not as we know it.\n".gsub(/[\s\b\v]+/, " ") # => "It's whitespace, Jim, but not as we know it."
To remove whitespace from only one end of a string, use thelstriporrstripmethod:s = " Whitespace madness! " s.lstrip # => "Whitespace madness! " s.rstrip # => " Whitespace madness!"
The methods for adding whitespace to a string (center, ljust, and rjust) take a single argument: the total length of the string they should return, counting the original string and any added whitespace. IfEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Testing Whether an Object Is String-Like
- InhaltsvorschauYou want to see whether you can treat an object as a string.Check whether the object defines the
to_strmethod.'A string'.respond_to? :to_str # => true Exception.new.respond_to? :to_str # => true 4.respond_to? :to_str # => false
More generally, check whether the object defines the specific method ofStringyou're thinking about calling. If the object defines that method, the right thing to do is usually to go ahead and call the method. This will make your code work in more places:def join_to_successor(s) raise ArgumentError, 'No successor method!' unless s.respond_to? :succ return "#{s}#{s.succ}" end join_to_successor('a') # => "ab" join_to_successor(4) # => "45" join_to_successor(4.01) # ArgumentError: No successor method!If I'd checkeds.is_a? Stringinstead ofs.respond_to? :succ, then I wouldn't have been able to calljoin_to_successoron an integer.This is the simplest example of Ruby's philosophy of "duck typing:" if an object quacks like a duck (or acts like a string), just go ahead and treat it as a duck (or a string). Whenever possible, you should treat objects according to the methods they define rather than the classes from which they inherit or the modules they include.Callingobj.is_a? Stringwill tell you whether an object derives from theStringclass, but it will overlook objects that, though intended to be used as strings, don't inherit fromString.Exceptions, for instance, are essentially strings that have extra information associated with them. But they don't subclass class name"String". Code that usesis_a? Stringto check for stringness will overlook the essential stringness ofExceptions. Many add-on Ruby modules define other classes that can act as strings: code that callsis_a? Stringwill break when given an instance of one of those classes.The idea to take to heart here is the general rule of duck typing: to see whether provided data implements a certain method, userespond_to? instead of checking the class. This lets a future user (possibly yourself!) create new classes that offer the same capability, without being tied down to the preexisting class structure. All you have to do is make the method names match up.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Getting the Parts of a String You Want
- InhaltsvorschauYou want only certain pieces of a string.To get a substring of a string, call its
slicemethod, or use the array index operator (that is, call the[]method). Either method accepts aRangedescribing which characters to retrieve, or twoFixnumarguments: the index at which to start, and the length of the substring to be extracted.s = 'My kingdom for a string!' s. slice(3,7) # => "kingdom" s[3,7] # => "kingdom" s[0,3] # => "My " s[11, 5] # => "for a" s[11, 17] # => "for a string!"
To get the first portion of a string that matches a regular expression, pass the regular expression intosliceor[]:s[/.ing/] # => "king" s[/str.*/] # => "string!"
To access a specific byte of a string as aFixnum, pass only one argument (the zerobased index of the character) intoString#sliceor[]method. To access a specific byte as a single-character string, pass in its index and the number 1.s.slice(3) # => 107 s[3] # => 107 107.chr # => "k" s.slice(3,1) # => "k" s[3,1] # => "k"
To count from the end of the string instead of the beginning, use negative indexes:s.slice(-7,3) # => "str" s[-7,6] # => "string"
If the length of your proposed substring exceeds the length of the string,sliceor[]will return the entire string after that point. This leads to a simple shortcut for getting the rightmost portion of a string:s[15…s.length] # => "a string!"
- Recipe 1.9, "Processing a String One Word at a Time"
- Recipe 1.17, "Matching Strings with Regular Expressions"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Handling International Encodings
- InhaltsvorschauYou need to handle strings that contain nonASCII characters: probably Unicode characters encoded in UTF-8.To use Unicode in Ruby, simply add the following to the beginning of code.
$KCODE='u' require 'jcode'
You can also invoke the Ruby interpreter with arguments that do the same thing:$ ruby -Ku -rjcode
If you use a Unix environment, you can add the arguments to the shebang line of your Ruby application:#!/usr/bin/ruby -Ku -rjcode
Thejcodelibrary overrides most of the methods ofStringand makes them capable of handling multibyte text. The exceptions areString#length, String#count, andString#size, which are not overridden. Insteadjcodedefines three new methods:String#jlength, string#jcount, andString#jsize.Consider a UTF-8 string that encodes six Unicode characters:efbca1(A),efbca2(B), and so on up to UTF-8efbca6(F):string = "\xef\xbc\xa1" + "\xef\xbc\xa2" + "\xef\xbc\xa3" + "\xef\xbc\xa4" + "\xef\xbc\xa5" + "\xef\xbc\xa6"
The string contains 18 bytes that encode 6 characters:string.size # => 18 string.jsize # => 6
String#countis a method that takes a strong of bytes, and counts how many times those bytes occurs in the string.String#jcounttakes a string of characters and counts how many times those characters occur in the string:string.count "\xef\xbc\xa2" # => 13 string.jcount "\xef\xbc\xa2" # => 1
String#count treats"\xef\xbc\xa2"as three separate bytes, and counts the number of times each of those bytes shows up in the string.String#jcounttreats the same string as a single character, and looks for that character in the string, finding it only once."\xef\xbc\xa2".length # => 3 "\xef\xbc\xa2".jlength # => 1
Apart from these differences, Ruby handles most Unicode behind the scenes. Once you have your data in UTF-8 format, you really don't have to worry. Given that Ruby's creator Yukihiro Matsumoto is Japanese, it is no wonder that Ruby handles Unicode so elegantly.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Word-Wrapping Lines of Text
- InhaltsvorschauYou want to turn a string full of miscellaneous whitespace into a string formatted with linebreaks at appropriate intervals, so that the text can be displayed in a window or sent as an email.The simplest way to add newlines to a piece of text is to use a regular expression like the following.
def wrap(s, width=78) s.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n") end wrap("This text is too short to be wrapped.") # => "This text is too short to be wrapped.\n" puts wrap("This text is not too short to be wrapped.", 20) # This text is not too # short to be wrapped. puts wrap("These ten-character columns are stifling my creativity!", 10) # These # ten-character # columns # are # stifling # my # creativity!The code given in the Solution preserves the original formatting of the string, inserting additional line breaks where necessary. This works well when you want to preserve the existing formatting while squishing everything into a smaller space:poetry = %q{It is an ancient Mariner, And he stoppeth one of three. "By thy long beard and glittering eye, Now wherefore stopp'st thou me?} puts wrap(poetry, 20) # It is an ancient # Mariner, # And he stoppeth one # of three. # "By thy long beard # and glittering eye, # Now wherefore # stopp'st thou me?But sometimes the existing whitespace isn't important, and preserving it makes the result look bad:prose = %q{I find myself alone these days, more often than not, watching the rain run down nearby windows. How long has it been raining? The newspapers now print the total, but no one reads them anymore.} puts wrap(prose, 60) # I find myself alone these days, more often than not, # watching the rain run down nearby windows. How long has it # been # raining? The newspapers now print the total, but no one # reads them # anymore.Looks pretty ragged. In this case, we want to get replace the original newlines with new ones. The simplest way to do this is to preprocess the string with another regular expression:def reformat_wrapped(s, width=78) s.gsub(/\s+/, " ").gsub(/(.{1,#{width}})( |\Z)/, "\\1\n") endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Generating a Succession of Strings
- InhaltsvorschauYou want to iterate over a series of alphabetically-increasing strings as you would over a series of numbers.If you know both the start and end points of your succession, you can simply create a range and use
Range#each, as you would for numbers:('aa'..'ag').each { |x| puts x } # aa # ab # ac # ad # ae # af # agThe method that generates the successor of a given string isString#succ. If you don't know the end point of your succession, you can define a generator that usessucc, and break from the generator when you're done.def endless_string_succession(start) while true yield start start = start.succ end end
This code iterates over an endless succession of strings, stopping when the last two letters are the same:endless_string_succession('fol') do |x| puts x break if x[-1] == x[-2] end # fol # fom # fon # fooImagine a string as an odometer. Each character position of the string has a separate dial, and the current odometer reading is your string. Each dial always shows the same kind of character. A dial that starts out showing a number will always show a number. A character that starts out showing an uppercase letter will always show an uppercase letter.The string succession operation increments the odometer. It moves the rightmost dial forward one space. This might make the rightmost dial wrap around to the beginning: if that happens, the dial directly to its left is also moved forward one space. This might make that dial wrap around to the beginning, and so on:'89999'.succ # => "90000" 'nzzzz'.succ # => "oaaaa"
When the leftmost dial wraps around, a new dial is added to the left of the odometer. The new dial is always of the same type as the old leftmost dial. If the old leftmost dial showed capital letters, then so will the new leftmost dial:'Zzz'.succ # => "AAaa"
Lowercase letters wrap around from "z" to "a". If the first character is a lowercase letter, then when it wraps around, an "a" is added on to the beginning of the string:'z'.succ # => "aa" 'aa'.succ # => "ab" 'zz'.succ # => "aaa"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Matching Strings with Regular Expressions
- InhaltsvorschauYou want to know whether or not a string matches a certain pattern.You can usually describe the pattern as a regular expression. The
=~operator tests a string against a regular expression:string = 'This is a 30-character string.' if string =~ /([0-9]+)-character/ and $1.to_i == string.length "Yes, there are #$1 characters in that string." end # => "Yes, there are 30 characters in that string."
You can also useRegexp#match:match = Regexp.compile('([0-9]+)-character').match(string) if match && match[1].to_i == string.length "Yes, there are #{match[1]} characters in that string." end # => "Yes, there are 30 characters in that string."You can check a string against a series of regular expressions with acasestatement:string = "123" case string when /^[a-zA-Z]+$/ "Letters" when /^[0-9]+$/ "Numbers" else "Mixed" end # => "Numbers"
Regular expressions are a cryptic but powerful minilanguage for string matching and substring extraction. They've been around for a long time in Unix utilities likesed, but Perl was the first general-purpose programming language to include them. Now almost all modern languages have support for Perl-style regular expression.Ruby provides several ways of initializing regular expressions. The following are all equivalent and create equivalentRegexpobjects:/something/ Regexp.new("something") Regexp.compile("something") %r{something}The following modifiers are also of note.Regexp::IGNORECASEiMakes matches case-insensitive.Regexp::MULTILINEmNormally, a regexp matches against a single line of a string. This will cause a regexp to treat line breaks like any other character.Regexp::EXTENDEDxEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Replacing Multiple Patterns in a Single Pass
- InhaltsvorschauYou want to perform multiple, simultaneous search-and-replace operations on a string.Use the
Regexp.unionmethod to aggregate the regular expressions you want to match into one big regular expression that matches any of them. Pass the big regular expression intoString#gsub, along with a code block that takes aMatchDataobject. You can detect which of your search terms actually triggered the regexp match, and choose the appropriate replacement term:class String def mgsub(key_value_pairs=[].freeze) regexp_fragments = key_value_pairs.collect { |k,v| k } gsub( Regexp.union(*regexp_fragments)) do |match| key_value_pairs.detect{|k,v| k =~ match}[1] end end endHere's a simple example:"GO HOME!".mgsub([[/.*GO/i, 'Home'], [/home/i, 'is where the heart is']]) # => "Home is where the heart is!"
This example replaces all letters with pound signs, and all pound signs with the letter P:"Here is number #123".mgsub([[/[a-z]/i, '#'], [/#/, 'P']]) # => "#### ## ###### P123"
The naive solution is to simply string together multiplegsubcalls. The following examples, copied from the solution, show why this is often a bad idea:"GO HOME!".gsub(/.*GO/i, 'Home').gsub(/home/i, 'is where the heart is') # => "is where the heart is is where the heart is!" "Here is number #123".gsub(/[a-z]/i, "#").gsub(/#/, "P") # => "PPPP PP PPPPPP P123"
In both cases, our replacement strings turned out to match the search term of a latergsubcall. Our replacement strings were themselves subject to search-and-replace. In the first example, the conflict can be fixed by reversing the order of the substitutions. The second example shows a case where reversing the order won't help. You need to do all your replacements in a single pass over the string.Themgsubmethod will take a hash, but it's safer to pass in an array of key-value pairs. This is because elements in a hash come out in no particular order, so you can't control the order of substution. Here's a demonstration of the problem:"between".mgsub(/ee/ => 'AA', /e/ => 'E') # Bad code # => "bEtwEEn" "between".mgsub([[/ee/, 'AA'], [/e/, 'E']]) # Good code # => "bEtwAAn"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Validating an Email Address
- InhaltsvorschauYou need to see whether an email address is valid.Here's a sampling of valid email addresses you might encounter:
test_addresses = [ #The following are valid addresses according to RFC822. 'joe@example.com', 'joe.bloggs@mail.example.com', 'joe+ruby-mail@example.com', 'joe(and-mary)@example.museum', 'joe@localhost',
Here are some invalid email addresses you might encounter:# Complete the list with some invalid addresses 'joe', 'joe@', '@example.com', 'joe@example@example.com', 'joe and mary@example.com' ]
And here are some regular expressions that do an okay job of filtering out bad email addresses. The first one does very basic checking for ill-formed addresses:valid = '[^ @]+' # Exclude characters always invalid in email addresses username_and_machine = /^#{valid}@#{valid}$/ test_addresses.collect { |i| i =~ username_and_machine } # => [0, 0, 0, 0, 0, nil, nil, nil, nil, nil]The second one prohibits the use of local-network addresses like "joe@localhost". Most applications should prohibit such addresses.username_and_machine_with_tld = /^#{valid}@#{valid}\.#{valid}$/ test_addresses.collect { |i| i =~ username_and_machine_with_tld } # => [0, 0, 0, 0, nil, nil, nil, nil, nil, nil]However, the odds are good that you're solving the wrong problem.Most email address validation is done with naive regular expressions like the ones given above. Unfortunately, these regular expressions are usually written too strictly, and reject many email addresses. This is a common source of frustration for people with unusual email addresses like joe(and-mary)@example.museum, or people taking advantage of special features of email, as in joe+ruby-mail@example.com. The regular expressions given above err on the opposite side: they'll accept some syntactically invalid email addresses, but they won't reject valid addresses.Why not give a simple regular expression that always works? Because there's no such thing. The definition of the syntax is anything but simple. Perl hacker Paul Warren defined an 6343-character regular expression for Perl's Mail::RFC822::Address module, and even it needs some preprocessing to accept absolutely every allowable email address. Warren's regular expression will work unaltered in Ruby, but if you really want it, you should go online and find it, because it would be foolish to try to type it in.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Classifying Text with a Bayesian Analyzer
- InhaltsvorschauYou want to classify chunks of text by example: an email message is either spam or not spam, a joke is either funny or not funny, and so on.Use Lucas Carlson's
Classifierlibrary, available as theclassifiergem. It provides a naive Bayesian classifier, and one that implements Latent Semantic Indexing, a more advanced technique.The interface for the naive Bayesian classifier is very straightforward. You create aClassifier::Bayesobject with some classifications, and train it on text chunks whose classification is known:require 'rubygems' require 'classifier' classifier = Classifier::Bayes.new('Spam', 'Not spam') classifier.train_spam 'are you in the market for viagra? we sell viagra' classifier.train_not_spam 'hi there, are we still on for lunch?'You can then feed the classifier text chunks whose classification is unknown, and have it guess:classifier.classify "we sell the cheapest viagra on the market" # => "Spam" classifier.classify "lunch sounds great" # => "Not spam"
Bayesian analysis is based on probablities. When you train the classifier, you are giving it a set of words and the classifier keeps track of how often words show up in each category. In the simple spam filter built in the Solution, the frequency hash looks like the@categoriesvariable below:classifier # => #<Classifier::Bayes:0xb7cec7c8 # @categories={:"Not spam"=> # { :lunch=>1, :for=>1, :there=>1, # :"?"=>1, :still=>1, :","=>1 }, # :Spam=> # { :market=>1, :for=>1, :viagra=>2, :"?"=>1, :sell=>1 } # }, # @total_words=12>These hashes are used to build probability calculations. Note that since we mentioned the word "viagra" twice in spam messages, there is a 2 in the "Spam" frequency hash for that word. That makes it more spam-like than other words like "for" (which also shows up in nonspam) or "sell" (which only shows up once in spam). The classifier can apply these probabilities to previously unseen text and guess at a classification for it.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 2: Numbers
- InhaltsvorschauNumbers are as fundamental to computing as breath is to human life. Even programs that have nothing to do with math need to count the items in a data structure, display average running times, or use numbers as a source of randomness. Ruby makes it easy to represent numbers, letting you breathe easy and tackle the harder problems of programming.An issue that comes up when you're programming with numbers is that there are several different implementations of "number," optimized for different purposes: 32bit integers, floating-point numbers, and so on. Ruby tries to hide these details from you, but it's important to know about them because they often manifest as mysteriously incorrect calculations.The first distinction is between small numbers and large ones. If you've used other programming languages, you probably know that you must use different data types to hold small numbers and large numbers (assuming that the language supports large numbers at all). Ruby has different classes for small numbers (
Fixnum) and large numbers (Bignum), but you don't usually have to worry about the difference. When you type in a number, Ruby sees how big it is and creates an object of the appropriate class.1000.class # => Fixnum 10000000000.class # => Bignum (2**30 - 1).class # => Fixnum (2**30).class # => Bignum
When you perform arithmetic, Ruby automatically does any needed conversions. You don't have to worry about the difference between small and large numbers:small = 1000 big = small ** 5 # => 1000000000000000 big.class # => Bignum smaller = big / big # => 1 smaller.class # => Fixnum
The other major distinction is between whole numbers (integers) and fractional numbers. Like all modern programming languages, Ruby implements the IEEE floating-point standard for representing fractional numbers. If you type a number that includes a decimal point, Ruby creates aFloatobject instead of aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Parsing a Number from a String
- InhaltsvorschauGiven a string that contains some representation of a number, you want to get the corresponding integer or floating-point value.Use
String#to_ito turn a string into an integer. UseString#to_fto turn a string into a floating-point number.'400'.to_i # => 400 '3.14'.to_f # => 3.14 '1.602e-19'.to_f # => 1.602e-19
Unlike Perl and PHP, Ruby does not automatically make a number out of a string that contains a number. You must explicitly call a conversion method that tells Ruby how you want the string to be converted.Along withto_iandto_f, there are other ways to convert strings into numbers. If you have a string that represents a hex or octal string, you can callString#hexorString#octto get the decimal equivalent. This is the same as passing the base of the number intoto_i:'405'.oct # => 261 '405'.to_i(8) # => 261 '405'.hex # => 1029 '405'.to_i(16) # => 1029 'fed'.hex # => 4077 'fed'.to_i(16) # => 4077
Ifto_i,to_f,hex,oroctfind a character that can't be part of the kind of number they're looking for, they stop processing the string at that character and return the number so far. If the string's first character is unusable, the result is zero."13: a baker's dozen".to_i # => 13 '1001 Nights'.to_i # => 1001 'The 1000 Nights and a Night'.to_i # => 0 '60.50 Misc. Agricultural Equipment'.to_f # => 60.5 '$60.50'.to_f # => 0.0 'Feed the monster!'.hex # => 65261 'I fed the monster at Canoga Park Waterslides'.hex # => 0 '0xA2Z'.hex # => 162 '-10'.oct # => -8 '-109'.oct # => -8 '3.14'.to_i # => 3
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Comparing Floating-Point Numbers
- InhaltsvorschauFloating-point numbers are not suitable for exact comparison. Often, two numbers that should be equal are actually slightly different. The Ruby interpreter can make seemingly nonsensical assertions when floating-point numbers are involved:
1.8 + 0.1 # => 1.9 1.8 + 0.1 == 1.9 # => false 1.8 + 0.1 > 1.9 # => true
You want to do comparison operations approximately, so that floating-point numbers infintesimally close together can be treated equally.You can avoid this problem altogether by usingBigDecimalnumbers instead of floats (see Recipe 2.3).BigDecimalnumbers are completely precise, and work as well as as floats for representing numbers that are relatively small and have few decimal places: everyday numbers like the prices of fruits. But math onBigDecimalnumbers is much slower than math on floats. Databases have native support for floating-point numbers, but not forBigDecimals. And floating-point numbers are simpler to create (simply type10.2in an interactive Ruby shell to get aFloatobject).BigDecimalscan't totally replace floats, and when you use floats it would be nice not to have to worry about tiny differences between numbers when doing comparisons.But how tiny is "tiny"? How large can the difference be between two numbers before they should stop being considered equal? As numbers get larger, so does the range of floating-point values that can reasonably be expected to model that number. 1.1 is probably not "approximately equal" to 1.2, but 1020 + 0.1 is probably "approximately equal" to 1020 + 0.2.The best solution is probably to compare the relative magnitudes of large numbers, and the absolute magnitudes of small numbers. The following code accepts both two thresholds: a relative threshold and an absolute threshold. Both default toFloat::EPSILON, the smallest possible difference between twoFloatobjects. Two floats are considered approximately equal if they are withinabsolute_epsilonof each other, or if the difference between them isEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Representing Numbers to Arbitrary Precision
- InhaltsvorschauYou're doing high- precision arithmetic, and floating-point numbers are not precise enough.A
BigDecimalnumber can represent a real number to an arbitrary number of decimal places.require 'bigdecimal' BigDecimal("10").to_s # => "0.1E2" BigDecimal("1000").to_s # => "0.1E4" BigDecimal("1000").to_s("F") # => "1000.0" BigDecimal("0.123456789").to_s # => "0.123456789E0"Compare howFloatandBigDecimalstore the same high-precision number:nm = "0.123456789012345678901234567890123456789" nm.to_f # => 0.123456789012346 BigDecimal(nm).to_s # => "0.123456789012345678901234567890123456789E0"
BigDecimalnumbers store numbers in scientific notation format. ABigDecimalconsists of a sign (positive or negative), an arbitrarily large decimal fraction, and an arbitrarily large exponent. This is similar to the way floating-point numbers are stored, but a double- precision floating-point implementation like Ruby's cannot represent an exponent less thanFloat::MIN_EXP(–1021) or greater thanFloat::MAX_EXP(1024). Float objects also can't represent numbers at a greater precision thanFloat::EPSILON, or about 2.2*10-16.You can useBigDecimal#splitto split aBigDecimalobject into the parts of its scientific-notation representation. It returns an array of four numbers: the sign (1 for positive numbers,–1 for negative numbers), the fraction (as a string), the base of the exponent (which is always 10), and the exponent itself.BigDecimal("105000").split # => [1, "105", 10, 6] # That is, 0.105*(10**6) BigDecimal("-0.005").split # => [-1, "5", 10, -2] # That is, -1 * (0.5*(10**-2))A good way to test different precision settings is to create an infinitely repeating decimal like 2/3, and see how much of it gets stored. By default,BigDecimalsgive 16 digits of precision, roughly comparable to what a Float can give.(BigDecimal("2") / BigDecimal("3")).to_s # => "0.6666666666666667E0" 2.0/3 # => 0.666666666666667You can store additional significant digits by passing in a second argumentEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Representing Rational Numbers
- InhaltsvorschauYou want to precisely represent a rational number like 2/3, one that has no finite decimal expansion.Use a
Rationalobject; it represents a rational number as an integer numerator and denominator.float = 2.0/3.0 # => 0.666666666666667 float * 100 # => 66.6666666666667 float * 100 / 42 # => 1.58730158730159 require 'rational' rational = Rational(2, 3) # => Rational(2, 3) rational.to_f # => 0.666666666666667 rational * 100 # => Rational(200, 3) rational * 100 / 42 # => Rational(100, 63)
Rationalobjects can store numbers that can't be represented in any other form, and arithmetic onRationalobjects is completely precise.Since the numerator and denominator of aRationalcan beBignums, aRationalobject can also represent numbers larger and smaller than those you can represent in floating-point. But math onBigDecimalobjects is faster than onRationals.BigDecimalobjects are also usually easier to work with thanRationals, because most of us think of numbers in terms of their decimal expansions.You should only useRationalobjects when you need to represent rational numbers with perfect accuracy. When you do, be sure to use onlyRationals, Fixnums, andBignumsin your calculations. Don't use anyBigDecimalsor floating-point numbers: arithmetic operations between aRationaland those types will return floating-point numbers, and you'll have lost precision forever.10 + Rational(2,3) # => Rational(32, 3) require 'bigdecimal' BigDecimal('10') + Rational(2,3) # => 10.6666666666667The methods in Ruby'sMathmodule implement operations like square root, which usually give irrational results. When you pass aRationalnumber into one of the methods in theMathmodule, you get a floating-point number back:Math::sqrt(Rational(2,3)) # => 0.816496580927726 Math::sqrt(Rational(25,1)) # => 5.0 Math::log10(Rational(100, 1)) # => 2.0
Themathnlibrary adds miscellaneous functionality to Ruby's math functions. Among other things, it modifies theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Generating Random Numbers
- InhaltsvorschauYou want to generate pseudorandom numbers, select items from a data structure at random, or repeatedly generate the same "random" numbers for testing purposes.Use the
Kernel#randfunction with no arguments to select a psuedorandom floating-point number from a uniform distribution between 0 and 1.rand # => 0.517297883846589 rand # => 0.946962603814814
Pass in a single integer argument n toKernel#rand, and it returns an integer between 0 and n–1:rand(5) # => 0 rand(5) # => 4 rand(5) # => 3 rand(1000) # => 39
You can use the single-argument form ofKernel#randto build many common tasks based on randomness. For instance, this code selects a random item from an array.a = ['item1', 'item2', 'item3'] a[rand(a.size)] # => "item3"
To select a random key or value from a hash, turn the keys or values into an array and select one at random.m = { :key1 => 'value1', :key2 => 'value2', :key3 => 'value3' } values = m.values values[rand(values.size)] # => "value1"This code generates pronounceable nonsense words:def random_word letters = { ?v => 'aeiou', ?c => 'bcdfghjklmnprstvwyz' } word = '' 'cvcvcvc'.each_byte do |x| source = letters[x] word << source[rand(source.length)].chr end return word end random_word # => "josuyip" random_word # => "haramic"The Ruby interpreter initializes its random number generator on startup, using a seed derived from the current time and the process number. To reliably generate the same random numbers over and over again, you can set the random number seed manually by calling theKernel#srandfunction with the integer argument of your choice. This is useful when you're writing automated tests of "random" functionality:#Some random numbers based on process number and current time rand(1000) # => 187 rand(1000) # => 551 rand(1000) # => 911 #Start the seed with the number 1 srand 1 rand(1000) # => 37 rand(1000) # => 235 rand(1000) # => 908 #Reset the seed to its previous state srand 1 rand(1000) # => 37 rand(1000) # => 235 rand(1000) # => 908
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Converting Between Numeric Bases
- InhaltsvorschauYou want to convert numbers from one base to another.You can convert specific binary, octal, or hexadecimal numbers to decimal by representing them with the
0b,0o, or0xprefixes:0b100 # => 4 0o100 # => 64 0x100 # => 256
You can also convert between decimal numbers and string representations of those numbers in any base from 2 to 36. Simply pass the base intoString#to_iorInteger#to_s.Here are some conversions between string representations of numbers in various bases, and the corresponding decimal numbers:"1045".to_i(10) # => 1045 "-1001001".to_i(2) # => -73 "abc".to_i(16) # => 2748 "abc".to_i(20) # => 4232 "number".to_i(36) # => 1442151747 "zz1z".to_i(36) # => 1678391 "abcdef".to_i(16) # => 11259375 "AbCdEf".to_i(16) # => 11259375
Here are some reverse conversions of decimal numbers to the strings that represent those numbers in various bases:42.to_s(10) # => "42" -100.to_s(2) # => "-1100100" 255.to_s(16) # => "ff" 1442151747.to_s(36) # => "number"
Some invalid conversions:"6".to_i(2) # => 0 "0".to_i(1) # ArgumentError: illegal radix 1 40.to_s(37) # ArgumentError: illegal radix 37
String#to_ican parse andInteger#to_scan create a string representation in every common integer base: from binary (the familiar base 2, which uses only the digits 0 and 1) to hexatridecimal (base 36). Hexatridecimal uses the digits 0–9 and the letters a–z; it's sometimes used to generate alphanumeric mneumonics for long numbers.The only commonly used counting systems with bases higher than 36 are the variants of base-64 encoding used in applications like MIME mail attachments. These usually encode strings, not numbers; to encode a string in MIME-style base-64, use theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Taking Logarithms
- InhaltsvorschauYou want to take the logarithm of a number, possibly a huge one.
Math.logcalculates the natural log of a number: that is, the log base e.Math.log(1) # => 0.0 Math.log(Math::E) # => 1.0 Math.log(10) # => 2.30258509299405 Math::E ** Math.log(25) # => 25.0
Math.log10calculates the log base 10 of a number:Math.log10(1) # => 0.0 Math.log10(10) # => 1.0 Math.log10(10.1) # => 1.00432137378264 Math.log10(1000) # => 3.0 10 ** Math.log10(25) # => 25.0
To calculate a logarithm in some other base, use the fact that, for any bases b1 and b2 , logb1(x) = logb2(x) / logb2(k).module Math def Math.logb(num, base) log(num) / log(base) end end
A logarithm function inverts an exponentiation function. The log base k of x,or logk(x), is the number that gives x when raised to the k power. That is,Math. log10(1000)==3.0because 10 cubed is1000.Math.log(Math::E)==1because e to the first power is e.The logarithm functions for all numeric bases are related (you can get from one base to another by dividing by a constant factor), but they're used for different purposes.Scientific applications often use the natural log: this is the fastest log implementation in Ruby. The log base 10 is often used to visualize datasets that span many orders of magnitude, such as the pH scale for acidity and the Richter scale for earthquake intensity. Analyses of algorithms often use the log base 2, or binary logarithm.If you intend to do a lot of algorithms in a base that Ruby doesn't support natively, you can speed up the calculation by precalculating the dividend:dividend = Math.log(2) (1..6).collect { |x| Math.log(x) / dividend } # => [0.0, 1.0, 1.58496250072116, 2.0, 2.32192809488736, 2.58496250072116]The logarithm functions inMathwill only accept integers or floating-point numbers, notBigDecimalorBignumEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Finding Mean, Median, and Mode
- InhaltsvorschauYou want to find the average of an array of numbers: its mean, median, or mode.Usually when people speak of the "average" of a set of numbers they're referring to its mean, or arithmetic mean. The mean is the sum of the elements divided by the number of elements.
def mean(array) array.inject(array.inject(0) { |sum, x| sum += x } / array.size.to_f end mean([1,2,3,4]) # => 2.5 mean([100,100,100,100.1]) # => 100.025 mean([-100, 100]) # => 0.0 mean([3,3,3,3]) # => 3.00The median is the item x such that half the items in the array are greater than x and the other half are less than x. Consider a sorted array: if it contains an odd number of elements, the median is the one in the middle. If the array contains an even number of elements, the median is defined as the mean of the two middle elements.def median(array, already_sorted=false) return nil if array.empty? array = array.sort unless already_sorted m_pos = array.size / 2 return array.size % 2 == 1 ? array[m_pos] : mean(array[m_pos-1..m_pos]) end median([1,2,3,4,5]) # => 3 median([5,3,2,1,4]) # => 3 median([1,2,3,4]) # => 2.5 median([1,1,2,3,4]) # => 2 median([2,3,-100,100]) # => 2.5 median([1, 1, 10, 100, 1000]) # => 10
The mode is the single most popular item in the array. If a list contains no repeated items, it is not considered to have a mode. If an array contains multiple items at the maximum frequency, it is "multimodal." Depending on your application, you might handle each mode separately, or you might just pick one arbitrarily.def modes(array, find_all=true) histogram = array.inject(Hash.new(0)) { |h, n| h[n] += 1; h } modes = nil histogram.each_pair do |item, times| modes << item if modes && times == modes[0] and find_all modes = [times, item] if (!modes && times>1) or (modes && times>modes[0]) end return modes ? modes[1…modes.size] : modes end modes([1,2,3,4]) # => nil modes([1,1,2,3,4]) # => [1] modes([1,1,2,2,3,4]) # => [1, 2] modes([1,1,2,2,3,4,4]) # => [1, 2, 4] modes([1,1,2,2,3,4,4], false) # => [1] modes([1,1,2,2,3,4,4,4,4,4]) # => [4]Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Converting Between Degrees and Radians
- InhaltsvorschauThe trigonometry functions in Ruby's
Mathlibrary take input in radians (2π radians in a circle). Most real-world applications measure angles in degrees (360 degrees in a circle). You want an easy way to do trigonometry with degrees.The simplest solution is to define a conversion method inNumericthat will convert a number of degrees into radians.class Numeric def degrees self * Math::PI / 180 end end
You can then treat any numeric object as a number of degrees and convert it into the corresponding number of radians, by calling itsdegreesmethod. Trigonometry on the result will work as you'd expect:90.degrees # => 1.5707963267949 Math::tan(45.degrees) # => 1.0 Math::cos(90.degrees) # => 6.12303176911189e-17 Math::sin(90.degrees) # => 1.0 Math::sin(89.9.degrees) # => 0.999998476913288 Math::sin(45.degrees) # => 0.707106781186547 Math::cos(45.degrees) # => 0.707106781186548
I named the conversion methoddegreesby analogy to the methods likehoursdefined by Rails. This makes the code easy to read, but if you look at the actual numbers, it's not obvious why45.degreesshould equal the floating-point number 0.785398163397448.If this troubles you, you could name the method something likedegrees_to_radians. Or you could use Lucas Carlson'sunitsgem, which lets you define customized unit conversions, and tracks which unit is being used for a particular number.require 'rubygems' require 'units/base' class Numeric remove_method(:degrees) # Remove the implementation given in the Solution add_unit_conversions(:angle => { :radians => 1, :degrees => Math::PI/180 }) add_unit_aliases(:angle => { :degrees => [:degree], :radians => [:radian] }) end 90.degrees # => 90.0 90.degrees.unit # => :degrees 90.degrees.to_radians # => 1.5707963267949 90.degrees.to_radians.unit # => :radians 1.degree.to_radians # => 0.0174532925199433 1.radian.to_degrees # => 57.2957795130823Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Multiplying Matrices
- InhaltsvorschauYou want to turn arrays of arrays of numbers into mathematical matrices, and multiply the matrices together.You can create
Matrixobjects from arrays of arrays, and multiply them together with the*operator:require ' matrix' require 'mathn' a1 = [[1, 1, 0, 1], [2, 0, 1, 2], [3, 1, 1, 2]] m1 = Matrix[*a1] # => Matrix[[1, 1, 0, 1], [2, 0, 1, 2], [3, 1, 1, 2]] a2 = [[1, 0], [3, 1], [1, 0], [2, 2.5]] m2 = Matrix[*a2] # => Matrix[[1, 0], [3, 1], [1, 0], [2, 2.5]] m1 * m2 # => Matrix[[6, 3.5], [7, 5.0], [11, 6.0]]
Note the unusual syntax for creating aMatrixobject: you pass the rows of the matrix into the array indexing operator, not intoMatrix#new(which is private).Ruby'sMatrixclass overloads the arithmetic operators to support all the basic matrix arithmetic operations, including multiplication, between matrices of compatible dimension. If you perform an arithmetic operation on incompatible matrices, you'll get anExceptionForMatrix::ErrDimensionMismatch.Multiplying one matrix by another is simple enough, but multiplying a chain of matrices together can be faster or slower depending on the order in which you do the multiplications. This follows from the fact that multiplying a matrix with dimensions K x M, by a matrix with dimensions MxN, requires K * M * N operations and gives a matrix with dimension K * N. If K is large for some matrix, you can save time by waiting til the end before doing multiplications involving that matrix.Consider three matrices A, B, and C, which you want to multiply together. A has 100 rows and 20 columns. B has 20 rows and 10 columns. C has 10 rows and one column.Since matrix multiplication is associative, you'll get the same results whether you multiply A by B and then the result by C, or multiply B by C and then the result by A. But multiplying A by B requires 20,000 operations (100 * 20 * 10), and multiplying (AB) by C requires another 1,000 (100 * 10 * 1). Multiplying B by C only requires 200 operations (20 * 10 * 1), and multiplying the result by A requires 2,000 more (100 * 20 * 1). It's almost 10 times faster to multiply A(BC) instead of the naive order of (AB)C.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Solving a System of Linear Equations
- InhaltsvorschauYou have a number of linear equations (that is, equations that look like "2x + 10y + 8z = 54"), and you want to figure out the solution: the values of x, y, and z. You have as many equations as you have variables, so you can be certain of a unique solution.Create two
Matrixobjects. The firstMatrixshould contain the coefficients of your equations (the 2, 10, and 8 of "2x + 10y + 8z = 54"), and the second should contain the constant results (the 54 of the same equation). The numbers in both matrices should be represented as floating-point numbers, rational numbers, orBigDecimalobjects: anything other than plain Ruby integers.Then invert the coefficient matrix withMatrix#inverse, and multiply the result by the matrix full of constants. The result will be a thirdMatrixcontaining the solutions to your equations.For instance, consider these three linear equations in three variables:2x + 10y + 8z = 54 7y + 4z = 30 5x + 5y + 5z = 35
To solve these equations, create the two matrices:require 'matrix' require 'rational' coefficients = [[2, 10, 8], [0, 7, 4], [5, 5, 5]].collect! do |row| row.collect! { |x| Rational(x) } end coefficients = Matrix[*coefficients] # => Matrix[[Rational(2, 1), Rational(10, 1), Rational(8, 1)], # => [Rational(0, 1), Rational(7, 1), Rational(4, 1)], # => [Rational(5, 1), Rational(5, 1), Rational(5, 1)]] constants = Matrix[[Rational(54)], [Rational(30)], [Rational(35)]]Take the inverse of the coefficient matrix, and multiply it by the results matrix. The result will be a matrix containing the values for your variables.solutions = coefficients.inverse * constants # => Matrix[[Rational(1, 1)], [Rational(2, 1)], [Rational(4, 1)]]
This means that, in terms of the original equations, x=1, y=2, and z=4.This may seem like magic, but it's analagous to how you might use algebra to solve a single equation in a single variable. Such an equation looks something like Ax = B: for instance, 6x = 18. To solve for x, you divide both sides by the coefficient:
The sixes on the left side of the equation cancel out, and you can show thatEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using Complex Numbers
- InhaltsvorschauYou want to represent complex ("imaginary") numbers and perform math on them.Use the
Complexclass, defined in thecomplexlibrary. All mathematical and trigonometric operations are supported.require 'complex' Complex::I # => Complex(0, 1) a = Complex(1, 4) # => Complex(1, 4) a.real # => 1 a.image # => 4 b = Complex(1.5, 4.25) # => Complex(1.5, 4.25) b + 1.5 # => Complex(3.0, 4.25) b + 1.5*Complex::I # => Complex(1.5, 5.75) a - b # => Complex(-0.5, -0.25) a * b # => Complex(-15.5, 10.25) b.conjugate # => Complex(1.5, -4.25) Math::sin(b) # => Complex(34.9720129257216, 2.47902583958724)
You can use two floating-point numbers to keep track of the real and complex parts of a complex number, but that makes it complicated to do mathematical operations such as multiplication. If you were to write functions to do these operations, you'd have more or less reimplemented theComplexclass.Complexsimply keeps two instances ofNumeric, and implements the basic math operations on them, keeping them together as a complex number. It also implements the complex-specific mathematical operationComplex#conjugate.Complex numbers have many uses in scientific applications, but probably their coolest application is in drawing certain kinds of fractals. Here's a class that uses complex numbers to calculate and draw a character-based representation of the Mandelbrot set, scaled to whatever size your screen can handle.class Mandelbrot # Set up the Mandelbrot generator with the basic parameters for # deciding whether or not a point is in the set. def initialize(bailout=10, iterations=100) @bailout, @iterations = bailout, iterations end
A point (x,y) on the complex plane is in the Mandelbrot set unless a certain iterative calculation tends to infinity. We can't calculate "tends towards infinity" exactly, but we can iterate the calculation a certain number of times waiting for the result to exceed some "bail-out" value.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Simulating a Subclass of Fixnum
- InhaltsvorschauYou want to create a class that acts like a subclass of
Fixnum,Float, or one of Ruby's other built-in numeric classes. This wondrous class can be used in arithmetic along with realIntegerorFloatobjects, and it will usually act like one of those objects, but it will have a different representation or implement extra functionality.Let's take a concrete example and consider the possibilities. Suppose you wanted to create a class that acts just likeInteger, except its string representation is a hexadecimal string beginning with "0x". Where aFixnum's string representation might be "208", this class would represent 208 as "0xc8".You could modifyInteger#to_sto output a hexadecimal string. This would probably drive you insane because it would change the behavior for allIntegerobjects. From that point on, nearly all the numbers you use would have hexadecimal string representations. You probably want hexadecimal string representations only for a few of your numbers.This is a job for a subclass, but you can't usefully subclassFixnum(the Discussion explains why this is so). The only alternative is delegation. You need to create a class that contains an instance ofFixnum, and almost always delegates method calls to that instance. The only method calls it doesn't delegate should be the ones that it wants to override.The simplest way to do this is to create a custom delegator class with thedelegatelibrary. A class created withDelegateClassaccepts another object in its constructor, and delegates all methods to the corresponding methods of that object.require 'delegate' class HexNumber < DelegateClass( Fixnum) # The string representations of this class are hexadecimal numbers def to_s sign = self < 0 ? "-" : "" hex = abs.to_s(16) "#{sign}0x#{hex}" end def inspect to_s end end HexNumber.new(10) # => 0xa HexNumber.new(-10) # => -0xa HexNumber.new(1000000) # => 0xf4240 HexNumber.new(1024 ** 10) # => 0x10000000000000000000000000 HexNumber.new(10).succ # => 11 HexNumber.new(10) * 2 # => 20Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Doing Math with Roman Numbers
- InhaltsvorschauYou want to convert between Arabic and Roman numbers, or do arithmetic with Roman numbers and get Roman numbers as your result.The simplest way to define a
Romanclass that acts likeFixnumis to have its instances delegate most of their method calls to a realFixnum(as seen in the previous recipe, Recipe 2.13). First we'll implement a container for theFixnumdelegate, and methods to convert between Roman and Arabic numbers:class Roman # These arrays map all distinct substrings of Roman numbers # to their Arabic equivalents, and vice versa. @@roman_to_arabic = [['M', 1000], ['CM', 900], ['D', 500], ['CD', 400], ['C', 100], ['XC', 90], ['L', 50], ['XL', 40], ['X', 10], ['IX', 9], ['V', 5], ['IV', 4], ['I', 1]] @@arabic_to_roman = @@roman_to_arabic.collect { |x| x.reverse }.reverse # The Roman symbol for 5000 (a V with a bar over it) is not in # ASCII nor Unicode, so we won't represent numbers larger than 3999. MAX = 3999 def initialize(number) if number.respond_to? :to_str @value = Roman.to_arabic(number) else Roman.assert_within_range(number) @value = number end end # Raise an exception if a number is too large or small to be represented # as a Roman number. def Roman.assert_within_range(number) unless number.between?(1, MAX) msg = "#{number} can't be represented as a Roman number." raise RangeError.new(msg) end end #Find the Fixnum value of a string containing a Roman number. def Roman.to_arabic(s) value = s if s.respond_to? :to_str c = s.dup value = 0 invalid = ArgumentError.new("Invalid Roman number: #{s}") value_of_previous_number = MAX+1 value_from_previous_number = 0 @@roman_to_arabic.each_with_index do |(roman, arabic), i| value_from_this_number = 0 while c.index(roman) == 0 value_from_this_number += arabic if value_from_this_number >= value_of_previous_number raise invalid end c = c[roman.size..s.size] end #This one's a little tricky. We reject numbers like "IVI" and #"IXV", because they use the subtractive notation and then #tack on a number that makes the total overshoot the number #they'd have gotten without using the subtractive #notation. Those numbers should be V and XIV, respectively. if i > 2 and @@roman_to_arabic[i-1][0].size > 1 and value_from_this_number + value_from_previous_number >= @@roman_to_arabic[i-2][1] raise invalid end value += value_from_this_number value_from_previous_number = value_from_this_number value_of_previous_number = arabic break if c.size == 0 end raise invalid if c.size > 0 end return value end def to_arabic @value end #Render a Fixnum as a string depiction of a Roman number def to_ roman value = to_arabic Roman.assert_within_range(value) repr = "" @@arabic_to_roman.reverse_each do |arabic, roman| num, value = value.divmod(arabic) repr << roman * num end repr endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Generating a Sequence of Numbers
- InhaltsvorschauYou want to iterate over a (possibly infinite) sequence of numbers the way you can iterate over an array or a range.Write a generator function that
yields each number in the sequence.def fibonacci(limit = nil) seed1 = 0 seed2 = 1 while not limit or seed2 <= limit yield seed2 seed1, seed2 = seed2, seed1 + seed2 end end fibonacci(3) { |x| puts x } # 1 # 1 # 2 # 3 fibonacci(1) { |x| puts x } # 1 # 1 fibonacci { |x| break if x > 20; puts x } # 1 # 1 # 2 # 3 # 5 # 8 # 13A generator for a sequence of numbers works just like one that iterates over an array or other data structure. The main difference is that iterations over a data structure usually have a natural stopping point, whereas most common number sequences are infinite.One strategy is to implement a method calledeachthat yields the entire sequence. This works especially well if the sequence is finite. If not, it's the responsibility of the code block that consumes the sequence to stop the iteration with thebreakkeyword.Range#eachis an example of an iterator over a finite sequence, whilePrime#eachenumerates the infinite set of prime numbers.Range#eachis implemented in C, but here's a (much slower) pure Ruby implementation for study. This code usesself.beginandself.endto callRange#beginandRange#end, becausebeginandendare reserved words in Ruby.class Range def each_slow x = self.begin while x <= self.end yield x x = x.succ end end end (1..3).each_slow {|x| puts x} # 1 # 2 # 3The other kind of sequence generator iterates over a finite portion of an infinite sequence. These are methods likeFixnum#uptoandFixnum#step: they take a start and/ or an end point as input, and generate a finite sequence within those boundaries.class Fixnum def double_upto(stop) x = self until x > stop yield x x = x * 2 end end end 10.double_upto(50) { |x| puts x } # 10 # 20 # 40Most sequences move monotonically up or down, but it doesn't have to be that way:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Generating Prime Numbers
- InhaltsvorschauYou want to generate a sequence of prime numbers, or find all prime numbers below a certain threshold.Instantiate the
Primeclass to create a prime number generator. CallPrime#succto get the next prime number in the sequence.require 'mathn' primes = Prime.new primes.succ # => 2 primes.succ # => 3
UsePrime#eachto iterate over the prime numbers:primes.each { |x| puts x; break if x > 15; } # 5 # 7 # 11 # 13 # 17 primes.succ # => 19Because prime numbers are both mathematically interesting and useful in cryptographic applications, a lot of study has been lavished on them. Many algorithms have been devised for generating prime numbers and determining whether a number is prime. The code in this recipe walks a line between efficiency and ease of implementation.The best-known prime number algorithm is the Sieve of Eratosthenes, which finds all primes in a certain range by iterating over that range multiple times. On the first pass, it eliminates every even number greater than 2, on the second pass every third number after 3, on the third pass every fifth number after 5, and so on. This implementation of the Sieve is based on a sample program packaged with the Ruby distribution:def sieve(max=100) sieve = [] (2..max).each { |i| sieve[i] = i } (2..Math.sqrt(max)).each do |i| (i*i).step(max, i) { |j| sieve[j] = nil } if sieve[i] end sieve.compact end sieve(10) # => [2, 3, 5, 7] sieve(100000).size # => 9592The Sieve is a fast way to find the primes smaller than a certain number, but it's memory-inefficient and it's not suitable for generating an infinite sequence of prime numbers. It's also not very compatible with the Ruby idiom of generator methods. This is where thePrimeclass comes in.APrimeobject stores the current state of one iteration over the set of primes. It contains all information necessary to calculate the next prime number in the sequence.Prime#eachrepeatedly callsPrime#succandyields it up to whatever code block was passed in.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Checking a Credit Card Checksum
- InhaltsvorschauYou want to know whether a credit card number was entered correctly.The last digit of every credit card is a checksum digit. You can compare the other digits against the checksum to catch mistakes someone might make when typing their credit card number.Lucas Carlson's CreditCard library, available as the
creditcardgem, contains Ruby implementations of the checksum algorithms. It adds methods to theStringandIntegerclasses to check the internal consistency of a credit card number:require 'rubygems' require 'creditcard' '5276 4400 6542 1319'.creditcard? # => true '5276440065421313'.creditcard? # => false 1276440065421319.creditcard? # => false
CreditCard can also determine which brand of credit card a certain number is for:5276440065421313.creditcard_type # => "mastercard"
The CreditCard library uses a well-known algorithm for finding the checksum digit of a credit card. If you can't or don't want to install thecreditcardgem, you can just implement the algorithm yourself:module CreditCard def creditcard? numbers = self.to_s.gsub(/[^\d]+/, '').split(//) checksum = 0 0.upto numbers.length do |i| weight = numbers[-1*(i+2)].to_i * (2 - (i%2)) checksum += weight % 9 end return numbers[-1].to_i == 10 - checksum % 10 end end class String include CreditCard end class Integer include CreditCard end '5276 4400 6542 1319'.creditcard? # => true
How does it work? First, it converts the object to an array of numbers:numbers = '5276 4400 6542 1319'.gsub(/[^\d]+/, '').split(//) # => ["5", "2", "7", "6", "4", "4", "0", "0", # => "6", "5", "4", "2", "1", "3", "1", "9"]
It then calculates a weight for each number based on its position, and adds that weight to a running checksum:checksum = 0 0.upto numbers.length do |i| weight = numbers[-1*(i+2)].to_i * (2 - (i%2)) checksum += weight % 9 end checksum # => 51
If the last number of the card is equal to 10 minus the last digit of the checksum, the number is self-consistent:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 3: Date and Time
- InhaltsvorschauWith no concept of time, our lives would be a mess. Without software programs to constantly manage and record this bizarre aspect of our universe…well, we might actually be better off. But why take the risk?Some programs manage real-world time on behalf of the people who'd otherwise have to do it themselves: calendars, schedules, and data gatherers for scientific experiments. Other programs use the human concept of time for their own purposes: they may run experiments of their own, making decisions based on microsecond variations. Objects that have nothing to do with time are sometimes given timestamps recording when they were created or last modified. Of the basic data types, a time is the only one that directly corresponds to something in the real world.Ruby supports the date and time interfaces you might be used to from other programming languages, but on top of them are Ruby-specific idioms that make programming easier. In this chapter, we'll show you how to use those interfaces and idioms, and how to fill in the gaps left by the language as it comes out of the box.Ruby actually has two different time implementations. There's a set of time libraries written in C that have been around for decades. Like most modern programming languages, Ruby provides a native interface to these C libraries. The libraries are powerful, useful, and reliable, but they also have some significant shortcomings, so Ruby compensates with a second time library written in pure Ruby. The pure Ruby library isn't used for everything because it's slower than the C interface, and it lacks some of the features buried deep in the C library, such as the management of Daylight Saving Time.The
Timeclass contains Ruby's interface to the C libraries, and it's all you need for most applications. TheTimeclass has a lot of Ruby idiom attached to it, but most of its methods have strange unRuby-like names likestrftimeandstrptime. This is for the benefit of people who are already used to the C library, or one of its other interfaces (like Perl or Python's).The internal representation of aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Finding Today's Date
- InhaltsvorschauYou need to create an object that represents the current date and time, or a time in the future or past.The factory method
Time.nowcreates aTimeobject containing the current local time. If you want, you can then convert it to GMT time by callingTime#gmtime. Thegmtimemethod actually modifies the underlying time object, though it doesn't follow the Ruby naming conventions for such methods (it should be called something likegmtime!).now = Time.now # => Sat Mar 18 16:58:07 EST 2006 now.gmtime # => Sat Mar 18 21:58:07 UTC 2006 #The original object was affected by the time zone conversion. now # => Sat Mar 18 21:58:07 UTC 2006
To create aDateTimeobject for the current local time, use the factory methodDateTime.now. Convert aDateTimeobject to GMT by callingDateTime#new_offsetwith no argument. UnlikeTime#gmtime, this method returns a secondDateTimeobject instead of modifying the original in place.require 'date' now = DateTime.now # => #<DateTime: 70669826362347677/28800000000,-5/24,2299161> now.to_s # => "2006-03-18T16:58:07-0500" now.new_offset.to_s # => "2006-03-18T21:58:07Z" #The original object was not affected by the time zone conversion. now.to_s # => "2006-03-18T16:58:07-0500"
BothTimeandDateTimeobjects provide accessor methods for the basic ways in which the Western calendar and clock divide a moment in time. Both classes provideyear, month, day, hour(in 24-hour format),min, sec, andzoneaccessors.Time#isdstlets you know if the underlying time of aTimeobject has been modified by Daylight Saving Time in its time zone.DateTimepretends Daylight Saving Time doesn't exist.now_time = Time.new now_datetime = DateTime.now now_time.year # => 2006 now_ datetime.year # => 2006 now_time.hour # => 18 now_ datetime.hour # => 18 now_time.zone # => "EST" now_ datetime.zone # => "-0500" now_time.isdst # => false
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Parsing Dates, Precisely or Fuzzily
- InhaltsvorschauYou want to transform a string describing a date or date/time into a
Dateobject. You might not know the format of the string ahead of time.The best solution is to pass the date string intoDate.parseorDateTime.parse. These methods use heuristics to guess at the format of the string, and they do a pretty good job:require 'date' Date. parse('2/9/2007').to_s # => "2007-02-09" DateTime.parse('02-09-2007 12:30:44 AM').to_s # => "2007-09-02T00:30:44Z" DateTime. parse('02-09-2007 12:30:44 PM EST').to_s # => "2007-09-02T12:30:44-0500" Date.parse('Wednesday, January 10, 2001').to_s # => "2001-01-10"Theparsemethods can save you a lot of the drudgework associated with parsing times in other programming languages, but they don't always give you the results you want. Notice in the first example howDate.parseassumed that 2/9/2007 was an American (month first) date instead of a European (day first) date.parsealso tends to misinterpret two-digit years:Date.parse('2/9/07').to_s # => "0007-02-09"Let's say thatDate.parsedoesn't work for you, but you know that all the dates you're processing will be formatted a certain way. You can create a format string using the standardstrftimedirectives, and pass it along with a date string intoDateTime.strptimeorDate.strptime. If the date string matches up with the format string, you'll get aDateorDateTimeobject back. You may already be familiar with this technique, since this many languages, as well as the Unixdatecommand, do date formatting this way.Some common date and time formats include:american_date = '%m/%d/%y' Date.strptime('2/9/07', american_date).to_s # => "2007-02-09" DateTime.strptime('2/9/05', american_date).to_s # => "2005-02-09T00:00:00Z" Date.strptime('2/9/68', american_date).to_s # => "2068-02-09" Date.strptime('2/9/69', american_date).to_s # => "1969-02-09" european_date = '%d/%m/%y' Date.strptime('2/9/07', european_date).to_s # => "2007-09-02" Date.strptime('02/09/68', european_date).to_s # => "2068-09-02" Date.strptime('2/9/69', european_date).to_s # => "1969-09-02" four_digit_year_date = '%m/%d/%Y' Date.strptime('2/9/2007', four_digit_year_date).to_s # => "2007-02-09" Date.strptime('02/09/1968', four_digit_year_date).to_s # => "1968-02-09" Date.strptime('2/9/69', four_digit_year_date).to_s # => "0069-02-09" date_and_time = '%m-%d-%Y %H:%M:%S %Z' DateTime.strptime('02-09-2007 12:30:44 EST', date_and_time).to_s # => "2007-02-09T12:30:44-0500" DateTime.strptime('02-09-2007 12:30:44 PST', date_and_time).to_s # => "2007-02-09T12:30:44-0800" DateTime.strptime('02-09-2007 12:30:44 GMT', date_and_time).to_s # => "2007-02-09T12:30:44Z" twelve_hour_clock_time = '%m-%d-%Y %I:%M:%S %p' DateTime.strptime('02-09-2007 12:30:44 AM', twelve_hour_clock_time).to_s # => "2007-02-09T00:30:44Z" DateTime.strptime('02-09-2007 12:30:44 PM', twelve_hour_clock_time).to_s # => "2007-02-09T12:30:44Z" word_date = '%A, %B %d, %Y' Date.strptime('Wednesday, January 10, 2001', word_date).to_s # => "2001-01-10"Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Printing a Date
- InhaltsvorschauYou want to print a date object as a string.If you just want to look at a date, you can call
Time#to_sorDate#to_sand not bother with fancy formatting:require 'date' Time.now.to_s # => "Sat Mar 18 19:05:50 EST 2006" DateTime.now.to_s # => "2006-03-18T19:05:50-0500"
If you need the date in a specific format, you'll need to define that format as a string containing time-format directives. Pass the format string intoTime#strftimeorDate#strftime. You'll get back a string in which the formatting directives have been replaced by the correpsonding parts of theTimeorDateTimeobject.A formatting directive looks like a percent sign and a letter:%x. Everything in a format string that's not a formatting directive is treated as a literal:Time.gm(2006).strftime('The year is %Y!') # => "The year is 2006!"The Discussion lists all the time formatting directives defined byTime#strftimeandDate#strftime. Here are some common time-formatting strings, shown against a sample date of about 1:30 in the afternoon, GMT, on the last day of 2005:time = Time.gm(2005, 12, 31, 13, 22, 33) american_date = '%D' time.strftime(american_date) # => "12/31/05" european_date = '%d/%m/%y' time.strftime(european_date) # => "31/12/05" four_digit_year_date = '%m/%d/%Y' time.strftime(four_digit_year_date) # => "12/31/2005" date_and_time = '%m-%d-%Y %H:%M:%S %Z' time.strftime(date_and_time) # => "12-31-2005 13:22:33 GMT" twelve_hour_clock_time = '%m-%d-%Y %I:%M:%S %p' time.strftime(twelve_hour_clock_time) # => "12-31-2005 01:22:33 PM" word_date = '%A, %B %d, %Y' time.strftime(word_date) # => "Saturday, December 31, 2005"
Printed forms, parsers, and people can all be very picky about the formatting of dates. Having a date in a standard format makes dates easier to read and scan for errors. Agreeing on a format also prevents ambiguities (is 4/12 the fourth of December, or the twelfth of April?)If yourequire 'time', yourTimeobjects will sprout special-purpose formatting methods for common date representation standards:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Iterating Over Dates
- InhaltsvorschauGiven a point in time, you want to get somewhere else.All of Ruby's time objects can be used in ranges as though they were numbers.
DateandDateTimeobjects iterate in increments of one day, andTimeobjects iterate in increments of one second:require 'date' (Date.new(1776, 7, 2)..Date.new(1776, 7, 4)).each { |x| puts x } # 1776-07-02 # 1776-07-03 # 1776-07-04 span = DateTime.new(1776, 7, 2, 1, 30, 15)..DateTime.new(1776, 7, 4, 7, 0, 0) span.each { |x| puts x } # 1776-07-02T01:30:15Z # 1776-07-03T01:30:15Z # 1776-07-04T01:30:15Z (Time.at(100)..Time.at(102)).each { |x| puts x } # Wed Dec 31 19:01:40 EST 1969 # Wed Dec 31 19:01:41 EST 1969 # Wed Dec 31 19:01:42 EST 1969Ruby'sDateclass definesstepandupto, the same convenient iterator methods used by numbers:the_first = Date.new(2004, 1, 1) the_fifth = Date.new(2004, 1, 5) the_first.upto(the_fifth) { |x| puts x } # 2004-01-01 # 2004-01-02 # 2004-01-03 # 2004-01-04 # 2004-01-05Ruby date objects are stored internally as numbers, and a range of those objects is treated like a range of numbers. ForDateandDateTimeobjects, the internal representation is the Julian day: iterating over a range of those objects adds one day at a time. ForTimeobjects, the internal representation is the number of seconds since the Unix epoch: iterating over a range ofTimeobjects adds one second at a time.Timedoesn't define thestepanduptomethod, but it's simple to add them:class Time def step(other_time, increment) raise ArgumentError, "step can't be 0" if increment == 0 increasing = self < other_time if (increasing && increment < 0) || (!increasing && increment > 0) yield self return end d = self begin yield d d += increment end while (increasing ? d <= other_time : d >= other_time) end def upto(other_time) step(other_time, 1) { |x| yield x } end end the_first = Time.local(2004, 1, 1) the_second = Time.local(2004, 1, 2) the_first.step(the_second, 60 * 60 * 6) { |x| puts x } # Thu Jan 01 00:00:00 EST 2004 # Thu Jan 01 06:00:00 EST 2004 # Thu Jan 01 12:00:00 EST 2004 # Thu Jan 01 18:00:00 EST 2004 # Fri Jan 02 00:00:00 EST 2004 the_first.upto(the_first) { |x| puts x } # Thu Jan 01 00:00:00 EST 2004Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Doing Date Arithmetic
- InhaltsvorschauYou want to find how much time has elapsed between two dates, or add a number to a date to get an earlier or later date.Adding or subtracting a
Timeobject and a number adds or subtracts that number of seconds. Adding or subtracting aDateobject and a number adds or subtracts that number of days:require 'date' y2k = Time.gm(2000, 1, 1) # => Sat Jan 01 00:00:00 UTC 2000 y2k + 1 # => Sat Jan 01 00:00:01 UTC 2000 y2k - 1 # => Fri Dec 31 23:59:59 UTC 1999 y2k + (60 * 60 * 24 * 365) # => Sun Dec 31 00:00:00 UTC 2000 y2k_dt = DateTime.new(2000, 1, 1) (y2k_dt + 1).to_s # => "2000-01-02T00:00:00Z" (y2k_dt - 1).to_s # => "1999-12-31T00:00:00Z" (y2k_dt + 0.5).to_s # => "2000-01-01T12:00:00Z" (y2k_dt + 365).to_s # => "2000-12-31T00:00:00Z"
Subtracting oneTimefrom another gives the interval between the dates, in seconds. Subtracting oneDatefrom another gives the interval in days:day_one = Time.gm(1999, 12, 31) day_two = Time.gm(2000, 1, 1) day_two - day_one # => 86400.0 day_one - day_two # => -86400.0 day_one = DateTime.new(1999, 12, 31) day_two = DateTime.new(2000, 1, 1) day_two - day_one # => Rational(1, 1) day_one - day_two # => Rational(-1, 1) # Compare times from now and 10 seconds in the future. before_time = Time.now before_datetime = DateTime.now sleep(10) Time.now - before_time # => 10.003414 DateTime.now - before_datetime # => Rational(5001557, 43200000000)
Theactivesupportgem, a prerequisite of Ruby on Rails, defines many useful functions onNumericandTimefor navigating through time:require 'rubygems' require 'active_support' 10.days.ago # => Wed Mar 08 19:54:17 EST 2006 1.month.from_now # => Mon Apr 17 20:54:17 EDT 2006 2.weeks.since(Time.local(2006, 1, 1)) # => Sun Jan 15 00:00:00 EST 2006 y2k - 1.day # => Fri Dec 31 00:00:00 UTC 1999 y2k + 6.3.years # => Thu Apr 20 01:48:00 UTC 2006 6.3.years.since y2k # => Thu Apr 20 01:48:00 UTC 2006
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Counting the Days Since an Arbitrary Date
- InhaltsvorschauYou want to see how many days have elapsed since a particular date, or how many remain until a date in the future.Subtract the earlier date from the later one. If you're using
Timeobjects, the result will be a floating-point number of seconds, so divide by the number of seconds in a day:def last_modified(file) t1 = File.stat(file).ctime t2 = Time.now elapsed = (t2-t1)/(60*60*24) puts "#{file} was last modified #{elapsed} days ago." end last_modified("/etc/passwd") # /etc/passwd was last modified 125.873605469919 days ago. last_modified("/home/leonardr/") # /home/leonardr/ was last modified 0.113293513796296 days ago.If you're usingDateTimeobjects, the result will be a rational number. You'll probably want to convert it to an integer or floating-point number for display:require 'date' def advent_calendar(date=DateTime.now) christmas = DateTime.new(date.year, 12, 25) christmas = DateTime.new(date.year+1, 12, 25) if date > christmas difference = (christmas-date).to_i if difference == 0 puts "Today is Christmas." else puts "Only #{difference} day#{"s" unless difference==1} until Christmas." end end advent_calendar(DateTime.new(2006, 12, 24)) # Only 1 day until Christmas. advent_calendar(DateTime.new(2006, 12, 25)) # Today is Christmas. advent_calendar(DateTime.new(2006, 12, 26)) # Only 364 days until Christmas.Since times are stored internally as numbers, subtracting one from another will give you a number. Since both numbers measure the same thing (time elapsed since some "time zero"), that number will actually mean something: it'll be the number of seconds or days that separate the two times on the timeline.Of course, this works with other time intervals as well. To display a difference in hours, forTimeobjects divide the difference by the number of seconds in an hour (3,600, or1.hourif you're using Rails). ForDateTimeobjects, divide by the number of days in an hour (that is, multiply the difference by 24):sent = DateTime.new(2006, 10, 4, 3, 15) received = DateTime.new(2006, 10, 5, 16, 33) elapsed = (received-sent) * 24 puts "You responded to my email #{elapsed.to_f} hours after I sent it." # You responded to my email 37.3 hours after I sent it.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Converting Between Time Zones
- InhaltsvorschauYou want to change a time object so that it represents the same moment of time in some other time zone.The most common time zone conversions are the conversion of system local time to UTC, and the conversion of UTC to local time. These conversions are easy for both
TimeandDateTimeobjects.TheTime#gmtimemethod modifies aTimeobject in place, converting it to UTC. TheTime#localtimemethod converts in the opposite direction:now = Time.now # => Sat Mar 18 20:15:58 EST 2006 now = now.gmtime # => Sun Mar 19 01:15:58 UTC 2006 now = now.localtime # => Sat Mar 18 20:15:58 EST 2006
TheDateTime.new_offsetmethod converts aDateTimeobject from one time zone to another. You must pass in the dstination time zone's offset from UTC; to convert local time to UTC, pass in zero. SinceDateTimeobjects are immutable, this method creates a new object identical to the oldDateTimeobject, except for the time zone offset:require 'date' local = DateTime.now local.to_s # => "2006-03-18T20:15:58-0500" utc = local.new_offset(0) utc.to_s # => "2006-03-19T01:15:58Z"
To convert a UTCDateTimeobject to local time, you'll need to callDateTime#new_offsetand pass in the numeric offset for your local time zone. The easiest way to get this offset is to calloffseton aDateTimeobject known to be in local time. The offset will usually be a rational number with a denominator of 24:local = DateTime.now utc = local.new_offset local.offset # => Rational(-5, 24) local_from_utc = utc.new_offset(local.offset) local_from_utc.to_s # => "2006-03-18T20:15:58-0500" local == local_from_utc # => true
Timeobjects created withTime.at, Time.local, Time.mktime, Time.new, andTime.noware created using the current system time zone.Timeobjects created withTime.gmandTime.utcare created using the UTC time zone.Timeobjects can represent any time zone, but it's difficult to use a time zone withTimeother than local time or UTC.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Checking Whether Daylight Saving Time Is in Effect
- InhaltsvorschauYou want to see whether the current time in your locale is normal time or Daylight Saving/Summer Time.Create a
Timeobject and check itsisdstmethod:Time.local(2006, 1, 1) # => Sun Jan 01 00:00:00 EST 2006 Time.local(2006, 1, 1). isdst # => false Time.local(2006, 10, 1) # => Sun Oct 01 00:00:00 EDT 2006 Time.local(2006, 10, 1).isdst # => true
Timeobjects representing UTC times will always return false whenisdstis called, because UTC is the same year-round. OtherTimeobjects will consult the daylight saving time rules for the time locale used to create theTimeobject. This is usually the sysem locale on the computer you used to create it: see Recipe 3.7 for information on changing it. The following code demonstrates some of the rules pertaining to Daylight Saving Time across the United States:eastern = Time.local(2006, 10, 1) # => Sun Oct 01 00:00:00 EDT 2006 eastern.isdst # => true ENV['TZ'] = 'US/Pacific' pacific = Time.local(2006, 10, 1) # => Sun Oct 01 00:00:00 PDT 2006 pacific.isdst # => true # Except for the Navajo Nation, Arizona doesn't use Daylight Saving Time. ENV['TZ'] = 'America/Phoenix' arizona = Time.local(2006, 10, 1) # => Sun Oct 01 00:00:00 MST 2006 arizona.isdst # => false # Finally, restore the original time zone. ENV['TZ'] = nil
The C library on which Ruby'sTimeclass is based handles the complex rules for Daylight Saving Time across the history of a particular time zone or locale. For instance,Daylight Saving Time was mandated across the U.S. in 1918, but abandoned in most locales shortly afterwards. The "zoneinfo" file used by the C library contains this information, along with many other rules:# Daylight saving first took effect on March 31, 1918. Time.local(1918, 3, 31).isdst # => false Time.local(1918, 4, 1).isdst # => true Time.local(1919, 4, 1).isdst # => true # The federal law was repealed later in 1919, but some places # continued to use Daylight Saving Time. ENV['TZ'] = 'US/Pacific' Time.local(1920, 4, 1) # => Thu Apr 01 00:00:00 PST 1920 ENV['TZ'] = nil Time.local(1920, 4, 1) # => Thu Apr 01 00:00:00 EDT 1920 # Daylight Saving Time was reintroduced during the Second World War. Time.local(1942,2,9) # => Mon Feb 09 00:00:00 EST 1942 Time.local(1942,2,10) # => Tue Feb 10 00:00:00 EWT 1942 # EWT stands for "Eastern War Time"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Converting Between Time and DateTime Objects
- InhaltsvorschauYou're working with both
DateTimeandTimeobjects, created from Ruby's two standard date/time libraries. You can't mix these objects in comparisons, iterations, or date arithmetic because they're incompatible. You want to convert all the objects into one form or another so that you can treat them all the same way.To convert aTimeobject to aDateTime, you'll need some code like this:require 'date' class Time def to_datetime # Convert seconds + microseconds into a fractional number of seconds seconds = sec + Rational(usec, 10**6) # Convert a UTC offset measured in minutes to one measured in a # fraction of a day. offset = Rational(utc_offset, 60 * 60 * 24) DateTime.new(year, month, day, hour, min, seconds, offset) end end time = Time.gm(2000, 6, 4, 10, 30, 22, 4010) # => Sun Jun 04 10:30:22 UTC 2000 time.to_datetime.to_s # => "2000-06-04T10:30:22Z"
Converting aDateTimeto aTimeis similar; you just need to decide whether you want theTimeobject to use local time or GMT. This code adds the conversion method toDate, the superclass ofDateTime, so it will work on bothDateandDateTimeobjects.class Date def to_gm_time to_time(new_offset, :gm) end def to_local_time to_time(new_offset(DateTime.now.offset-offset), :local) end private def to_time(dest, method) #Convert a fraction of a day to a number of microseconds usec = (dest.sec_fraction * 60 * 60 * 24 * (10**6)).to_i Time.send(method, dest.year, dest.month, dest.day, dest.hour, dest.min, dest.sec, usec) end end (datetime = DateTime.new(1990, 10, 1, 22, 16, Rational(41,2))).to_s # => "1990-10-01T22:16:20Z" datetime.to_gm_time # => Mon Oct 01 22:16:20 UTC 1990 datetime.to_local_time # => Mon Oct 01 17:16:20 EDT 1990
Ruby's two ways of representing dates and times don't coexist very well. But since neither can be a total substitute for the other, you'll probably use them both during your Ruby career. The conversion methods let you get around incompatibilities by simply converting one type to the other:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Finding the Day of the Week
- InhaltsvorschauYou want to find the day of the week for a certain date.Use the
wdaymethod (supported by bothTimeandDateTime) to find the day of the week as a number between 0 and 6. Sunday is day zero.The following code yields to a code block the date of every Sunday between two dates. It useswdayto find the first Sunday following the start date (keeping in mind that the first date may itself be a Sunday). Then it adds seven days at a time to get subsequent Sundays:def every_sunday(d1, d2) # You can use 1.day instead of 60*60*24 if you're using Rails. one_day = d1.is_a?(Time) ? 60*60*24 : 1 sunday = d1 + ((7-d1.wday) % 7) * one_day while sunday < d2 yield sunday sunday += one_day * 7 end end def print_every_sunday(d1, d2) every_sunday(d1, d2) { |sunday| puts sunday.strftime("%x")} end print_every_sunday(Time.local(2006, 1, 1), Time.local(2006, 2, 4)) # 01/01/06 # 01/08/06 # 01/15/06 # 01/22/06 # 01/29/06The most commonly used parts of a time are its calendar and clock readings: year, day, hour, and so on.TimeandDateTimelet you access these, but they also give you access to a few other aspects of a time: the Julian day of the year (yday), and, more usefully, the day of the week (wday).Theevery_sundaymethod will accept either twoTimeobjects or twoDateTimeobjects. The only difference is the number you need to add to an object to increment it by one day. If you're only going to be using one kind of object, you can simplify the code a little.To get the day of the week as an English string, use thestrftimedirectives%Aand%a:t = Time.local(2006, 1, 1) t.strftime("%A %A %A!") # => "Sunday Sunday Sunday!" t.strftime("%a %a %a!") # => "Sun Sun Sun!"You can find the day of the week and the day of the year, but Ruby has no built-in method for finding the week of the year (there is a method to find the commercial week of the year; see Recipe 3.11). If you need such a method, it's not hard to create one using the day of the year and the day of the week. This code defines aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Handling Commercial Dates
- InhaltsvorschauWhen writing a business or financial application, you need to deal with commercial dates instead of civil or calendar dates.
DateTimeoffers some methods for working with commercial dates.Date#cwdaygives the commercial day of the week,Date#cweekgives the commercial week of the year, andDate#cwyeargives the commercial year.Consider January 1, 2006. This was the first day of calendar 2006, but since it was a Sunday, it was the last day of commercial 2005:require 'date' sunday = DateTime.new(2006, 1, 1) sunday.year # => 2006 sunday.cwyear # => 2005 sunday.cweek # => 52 sunday.wday # => 0 sunday.cwday # => 7
Commercial 2006 started on the first weekday in 2006:monday = sunday + 1 monday.cwyear # => 2006 monday.cweek # => 1
Unless you're writing an application that needs to use commercial dates, you probably don't care about this, but it's kind of interesting (if you think dates are interesting). The commercial week starts on Monday, not Sunday, because Sunday's part of the weekend.DateTime#cwdayis just likeDateTime#wday, except it gives Sunday a value of seven instead of zero.This means thatDateTime#cwdayhas a range from one to seven instead of from zero to six:(sunday…sunday+7).each do |d| puts "#{d.strftime("%a")} #{d.wday} #{d.cwday}" end # Sun 0 7 # Mon 1 1 # Tue 2 2 # Wed 3 3 # Thu 4 4 # Fri 5 5 # Sat 6 6Thecweekandcwyearmethods have to do with the commercial year, which starts on the first Monday of a year. Any days before the first Monday are considered part of the previous commercial year. The example given in the Solution demonstrates this: January 1, 2006 was a Sunday, so by the commercial reckoning it was part of the last week of 2005.- See Recipe 3.3, "Printing a Date," for the
strftimedirectives used to print parts of commercial dates
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Running a Code Block Periodically
- InhaltsvorschauYou want to run some Ruby code (such as a call to a shell command) repeatedly at a certain interval.Create a method that runs a code block, then sleeps until it's time to run the block again:
def every_n_seconds(n) loop do before = Time.now yield interval = n-(Time.now-before) sleep(interval) if interval > 0 end end every_n_seconds(5) do puts "At the beep, the time will be #{Time.now.strftime("%X")}…beep!" end # At the beep, the time will be 12:21:28… beep! # At the beep, the time will be 12:21:33… beep! # At the beep, the time will be 12:21:38… beep! # …There are two main times when you'd want to run some code periodically. The first is when you actually want something to happen at a particular interval: say you're appending your status to a log file every 10 seconds. The other is when you would prefer for something to happen continuously, but putting it in a tight loop would be bad for system performance. In this case, you compromise by putting some slack time in the loop so that your code isn't always running.The implementation ofevery_n_secondsdeducts from the sleep time the time spent running the code block. This ensures that calls to the code block are spaced evenly apart, as close to the desired interval as possible. If you tellevery_n_secondsto call a code block every five seconds, but the code block takes four seconds to run,every_n_secondsonly sleeps for one second. If the code block takes six seconds to run,every_n_secondswon't sleep at all: it'll come back from a call to the code block, and immediatelyyieldto the block again.If you always want to sleep for a certain interval, no matter how long the code block takes to run, you can simplify the code:def every_n_seconds(n) loop do yield sleep(n) end end
In most cases, you don't wantevery_n_secondsto take over the main loop of your program. Here's a version ofevery_n_secondsthat spawns a separate thread to run your task. If your code block stops the loop by with thebreakkeyword, the thread stops running:def every_n_seconds(n) thread = Thread.new do while true before = Time.now yield interval = n-(Time.now-before) sleep(interval) if interval > 0 end end return thread end
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Waiting a Certain Amount of Time
- InhaltsvorschauYou want to pause your program, or a single thread of it, for a specific amount of time.The
Kernel#sleepmethod takes a floating-point number and puts the current thread to sleep for some (possibly fractional) number of seconds:3.downto(1) { |i| puts "#{i}…"; sleep(1) }; puts "Go!" # 3… # 2… # 1… # Go! Time.new # => Sat Mar 18 21:17:58 EST 2006 sleep(10) Time.new # => Sat Mar 18 21:18:08 EST 2006 sleep(1) Time.new # => Sat Mar 18 21:18:09 EST 2006 # Sleep for less then a second. Time.new.usec # => 377185 sleep(0.1) Time.new.usec # => 479230Timers are often used when a program needs to interact with a source much slower than a computer's CPU: a network pipe, or human eyes and hands. Rather than constantly poll for new data, a Ruby program can sleep for a fraction of a second between each poll, giving other programs on the CPU a chance to run. That's not much time by human standards, but sleeping for a fraction of a second at a time can greatly improve a system's overall performance.You can pass any floating-point number tosleep, but that gives an exaggerated picture of how finely you can control a thread's sleeping time. For instance, you can't sleep for 10-50 seconds, because it's physically impossible (that's less than the Planck time). You can't sleep forFloat::EPSILONseconds, because that's almost certainly less than the resolution of your computer's timer.You probably can't even reliablysleepfor a microsecond, even though most modern computer clocks have microsecond precision. By the time yoursleepcommand is processed by the Ruby interpreter and the thread actually starts waiting for its timer to go off, some small amount of time has already elapsed. At very small intervals, this time can be greater than the time you asked Ruby to sleep in the first place.Here's a simple benchmark that shows how longsleepon your system will actually make a thread sleep. It starts with asleepinterval of one second, which is fairly accurate. It then sleeps for shorter and shorter intervals, with lessening accuracy each time:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Adding a Timeout to a Long-Running Operation
- InhaltsvorschauYou're running some code that might take a long time to complete, or might never complete at all. You want to interrupt the code if it takes too long.Use the built-in
timeoutlibrary. TheTimeout.timeoutmethod takes a code block and a deadline (in seconds). If the code block finishes running in time, it returns true. If the deadline passes and the code block is still running,Timeout.timeoutterminates the code block and raises an exception.The following code would never finish running were it not for thetimeoutcall. But after five seconds,timeoutraises aTimeout::Errorand execution halts:# This code will sleep forever… OR WILL IT? require 'timeout' before = Time.now begin status = Timeout.timeout(5) { sleep } rescue Timeout::Error puts "I only slept for #{Time.now-before} seconds." end # I only slept for 5.035492 seconds.Sometimes you must make a network connection or take some other action that might be incredibly slow, or that might never complete at all. With a timeout, you can impose an upper limit on how long that operation can take. If it fails, you can try it again later, or forge ahead without the information you were trying to get. Even when you can't recover, you can report your failure and gracefully exit the program, rather than sitting around forever waiting for the operation to complete.By default,Timeout.timeoutraises aTimeout::Error. You can pass in a custom exception class as the second argument toTimeout.timeout: this saves you from having to rescue theTimeout:Errorjust so you can raise some other error that your application knows how to handle.If the code block had side effects, they will still be visible after the timeout kills the code block:def count_for_five_seconds $counter = 0 begin Timeout::timeout(5) { loop { $counter += 1 } } rescue Timeout::Error puts "I can count to #{$counter} in 5 seconds." end end count_for_five_seconds # I can count to 2532825 in 5 seconds. $counter # => 2532825This may mean that your dataset is now in an inconsistent state.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 4: Arrays
- InhaltsvorschauLike all high-level languages, Ruby has built-in support for arrays, objects that contain ordered lists of other objects. You can use arrays (often in conjunction with hashes) to build and use complex data structures without having to define any custom classes.An array in Ruby is an ordered list of elements. Each element is a reference to some object, the way a Ruby variable is a reference to some object. For convenience, throughout this book we usually talk about arrays as though the array elements were the actual objects, not references to the objects. Since Ruby (unlike languages like C) gives no way of manipulating object references directly, the distinction rarely matters.The simplest way to create a new array is to put a comma-separated list of object references between square brackets. The object references can be predefined variables (
my_var), anonymous objects created on the spot ('my string',4.7, orMyClass.new), or expressions (a+b, object.method). A single array can contain references to objects of many different types:a1 = [] # => [] a2 = [1, 2, 3] # => [1, 2, 3] a3 = [1, 2, 3, 'a', 'b', 'c', nil] # => [1, 2, 3, "a", "b", "c", nil] n1 = 4 n2 = 6 sum_and_difference = [n1, n2, n1+n2, n1-n2] # => [4, 6, 10, -2]
If your array contains only strings, you may find it simpler to build your array by enclosing the strings in thew{}syntax, separated by whitespace. This saves you from having to write all those quotes and comma:%w{1 2 3} # => ["1", "2", "3"] %w{The rat sat on the mat} # => ["The", "rat", "sat", "on", "the", "mat"]The<<operator is the simplest way to add a value to an array. Ruby dynamically resizes arrays as elements are added and removed.a = [1, 2, 3] # => [1, 2, 3] a << 4.0 # => [1, 2, 3, 4.0] a << 'five' # => [1, 2, 3, 4.0, "five"]
An array element can be any object reference, including a reference to another array. An array can even contain a reference to itself, though this is usually a bad idea, since it can send your code into infinite loops.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Iterating Over an Array
- InhaltsvorschauYou want to perform some operation on each item in an array.Iterate over the array with
Enumerable#each. Put into a block the code you want to execute for each item in the array.[1, 2, 3, 4].each { |x| puts x } # 1 # 2 # 3 # 4If you want to produce a new array based on a transformation of some other array, useEnumerable#collectalong with a block that takes one element and transforms it:[1, 2, 3, 4].collect { |x| x ** 2 } # => [1, 4, 9, 16]Ruby supportsforloops and the other iteration constructs found in most modern programming languages, but its prefered idiom is a code block fed to an method likeeachorcollect.Methods likeeachandcollectare called generators or iterators: they iterate over a data structure,yielding one element at a time to whatever code block you've attached. Once your code block completes, they continue the iteration andyieldthe next item in the data structure (according to whatever definition of "next" the generator supports). These methods are covered in detail in Chapter 7.In a method likeeach, the return value of the code block, if any, is ignored. Methods likecollecttake a more active role. After theyyieldan element of a data structure to a code block, they use the return value in some way. Thecollectmethod uses the return value of its attached block as an element in a new array.Although commonly used in arrays, thecollectmethod is actually defined in theEnumerablemodule, which theArrayclass includes. Many other Ruby classes (HashandRangeare just two) include theEnumerablemethods; it's a sort of baseline for Ruby objects that provide iterators. ThoughEnumerabledoes not define theeachmethod, it must be defined by any class that includesEnumerable, so you'll see that method a lot, too. This is covered in Recipe 9.4.If you need to have the array indexes along with the array elements, useEnumerable#each_with_index.['a', 'b', 'c'].each_with_index do |item, index| puts "At position #{index}: #{item}" end # At position 0: a # At position 1: b # At position 2: cEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Rearranging Values Without Using Temporary Variables
- InhaltsvorschauYou want to rearrange a number of variables, or assign the elements of an array to individual variables.Use a single assignment statement. Put the destination variables on the left-hand side, and line each one up with a variable (or expression) on the right side.A simple swap:
a = 1 b = 2 a, b = b, a a # => 2 b # => 1
A more complex rearrangement:a, b, c = :red, :green, :blue c, a, b = a, b, c a # => :green b # => :blue c # => :red
You can split out an array into its components:array = [:red, :green, :blue] c, a, b = array a # => :green b # => :blue c # => :red
You can even use the splat operator to extract items from the front of the array:a, b, *c = [12, 14, 178, 89, 90] a # => 12 b # => 14 c # => [178, 89, 90]
Ruby assignment statements are very versatile. When you put a comma-separated list of variables on the left-hand side of an assignment statement, it's equivalent to assigning each variable in the list the corresponding right-hand value. Not only does this make your code more compact and readable, it frees you from having to keep track of temporary variables when you swap variables.Ruby works behind the scenes to allocate temporary storage space for variables that would otherwise be overwritten, so you don't have to do it yourself. You don't have to write this kind of code in Ruby:a, b = 1, 2 x = a a = b b = x
The right-hand side of the assignment statement can get almost arbitrarily complicated:a, b = 5, 10 a, b = b/a, a-1 # => [2, 4] a, b, c = 'A', 'B', 'C' a, b, c = [a, b], { b => c }, a a # => ["A", "B"] b # => {"B"=>"C"} c # => "A"If there are more variables on the left side of the equal sign than on the right side, the extra variables on the left side get assignedEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Stripping Duplicate Elements from an Array
- InhaltsvorschauYou want to strip all duplicate elements from an array, or prevent duplicate elements from being added in the first place.Use
Array#uniqto create a new array, based on an existing array but with no duplicate elements.Array#uniq! strips duplicate elements from an existing array.survey_results = [1, 2, 7, 1, 1, 5, 2, 5, 1] distinct_answers = survey_results.uniq # => [1, 2, 7, 5] survey_results.uniq! survey_results # => [1, 2, 7, 5]
To ensure that duplicate values never get into your list, use aSetinstead of an array. If you try to add a duplicate element to aSet, nothing will happen.require 'set' survey_results = [1, 2, 7, 1, 1, 5, 2, 5, 1] distinct_answers = survey_results.to_set # => #<Set: {5, 1, 7, 2}> games = [["Alice", "Bob"], ["Carol", "Ted"], ["Alice", "Mallory"], ["Ted", "Bob"]] players = games.inject(Set.new) { |set, game| game.each { |p| set << p }; set } # => #<Set: {"Alice", "Mallory", "Ted", "Carol", "Bob"}> players << "Ted" # => #<Set: {"Alice", "Mallory", "Ted", "Carol", "Bob"}>The common element between these two solutions is the hash (see Chapter 5).Array#uniqiterates over an array, using each element as a key in a hash that it always checks to see if it encountered an element earlier in the iteration. ASetkeeps the same kind of hash from the beginning, and rejects elements already in the hash. You see something that acts like an array, but it won't accept duplicates. In either case, two objects are considered "duplicates" if they have the same result for==.The return value ofArray#uniqis itself an array, and nothing prevents you from adding duplicate elements to it later on. If you want to start enforcing uniqueness in perpetuity, you should turn the array into aSetinstead of callinguniq. Requiring thesetlibrary will define a new methodEnumerable#to_set, which does this.Array#uniqpreserves the original order of the array (that is, the first instance of an object remains in its original location), but aSethas no order, because its internal implementation is a hash. To get array-like order in aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Reversing an Array
- InhaltsvorschauYour array is the wrong way around: the last item should be first and the first should be last.Use
reverseto create a new array with the items reversed. Internal subarrays will not themselves be reversed.[1,2,3].reverse # => [3, 2, 1] [1,[2,3,4],5].reverse # => [5, [2, 3, 4], 1]
Like many operations on basic Ruby types,reversehas a corresponding method,reverse!, which reverses an array in place:a = [1,2,3] a. reverse! a # => [3, 2, 1]
Don't reverse an array if you just need to iterate over it backwards. Don't use aforloop either; thereverse_eachiterator is more idiomatic.- Recipe 1.4, " Reversing a String by Words or Characters"
- Recipe 4.1, "Iterating Over an Array," talks about using
Array#reverse_eachto iterate over an array in reverse order - Recipe 4.2, "Rearranging Values Without Using Temporary Variables"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Sorting an Array
- InhaltsvorschauYou want to sort an array of objects, possibly according to some custom notion of what "sorting" means.Homogeneous arrays of common data types, like strings or numbers, can be sorted "naturally" by just calling
Array#sort:[5.01, -5, 0, 5].sort # => [-5, 0, 5, 5.01] ["Utahraptor", "Ankylosaur", "Maiasaur"].sort # => ["Ankylosaur", "Maiasaur", "Utahraptor"]
To sort objects based on one of their data members, or by the results of a method call, useArray#sort_by. This code sorts an array of arrays by size, regardless of their contents:arrays = [[1,2,3], [100], [10,20]] arrays.sort_by { |x| x.size } # => [[100], [10, 20], [1, 2, 3]]To do a more general sort, create a code block that compares the relevant aspect of any two given objects. Pass this block into thesortmethod of the array you want to sort.This code sorts an array of numbers in ascending numeric order, except that the number 42 will always be at the end of the list:[1, 100, 42, 23, 26, 10000].sort do |x, y| x == 42 ? 1 : x <=> y end # => [1, 23, 26, 100, 10000, 42]
If there is one "canonical" way to sort a particular class of object, then you can have that class implement the<=>comparison operator. This is how Ruby automatically knows how to sort numbers in ascending order and strings in ascending ASCII order:NumericandStringboth implement the comparison operator.Thesort_bymethod sorts an array using a Schwartzian transform (see Recipe 4.6 for an in-depth discussion). This is the most useful customized sort, because it's fast and easy to define. In this example, we usesort_byto sort on any one of an object's fields.class Animal attr_reader :name, :eyes, :appendages def initialize(name, eyes, appendages) @name, @eyes, @appendages = name, eyes, appendages end def inspect @name end end animals = [Animal.new("octopus", 2, 8), Animal.new("spider", 6, 8), Animal.new("bee", 5, 6), Animal.new("elephant", 2, 4), Animal.new("crab", 2, 10)] animals.sort_by { |x| x.eyes } # => [octopus, elephant, crab, bee, spider] animals.sort_by { |x| x.appendages } # => [elephant, bee, octopus, spider, crab]Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Ignoring Case When Sorting Strings
- InhaltsvorschauWhen you sort a list of strings, the strings beginning with uppercase letters sort before the strings beginning with lowercase letters.
list = ["Albania", "anteater", "zorilla", "Zaire"] list.sort # => ["Albania", "Zaire", "anteater", "zorilla"]
You want an alphabetical sort, regardless of case.UseArray#sort_by. This is both the fastest and the shortest solution.list.sort_by { |x| x.downcase } # => ["Albania", "anteater", "Zaire", "zorilla"]TheArray#sort_bymethod was introduced in Recipe 4.5, but it's worth discussing in detail because it's so useful. It uses a technique called a Schwartzian Transform. This common technique is like writing the following Ruby code (but it's a lot faster, because it's implemented in C):list.collect { |s| [s.downcase, s] }. sort.collect { |subarray| subarray[1] }It works like this: Ruby creates a new array containing two-element subarrays. Each subarray contains a value ofString#downcase, along with the original string. This new array is sorted, and then the original strings (now sorted by their values forString#downcase) are recovered from the subarrays.String#downcaseis called only once for each string.A sort is the most common occurance of this pattern, but it shows up whenever an algorithm calls a particular method on the same objects over and over again. If you're not sorting, you can't use Ruby's internal Schwartzian Transform, but you can save time by caching, or memoizing, the results of each distinct method call.If you need to implement a Schwartzian Transform in Ruby, it's faster to use a hash than an array:m = {} list.sort { |x,y| (m[x] ||= x.downcase) <=> (m[y] ||= y.downcase) }This technique is especially important if the method you need to call has side effects. You certainly don't want to call such methods more than once!- The Ruby FAQ, question 9.15
- Recipe 4.5, "Sorting an Array"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Making Sure a Sorted Array Stays Sorted
- InhaltsvorschauYou want to make sure an array stays sorted, even as you replace its elements or add new elements to it.Subclass
Arrayand override the methods that add items to the array. The new implementations add every new item to a position that maintains the sortedness of the array.As you can see below, there are a lot of these methods. If you can guarantee that a particular method will never be called, you can get away with not overriding it.class SortedArray < Array def initialize(*args, &sort_by) @sort_by = sort_by || Proc.new { |x,y| x <=> y } super(*args) sort! &sort_by end def insert(i, v) # The next line could be further optimized to perform a # binary search. insert_before = index(find { |x| @sort_by.call(x, v) == 1 }) super(insert_before ? insert_before : -1, v) end def <<(v) insert(0, v) end alias push << alias unshift <<Some methods, likecollect!, can modify the items in an array, taking them out of sort order. Some methods, likeflatten!, can add new elements to strange places in an array. Rather than figuring out a way to implement these methods in a way that preserves the sortedness of the array, we'll just let them run and then re-sort the array.["collect!", "flatten!", "[]="].each do |method_name| module_eval %{ def #{method_name}(*args) super sort! &@sort_by end } end def reverse! #Do nothing; reversing the array would disorder it. end endASortedArraycreated from an unsorted array will end up sorted:a = SortedArray.new([3,2,1]) # => [1, 2, 3]
Many methods ofArrayare much faster on sorted arrays, so it's often useful to expend some overhead on keeping an array sorted over time. Removing items from a sorted array won't unsort it, but adding or modifying items can. Keeping a sorted array sorted means intercepting and reimplementing every sneaky way of putting objects into the array.TheSortedArrayconstructor accepts any code block you can pass intoArray#sort, and keeps the array sorted according to that code block. The default code block uses the comparison operator (Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Summing the Items of an Array
- InhaltsvorschauYou want to add together many objects in an array.There are two good ways to accomplish this in Ruby. Plain vanilla iteration is a simple way to approach the problem:
collection = [1, 2, 3, 4, 5] sum = 0 collection.each {|i| sum += i} sum # => 15However this is such a common action that Ruby has a special iterator method calledinject, which saves a little code:collection = [1, 2, 3, 4, 5] collection. inject(0) {|sum, i| sum + i} # => 15Notice that in theinjectsolution, we didn't need to define the variabletotalvariable outside the scope of iteration. Instead, its scope moved into the iteration. In the example above, the initial value fortotalis the first argument toinject. We changed the+=to+because the block given toinjectis evaluated on each value of the collection, and thetotalvariable is set to its output every time.You can think of theinjectexample as equivalent to the following code:collection = [1, 2, 3, 4, 5] sum = 0 sum = sum + 1 sum = sum + 2 sum = sum + 3 sum = sum + 4 sum = sum + 5
Althoughinjectis the preferred way of summing over a collection,injectis generally a few times slower thaneach. The speed difference does not grow exponentially, so you don't need to always be worrying about it as you write code. But after the fact, it's a good idea to look forinjectcalls in crucial spots that you can change to use faster iteration methods likeeach.Nothing stops you from using other kinds of operators in yourinjectcode blocks. For example, you could multiply:collection = [1, 2, 3, 4, 5] collection.inject(1) {|total, i| total * i} # => 120Many of the other recipes in this book useinjectto build data structures or run calculations on them.- Recipe 2.8, "Finding Mean, Median, and Mode"
- Recipe 4.12, "Building Up a Hash Using Injection"
- Recipe 5.12, "Building a Histogram"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Sorting an Array by Frequency of Appearance
- InhaltsvorschauYou want to sort an array so that its least-frequently-appearing items come first.Build a histogram of the frequencies of the objects in the array, then use it as a lookup table in conjunction with the
sort_ bymethod.The following method puts the least frequently-appearing objects first. Objects that have the same frequency are sorted normally, with the comparison operator.module Enumerable def sort_by_frequency histogram = inject(Hash.new(0)) { |hash, x| hash[x] += 1; hash} sort_by { |x| [histogram[x], x] } end end [1,2,3,4,1,2,4,8,1,4,9,16]. sort_by_frequency # => [3, 8, 9, 16, 2, 2, 1, 1, 1, 4, 4, 4]Thesort_by_frequencymethod usessort_by, a method introduced in Recipe 4.5 and described in detail in Recipe 4.6. The technique here is a little different from other uses ofsort_by, because it sorts by two different criteria. We want to first compare the relative frequencies of two items. If the relative frequencies are equal, we want to compare the items themselves. That way, all the instances of a given item will show up together in the sorted list.The block you pass toEnumerable#sort_bycan return only a single sort key for each object, but that sort key can be an array. Ruby compares two arrays by comparing their corresponding elements, one at a time. As soon as an element of one array is different from an element of another, the comparison stops, returning the comparison of the two different elements. If one of the arrays runs out of elements, the longer one sorts first. Here are some quick examples:[1,2] <=> [0,2] # => 1 [1,2] <=> [1,2] # => 0 [1,2] <=> [2,2] # => -1 [1,2] <=> [1,1] # => 1 [1,2] <=> [1,3] # => -1 [1,2] <=> [1] # => 1 [1,2] <=> [3] # => -1 [1,2] <=> [0,1,2] # => 1 [1,2] <=> [] # => 1
In our case, all the arrays contain two elements: the relative frequency of an object in the array, and the object itself. If two objects have different frequencies, the first elements of their arrays will differ, and the items will be sorted based on their frequencies. If two items have the same frequency, the first element of each array will be the same. The comparison method will move on to the second array element, which means the two objects will be sorted based on their values.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Shuffling an Array
- InhaltsvorschauYou want to put the elements of an array in random order.The simplest way to shuffle an array (in Ruby 1.8 and above) is to sort it randomly:
[1,2,3].sort_by { rand } # => [1, 3, 2]This is not the fastest way, though.It's hard to beat a random sort for brevity of code, but it does a lot of extra work. Like any general sort, a random sort will do about n log n variable swaps. But to shuffle a list, it suffices to put a randomly selected element in each position of the list. This can be done with only n variable swaps.class Array def shuffle! each_index do |i| j = rand(length-i) + i self[j], self[i] = self[i], self[j] end end def shuffle dup.shuffle! end end
If you're shuffling a very large list, eitherArray#shuffleorArray#shuffle! will be significantly faster than a random sort. Here's a real-world example of shuffling usingArray#shuffle:class Card def initialize(suit, rank) @suit = suit @rank = rank end def to_s "#{@suit} of #{@rank}" end end class Deck < Array attr_reader :cards @@suits = %w{Spades Hearts Clubs Diamonds} @@ranks = %w{Ace 2 3 4 5 6 7 8 9 10 Jack Queen King} def initialize @@suits.each { |suit| @@ranks.each { |rank| self << Card.new(rank, suit) } } end end deck = Deck.new deck.collect { |card| card.to_s } # => ["Ace of Spades", "2 of Spades", "3 of Spades", "4 of Spades",…] deck.shuffle! deck.collect { |card| card.to_s } # => ["6 of Clubs", "8 of Diamonds", "2 of Hearts", "5 of Clubs",…]- Recipe 2.5, "Generating Random Numbers"
- The Facets Core library provides implementations of
Array#shuffleandArray#shuffle!
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Getting the N Smallest Items of an Array
- InhaltsvorschauYou want to find the smallest few items in an array, or the largest, or the most extreme according to some other measure.If you only need to find the single smallest item according to some measure, use
Enumerable#min. By default, it uses the<=>method to see whether one item is "smaller" than another, but you can override this by passing in a code block.[3, 5, 11, 16].min # => 3 ["three", "five", "eleven", "sixteen"].min # => "eleven" ["three", "five", "eleven", "sixteen"].min { |x,y| x.size <=> y.size } # => "five"Similarly, if you need to find the single largest item, useEnumerable#max.[3, 5, 11, 16].max # => 16 ["three", "five", "eleven", "sixteen"].max # => "three" ["three", "five", "eleven", "sixteen"].max { |x,y| x.size <=> y.size } # => "sixteen"By default, arrays are sorted by their natural order: numbers are sorted by value, strings by their position in the ASCII collating sequence (basically alphabetical order, but all lowercase characters precede all uppercase characters). Hence, in the previous examples, "three" is the largest string, and "eleven" the smallest.It gets more complicated when you need to get a number of the smallest or largest elements according to some measurement: say, the top 5 or the bottom 10. The simplest solution is to sort the list and skim the items you want off of the top or bottom.l = [1, 60, 21, 100, -5, 20, 60, 22, 85, 91, 4, 66] sorted = l.sort #The top 5 sorted[-5…sorted.size] # => [60, 66, 85, 91, 100] #The bottom 5 sorted[0…5] # => [-5, 1, 4, 20, 21]
Despite the simplicity of this technique, it's inefficient to sort the entire list unless the number of items you want to extract approaches the size of the list.Theminandmaxmethods work by picking the first element of the array as a "champion," then iterating over the rest of the list trying to find an element that can beat the current champion on the appropriate metric. When it finds one, that element becomes the new champion. An element that can beat the old champion can also beat any of the other contenders seen up to that point, so one run through the list suffices to find the maximum or minimum.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Building Up a Hash Using Injection
- InhaltsvorschauYou want to create a hash from the values in an array.As seen in Recipe 4.8, the most straightforward way to solve this kind of problem is to use
Enumerable#inject. Theinjectmethod takes one parameter (the object to build up, in this case a hash), and a block specifying the action to take on each item. The block takes two parameters: the object being built up (the hash), and one of the items from the array.Here's a straightforward use ofinjectto build a hash out of an array of key-value pairs:collection = [ [1, 'one'], [2, 'two'], [3, 'three'], [4, 'four'], [5, 'five'] ] collection.inject({}) do |hash, value| hash[value.first] = value.last hash end # => {5=>"five", 1=>"one", 2=>"two", 3=>"three", 4=>"four"}Why is there that somewhat incongrous expressionhashat the end of theinjectblock above? Because the next time it calls the block,injectuses the value it got from the block the last time it called the block. When you're usinginjectto build a data structure, the last line of code in the block should evaluate to the object you're building up: in this case, our hash.This is probably the most commoninject-related gotcha. Here's some code that doesn't work:collection.dup.inject({}) { |hash, value| hash[value.first] = value.last } # IndexError: index 3 out of stringWhy doesn't this work? Because hash assignment returns the assigned value, not the hash.Hash.new["key"] = "some value" # => "some value"
In the broken example above, wheninjectcalls the code block for the second and subsequent times, it does not pass the hash as the code block's first argument. It passes in the last value to be assigned to the hash. In this case, that's a string (maybe "one" or "four"). The hash has been lost forever, and theinjectblock crashes when it tries to treat a string as a hash.Hash#updatecan be used like hash assignment, except it returns the hash instead of the assigned value (and it's slower). So this code will work:collection.inject({}) do |hash, value| hash.update value.first => value.last end # => {5=>"five", 1=>"ontwo", 2=>"two", 3=>"three", 4=>"four"}Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Extracting Portions of Arrays
- InhaltsvorschauGiven an array, you want to retrieve the elements of the array that occupy certain positions or have certain properties. You might to do this in a way that removes the matching elements from the original array.To gather a chunk of an array without modifying it, use the array retrieval operator
Array#[], or its aliasArray#slice.The array retrieval operator has three forms, which are the same as the corresponding forms for substring accesses. The simplest and most common form isarray[index].It takes a number as input, treats it as an index into the array, and returns the element at that index. If the input is negative, it counts from the end of the array. If the array is smaller than the index, it returnsnil. If performance is a big consideration for you,Array#atwill do the same thing, and it's a little faster thanArray#[]:a = ("a".."h").to_a # => ["a", "b", "c", "d", "e", "f", "g", "h"] a[0] # => "a" a[1] # => "b" a.at(1) # => "b" a.slice(1) # => "b" a[-1] # => "h" a[-2] # => "g" a[1000] # => nil a[-1000] # => nilThe second form isarray[range]. This form retrieves every element identified by an index in the given range, and returns those elements as a new array.A range in which both numbers are negative will retrieve elements counting from the end of the array. You can mix positive and negative indices where that makes sense:a[2..5] # => ["c", "d", "e", "f"] a[2…5] # => ["c", "d", "e"] a[0..0] # => ["a"] a[1..-4] # => ["b", "c", "d", "e"] a[5..1000] # => ["f", "g", "h"] a[2..0] # => [] a[0…0] # => [] a[-3..2] # => []
The third form isEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Computing Set Operations on Arrays
- InhaltsvorschauYou want to find the union, intersection, difference, or Cartesian product of two arrays, or the complement of a single array with respect to some universe.
Arrayobjects have overloaded arithmetic and logical operators to provide the three simplest set operations:#Union [1,2,3] | [1,4,5] # => [1, 2, 3, 4, 5] #Intersection [1,2,3] & [1,4,5] # => [1] #Difference [1,2,3] - [1,4,5] # => [2, 3]
Setobjects overload the same operators, as well as the exclusive-or operator (^).If you already haveArrays, though, it's more efficient to deconstruct the XOR operation into its three component operations.require 'set' a = [1,2,3] b = [3,4,5] a.to_set ^ b.to_set # => #<Set: {5, 1, 2, 4}> (a | b) - (a & b) # => [1, 2, 4, 5]Setobjects are intended to model mathematical sets: where arrays are ordered and can contain duplicate entries,Sets model an unordered collection of unique items.Setnot only overrides operators for set operations, it provides English-language aliases for the three most common operators:Set#union, Set#intersection, andSet#difference. An array can only perform a set operation on another array, but aSetcan perform a set operation on anyEnumerable.array = [1,2,3] set = [3,4,5].to_s array & set # => TypeError: can't convert Set into Array set & array # => #<Set: {3}>You might think thatSetobjects would be optimized for set operations, but they're actually optimized for constant-time membership checks (internally, aSetis based on a hash). Set union is faster when the left-hand object is aSetobject, but intersection and difference are significantly faster when both objects are arrays. It's not worth it to convert arrays intoSets just so you can say you performed set operations onSetobjects.The union and intersection set operations remove duplicate entries from arrays. The difference operation does not remove duplicate entries from an array except as part of a subtraction.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Partitioning or Classifying a Set
- InhaltsvorschauYou want to partition a
Setor array based on some attribute of its elements. All elements that go "together" in some code-specific sense should be grouped together in distinct data structures. For instance, if you're partitioning by color, all the green objects in aSetshould be grouped together, separate from the group of all the red objects in theSet.UseSet#divide, passing in a code block that returns the partition of the object it's passed. The result will be a newSetcontaining a number of partitioned subsets of your originalSet.The code block can accept either a single argument or two arguments. The single-argument version examines each object to see which subset it should go into.require 'set' s = Set.new((1..10).collect) # => #<Set: {5, 6, 1, 7, 2, 8, 3, 9, 4, 10}> # Divide the set into the "true" subset and the "false" subset: that # is, the "less than 5" subset and the "not less than 5" subset. s.divide { |x| x < 5 } # => #<Set: {#<Set: {5, 6, 7, 8, 9, 10}>, #<Set: {1, 2, 3, 4}>}> # Divide the set into the "0" subset and the "1" subset: that is, the # "even" subset and the "odd" subset. s.divide { |x| x % 2 } # => #<Set: {#<Set: {6, 2, 8, 4, 10}>, #<Set: {5, 1, 7, 3, 9}>}> s = Set.new([1, 2, 3, 'a', 'b', 'c', -1.0, -2.0, -3.0]) # Divide the set into the "String subset, the "Fixnum" subset, and the # "Float" subset. s.divide { |x| x.class } # => #<Set: {#<Set: {"a", "b", "c"}>, # => #<Set: {1, 2, 3}>, # => #<Set: {-1.0, -3.0, -2.0}>}>For the two-argument code block version ofSet#divide, the code block should return true if both the arguments it has been passed should be put into the same subset.s = [1, 2, 3, -1, -2, -4].to_set # Divide the set into sets of numbers with the same absolute value. s.divide { |x,y| x.abs == y.abs } # => #<Set: {#<Set: {-1, 1}>, # => #<Set: {2, -2}>, # => #<Set: {-4}>, # => #<Set: {3}>}> # Divide the set into sets of adjacent numbers s.divide { |x,y| (x-y).abs == 1 } # => #<Set: {#<Set: {1, 2, 3}>, # => #<Set: {-1}>, # => #<Set: {-4, -3}>}>Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 5: Hashes
- InhaltsvorschauHashes and arrays are the two basic "aggregate" data types supported by most modern programming lagnguages. The basic interface of a hash is similar to that of an array. The difference is that while an array stores items according to a numeric index, the index of a hash can be any object at all.Arrays and strings have been built into programming languages for decades, but built-in hashes are a relatively recent development. Now that they're around, it's hard to live without them: they're at least as useful as arrays.You can create a Hash by calling
Hash.newor by using one of the special sytaxesHash[]or{}. With theHash[]syntax, you pass in the initial elements as comma-separated object references. With the{}syntax, you pass in the initial contents as comma-separated key-value pairs.empty = Hash.new # => {} empty ={} # => {} numbers = { 'two' => 2, 'eight' => 8} # => {"two"=>2, "eight"=>8} numbers = Hash['two', 2, 'eight', 8] # => {"two"=>2, "eight"=>8}Once the hash is created, you can do hash lookups and element assignments using the same syntax you would use to view and modify array elements:numbers["two"] # => 2 numbers["ten"] = 10 # => 10 numbers # => {"two"=>2, "eight"=>8, "ten"=>10}You can get an array containing the keys or values of a hash withHash#keysorHash#values. You can get the entire hash as an array withHash#to_a:numbers.keys # => ["two", "eight", "ten"] numbers.values # => [2, 8, 10] numbers.to_a # => [["two", 2], ["eight", 8], ["ten", 10]]
Like an array, a hash contains references to objects, not copies of them. Modifications to the original objects will affect all references to them:motto = "Don't tread on me" flag = { :motto => motto, :picture => "rattlesnake.png"} motto.upcase! flag[:motto] # => "DON'T TREAD ON ME"The defining feature of an array is its ordering. Each element of an array is assigned aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using Symbols as Hash Keys
- InhaltsvorschauCredit: Ben GiddingsWhen using a hash, you want the slight optimization you can get by using symbols as keys instead of strings.Whenever you would otherwise use a quoted string, use a symbol instead. A symbol can be created by either using a colon in front of a word, like
:keyname, or by transforming a string to a symbol usingString#intern.people = Hash.new people[:nickname] = 'Matz' people[:language] = 'Japanese' people['last name'.intern] = 'Matsumoto' people[:nickname] # => "Matz" people['nickname'.intern] # => "Matz"
While 'name' and 'name' appear exactly identical, they're actually different. Each time you create a quoted string in Ruby, you create a unique object. You can see this by looking at theobject_idmethod.'name'.object_id # => -605973716 'name'.object_id # => -605976356 'name'.object_id # => -605978996
By comparison, each instance of a symbol refers to a single object.:name.object_id # => 878862 :name.object_id # => 878862 'name'.intern.object_id # => 878862 'name'.intern.object_id # => 878862
Using symbols instead of strings saves memory and time. It saves memory because there's only one symbol instance, instead of many string instances. If you have many hashes that contain the same keys, the memory savings adds up.Using symbols as hash keys is faster because the hash value of a symbol is simply its object ID. If you use strings in a hash, Ruby must calculate the hash value of a string each time it's used as a hash key.- Recipe 1.7, "Converting Between Strings and Symbols"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Creating a Hash with a Default Value
- InhaltsvorschauCredit: Ben GiddingsYou're using a hash, and you don't want to get
nilas a value when you look up a key that isn't present in the hash. You want to get some more convenient value instead, possibly one calculated dynamically.A normal hash has a default value ofnil:h = Hash.new h[1] # => nil h['do you have this string?'] # => nil
There are two ways of creating default values for hashes. If you want the default value to be the same object for every hash key, pass that value into theHashconstructor.h = Hash.new("nope") h[1] # => "nope" h['do you have this string?'] # => "nope"If you want the default value for a missing key to depend on the key or the current state of the hash, pass a code block into the hash constructor. The block will be called each time someone requests a missing key.h = Hash.new { |hash, key| (key.respond_to? :to_str) ? "nope" : nil } h[1] # => nil h['do you have this string'] # => "nope"The first type of custom default value is most useful when you want a default value of zero. For example, this form can be used to calculate the frequency of certain words in a paragraph of text:text = 'The rain in Spain falls mainly in the plain.' word_count_hash = Hash.new 0 # => {} text.split(/\W+/).each { |word| word_count_hash[word.downcase] += 1 } word_count_hash # => {"rain"=>1, "plain"=>1, "in"=>2, "mainly"=>1, "falls"=>1, # "the"=>2, "spain"=>1}What if you wanted to make lists of the words starting with a given character? Your first attempt might look like this:first_letter_hash = Hash.new [] text.split(/\W+/).each { |word| first_letter_hash[word[0,1].downcase] << word } first_letter_hash # => {} first_letter_hash["m"] # => ["The", "rain", "in", "Spain", "falls", "mainly", "in", "the", "plain"]What's going on here? All those words don't start with "m"….Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Adding Elements to a Hash
- InhaltsvorschauYou have some items, loose or in some other data structure, which you want to put into an existing hash.To add a single key-value pair, assign the value to the element lookup expression for the key: that is, call
hash[key]=value. Assignment will override any previous value for that key.h = {} h["Greensleeves"] = "all my joy" h # => {"Greensleeves"=>"all my joy"} h["Greensleeves"] = "my delight" h # => {"Greensleeves"=>"my delight"}When you use a string as a hash key, the string is transparently copied and the copy is frozen. This is to avoid confusion should you modify the string in place, then try to use its original form to do a hash lookup:key = "Modify me if you can" h = { key => 1 } key.upcase! # => "MODIFY ME IF YOU CAN" h[key] # => nil h["Modify me if you can"] # => 1 h.keys # => ["Modify me if you can"] h.keys[0].upcase! # TypeError: can't modify frozen stringTo add an array of key-value pairs to a hash, either iterate over the array withArray#each, or pass the hash intoArray#inject. Usinginjectis slower but the code is more concise.squares = [[1,1], [2,4], [3,9]] results = {} squares.each { |k,v| results[k] = v } results # => {1=>1, 2=>4, 3=>9} squares.inject({}) { |h, kv| h[kv[0]] = kv[1]; h } # => {1=>1, 2=>4, 3=>9}To turn a flat array into the key-value pairs of a hash, iterate over the array elements two at a time:class Array def into_hash(h) unless size % 2 == 0 raise StandardError, "Expected array with even number of elements" end 0.step(size-1, 2) { |x| h[self[x]] = self[x+1] } h end end squares = [1,1,2,3,4,9] results = {} squares.into_hash(results) # => {1=>1, 2=>3, 4=>9} [1,1,2].into_hash(results) # StandardError: Expected array with even number of elementsTo insert into a hash every key-value from another hash, useHash#merge!. If a key is present in both hashes whenEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Removing Elements from a Hash
- InhaltsvorschauCertain elements of your hash have got to go!Most of the time you want to remove a specific element of a hash. To do that, pass the key into
Hash#delete.h = {} h[1] = 10 h # => {1=>10} h.delete(1) h # => {}Don't try to delete an element from a hash by mapping it tonil. It's true that, by default, you getnilwhen you look up a key that's not in the hash, but there's a difference between a key that's missing from the hash and a key that's present but mapped tonil. Hash#has_key?will see a key mapped tonil, as willHash#eachand all other methods except for a simple fetch:h = {} h[5] # => nil h[5] = 10 h[5] # => 10 h[5] = nil h[5] # => nil h.keys # => [5] h.delete(5) h.keys # => []Hash#deleteworks well when you need to remove elements on an ad hoc basis, but sometimes you need to go through the whole hash looking for things to remove. Use theHash#delete_ifiterator to delete key-value pairs for which a certain code block returns true (Hash#rejectworks the same way, but it works on a copy of theHash). The following code deletes all key-value pairs with a certain value:class Hash def delete_value(value) delete_if { |k,v| v == value } end end h = {'apple' => 'green', 'potato' => 'red', 'sun' => 'yellow', 'katydid' => 'green' } h.delete_value('green') h # => {"sun"=>"yellow", "potato"=>"red"}This code implements the opposite ofHash#merge;it extracts one hash from another:class Hash def remove_hash(other_hash) delete_if { |k,v| other_hash[k] == v } end end squares = { 1 => 1, 2 => 4, 3 => 9 } doubles = { 1 => 2, 2 => 4, 3 => 6 } squares.remove_hash(doubles) squares # => {1=>1, 3=>9}Finally, to wipe out the entire contents of aHash, useHash#clear:h = {} 1.upto(1000) { |x| h[x] = x } h.keys.size # => 1000 h.clear h # => {}Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using an Array or Other Modifiable Object as a Hash Key
- InhaltsvorschauYou want to use a modifiable built-in object (an array or a hash, but not a string) as a key in a hash, even while you modify the object in place. A naive solution tends to lose hash values once the keys are modified:
coordinates = [10, 5] treasure_map = { coordinates => 'jewels' } treasure_map[coordinates] # => "jewels" # Add a z-coordinate to indicate how deep the treasure is buried. coordinates << -5 coordinates # => [10, 5, -5] treasure_map[coordinates] # => nil # Oh no!The easiest solution is to call theHash#rehashmethod every time you modify one of the hash's keys.Hash#rehashwill repair the broken treasure map defined above:treasure_map.rehash treasure_map[coordinates] # => "jewels"
If this is too much code, you might consider changing the definition of the object you use as a hash key, so that modifications don't affect the way the hash treats it.Suppose you want a reliably hashableArrayclass. If you want this behavior universally, you can reopen theArrayclass and redefinehashto give you the new behavior. But it's safer to define a subclass ofArraythat implements a reliable-hashing mixin, and to use that subclass only for theArraysyou want to use as hash keys:module ReliablyHashable def hash return object_id end end class ReliablyHashableArray < Array include ReliablyHashable end
It's now possible to keep track of the jewels:coordinates = ReliablyHashableArray.new([10,5]) treasure_map = { coordinates => 'jewels' } treasure_map[coordinates] # => "jewels" # Add a z-coordinate to indicate how deep the treasure is buried. coordinates.push(-5) treasure_map[coordinates] # => "jewels"Ruby performs hash lookups using not the key object itself but the object's hash code (an integer obtained from the key by calling itshashmethod). The default implementation ofhash, inObjectEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Keeping Multiple Values for the Same Hash Key
- InhaltsvorschauYou want to build a hash that might have duplicate values for some keys.The simplest way is to create a hash that initializes missing values to empty arrays. You can then append items onto the automatically created arrays:
hash = Hash.new { |hash, key| hash[key] = [] } raw_data = [ [1, 'a'], [1, 'b'], [1, 'c'], [2, 'a'], [2, ['b', 'c']], [3, 'c'] ] raw_data.each { |x,y| hash[x] << y } hash # => {1=>["a", "b", "c"], 2=>["a", ["b", "c"]], 3=>["c"]}A hash maps any given key to only one value, but that value can be an array. This is a common phenomenon when reading data structures from the outside world. For instance, a list of tasks with associated priorities may contain multiple items with the same priority. Simply reading the tasks into a hash keyed on priority would create key collisions, and obliterate all but one task with any given priority.It's possible to subclassHashto act like a normal hash until a key collision occurs, and then start keeping an array of values for the key that suffered the collision:class MultiValuedHash < Hash def []=(key, value) if has_key?(key) super(key, [value, self[key]].flatten) else super end end end hash = MultiValuedHash.new raw_data.each { |x,y| hash[x] = y } hash # => {1=>["c", "b", "a"], 2=>["b", "c", "a"], 3=>"c"}This saves a little bit of memory, but it's harder to write code for this class than for one that always keeps values in an array. There's also no way of knowing whether a value[1,2,3]is a single array value or three numeric values.- Recipe 5.2, "Creating a Hash with a Default Value," explains the technique of the dynamic default value in more detail, and explains why you must initalize the empty list within a code block—never within the arguments to
Hash.new
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Iterating Over a Hash
- InhaltsvorschauYou want to iterate over a hash's key-value pairs as though it were an array.Most likely, the iterator you want is
Hash#each_pairorHash#each. These methods yield every key-value pair in the hash:hash = { 1 => 'one', [1,2] => 'two', 'three' => 'three' } hash.each_pair { |key, value| puts "#{key.inspect} maps to #{value}"} # [1, 2] maps to two # "three" maps to three # 1 maps to oneNote thateachandeach_pairreturn the key-value pairs in an apparently random order.Hash#each_pairandHash#eachlet you iterate over a hash as though it were an array full of key-value pairs.Hash#each_pairis more commonly used and slightly more efficient, butHash#eachis more array-like.Hashalso provides several other iteration methods that can be more efficient thaneach.UseHash#each_keyif you only need the keys of a hash. In this example, a list has been stored as a hash to allow for quick lookups (this is how theSetclass works). The values are irrelevant, buteach_keycan be used to iterate over the keys:active_toggles = { 'super' => true, 'meta' => true, 'hyper' => true } active_toggles.each_key { |active| puts active } # hyper # meta # superUseHash#each_valueif you only need the values of a hash. In this example,each_valueis used to summarize the results of a survey. Here it's the keys that are irrelevant:favorite_colors = { 'Alice' => :red, 'Bob' => :violet, 'Mallory' => :blue, 'Carol' => :blue, 'Dave' => :violet } summary = Hash.new 0 favorite_colors.each_value { |x| summary[x] += 1 } summary # => {:red=>1, :violet=>2, :blue=>2}Don't iterate overHash#each_valuelooking for a particular value: it's simpler and faster to usehas_value?instead.hash = {} 1.upto(10) { |x| hash[x] = x * x } hash.has_value? 49 # => true hash.has_value? 81 # => true hash.has_value? 50 # => falseRemoving unprocessed elements from a hash during an iteration prevents those items from being part of the iteration. However, adding elements to a hash during an iteration will not make them part of the iteration.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Iterating Over a Hash in Insertion Order
- InhaltsvorschauIterations over a hash happen in a seemingly random order. Sorting the keys or values only works if the keys or values are all mutually comparable. You'd like to iterate over a hash in the order in which the elements were added to the hash.Use the
orderedhashlibrary (see below for how to get it). ItsOrderedHashclass acts like a hash, but it keeps the elements of the hash in insertion order.require ' orderedhash' h = OrderedHash.new h[1] = 1 h["second"] = 2 h[:third] = 3 h.keys # => [1, "second", :third] h.values # => [1, 2, 3] h.each { |k,v| puts "The #{k} counting number is #{v}" } # The 1 counting number is 1 # The second counting number is 2 # The third counting number is 3OrderedHashis a subclass ofHashthat also keeps an array of the keys in insertion order. When you add a key-value pair to the hash,OrderedHashmodifies both the underlying hash and the array. When you ask for a specific hash element, you're using the hash. When you ask for thekeysor thevalues, the data comes from the array, and you get it in insertion order.SinceOrderedHashis a real hash, it supports all the normal hash operations. But any operation that modifies anOrderedHashmay also modify the internal array, so it's slower than just using a hash.OrderedHash#deleteis especially slow, since it must perform a linear search of the internal array to find the key being deleted.Hash#deleteruns in constant time, butOrderedHash#deletetakes time proportionate to the size of the hash.- You can get
OrderedHashfrom the RAA at http://raa.ruby-lang.org/project/orderedhash/; it's not available as a gem, and it has nosetup.rbscript, so you'll need to distributeorderedhash.rbwith your project, or copy it into your Ruby library path - There is a
queuehashgem that provides much the same functionality, but it has worse performance thanOrderedHash
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Printing a Hash
- InhaltsvorschauCredit: Ben GiddingsYou want to print out the contents of a Hash, but
Kernel#putsdoesn't give very useful results.h = {} h[:name] = "Robert" h[:nickname] = "Bob" h[:age] = 43 h[:email_addresses] = {:home => "bob@example.com", :work => "robert@example.com"} h # => {:email_addresses=>["bob@example.com", "robert@example.com"], # :nickname=>"Bob", :name=>"Robert", :age=>43} puts h # nicknameBobage43nameRobertemail_addresseshomebob@example.comworkrobert@example.com puts h[:email_addresses] # homebob@example.comworkrobert@example.comIn other recipes, we sometimes reformat the results or output of Ruby statements so they'll look better on the printed page. In this recipe, you'll see raw, unretouched output, so you can compare different ways of printing hashes.The easiest way to print a hash is to useKernel#p. Kernel#pprints out the "inspected" version of its arguments: the string you get by callinginspecton the hash. The "inspected" version of an object often looks like Ruby source code for creating the object, so it's usually readable:p h[:email_addresses] # {:home=>"bob@example.com", :work=>"robert@example.com"}For small hashes intended for manual inspection, this may be all you need. However, there are two difficulties. One is thatKernel#ponly prints tostdout. The second is that the printed version contains no newlines, making it difficult to read large hashes.p h # {:nickname=>"Bob", :age=>43, :name=>"Robert", :email_addresses=>{:home=> # "bob@example.com", :work=>"robert@example.com"}}When the hash you're trying to print is too large, thepp("pretty-print") module produces very readable results:require ' pp' pp h[:email_addresses] # {:home=>"bob@example.com", :work=>"robert@example.com"} pp h # {:email_addresses=>{:home=>"bob@example.com", :work=>"robert@example.com"} # :nickname=>"Bob", # :name=>"Robert", # :age=>43}There are a number of ways of printing hash contents. The solution you choose depends on the complexity of the hash you're trying to print, where you're trying to print the hash, and your personal preferences. The best general-purpose solution is theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Inverting a Hash
- InhaltsvorschauGiven a hash, you want to switch the keys and values. That is, you want to create a new hash whose keys are the values of the old hash, and whose values are the keys of the old hash. If the old hash mapped "human" to "wolf;" you want the new hash to map "wolf" to "human."The simplest technique is to use the
Hash#invertmethod:phone_directory = { 'Alice' => '555-1212', 'Bob' => '555-1313', 'Mallory' => '111-1111' } phone_directory.invert # => {"111-1111"=>"Mallory", "555-1212"=>"Alice", "555-1313"=>"Bob"}Hash#invertprobably won't do what you want if your hash maps more than one key to the same value. Only one of the keys for that value will show up as a value in the inverted hash:phone_directory = { 'Alice' => '555-1212', 'Bob' => '555-1313', 'Carol' => '555-1313', 'Mallory' => '111-1111', 'Ted' => '555-1212' } phone_directory.invert # => {"111-1111"=>"Mallory", "555-1212"=>"Ted", "555-1313"=>"Bob"}To preserve all the data from the original hash, borrow the idea behind Recipe 5.6, and write a version ofinvertthat keeps an array of values for each key. The following is based on code by Tilo Sloboda:class Hash def safe_invert new_hash = {} self.each do |k,v| if v.is_a? Array v.each { |x| new_hash.add_or_append(x, k) } else new_hash.add_or_append(v, k) end end return new_hash endTheadd_or_appendmethod a lot like the methodMultivaluedHash#[]=defined in Recipe 5.6:def add_or_append(key, value) if has_key?(key) self[key] = [value, self[key]].flatten else self[key] = value end end end
Here'ssafe_invertin action:phone_directory.safe_invert # => {"111-1111"=>"Mallory", "555-1212"=>["Ted", "Alice"], # "555-1313"=>["Bob", "Carol"]} phone_directory.safe_invert.safe_invert # => {"Alice"=>"555-1212", "Mallory"=>"111-1111", "Ted"=>"555-1212", # => "Carol"=>"555-1313", "Bob"=>"555-1313"}Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Choosing Randomly from a Weighted List
- InhaltsvorschauYou want to pick a random element from a collection, where each element in the collection has a different probability of being chosen.Store the elements in a hash, mapped to their relative probabilities. The following code will work with a hash whose keys are mapped to relative integer probabilities:
def choose_ weighted(weighted) sum = weighted.inject(0) do |sum, item_and_weight| sum += item_and_weight[1] end target = rand(sum) weighted.each do |item, weight| return item if target <= weight target -= weight end end
For instance, if all the keys in the hash map to 1, the keys will be chosen with equal probability. If all the keys map to 1, except for one which maps to 10, that key will be picked 10 times more often than any single other key. This algorithm lets you simulate those probability problems that begin like, "You have a box containing 51 black marbles and 17 white marbles…":marbles = { :black => 51, :white => 17 } 3.times { puts choose_weighted(marbles) } # black # white # blackI'll use it to simulate a lottery in which the results have different probabilities of showing up:lottery_probabilities = { "You've wasted your money!" => 1000, "You've won back the cost of your ticket!" => 50, "You've won two shiny zorkmids!" => 20, "You've won five zorkmids!" => 10, "You've won ten zorkmids!" => 5, "You've won a hundred zorkmids!" => 1 } # Let's buy some lottery tickets. 5.times { puts choose_weighted(lottery_probabilities) } # You've wasted your money! # You've wasted your money! # You've wasted your money! # You've wasted your money! # You've won five zorkmids!An extremely naive solution would put the elements in a list and choose one at random. This doesn't solve the problem because it ignores weights altogether: low-weight elements will show up exactly as often as high-weight ones. A less naive solution would be to repeat each element in the list a number of times proportional to its weight. Under this implementation, our simulation of the marble box would containEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Building a Histogram
- InhaltsvorschauYou have an array that contains a lot of references to relatively few objects. You want to create a histogram, or frequency map: something you can use to see how often a given object shows up in the array.Build the histogram in a hash, mapping each object found to the number of times it appears.
module Enumerable def to_histogram inject(Hash.new(0)) { |h, x| h[x] += 1; h} end end [1, 2, 2, 2, 3, 3].to_histogram # => {1=>1, 2=>3, 3=>2} ["a", "b", nil, "c", "b", nil, "a"].to_histogram # => {"a"=>2, "b"=>2, "c"=>1, nil=>2} "Aye\nNay\nNay\nAbstaining\nAye\nNay\nNot Present\n".to_histogram # => {"Abstaining\n"=>1, "Nay\n"=>3, "Not Present\n"=>1, "Aye\n"=>2} survey_results = { "Alice" => :red, "Bob" => :green, "Carol" => :green, "Mallory" => :blue } survey_results.values.to_histogram # => {:red=>1, :green=>2, :blue=>1}Making a histogram is an easy and fast (linear-time) way to summarize a dataset. Histograms expose the relative popularity of the items in a dataset, so they're useful for visualizing optimization problems and dividing the "head" from the "long tail."Once you have a histogram, you can find the most or least common elements in the list, sort the list by frequency of appearance, or see whether the distribution of items matches your expectations. Many of the other recipes in this book build a histogram as a first step towards a more complex algorithm.Here's a quick way of visualizing a histogram as an ASCII chart. First, we convert the histogram keys to their string representations so they can be sorted and printed. We also store the histogram value for the key, since we can't do a histogram lookup later based on the string value we'll be using.def draw_graph(histogram, char="#") pairs = histogram.keys.collect { |x| [x.to_s, histogram[x]] }.sortThen we find the key with the longest string representation. We'll pad the rest of the histogram rows to this length, so that the graph bars will line up correctly.largest_key_size = pairs.max { |x,y| x[0].size <=> y[0].size }[0].sizeThen we print each key-value pair, padding with spaces as necessary.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Remapping the Keys and Values of a Hash
- InhaltsvorschauYou have two hashes with common keys but differing values. You want to create a new hash that maps the values of one hash to the values of another.
class Hash def tied_with(hash) remap do |h,key,value| h[hash[key]] = value end.delete_if { |key,value| key.nil? || value.nil? } endHere is theHash#remapmethod:def remap(hash={}) each { |k,v| yield hash, k, v } hash end endHere's how to useHash#tied_withto merge two hashes:a = {1 => 2, 3 => 4} b = {1 => 'foo', 3 => 'bar'} a.tied_with(b) # => {"foo"=>2, "bar"=>4} b.tied_with(a) # => {2=>"foo", 4=>"bar"}Thisremapmethod can be handy when you want to make a similar change to every item in a hash. It is also a good example of using theyieldmethod.Hash#remapis conceptually similar toHash#collect, butHash#collectbuilds up a nested array of key-value pairs, not a new hash.- The Facets library defines methods
Hash#update_eachandHash#replace_each!for remapping the keys and values of a hash
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Extracting Portions of Hashes
- InhaltsvorschauYou have a hash that contains a lot of values, but only a few of them are interesting. You want to select the interesting values and ignore the rest.You can use the
Hash#selectmethod to extract part of a hash that follows a certain rule. Suppose you had a hash where the keys wereTimeobjects representing a certain date, and the values were the number of web site clicks for that given day. We'll simulate such as hash with random data:require 'time' click_counts = {} 1.upto(30) { |i| click_counts[Time.parse("2006-09-#{i}")] = 400 + rand(700) } p click_counts # {Sat Sep 23 00:00:00 EDT 2006=>803, Tue Sep 12 00:00:00 EDT 2006=>829, # Fri Sep 01 00:00:00 EDT 2006=>995, Mon Sep 25 00:00:00 EDT 2006=>587, # …You might want to know the days when your click counts were low, to see if you could spot a trend.Hash#selectcan do that for you:low_click_days = click_counts.select {|key, value| value < 450 } # [[Thu Sep 14 00:00:00 EDT 2006, 449], [Mon Sep 11 00:00:00 EDT 2006, 406], # [Sat Sep 02 00:00:00 EDT 2006, 440], [Mon Sep 04 00:00:00 EDT 2006, 431], # …The array returned byHash#selectcontains a number of key-value pairs as two-element arrays. The first element of one of these inner arrays is a key into the hash, and the second element is the corresponding value. This is similar to howHash#eachyields a succession of two-element arrays.If you want another hash instead of an array of key-value pairs, you can useHash#injectinstead ofHash#select. In the code below,kvis a two-element array containing a key-value pair.kv[0]is a key fromclick_counts, andkv[1]is the corresponding value.low_click_days_hash = click_counts.inject({}) do |h, kv| k, v = kv h[k] = v if v < 450 h end # => {Mon Sep 25 00:00:00 EDT 2006=>403, # Wed Sep 06 00:00:00 EDT 2006=>443, # Thu Sep 28 00:00:00 EDT 2006=>419}You can also use theHash.[]constructor to create a hash from the array result ofHash#select:low_click_days_hash = Hash[*low_click_days.flatten] # => {Thu Sep 14 00:00:00 EDT 2006=>449, Mon Sep 11 00:00:00 EDT 2006=>406, # Sat Sep 02 00:00:00 EDT 2006=>440, Mon Sep 04 00:00:00 EDT 2006=>431, # …Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Searching a Hash with Regular Expressions
- InhaltsvorschauCredit: Ben GiddingsYou want to grep a hash: that is, find all keys and/or values in the hash that match a regular expression.The fastest way to grep the keys of a hash is to get the keys as an array, and grep that:
h = { "apple tree" => "plant", "ficus" => "plant", "shrew" => "animal", "plesiosaur" => "animal" } h.keys.grep /p/ # => ["apple tree", "plesiosaur"]The solution for grepping the values of a hash is similar (substituteHash#valuesforHash#keys), unless you need to map the values back to the keys of the hash. If that's what you need, the fastest way is to useHash#eachto get key-value pairs, and match the regular expression against each value.h.inject([]) { |res, kv| res << kv if kv[1] =~ /p/; res } # => [["ficus", "plant"], ["apple tree", "plant"]]The code is similar if you need to find key-value pairs where either the key or the value matches a regular expression:class Hash def grep(pattern) inject([]) do |res, kv| res << kv if kv[0] =~ pattern or kv[1] =~ pattern res end end end h.grep(/pl/) # => [["ficus", "plant"], ["apple tree", "plant"], ["plesiosaur", "animal"]] h.grep(/plant/) # => [["ficus", "plant"], ["apple tree", "plant"]] h.grep(/i.*u/) # => [["ficus", "plant"], ["plesiosaur", "animal"]]
Hashdefines its owngrepmethod, but it will never give you any results.Hash#grepis inherited fromEnumerable#grep, which tries to match the output ofeachagainst the given regular expression.Hash#eachreturns a series of two-item arrays containing key-value pairs, and an array will never match a regular expression. TheHash#grepimplementation above is more useful.Hash#keys.grepandHash#values.grepare more efficient than matching a regular expression against each key or value in aHash, but those methods create a new array containing all the keys in the Hash. If memory usage is your primary concern, iterate overeach_keyoreach_valueinstead:res = [] h.each_key { |k| res << k if k =~ /p/ } res # => ["apple tree", "plesiosaur"]Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 6: Files and Directories
- InhaltsvorschauAs programming languages increase in power, we programmers get further and further from the details of the underlying machine language. When it comes to the operating system, though, even the most modern programming languages live on a level of abstraction that looks a lot like the C and Unix libraries that have been around for decades.We covered this kind of situation in Chapter 3 with Ruby's
Timeobjects, but the issue really shows up when you start to work with files. Ruby provides an elegant object-oriented interface that lets you do basic file access, but the more advanced file libraries tend to look like the C libraries they're based on. To lock a file, change its Unix permissions, or read its metadata, you'll need to remember method names likemtime, and the meaning of obscure constants likeFile::LOCK_EXand0644. This chapter will show you how to use the simple interfaces, and how to make the more obscure interfaces easier to use.Looking at Ruby's support for file and directory operations, you'll see four distinct tiers of support. The most common operations tend to show up on the lowernumbered tiers:Fileobjects to read and write the contents of files, andDirobjects to list the contents of directories. For examples, see Recipes 6.5, 6.7, and 6.17. Also see Recipe 6.13 for a Ruby-idiomatic approach.- Class methods of
Fileto manipulate files without opening them. For instance, to delete a file, examine its metadata, or change its permissions. For examples, see Recipes 6.1, 6.3, and 6.4. - Standard libraries, such as
findto walk directory trees, andfileutilsto perform common filesystem operations like copying files and creating directories. For examples, see Recipes 6.8, 6.12, and 6.20. - Gems like
file-tail,lockfile, andrubyzip, which fill in the gaps left by the standard library. Most of the file-related gems covered in this book deal with specific file formats, and are covered in Chapter 12.
Kernel#openis the simplest way to open a file. It returns aFilelobject that you can read from or write to, depending on the "mode" constant you pass in. I'll introduce read mode and write mode here; there are several others, but I'll talk about most of those as they come up in recipes.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Checking to See If a File Exists
- InhaltsvorschauGiven a filename, you want to see whether the corresponding file exists and is the right kind for your purposes.Most of the time you'll use the
File.file? predicate, which returns true only if the file is an existing regular file (that is, not a directory, a socket, or some other special file).filename = 'a_file.txt' File.file? filename # => false require 'fileutils' FileUtils.touch(filename) File.file? filename # => true
Use theFile.exists? predicate instead if the file might legitimately be a directory or other special file, or if you plan to create a file by that name if it doesn't exist.File.exists? will return true if a file of the given name exists, no matter what kind of file it is.directory_name = 'a_directory' FileUtils.mkdir(directory_name) File.file? directory_name # => false File.exists? directory_name # => true
A true response fromFile.exists? means that the file is present on the filesystem, but says nothing about what type of file it is. If you open up a directory thinking it's a regular file, you're in for an unpleasant surprise. This is whyFile.file? is usually more useful thanFile.exists?.Ruby provides several other predicates for checking the type of a file: the other commonly useful one isFile.directory?:File.directory? directory_name # => true File.directory? filename # => false
The rest of the predicates are designed to work on Unix systems.File.blockdev? tests or block-device files (such as hard-drive partitions),File.chardev? tests for character-device files (such as TTYs),File.socket? tests for socket files, andFile.pipe? tests for named pipes,File.blockdev? '/dev/hda1' # => true File.chardev? '/dev/tty1' # => true File.socket? '/var/run/mysqld/mysqld.sock' # => true system('mkfifo named_pipe') File.pipe? 'named_pipe' # => trueFile.symlink? tests whether a file is a symbolic link to another file, but you only need to use it when you want to treat symlinks differently from other files. A symlink to a regular file will satisfyEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Checking Your Access to a File
- InhaltsvorschauYou want to see what you can do with a file: whether you have read, write, or (on Unix systems) execute permission on it.Use the class methods
File.readable?,File.writeable?, andFile.executable?.File.readable?('/bin/ls') # => true File.readable?('/etc/passwd-') # => false filename = 'test_file' File.open(filename, 'w') {} File.writable?(filename) # => true File.writable?('/bin/ls') # => false File.executable?('/bin/ls') # => true File.executable?(filename) # => falseRuby's file permission tests are Unix-centric, butreadable? andwritable? work on any platform; the rest fail gracefully when the OS doesn't support them. For instance, Windows doesn't have the Unix notion of execute permission, soFile.executable? always returnstrueon Windows.The return value of a Unix permission test depends in part on whether your user owns the file in question, or whether you belong to the Unix group that owns it. Ruby provides convenience testsFile.owned? andFile.grpowned? to check this.File.owned? 'test_file' # => true File.grpowned? 'test_file' # => true File.owned? '/bin/ls' # => false
On Windows,File.owned? always returns true (even for a file that belongs to another user) andFile.grpowned? always returns false.TheFilemethods described above should be enough to answer most permission questions about a file, but you can also see a file's Unix permissions in their native form by looking at the file's mode. The mode is a number, each bit of which has a different meaning within the Unix permission system. You can view a file's mode withFile::Lstat#mode.The result ofmodecontains some extra bits describing things like the type of a file. You probably want to strip that information out by masking those bits. This example demonstrates that the file originally created in the solution has a Unix permission mask of0644:File.lstat('test_file').mode & 0777 # Keep only the permission bits. # => 420 # That is, 0644 octal.readable?,writableEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Changing the Permissions on a File
- InhaltsvorschauYou want to control access to a file by modifying its Unix permissions. For instance, you want to make it so that everyone on your system can read a file, but only you can write to it.Unless you've got a lot of Unix experience, it's hard to remember the numeric codes for the nine Unix permission bits. Probably the first thing you should do is define constants for them. Here's one constant for every one of the permission bits. If these names are too concise for you, you can name them
USER_READ, GROUP_WRITE, OTHER_ EXECUTE, and so on.class File U_R = 0400 U_W = 0200 U_X = 0100 G_R = 0040 G_W = 0020 G_X = 0010 O_R = 0004 O_W = 0002 O_X = 0001 end
You might also want to define these three special constants, which you can use to set the user, group, and world permissions all at once:class File A_R = 0444 A_W = 0222 A_X = 0111 end
Now you're ready to actually change a file's permissions. Every Unix file has a permission bitmap, or mode, which you can change (assuming you have the permissions!) by callingFile.chmod. You can manipulate the constants defined above to get a new mode, then pass it in along with the filename toFile.chmod.The following threechmodcalls are equivalent: for the filemy_file, they give readwrite access to to the user who owns the file, and restrict everyone else to read-only access. This is equivalent to the permission bitmap 11001001, the octal number 0644, or the decimal number 420.open("my_file", "w") {} File.chmod(File::U_R | File::U_W | File::G_R | File::O_R, "my_file") File.chmod(File::A_R | File::U_W, "my_file") File.chmod(0644, "my_file") # Bitmap: 110001001 File::U_R | File::U_W | File::G_R | File::O_R # => 420 File::A_R | File::U_W # => 420 0644 # => 420 File.lstat("my_file").mode & 0777 # => 420Note how I build a full permission bitmap by combining the permission constants with theORoperator (|).A Unix file has nine associated permission bits that are consulted whenever anyone tries to access the file. They're divided into three sets of three bits. There's one set for the user who owns the file, one set is for the user group who owns the file, and one set is for everyone else.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Seeing When a File Was Last Used Problem
- InhaltsvorschauYou want to see when a file was last accessed or modified.The result of
File.statcontains a treasure trove of metadata about a file. Perhaps the most useful of its methods are the two time methodsmtime(the last time anyone wrote to the file), andatime(the last time anyone read from the file).open("output", "w") { |f| f << "Here's some output.\n" } stat = File.stat("output") stat.mtime # => Thu Mar 23 12:23:54 EST 2006 stat.atime # => Thu Mar 23 12:23:54 EST 2006 sleep(2) open("output", "a") { |f| f << "Here's some more output.\n" } stat = File.stat("output") stat.mtime # => Thu Mar 23 12:23:56 EST 2006 stat.atime # => Thu Mar 23 12:23:54 EST 2006 sleep(2) open("output") { |f| contents = f.read } stat = File.stat("output") stat.mtime # => Thu Mar 23 12:23:56 EST 2006 stat.atime # => Thu Mar 23 12:23:58 EST 2006A file'satimechanges whenever data is read from the file, and itsmtimechanges whenever data is written to the file.There's also actimemethod, but it's not as useful as the other two. Contrary to semi-popular belief,ctimedoes not track the creation time of the file (there's no way to track this in Unix). A file'sctimeis basically a more inclusive version of itsmtime. Thectimechanges not only when someone modifies the contents of a file, but when someone changes its permissions or its other metadata.All three methods are useful for separating the files that actually get used from the ones that just sit there on disk. They can also be used in sanity checks.Here's code for the part of a game that saves and loads the game state to a file. As a deterrent against cheating, when the game loads a save file it performs a simple check against the file's modification time. If it differs from the timestamp recorded inside the file, the game refuses to load the save file.Thesave_gamemethod is responsible for recording the timestamp:def save_game(file) score = 1000 open(file, "w") do |f| f.puts(score) f.puts(Time.new.to_i) end end
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Listing a Directory
- InhaltsvorschauYou want to list or process the files or subdirectories within a directory.If you're starting from a directory name, you can use
Dir.entriesto get an array of the items in the directory, orDir.foreachto iterate over the items. Here's an exampleof each run on a sample directory:# See the chapter intro to get the create_tree library require 'create_tree' create_tree 'mydir' => [ {'subdirectory' => [['file_in_subdirectory', 'Just a simple file.']] }, '.hidden_file', 'ruby_script.rb', 'text_file' ] Dir.entries('mydir') # => [".", "..", ".hidden_file", "ruby_script.rb", "subdirectory", # "text_file"] Dir.foreach('mydir') { |x| puts x if x != "." && x != ".."} # .hidden_file # ruby_script.rb # subdirectory # text_fileYou can also useDir[]to pick up all files matching a certain pattern, using a format similar to the bash shell's glob format (and somewhat less similar to the wildcard format used by the Windows command-line shell):# Find all the "regular" files and subdirectories in mydir. This excludes # hidden files, and the special directories . and .. Dir["mydir/*"] # => ["mydir/ruby_script.rb", "mydir/subdirectory", "mydir/text_file"] # Find all the .rb files in mydir Dir["mydir/*.rb"] # => ["mydir/ruby_script.rb"]
You can also open a directory handle withDir#open, and treat it like any other Enumerable. Methods likeeach,each_with_index, grep, andrejectwill all work (but see below if you want to call them more than once). As withFile#open, you should do your directory processing in a code block so that the directory handle will get closed once you're done with it.Dir.open('mydir') { |d| d.grep /file/ } # => [".hidden_file", "text_file"] Dir.open('mydir') { |d| d.each { |x| puts x } } # . # .. # .hidden_file # ruby_script.rb # subdirectory # text_fileReading entries from aDirobject is more like reading data from a file than iterating over an array. If you call one of theDirinstance methods and then want to call another one on the sameDirobject, you'll need to callEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Reading the Contents of a File
- InhaltsvorschauYou want to read some or all of a file into memory.Open the file with
Kernel#open, and pass in a code block that does the actual reading. To read the entire file into a single string, useIO#read:#Put some stuff into a file. open('sample_file', 'w') do |f| f.write("This is line one.\nThis is line two.") end # Then read it back out. open('sample_file') { |f| f.read } # => "This is line one.\nThis is line two."To read the file as an array of lines, useIO#readlines:open('sample_file') { |f| f.readlines } # => ["This is line one.\n", "This is line two."]To iterate over each line in the file, useIO#each. This technique loads only one line into memory at a time:open('sample_file').each { |x| p x } # "This is line one.\n" # "This is line two."How much of the file do you want to read into memory at once? Reading the entire file in one gulp uses memory equal to the size of the file, but you end up with a string, and you can use any of Ruby's string processing techniques on it.The alternative is to process the file one chunk at a time. This uses only the memory needed to store one chunk, but it can be more difficult to work with, because any given chunk may be incomplete. To process a chunk, you may end up reading the next chunk, and the next. This code reads the first 50-byte chunk from a file, but it turns out not to be enough:puts open('conclusion') { |f| f.read(50) } # "I know who killed Mr. Lambert," said Joe. "It wasIf a certain string always marks the end of a chunk, you can pass that string intoIO#eachto get one chunk at a time, as a series of strings. This lets you process each full chunk as a string, and it uses less memory than reading the entire file.If a certain string always marks the end of a chunk, you can pass that string intoIO#eachto get one chunk at a time, as a series of strings. This lets you process each full chunk as a string, and it uses less memory than reading the entire file.# Create a file… open('end_separated_records', 'w') do |f| f << %{This is record one. It spans multiple lines.ENDThis is record two.END} end # And read it back in. open('end_separated_records') { |f| f.each('END') { |record| p record } } # "This is record one.\nIt spans multiple lines.END" # "This is record two.END"Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Writing to a File
- InhaltsvorschauYou want to write some text or Ruby data structures to a file. The file might or might not exist. If it does exist, you might want to overwrite the old contents, or just append new data to the end of the file.Open the file in write mode ('w'). The file will be created if it doesn't exist, and truncated to zero bytes if it does exist. You can then use
IO#writeor the << operator to write strings to the file, as though the file itself were a string and you were appending to it.You can also useIO#putsorIO#pto write lines to the file, the same way you can useKernel#putsorKernel#pto write lines to standard output.Both of the following chunks of code destroy the previous contents of the fileoutput, then write a new string to the file:open('output', 'w') { |f| f << "This file contains great truths.\n" } open('output', 'w') do |f| f.puts 'The great truths have been overwritten with an advertisement.' end open('output') { |f| f.read } # => "The great truths have been overwritten with an advertisement.\n"To append to a file without overwriting its old contents, open the file in append mode ('a') instead of write mode:open('output', "a") { |f| f.puts 'Buy Ruby(TM) brand soy sauce!' } open('output') { |f| puts f.read } # The great truths have been overwritten with an advertisement. # Buy Ruby(TM) brand soy sauce!Sometimes you'll only need to write a single (possibly very large) string to a file. Usually, though, you'll be getting your strings one at a time from a data structure or some other source, and you'll call puts or the append operator within some kind of loop:open('output', 'w') do |f| [1,2,3].each { |i| f << i << ' and a ' } end open('output') { |f| f.read } # => "1 and a 2 and a 3 and a "Since the << operator returns the filehandle it wrote to, you can chain calls to it. As seen above, this feature lets you write multiple strings to a file in a single line of Ruby code.Because opening a file in write mode destroys the file's existing contents, you should only use it when you don't care about the old contents, or after you've read them into memory for later use. Append mode is nondestructive, making it useful for files like log iles, which need to be updated periodically without destroying their old contents.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Writing to a Temporary File
- InhaltsvorschauYou want to write data to a secure temporary file with a unique name.Create a
Tempfile object. It has all the methods of aFileobject, and it will be in a location on disk guaranteed to be unique.require 'tempfile' out = Tempfile.new("tempfile") out.path # => "/tmp/tempfile23786.0"ATempfileobject is opened for read-write access (mode w+), so you can write to it and then read from it without having to close and reopen it:out << "Some text." out.rewind out.read # => "Some text." out.close
Note that you can't pass a code block into theTempfileconstructor: you have to assign the temp file to an object, and callTempfile#closewhen you're done.To avoid security problems, use theTempfileclass to generate temp file names, instead of writing the code yourself. TheTempfileclass creates a file on disk guaranteed not to be in use by any other thread or process, and sets that file's permissions so that only you can read or write to it. This eliminates any possibility that a hostile process might inject fake data into the temp file, or read what you write.The name of a temporary file incorporates the string you pass into theTempfileconstructor, the process ID of the current process ($$, or$PIDif you've done aninclude English), and a unique number. By default, temporary files are created inDir:: tmpdir(usually /tmp), but you can pass in a different directory name:out = Tempfile.new("myhome_tempfile", "/home/leonardr/temp/")No matter where you create your temporary files, when your process exits, all of its temporary files are automatically destroyed. If you want the data you wrote to temporary files to live longer than your process, you should copy or move the temporary files to "real" files:require 'fileutils' FileUtils.mv(out.path, "/home/leonardr/old_tempfile")
Thetempfileassumes that the operating system can atomically open a file and get an exclusive lock on it. This doesn't work on all filesystems. Ara Howard'slockfilelibrary (available as a gem of the same name) uses linking, which is atomic everywhere.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Picking a Random Line from a File
- InhaltsvorschauYou want to choose a random line from a file, without loading the entire file into memory.Iterate over the file, giving each line a chance to be the randomly selected one:
module Enumerable def random_line selected = nil each_with_index { |line, lineno| selected = line if rand < 1.0/lineno } return selected.chomp if selected end end #Create a file with 1000 lines open('random_line_test', 'w') do |f| 1000.times { |i| f.puts "Line #{i}" } end #Pick random lines from the file. f = open('random_line_test') f.random_line # => "Line 520" f.random_line # => nil f.rewind f.random_line # => "Line 727"The obvious solution reads the entire file into memory;File.open('random_line_test') do |f| l = f.readlines l[rand(l.size)].chomp end # => "Line 708"The recommended solution is just as fast, and only reads one line at a time into memory. However, once it's done, the file pointer has been set to the end of the file and you can't access the file anymore without callingFile#rewind. If you want to pick a lot of random lines from a file, reading the entire file into memory might be preferable to iterating over it multiple times.This recipe makes for a good command-line tool. The following code uses the special variable $., which holds the number of the line most recently read from a file:$ ruby -e 'rand < 1.0/$. and line = $_ while gets; puts line.chomp if line'
The algorithm works because, although lines that come earlier in the file have a better chance of being selected initially, they also have more chances to be replaced by a later line. A proof by induction demonstrates that the algorithm gives equal weight to each line in the file.The base case is a file of a single line, where it will obviously work: any value ofKernel#randwill be less than 1, so the first line will always be chosen.Now for the inductive step. Assume that the algorithm works for a file of n lines: that is, each of the first n lines has a 1/n chance of being chosen. Then, add another line to the file and process the new line. The chance that line n+1 will become the randomly chosen line isEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Comparing Two Files
- InhaltsvorschauYou want to see if two files contain the same data. If they differ, you might want to represent the differences between them as a string: a patch from one to the other.If two files differ, it's likely that their sizes also differ, so you can often solve the problem quickly by comparing sizes. If both files are regular files with the same size, you'll need to look at their contents.This code does the cheap checks first:
- If one file exists and the other does not, they're not the same.
- If neither file exists, say they're the same.
- If the files are the same file, they're the same.
- If the files are of different types or sizes, they're not the same.
class File def File.same_contents(p1, p2) return false if File.exists?(p1) != File.exists?(p2) return true if !File.exists?(p1) return true if File.expand_path(p1) == File.expand_path(p2) return false if File.ftype(p1) != File.ftype(p2) || File.size(p1) != File.size(p2)
Otherwise, it compares the files contents, a block at a time:open(p1) do |f1| open(p2) do |f2| blocksize = f1.lstat.blksize same = true while same && !f1.eof? && !f2.eof? same = f1.read(blocksize) == f2.read(blocksize) end return same end end end end
To illustrate, I'll create two identical files and compare them. I'll then make them slightly different, and compare them again.1.upto(2) do |i| open("output#{i}", 'w') { |f| f << 'x' * 10000 } end File.same_contents('output1', 'output2') # => true open("output1", 'a') { |f| f << 'x' } open("output2", 'a') { |f| f << 'y' } File.same_contents('output1', 'output2') # => false File.same_contents('nosuchfile', 'output1') # => false File.same_contents('nosuchfile1', 'nosuchfile2') # => trueThe code in the Solution works well if you only need to determine whether two files are identical. If you need to see the differences between two files, the most useful tool is is Austin Ziegler'sDiff::LCSlibrary, available as thediff-lcsEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Performing Random Access on "Read-Once" Input Streams
- InhaltsvorschauYou have an
IOobject, probably a socket, that doesn't support random-access methods likeseek,pos=, andrewind. You want to treat this object like a file on disk, where you can jump around and reread parts of the file.The simplest solution is to read the entire contents of the socket (or as much as you're going to need) and put it into aStringIOobject. You can then treat theStringIOobject exactly like a file:require 'socket' require 'stringio' sock = TCPSocket.open("www.example.com", 80) sock.write("GET /\n") file = StringIO.new(sock.read) file.read(10) # => "<HTML>\r\n<H" file.rewind file.read(10) # => "<HTML>\r\n<H" file.pos = 90 file.read(15) # => " this web page "A socket is supposed to work just like a file, but sometimes the illusion breaks down. Since the data is coming from another computer over which you have no control, you can't just go back and reread data you've already read. That data has already been sent over the pipe, and the server doesn't care if you lost it or need to process it again.If you have enough memory to read the entire contents of a socket, it's easy to put the results into a form that more closely simulates a file on disk. But you might not want to read the entire socket, or the socket may be one that keeps sending data until you close it. In that case you'll need to buffer the data as you read it. Instead of using memory for the entire contents of the socket (which may be infinite), you'll only use memory for the data you've actually read.This code defines aBufferedIOclass that adds data to an internalStringIOas it's read from its source:class BufferedIO def initialize(io) @buff = StringIO.new @source = io @pos = 0 end def read(x=nil) to_read = x ? to_read = x+@buff.pos-@buff.size : nil _append(@source.read(to_read)) if !to_read or to_read > 0 @buff.read(x) end def pos=(x) read(x-@buff.pos) if x > @buff.size @buff.pos = x end def seek(x, whence=IO::SEEK_SET) case whence when IO::SEEK_SET then self.pos=(x) when IO::SEEK_CUR then self.pos=(@buff.pos+x) when IO::SEEK_END then read; self.pos=(@buff.size-x) # Note: SEEK END reads all the socket data. end pos end # Some methods can simply be delegated to the buffer. ["pos", "rewind", "tell"].each do |m| module_eval "def #{m}\n@buff.#{m}\nend" end private def _append(s) @buff << s @buff.pos -= s.size end endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Walking a Directory Tree
- InhaltsvorschauYou want to recursively process every subdirectory and file within a certain directory.Suppose that the directory tree you want to walk looks like this (see this chapter's introduction section for the
create_treelibrary that can build this directory tree automatically):require 'create_tree' create_tree './' => [ 'file1', 'file2', { 'subdir1/' => [ 'file1' ] }, { 'subdir2/' => [ 'file1', 'file2', { 'subsubdir/' => [ 'file1' ] } ] } ]The simplest solution is to load all the files and directories into memory with a big recursive file glob, and iterate over the resulting array. This uses a lot of memory because all the filenames are loaded into memory at once:Dir['**/**'] # => ["file1", "file2", "subdir1", "subdir2", "subdir1/file1", # "subdir2/file1", "subdir2/file2", "subdir2/subsubdir", # "subdir2/subsubdir/file1"]
A more elegant solution is to use thefindmethod in theFindmodule. It performs a depth-first traversal of a directory tree, and calls the given code block on each directory and file. The code block should take as an argument the full path to a directory or file.This snippet callsFind.findwith a code block that simply prints out each path it receives. This demonstrates how Ruby performs the traversal:require 'find' Find.find('./') { |path| puts path } # ./ # ./subdir2 # ./subdir2/subsubdir # ./subdir2/subsubdir/file1 # ./subdir2/file2 # ./subdir2/file1 # ./subdir1 # ./subdir1/file1 # ./file2 # ./file1Even if you're not a system administrator, the demands of keeping your own files organized will frequently call for you to process every file in a directory tree. You may want to backup, modify, or delete each file in the directory structure, or you may just want to see what's there.Normally you'll want to at least look at every file in the tree, but sometimes you'll want to skip certain directories. For instance, you might know that a certain directory is full of a lot of large files you don't want to process. When your block is passed a path to a directory, you can preventEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Locking a File
- InhaltsvorschauYou want to prevent other threads or processes from modifying a file that you're working on.Open the file, then lock it with
File#flock. There are two kinds of lock; pass in theFileconstant for the kind you want.File::LOCK_EXgives you an exclusive lock, or write lock. If your thread has an exclusive lock on a file, no other thread or process can get a lock on that file. Use this when you want to write to a file without anyone else being able to write to it.File::LOCK_SHwill give you a shared lock, or read lock. Other threads and processes can get their own shared locks on the file, but no one can get an exclusive lock. Use this when you want to read a file and know that it won't change while you're reading it.
Once you're done using the file, you need to unlock it. CallFile#flockagain, and pass inFile::LOCK_UNas the lock type. You can skip this step if you're running on Windows.The best way to handle all this is to enclose the locking and unlocking in a method that takes a block, the wayopendoes:def flock(file, mode) success = file.flock(mode) if success begin yield file ensure file.flock(File::LOCK_UN) end end return success end
This makes it possible to lock a file without having to worry about unlocking it later. Even if your block raises an exception, the file will be unlocked and another thread can use it.open('output', 'w') do |f| flock(f, File::LOCK_EX) do |f| f << "Kiss me, I've got a write lock on a file!" end endDifferent operating systems support different ways of locking files. Ruby'sflockimplementation tries to hide the differences behind a common interface that looks like Unix's file locking interface. In general, you can useflockas though you were on Unix, and your scripts will work across platforms.On Unix, both exclusive and shared locks work only if all threads and processes play by the rules. If one thread has an exclusive lock on a file, another thread can still open the file without locking it and wreak havoc by overwriting its contents. That's why it's important to get a lock on any file that might conceivably be used by another thread or another process on the system.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Backing Up to Versioned Filenames
- InhaltsvorschauYou want to copy a file to a numbered backup before overwriting the original file. More generally: rather than overwriting an existing file, you want to use a new file whose name is based on the original filename.Use
String#succto generate versioned suffixes for a filename until you find one that doesn't already exist:class File def File.versioned_filename(base, first_suffix='.0') suffix = nil filename = base while File.exists?(filename) suffix = (suffix ? suffix.succ : first_suffix) filename = base + suffix end return filename end end 5.times do |i| name = File.versioned_filename('filename.txt') open(name, 'w') { |f| f << "Contents for run #{i}" } puts "Created #{name}" end # Created filename.txt # Created filename.txt.0 # Created filename.txt.1 # Created filename.txt.2 # Created filename.txt.3If you want to copy or move the original file to the versioned filename as a prelude to writing to the original file, include theftoolslibrary to add the class methodsFile.copyandFile.move. Then callversioned_filenameand useFile.copyorFile.moveto put the old file in its new place:require 'ftools' class File def File.to_backup(filename, move=false) new_filename = nil if File.exists? filename new_filename = File. versioned_filename(filename) File.send(move ? :move : :copy, filename, new_filename) end return new_filename end end
Let's back upfilename.txta couple of times. Recall from earlier that the files filename.txt.[0-3] already exist.File.to_backup('filename.txt') # => "filename.txt.4" File.to_backup('filename.txt') # => "filename.txt.5"Now let's do a destructive backup:File.to_backup('filename.txt', true) # => "filename.txt.6" File.exists? 'filename.txt' # => falseYou can't back up what doesn't exist:File.to_backup('filename.txt') # => nilIf you anticipate more than 10 versions of a file, you should add additional zeroes to the initial suffix. Otherwise,Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Pretending a String Is a File
- InhaltsvorschauYou want to call code that expects to read from an open file object, but your source is a string in memory. Alternatively, you want to call code that writes its output to a file, but have it actually write to a string.The
StringIOclass wraps a string in the interface of theIOclass. You can treat it like a file, then get everything that's been "written" to it by calling itsstringmethod.Here's aStringIOused as an input source:require 'stringio' s = StringIO.new %{I am the very model of a modern major general. I've information vegetable, animal, and mineral.} s.pos # => 0 s.each_line { |x| puts x } # I am the very model of a modern major general. # I've information vegetable, animal, and mineral. s.eof? # => true s.pos # => 95 s.rewind s.pos # => 0 s.grep /general/ # => ["I am the very model of a modern major general.\n"]Here areStringIOobjects used as output sinks:s = StringIO.new s.write('Treat it like a file.') s.rewind s.write("Act like it's") s.string # => "Act like it's a file." require 'yaml' s = StringIO.new YAML.dump(['A list of', 3, :items], s) puts s.string # --- # - A list of # - 3 # - :itemsThe Adapter is a common design pattern: to make an object acceptable as input to a method, it's wrapped in another object that presents the appropriate interface. TheStringIOclass is an Adapter betweenStringandFile(orIO), designed for use with methods that work onFileorIOinstances. With aStringIO, you can disguise a string as a file and use those methods without them ever knowing they haven't really been given a file.For instance, if you want to write unit tests for a library that reads from a file, the simplest way is to pass in predefinedStringIOobjects that simulate files with various contents. If you need to modify the output of a method that writes to a file, aStringIOcan capture the output, making it easy to modify and send on to its final destination.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Redirecting Standard Input or Output
- InhaltsvorschauYou don't want the standard input, output, or error of your process to go to the default
IOobjects set up by the Ruby interpreter. You want them to go to other filetype objects of your own choosing.You can assign anyIOobject (aFile, aSocket, or what have you) to the global variables $stdin, $stdout, or$stderr. You can then read from or write to those objects as though they were the originals.This short Ruby program demonstrates how to redirect theKernelmethods that print to standard output. To avoid confusion, I'm presenting it as a standalone Ruby program rather than an interactiveirbsession.#!/usr/bin/ruby -w # ./redirect_stdout.rb require 'stringio' new_stdout = StringIO.new $stdout = new_stdout puts "Hello, hello." puts "I'm writing to standard output." $stderr.puts "#{new_stdout.size} bytes written to standard ouput so far." $stderr.puts "You haven't seen anything on the screen yet, but you soon will:" $stderr.puts new_stdout.stringRun this program and you'll see the following:$ ruby redirect_stdout.rb 46 bytes written to standard output so far. You haven't seen anything on the screen yet, but you soon will: Hello, hello. I'm writing to standard output.
If you have any Unix experience, you know that when you run a Ruby script from the command line, you can make the shell redirect its standard input, output, and error streams to files or other programs. This technique lets you do the same thing from within a Ruby script.You can use this as a quick and dirty way to write errors to a file, write output to aStringIOobject (as seen above), or even read input from a socket. Within a script, you can programatically decide where to send your output, or receive standard input from multiple sources. These things are generally not possible from the command line without a lot of fancy shell scripting.The redirection technique is especially useful when you've written or inherited a script that prints text to standard output, and you need to make it capable of printing to any file-like object. Rather than changing almost every line of your code, you can just setEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Processing a Binary File
- InhaltsvorschauYou want to read binary data from a file, or write it to one.Since Ruby strings make no distinction between binary and text data, processing a binary file needn't be any different than processing a text file. Just make sure you add "b" to your file mode when you open a binary file on Windows.This code writes 10 bytes of binary data to a file, then reads it back:
open('binary', 'wb') do |f| (0..100).step(10) { |b| f << b.chr } end s = open('binary', 'rb') { |f| f.read } # => "\000\n\024\036(2<FPZd"If you want to process a binary file one byte at a time, you'll probably enjoy the wayeach_bytereturns each byte of the file as a number, rather than as single-character strings:open('binary', 'rb') { |f| f.each_byte { |b| puts b } } # 0 # 10 # 20 # … # 90 # 100The methods introduced earlier to deal with text files work just as well for binary files, assuming that your binary files are supposed to be processed from beginning to end, the way text files typically are. If you want random access to the contents of a binary file, you can manipulate your file object's "cursor."Think of the cursor as a pointer to the first unread byte in the open file. The current position of the cursor is accessed by the methodIO#pos. When you open the file, it's set to zero, just before the first byte. You can then useIO#readto read a number of bytes starting from the current position of the cursor, incrementing the cursor as a side effect.f = open('binary') f.pos # => 0 f.read(1) # => "\000" f.pos # => 1You can also just setposto jump to a specific byte in the file:f.pos = 4 # => 4 f.read(2) # => "(2" f.pos # => 6
You can useIO#seekto move the cursor forward or backward relative to its current position (withFile::SEEK_CUR), or to move to a certain distance from the end of a file (withFile::SEEK_END). Unlike the iterator methods, which go through the entire file once, you can useEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Deleting a File
- InhaltsvorschauYou want to delete a single file, or a whole directory tree.Removing a file is simple, with
File.delete:import 'fileutils' FileUtils.touch "doomed_file" File.exists? "doomed_file" # => true File.delete "doomed_file" File.exists? "doomed_file" # => false
Removing a directory tree is also fairly simple. The most confusing thing about it is the number of different methods Ruby provides to do it. The method you want is probablyFileUtils.remove_dir, which recursively deletes the contents of a directory:Dir.mkdir "doomed_directory" File.exists? "doomed_directory" # => true FileUtils.remove_dir "doomed_directory" File.exists? "doomed_directory" # => false
Ruby provides several methods for removing directories, but you really only needremove_dir. Dir.deleteandFileUtils.rmdirwill only work if the directory is already empty. Therm_randrm_rfdefined inFileUtilsare similar toremove_dir, but if you're a Unix user you may find their names more mneumonic.You should also know about the:secureoption torm_rf, because theremove_dirmethod and all its variants are vulnerable to a race condition when you remove a world-writable directory. The risk is that a process owned by another user might create a symlink in that directory while you're deleting it. This would make you delete the symlinked file along with the files you actually meant to delete.Passing in the:secureoption torm_rfslows down deletions significantly (it has to change the permissions on the directory before deleting it), but it avoids the race condition. If you're running Ruby 1.8, you'll also need to hack theFileUtilsmodule a little bit to work around a bug (the bug is fixed in Ruby 1.9):?# A hack to make a method used by rm_rf actually available module FileUtils module_function :fu_world_writable? end Dir.mkdir "/tmp/doomed_directory" FileUtils.rm_rf("/tmp/doomed_directory", :secure=>true) File.exists? "/tmp/doomed_directory" # => falseWhy isn't the:secureoption the default for rm_rf? Because secure deletion isn't thread-safe: it actually changes the current working directory of the process. You need to choose between thread safety and a possible security hole.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Truncating a File
- InhaltsvorschauYou want to truncate a file to a certain length, probably zero bytes.Usually, you want to destroy the old contents of a file and start over. Opening a file for write access will automatically truncate it to zero bytes, and let you write new contents to the file:
filename = 'truncate.txt' open(filename, 'w') { |f| f << "All of this will be truncated." } File.size(filename) # => 30 f = open(filename, 'w') {} File.size(filename) # => 0If you just need to truncate the file to zero byt es, and not write any new contents to it, you can open it with an access mode ofFile::TRUNC.open(filename, 'w') { |f| f << "Here are some new contents." } File.size(filename) # => 27 f = open(filename, File::TRUNC) {} File.size(filename) # => 0You can't actually do anything with aFILEwhose access mode isFile::TRUNC:open(filename, File::TRUNC) do |f| f << "At last, an empty file to write to!" end # IOError: not opened for writing
Transient files are the most likely candidates for truncation. Log files are often truncated, automatically or by hand, before they grow too large.The most common type of truncation is truncating a file to zero bytes, but theFile.truncatemethod can truncate a file to any number of bytes, not just zero. You can also use the instance method,File#truncate, to truncate a file you've opened for writing:f = open(filename, 'w') do |f| f << 'These words will remain intact after the file is truncated.' end File.size(filename) # => 59 File.truncate(filename, 30) File.size(filename) # => 30 open(filename) { |f| f.read } # => "These words will remain intact"These methods don't always make a file smaller. If the file starts out smaller than the size you give, they append zero-bytes (\000) to the end of file until the file reaches the specified size.f = open(filename, "w") { |f| f << "Brevity is the soul of wit." } File.size(filename) # => 27 File.truncate(filename, 30) File.size(filename) # => 30 open(filename) { |f| f.read } # => "Brevity is the soul of wit.\000\000\000"Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Finding the Files You Want
- InhaltsvorschauYou want to locate all the files in a directory hierarchy that match some criteria. For instance, you might want to find all the empty files, all the MP3 files, or all the files named "README."Use the
Find.findmethod to walk the directory structure and accumulate a list of matching files.Pass in a block to the following method and it'll walk a directory tree, testing each file against the code block you provide. It returns an array of all files for which the value of the block is true.require 'find' module Find def match(*paths) matched = [] find(*paths) { |path| matched << path if yield path } return matched end module_function :match endHere's whatFind.matchmight return if you used it on a typical disorganized home directory:Find.match("./") { |p| File.lstat(p).size == 0 } # => ["./Music/cancelled_download.MP3", "./tmp/empty2", "./tmp/empty1"] Find.match("./") { |p| ext = p[-4…p.size]; ext && ext.downcase == ".mp3" } # => ["./Music/The Snails - Red Rocket.mp3", # => "./Music/The Snails - Moonfall.mp3", "./Music/cancelled_download.MP3"] Find.match("./") { |p| File.split(p)[1] == "README" } # => ["./rubyprog-0.1/README", "./tmp/README"]This is an especially useful chunk of code for system administration tasks. It gives you functionality at least as powerful as the Unixfindcommand, but you can write your search criteria in Ruby and you won't have to remember the arcane syntax offind.As withFind.walkitself, you can stopFind.matchfrom processing a directory by callingFind.prune:Find.match("./") do |p| Find.prune if p == "./tmp" File.split(p)[1] == "README" end # => ["./rubyprog-0.1/README"]You can even look inside each file to see whether you want it:# Find all files that start with a particular phrase. must_start_with = "This Ruby program" Find.match("./") do |p| if File.file? p open(p) { |f| f.read(must_start_with.size) == must_start_with } else false end end # => ["./rubyprog-0.1/README"]A few other useful things to search for using this function:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Finding and Changing the Current Working Directory
- InhaltsvorschauYou want to see which directory the Ruby process considers its current working directory, or change that directory.To find the current working directory, use
Dir.getwd:Dir.getwd # => "/home/leonardr"
To change the current working directory, useDir.chdir:Dir.chdir("/bin") Dir.getwd # => "/bin" File.exists? "ls" # => trueThe current working directory of a Ruby process starts out as the directory you were in when you started the Ruby interpreter. When you refer to a file without providing an absolute pathname, Ruby assumes you want a file by that name in the current working directory. Ruby also checks the current working directory when yourequirea library that can't be found anywhere else.The current working directory is a useful default. If you're writing a Ruby script that operates on a directory tree, you might start from the current working directory if the user doesn't specify one.However, you shouldn't rely on the current working directory being set to any particular value: this makes scripts brittle, and prone to break when run from a different directory. If your Ruby script comes bundled with libraries, or needs to load additional files from subdirectories of the script directory, you should set the working directory in code.You can change the working directory as often as necessary, but it's more reliable to use absolute pathnames, even though this can make your code less portable. This is especially true if you're writing multithreaded code.The current working directory is global to a process. If multiple threads are running code that changes the working directory to different values, you'll never know for sure what the working directory is at any given moment.- Recipe 6.18, "Deleting a File," shows some problems created by a process-global working directory
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 7: Code Blocks and Iteration
- InhaltsvorschauIn Ruby, a code block (or just "block") is an object that contains some Ruby code, and the context neccesary to execute it. Code blocks are the most visually distinctive aspect of Ruby, and also one of the most confusing to newcomers from other languages. Essentially, a Ruby code block is a method that has no name.Most other languages have something like a Ruby code block: C's function pointers, C++'s function objects, Python's lambdas and list comprehensions, Perl's anonymous functions, Java's anonymous inner classes. These features live mostly in the corners of those languages, shunned by novice programmers. Ruby can't be written without code blocks. Of the major languages, only Lisp is more block-oriented.Unlike most other languages, Ruby makes code blocks easy to create and imposes few restrictions on them. In every other chapter of this book, you'll see blocks passed into methods like it's no big deal (which it isn't):
[1,2,3].each { |i| puts i} # 1 # 2 # 3In this chapter, we'll show you how to write that kind of method, the kinds of method that are useful to write that way, and when and how to treat blocks as first class objects.Ruby provides two syntaxes for creating code blocks. When the entire block will fit on one line, it's most readable when enclosed in curly braces:[1,2,3].each { |i| puts i } # 1 # 2 # 3When the block is longer than one line, it's more readable to begin it with thedokeyword and end it with theendkeyword:[1,2,3].each do |i| if i % 2 == 0 puts "#{i} is even." else puts "#{i} is odd." end end # 1 is odd. # 2 is even. # 3 is odd.Some people use the bracket syntax when they're interested in the return value of the block, and thedo…endsyntax when they're interested in the block's side effects.Keep in mind that the bracket syntax has a higher precedence than thedo..endsyntax. Consider the following two snippets of code:1.upto 3 do |x| puts x end # 1 # 2 # 3 1.upto 3 { |x| puts x } # SyntaxError: compile errorIn the second example, the code block binds to the number 3, not to the function callEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Creating and Invoking a Block
- InhaltsvorschauYou want to put some Ruby code into an object so you can pass it around and call it later.By this time, you should familiar with a block as some Ruby code enclosed in curly brackets. You might think it possible to define a block object as follows:
aBlock = { |x| puts x } # WRONG # SyntaxError: compile errorThat doesn't work because a block is only valid Ruby syntax when it's an argument to a method call. There are several equivalent methods that take a block and return it as an object. The most favored method isKernel# lambda:aBlock = lambda { |x| puts x } # RIGHTTo call the block, use thecallmethod:aBlock.call "Hello World!" # Hello World!
The ability to assign a bit of Ruby code to a variable is very powerful. It lets you write general frameworks and plug in specific pieces of code at the crucial points.As you'll find out in Recipe 7.2, you can accept a block as an argument to a method by prepending&to the argument name. This way, you can write your own trivial version of thelambdamethod:def my_lambda(&aBlock) aBlock end b = my_lambda { puts "Hello World My Way!" } b.call # Hello World My Way!A newly defined block is actually aProcobject.b.class # => Proc
You can also initialize blocks with theProcconstructor or the methodKernel#proc. The methodsKernel#lambda, Kernel#proc, andProc.newall do basically the same thing. These three lines of code are nearly equivalent:aBlock = Proc.new { |x| puts x } aBlock = proc { |x| puts x } aBlock = lambda { |x| puts x }What's the difference?Kernel#lambdais the preferred way of creating block objects, because it gives you block objects that act more like Ruby methods. Consider what happens when you call a block with the wrong number of arguments:add_lambda = lambda { |x,y| x + y } add_lambda.call(4) # ArgumentError: wrong number of arguments (1 for 2) add_lambda.call(4,5,6) # ArgumentError: wrong number of arguments (3 for 2)Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Writing a Method That Accepts a Block
- InhaltsvorschauYou want to write a method that can accept and call an attached code block: a method that works like
Array#each, Fixnum#upto, and other built-in Ruby methods.You don't need to do anything special to make your method capable of accepting a block. Any method can use a block if the caller passes one in. At any time in your method, you can call the block withyield:def call_twice puts "I'm about to call your block." yield puts "I'm about to call your block again." yield end call_twice { puts "Hi, I'm a talking code block." } # I'm about to call your block. # Hi, I'm a talking code block. # I'm about to call your block again. # Hi, I'm a talking code block.Another example:def repeat(n) if block_given? n.times { yield } else raise ArgumentError.new("I can't repeat a block you don't give me!") end end repeat(4) { puts "Hello." } # Hello. # Hello. # Hello. # Hello. repeat(4) # ArgumentError: I can't repeat a block you don't give me!Since Ruby focuses so heavily on iterator methods and other methods that accept code blocks, it's important to know how to use code blocks in your own methods.You don't have to do anything special to make your method capable of taking a code block. A caller can pass a code block into any Ruby method; it's just that there's no point in doing that if the method never invokesyield.puts("Print this message.") { puts "And also run this code block!" } # Print this message.Theyieldkeyword acts like a special method, a stand-in for whatever code block was passed in. When you call it, it's exactly as the code block were aProcobject and you had invoked itscallmethod.This may seem mysterious if you're unfamiliar with the practice of passing blocks around, but it is usually the preferred method of calling blocks in Ruby. If you feel more comfortable receiving a code block as a "real" argument to your method, see Recipe 7.3.You can pass in arguments toyield(they'll be passed to the block) and you can do things with the value of theyieldstatement (this is the value of the last statement in the block).Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Binding a Block Argument to a Variable
- InhaltsvorschauYou've written a method that takes a code block, but it's not enough for you to simply call the block with
yield. You need to somehow bind the code block to a variable, so you can manipulate the block directly. Most likely, you need to pass it as the code block to another method.Put the name of the block variable at the end of the list of your method's arguments. Prefix it with an ampersand so that Ruby knows it's a block argument, not a regular argument.An incoming code block will be converted into aProcobject and bound to the block variable. You can pass it around to other methods, call it directly usingcall, oryieldto it as though you'd never bound it to a variable at all. All three of the following methods do exactly the same thing:def repeat(n) n.times { yield } if block_given? end repeat(2) { puts "Hello." } # Hello. # Hello. def repeat(n, &block) n.times { block.call } if block end repeat(2) { puts "Hello." } # Hello. # Hello. def repeat(n, &block) n.times { yield } if block end repeat(2) { puts "Hello." } # Hello. # Hello.If&foois the name of a method's last argument, it means that the method accepts an optional block namedfoo. If the caller chooses to pass in a block, it will be made available as aProcobject bound to the variablefoo. Since it is an optional argument, foo will benilif no block is actually passed in. This frees you from having to callKernel#block_given?to see whether or not you got a block.When you call a method, you can pass in anyProcobject as the code block by prefixing the appropriate variable name with an ampersand. You can even do this on aProcobject that was originally passed in as a code block to your method.Many methods for collections, likeeach, select, anddetect, accept code blocks. It's easy to wrap such methods when your own methods can bind a block to a variable. Here, a method calledbiggestfinds the largest element of a collection that gives a true result for the given block:def biggest(collection, &block) block ? collection.select(&block).max : collection.max end array = [1, 2, 3, 4, 5] biggest(array) {|i| i < 3} # => 2 biggest(array) {|i| i != 5 } # => 4 biggest(array) # => 5Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Blocks as Closures: Using Outside Variables Within a Code Block
- InhaltsvorschauYou want to share variables between a method, and a code block defined within it.Just reference the variables, and Ruby will do the right thing. Here's a method that adds a certain number to every element of an array:
def add_to_all(array, number) array.collect { |x| x + number } end add_to_all([1, 2, 3], 10) # => [11, 12, 13]Enumerable#collectcan't accessnumberdirectly, but it's passed a block that can access it, sincenumberwas in scope when the block was defined.A Ruby block is a closure: it carries around the context in which it was defined. This is useful because it lets you define a block as though it were part of your normal code, then tear it off and send it to a predefined piece of code for processing.A Ruby block contains references to the variable bindings, not copies of the values. If the variable changes later, the block will have access to the new value:tax_percent = 6 position = lambda do "I have always supported a #{tax_percent}% tax on imported limes." end position.call # => "I have always supported a 6% tax on imported limes." tax_percent = 7.25 position.call # => "I have always supported a 7.25% tax on imported limes."This works both ways: you can rebind or modify a variable from within a block.counter = 0 4.times { counter += 1; puts "Counter now #{counter}"} # Counter now 1 # Counter now 2 # Counter now 3 # Counter now 4 counter # => 4This is especially useful when you want to simulateinjectorcollectin conjunction with a strange iterator. You can create a storage object outside the block, and add things to it from within the block. This code simulatesEnumerable#collect, but it collects the elements of an array in reverse order:accumulator = [] [1, 2, 3].reverse_each { |x| accumulator << x + 1 } accumulator # => [4, 3, 2]Theaccumulatorvariable is not within the scope ofArray#reverse_each, but it is within the scope of the block.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Writing an Iterator Over a Data Structure
- InhaltsvorschauYou've created a custom data structure, and you want to implement an
eachmethod for it, or you want to implement an unusual way of iterating over an existing data structure.Complex data structures are usually constructed out of the basic data structures: hashes, arrays, and so on. All of the basic data structures have defined theeachmethod. If your data structure is composed entirely of scalar values and these simple data structures, you can write a neweachmethod in terms of theeachmethods of its components.Here's a simple tree data structure. A tree contains a single value, and a list of children (each of which is a smaller tree).class Tree attr_reader :value def initialize(value) @value = value @children = [] end def <<(value) subtree = Tree.new(value) @children << subtree return subtree end end
Here's code to create a specific Tree (Figure 7-1):t = Tree.new("Parent") child1 = t << "Child 1" child1 << "Grandchild 1.1" child1 << "Grandchild 1.2" child2 = t << "Child 2" child2 << "Grandchild 2.1"
Figure 7-1: A simple treeHow can we iterate over this data structure? Since a tree is defined recursively, it makes sense to iterate over it recursively. This implementation ofTree#eachyields the value stored in the tree, then iterates over its children (the children are stored in an array, which already supportseach) and recursively callsTree#eachon every child tree.class Tree def each yield value @children.each do |child_node| child_node.each { |e| yield e } end end endThe each method traverses the tree in a way that looks right:t.each { |x| puts x } # Parent # Child 1 # Grandchild 1.1 # Grandchild 1.2 # Child 2 # Grandchild 2.1The simplest way to build an iterator is recursively: to use smaller iterators until you've covered every element in your data structure. But what if those iterators aren't there? More likely, what if they're there but they give you elements in the wrong order? You'll need to go down a level and write some loops.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Changing the Way an Object Iterates
- InhaltsvorschauYou want to use a data structure as an
Enumerable, but the object's implementation of#eachdoesn't iterate the way you want. Since all ofEnumerable's methods are based oneach, this makes them all useless to you.Here's a concrete example: a simple array.array = %w{bob loves alice} array.collect { |x| x.capitalize } # => ["Bob", "Loves", "Alice"]Suppose we want to callcollecton this array, but we don't wantcollectto useeach: we want it to usereverse_each. Something like this hypotheticalcollect_reversemethod:array.collect_reverse { |x| x.capitalize } # => ["Alice", "Loves", "Bob"]Actually defining acollect_reversemethod would add significant new code and only solve part of the problem. We could overwrite the array's each implementation with a singleton method that callsreverse_each, but that's hacky and it would surely have undesired side effects.Fortunately, there's an elegant solution with no side effects: wrap the object in anEnumerator. This gives you a new object that acts like the old object would if you'd swapped out itseachmethod:require 'enumerator' reversed_array = array.to_enum(:reverse_each) reversed_array.collect { |x| x.capitalize } # => ["Alice", "Loves", "Bob"] reversed_array.each_with_index do |x, i| puts %{#{i}=>"#{x}"} end # 0=>"alice" # 1=>"loves" # 2=>"bob"Note that you can't use theEnumeratorfor our array as though it were the actual array. Only the methods ofEnumerableare supported:reversed_array[0] # NoMethodError: undefined method '[]' for #<Enumerable::Enumerator:0xb7c2cc8c>
Whenever you're tempted to reimplement one of the methods ofEnumerable, try using anEnumeratorinstead. It's like modifying an object'seachmethod, but it doesn't affect the original object.This can save you a lot of work. Suppose you have a tree data structure that provides three different iteration styles:each_prefix, each_postfix, andeach_infix. Rather than implementing the methods ofEnumerablefor all three iteration styles, you can leteach_prefixbe the default implementation ofeach, and calltree.to_enum(:each_postfixEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Writing Block Methods That Classify or Collect
- InhaltsvorschauThe basic block methods that come with the Ruby standard library aren't enough for you. You want to define your own method that classifies the elements in an enumeration (like
Enumerable#detectandEnumerable#find_all), or that does a transformation on each element in an enumeration (likeEnumerable#collect).You can usually useinjectto write a method that searches or classifies an enumeration of objects. Withinjectyou can write your own versions of methods such asdetectandfind_all:module Enumerable def find_no_more_than(limit) inject([]) do |a,e| a << e if yield e return a if a.size >= limit a end end end
This code finds at most three of the even numbers in a list:a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] a.find_no_more_than(3) { |x| x % 2 == 0 } # => [2, 4, 6]If you find yourself needing to write a method likecollect, it's probably because, for your purposes,collectitself yields elements in the wrong order. You can't useinject, because that yields elements in the same order ascollect.You need to find or write an iterator that yields elements in the order you want. Once you've done that, you have two options: you can write acollectequivalent on top of the iterator method, or you can use the iterator method to build anEnumerableobject, and call itscollectmethod (as seen in Recipe 7.6).We discussed these block methods in more detail in Chapter 4, because arrays are the simplest and most common Enumerable data type, and the most common. But almost any data structure can be enumerated, and a more complex data structure can be enumerated in more different ways.As you'll see in Recipe 9.4, theEnumerablemethods, likedetectandinject, are actually implemented in terms ofeach. Thedetectandinjectmethods yield to the code block every element that comes out ofeach. The value of theyieldstatement is used to determine whether the element matches some criteria.In a method likedetect, the iteration may stop once it finds an element that matches. In a method likefind_all, the iteration goes through all elements, collecting the ones that match.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Stopping an Iteration
- InhaltsvorschauYou want to interrupt an iteration from within the code block you passed into it.The simplest way to interrupt execution is to use
break. Abreakstatement will jump out of the closest enclosing loop defined in the current method:1.upto(10) do |x| puts x break if x == 3 end # 1 # 2 # 3
Thebreakstatement is simple but it has several limitations. You can't usebreakwithin a code block defined withProc.newor (in Ruby 1.9 and up)Kernel#proc. If this is a problem for you, uselambdainstead:aBlock = Proc.new do |x| puts x break if x == 3 puts x + 2 end aBlock.call(5) # 5 # 7 aBlock.call(3) # 3 # LocalJumpError: break from proc-closure
More seriously, you can't usebreakto jump out of multiple loops at once. Once a loop has run, there's no way to know whether it completed normally or by usingbreak.The simplest way around this problem is to enclose the code you want to skip within acatchblock with a descriptive symbolic name. You can thenthrowthe corresponding symbol when you want to jump to the end of thecatchblock. This lets you skip out of any number of nested loops and method calls.Thethrow/catchsyntax isn't exception handling—exceptions use araise/rescuesyntax. This is a special flow control construct designed to replace the use of exceptions for flow control (as sometimes happens in Java programs). It's a bit like an old style global GOTO, capable of suddenly moving execution to a faraway part of your program. It keeps your code more readable than a GOTO, though, because it's restricted: athrowcan only jump to the end of a correspondingcatchblock.The best example of thecatch..throwsyntax is theFind.findfunction described in Recipe 6.12. When you pass a code block intoFind.find, it yields up every directory and file in a certain directory tree. When your code block is given a directory, it can stopfindfrom recursing into that directory by callingFind.prune, which throws a:prunesymbol. Usingbreakwould stop thefindoperation altogether; throwing a symbol letsFind.pruneEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Looping Through Multiple Iterables in Parallel
- InhaltsvorschauYou want to traverse multiple iteration methods simultaneously, probably to match up the corresponding elements in several different arrays.The
SyncEnumeratorclass, defined in thegeneratorlibrary, makes it easy to iterate over a bunch of arrays or otherEnumerableobjects in parallel. Itseachmethod yields a series of arrays, each array containing one item from each underlyingEnumerableobject:require 'generator' enumerator = SyncEnumerator.new(%w{Four seven}, %w{score years}, %w{and ago}) enumerator.each do |row| row.each { |word| puts word } puts '---' end # Four # score # and # --- # seven # years # ago # --- enumerator = SyncEnumerator.new(%w{Four and}, %w{score seven years ago}) enumerator.each do |row| row.each { |word| puts word } puts '---' end # Four # score # --- # and # seven # --- # nil # years # --- # nil # ago # ---You can reproduce the workings of aSyncEnumeratorby wrapping each of yourEnumerableobjects in aGeneratorobject. This code acts likeSyncEnumerator#each, only it yields each individual item instead of arrays containing one item from eachEnumerable:def interosculate(*enumerables) generators = enumerables.collect { |x| Generator.new(x) } done = false until done done = true generators.each do |g| if g.next? yield g.next done = false end end end end interosculate(%w{Four and}, %w{score seven years ago}) do |x| puts x end # Four # score # and # seven # years # agoAny object that implements theeachmethod can be wrapped in aGeneratorobject. If you've used Java, think of aGeneratoras being like a JavaIterator object. It keeps track of where you are in a particular iteration over a data structure.Normally, when you pass a block into an iterator method likeeach, that block gets called for every element in the iterator without interruption. No code outside the block will run until the iterator is done iterating. You can stop the iteration by writing aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Hiding Setup and Cleanup in a Block Method
- InhaltsvorschauYou have a setup method that always needs to run before custom code, or a cleanup method that needs to run afterwards. You don't trust the person writing the code (possibly yourself) to remember to call the setup and cleanup methods.Create a method that runs the setup code, yields to a code block (which contains the custom code), then runs the cleanup code. To make sure the cleanup code always runs, even if the custom code throws an exception, use a
begin/finallyblock.def between_setup_and_cleanup setup begin yield finally cleanup end end
Here's a concrete example. It adds a DOCTYPE and an HTML tag to the beginning of an HTML document. At the end, it closes the HTML tag it opened earlier. This saves you a little bit of work when you're generating HTML files.def write_html(out, doctype=nil) doctype ||= %{<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">} out.puts doctype out.puts '<html>' begin yield out ensure out.puts '</html>' end end write_html($stdout) do |out| out.puts '<h1>Sorry, the Web is closed.</h1>' end # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" # "http://www.w3.org/TR/html4/loose.dtd"> # <html> # <h1>Sorry, the Web is closed.</h1> # </html>This useful technique shows up most often when there are scarce resources (such as file handles or database connections) that must be closed when you're done with them, lest they all get used up. A language that makes the programmer remember these resources tends to leak those resources, because programmers are lazy. Ruby makes it easy to be lazy and still do the right thing.You've probably used this technique already, with the theKernel#openandFile#openmethods for opening files on disk. These methods accept a code block that manipulates an already open file. They open the file, call your code block, and close the file once you're done:open('output.txt', 'w') do |out| out.puts 'Sorry, the filesystem is also closed.' endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Coupling Systems Loosely with Callbacks
- InhaltsvorschauYou want to combine different types of objects without hardcoding them full of references to each other.Use a callback system, in which objects register code blocks with each other to be executed as needed. An object can call out to its registered callbacks when it needs something, or it can send notification to the callbacks when it does something.To implement a callback system, write a "register" or "subscribe" method that accepts a code block. Store the registered code blocks as
Procobjects in a data structure: probably an array (if you only have one type of callback) or a hash (if you have multiple types). When you need to call the callbacks, iterate over the data structure andcalleach of the registered code blocks.Here's a mixin module that gives each instance of a class its own hash of "listener" callback blocks. An outside object can listen for a particular event by callingsubscribewith the name of the event and a code block. The dispatcher itself is responsible for callingnotifywith an appropriate event name at the appropriate time, and the outside object is responsible for passing in the name of the event it wants to "listen" for.module EventDispatcher def setup_ listeners @event_dispatcher_listeners = {} end def subscribe(event, &callback) (@event_dispatcher_listeners[event] ||= []) << callback end protected def notify(event, *args) if @event_dispatcher_listeners[event] @event_dispatcher_listeners[event].each do |m| m.call(*args) if m.respond_to? :call end end return nil end endHere's aFactoryclass that keeps a set of listeners. An outside object can choose to be notified every time aFactoryobject is created, or every time aFactoryobject produces a widget:class Factory include EventDispatcher def initialize setup_listeners end def produce_widget(color) #Widget creation code goes here… notify(:new_widget, color) end end
Here's a listener class that's interested in what happens withFactoryobjects:class WidgetCounter def initialize(factory) @counts = Hash.new(0) factory.subscribe(:new_widget) do |color| @counts[color] += 1 puts "#{@counts[color]} #{color} widget(s) created since I started watching." end end endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 8: Objects and Classes8
- InhaltsvorschauRuby is an object-oriented programming language; this chapter will show you what that really means. Like all modern languages, Ruby supports object-oriented notions like classes, inheiritance, and polymorphism. But Ruby goes further than other languages you may have used. Some languages are strict and some are permissive; Ruby is one of the most permissive languages around.Strict languages enforce strong typing, usually at compile type: a variable defined as an array can't be used as another data type. If a method takes an array as an argument, you can't pass in an array-like object unless that object happens to be a subclass of the array class or can be converted into an array.Ruby enforces dynamic typing, or duck typing ("if it quacks like a duck, it is a duck"). A strongly typed language enforces its typing everywhere, even when it's not needed. Ruby enforces its duck typing relative to a particular task. If a variable quacks like a duck, it is one—assuming you wanted to hear it quack. When you want "swims like a duck" instead, duck typing will enforce the swimming, and not the quacking.Here's an example. Consider the following three classes,
Duck, Goose, andDuckRecording:class Duck def quack 'Quack!' end def swim 'Paddle paddle paddle…' end end class Goose def honk 'Honk!' end def swim 'Splash splash splash…' end end class DuckRecording def quack play end def play 'Quack!' end end
If Ruby was a strongly typed language, a method that told aDuckto quack would fail when given aDuckRecording. The following code is written in the hypothetical language Strongly-Typed Ruby; it won't work in real Ruby.def make_it_quack(Duck duck) duck.quack end make_it_quack(Duck.new) # => "Quack!" make_it_quack(DuckRecording.new) # TypeException: object not of type Duck
If you were expecting aDuck, you wouldn't be able to tell aGooseto swim:def make_it_swim(Duck duck) duck.swim end make_it_swim(Duck.new) # => "Paddle paddle paddle…" make_it_swim(Goose.new) # TypeException: object not of type Goose
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Managing Instance Data
- InhaltsvorschauYou want to associate a variable with an object. You may also want the variable to be readable or writable from outside the object.Within the code for the object's class, define a variable and prefix its name with an at sign ( @). When an object runs the code, a variable by that name will be stored within the object.An instance of the
Frogclass defined below might eventually have two instance variables stored within it,@nameand@speaks_english:class Frog def initialize(name) @name = name end def speak # It's a well-known fact that only frogs with long names start out # speaking English. @speaks_english ||= @name.size > 6 @speaks_english ? "Hi. I'm #{@name}, the talking frog." : 'Ribbit.' end end Frog.new('Leonard').speak # => "Hi. I'm Leonard, the talking frog." lucas = Frog.new('Lucas') lucas.speak # => "Ribbit."If you want to make an instance variable readable from outside the object, call theattr_readermethod on its symbol:lucas.name # NoMethodError: undefined method 'name' for #<Frog:0xb7d0327c @speaks_english=true, @name="Lucas"> class Frog attr_reader :name end lucas.name # => "Lucas"
Similarly, to make an instance variable readable and writable from outside the object, call theattr_accessormethod on its symbol:lucas.speaks_english = false # => NoMethodError: undefined method 'speaks_english=' for #<Frog:0xb7d0327c @speaks_ # english=false, @name="Lucas"> class Frog attr_accessor :speaks_english end lucas.speaks_english = true lucas.speak # => "Hi. I'm Lucas, the talking frog."
Some programming languages have complex rules about when one object can directly access to another object's instance variables. Ruby has one simple rule: it's never allowed. To get or set the value of an instance variable from outside the object that owns it, you need to call an explicitly defined getter or setter method.Basic getter and setter methods look like this:class Frog def speaks_english @speaks_english end def speaks_english=(value) @speaks_english = value end end
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Managing Class Data
- InhaltsvorschauInstead of storing a bit of data along with every instance of a class, you want to store a bit of data along with the class itself.Instance variables are prefixed by a single at sign; class variables are prefixed by two at signs. This class contains both an instance variable and a class variable:
class Warning @@translations = { :en => 'Wet Floor', :es => 'Piso Mojado' } def initialize(language=:en) @language = language end def warn @@translations[@language] end end Warning.new.warn # => "Wet Floor" Warning.new(:es).warn # => "Piso Mojado"Class variables store information that's applicable to the class itself, or applicable to every instance of the class. They're often used to control, prevent, or react to the instantiation of the class. A class variable in Ruby acts like a static variable in Java.Here's an example that uses a class constant and a class variable to control when and how a class can be instantiated:class Fate NAMES = ['Klotho', 'Atropos', 'Lachesis'].freeze @@number_instantiated = 0 def initialize if @@number_instantiated >= NAMES.size raise ArgumentError, 'Sorry, there are only three Fates.' end @name = NAMES[@@number_instantiated] @@number_instantiated += 1 puts "I give you… #{@name}!" end end Fate.new # I give you… Klotho! # => #<Fate:0xb7d2c348 @name="Klotho"> Fate.new # I give you… Atropos! # => #<Fate:0xb7d28400 @name="Atropos"> Fate.new # I give you… Lachesis! # => #<Fate:0xb7d22168 @name="Lachesis"> Fate.new # ArgumentError: Sorry, there are only three Fates.It's not considered good form to write setter or getter methods for class variables. You won't usually need to expose any class-wide information apart from helpful constants, and those you can expose with class constants such asNAMESabove.If you do want to write setter or getter methods for class variables, you can use the following class-level equivalents ofModule#attr_readerandModule#attr_writerEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Checking Class or Module Membership
- InhaltsvorschauYou want to see if an object is of the right type for your purposes.If you plan to call a specific method on the object, just check to see whether the object reponds to that method:
def send_as_package(obj) if obj.respond_to? :package packaged = obj.package else $stderr.puts "Not sure how to package a #{obj.class}." $stderr.puts 'Trying generic packager.' package = Package.new(obj) end send(package) endIf you really can only accept objects of one specific class, or objects that include one specific module, use theis_a? predicate:def multiply_precisely(a, b) if a.is_a? Float or b.is_a? Float raise ArgumentError, "I can't do precise multiplication with floats." end a * b end multiply_precisely(4, 5) # => 20 multiply_precisely(4.0, 5) # ArgumentError: I can't do precise multiplication with floats.
Whenever possible, you should use duck typing (Object#respond_to?) in preference to class typing (Object#is_a?). Duck typing is one of the great strengths of Ruby, but it only works if everyone uses it. If you write a method that only accepts strings, instead of accepting anything that supportsto_str, then you've broken the duck typing illusion for everyone who uses your code.Sometimes you can't use duck typing, though, or sometimes you need to combine it with class typing. Sometimes two different classes define the same method (especially one of the operators) in completely different ways. Duck typing makes it possible to silently do the right thing, but if you know that duck typing would silently do the wrong thing, a little class typing won't hurt.Here's a method that uses duck typing to see whether an operation is supported, and class typing to cut short a possible problem before it occurs:def append_to_self(x) unless x.respond_to? :<< raise ArgumentError, "This object doesn't support the left-shift operator." end if x.is_a? Numeric raise ArgumentError, "The left-shift operator for this object doesn't do an append." end x << x end append_to_self('abc') # => "abcabc" append_to_self([1, 2, 3]) # => [1, 2, 3, […]] append_to_self({1 => 2}) # ArgumentError: This object doesn't support the left-shift operator. append_to_self(5) # ArgumentError: The left-shift operator for this object doesn't do an append. 5 << 5 # => 160 # That is, 5 * (2 ** 5)Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Writing an Inherited Class
- InhaltsvorschauYou want to create a new class that extends or modifies the behavior of an existing class.If you're writing a new method that conceptually belongs in the original class, you can reopen the class and append your method to the class definition. You should only do this if your method is generally useful, and you're sure it won't conflict with a method defined by some library you include in the future.This code adds a
scramblemethod to Ruby's built-inStringclass (see Recipe 4.10 for a faster way to sort randomly):class String def scramble split(//).sort_by { rand }.join end end "I once was a normal string.".scramble # => "i arg cn lnws.Ioateosma n r"If your method isn't generally useful, or you don't want to take the risk of modifying a class after its initial creation, create a subclass of the original class. The subclass can override its parent's methods, or add new ones. This is safer because the original class, and any code that depended on it, is unaffected. This subclass ofStringadds one new method and overrides one existing one:class UnpredictableString < String def scramble split (//).sort_by { rand }.join end def inspect scramble.inspect end end str = UnpredictableString.new("It was a dark and stormy night.") # => " hsar gsIo atr tkd naaniwdt.ym" str # => "ts dtnwIktsr oydnhgi .mara aa"All of Ruby's classes can be subclassed, though a few of them can't be usefully subclassed (see Recipe 8.18 for information on how to deal with the holdouts).Ruby programmers use subclassing less frequently than they would in other languages, because it's often acceptable to simply reopen an existing class (even a built-in class) and attach a new method. We do this throughout this book, adding useful new methods to built-in classes rather than defining them inKernel, or putting them in subclasses or utility classes. Libraries like Rails and Facets Core do the same.This improves the organization of your code. But the risk is that a library you include (or a library included by one you include) will define the same method in the same built-in class. Either the library will override your method (breaking your code), or you'll override its method (breaking its code, which will break your code). There is no general solution to this problem short of adopting naming conventions, or always subclassing and never modifying preexisting classes.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Overloading Methods
- InhaltsvorschauYou want to create two different versions of a method with the same name: two methods that differ in the arguments they take.A Ruby class can have only one method with a given name. Within that single method, though, you can put logic that branches depending on how many and what kinds of objects were passed in as arguments.Here's a
Rectangleclass that represents a rectangular shape on a grid. You can instantiate aRectanglein one of two ways: by passing in the coordinates of its top-left and bottom-left corners, or by passing in its top-left corner along with its length and width. There's only oneinitializemethod, but you can act as though there were two.# The Rectangle constructor accepts arguments in either of the following forms: # Rectangle.new([x_top, y_left], length, width) # Rectangle.new([x_top, y_left], [x_bottom, y_right]) class Rectangle def initialize(*args) case args.size when 2 @top_left, @bottom_right = args when 3 @top_left, length, width = args @bottom_right = [@top_left[0] + length, @top_left[1] - width] else raise ArgumentError, "This method takes either 2 or 3 arguments." end # Perform additional type/error checking on @top_left and # @bottom_right… end end
Here's theRectangleconstructor in action:' Rectangle.new([10, 23], [14, 13]) # => #<Rectangle:0xb7d15828 @bottom_right=[14, 13], @top_left=[10, 23]> Rectangle.new([10, 23], 4, 10) # => #<Rectangle:0xb7d0da4c @bottom_right=[14, 13], @top_left=[10, 23]> Rectangle.new # => ArgumentError: This method takes either 2 or 3 arguments.
In strongly typed languages like C++ and Java, you must often create multiple versions of the same method with different arguments. For instance, Java'sStringBufferclass implements over 10 variants of itsappendmethod: one that takes a boolean, one that takes a string, and so on.Ruby's equivalent ofStringBufferisStringIO, and its equivalent of theappendmethod isStringIO#<<. In Ruby, that method can only be defined once, but it can take an object of any type. There's no need to write different versions of the method for taking different kinds of object. If you need to do type checking (such as making sure the object has a string representation), you put it in the method body rather than in the method definition.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Validating and Modifying Attribute Values
- InhaltsvorschauYou want to let outside code set your objects' instance variables, but you also want to impose some control over the values your variables are set to. You might want a chance to validate new values before accepting them. Or you might want to accept values in a form convenient to the caller, but transform them into a different form for internal storage.Define your own setter method for each instance variable you want to control. The setter method for an instance variable
quantitywould be calledquantity=. When a user issues a statement likeobject.quantity = 10, the methodobject#quantity=is called with the argument 10.It's up to thequantity=method to decide whether the instance variablequantityshould actually take the value 10. A setter method is free to raise anArgumentExceptionif it's passed an invalid value. It may also modify the provided value, massaging it into the canonical form used by the class. If it can get an acceptable value, its last act should be to modify the instance variable.I'll define a class that keeps track of peoples' first and last names. It uses setter methods to enforce two somewhat parochial rules: everyone must have both a first and a last name, and everyone's first name must begin with a capital letter:class Name # Define default getter methods, but not setter methods. attr_reader :first, :last # When someone tries to set a first name, enforce rules about it. def first=(first) if first == nil or first.size == 0 raise ArgumentError.new('Everyone must have a first name.') end first = first.dup first[0] = first[0].chr.capitalize @first = first end # When someone tries to set a last name, enforce rules about it. def last=(last) if last == nil or last.size == 0 raise ArgumentError.new('Everyone must have a last name.') end @last = last end def full_name "#{@first} #{@last}" end # Delegate to the setter methods instead of setting the instance # variables directly. def initialize(first, last) self.first = first self.last = last end endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Defining a Virtual Attribute
- InhaltsvorschauYou want to create accessor methods for an attribute that isn't directly backed by any instance variable: it's a calculated value derived from one or more different instance variables.Define accessor methods for the attribute in terms of the instance variables that are actually used. There need not be any relationship between the names of the accessor methods and the names of the instance variables.The following class exposes four accessor methods:
degrees,degrees=,radians, andradians=. But it only stores one instance variable:@radians.class Arc attr_accessor :radians def degrees @radians * 180 / Math::PI end def degrees=(degrees) @radians = degrees * Math::PI / 180 end end arc = Arc.new arc.degrees = 180 arc.radians # => 3.14159265358979 arc.radians = Math::PI / 2 arc.degrees # => 90.0
Ruby accessor methods usually correspond to the names of the instance variables they access, but this is nothing more than a convention. Outside code has no way of knowing what your instance variables are called, or whether you have any at all, so you can create accessors for virtual attributes with no risk of outside code thinking they're backed by real instance variables.- Recipe 2.9, "Converting Between Degrees and Radians"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Delegating Method Calls to Another Object
- InhaltsvorschauYou'd like to delegate some of an object's method calls to a different object, or make one object capable of " impersonating" another.If you want to completely impersonate another object, or delegate most of one object's calls to another, use the
delegatelibrary. It generates custom classes whose instances can impersonate objects of any other class. These custom classes respond to all methods of the class they shadow, but they don't do any work of their own apart from calling the same method on some instance of the "real" class.Here's some code that usesdelegateto generateCardinalNumber, a class that acts almost like aFixnum. CardinalNumberdefines the same methods asFixnumdoes, and it takes a genuineFixnumas an argument to its constructor. It stores this object as a member, and when you call any ofFixnum's methods on aCardinalNumberobject, it delegates that method call to the storedFixnum. The only major exception is theto_smethod, which I've decided to override.require 'delegate' # An integer represented as an ordinal number (1st, 2nd, 3rd…), as # opposed to an ordinal number (1, 2, 3…) Generated by the # DelegateClass to have all the methods of the Fixnum class. class OrdinalNumber < DelegateClass(Fixnum) def to_s delegate_s = __getobj_ _.to_s check = abs if to_check == 11 or to_check == 12 suffix = "th" else case check % 10 when 1 then suffix = "st" when 2 then suffix = "nd" else suffix = "th" end end return delegate_s + suffix end end 4.to_s # => "4" OrdinalNumber.new(4).to_s # => "4th" OrdinalNumber.new(102).to_s # => "102nd" OrdinalNumber.new(11).to_s # => "11th" OrdinalNumber.new(-21).to_s # => "-21st" OrdinalNumber.new(5).succ # => 6 OrdinalNumber.new(5) + 6 # => 11 OrdinalNumber.new(5) + OrdinalNumber.new(6) # => 11
Thedelegatelibrary is useful when you want to extend the behavior of objects you don't have much control over. Usually these are objects you're not in charge of instantiating—they're instantiated by factory methods, or by Ruby itself. WithEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Converting and Coercing Objects to Different Types
- InhaltsvorschauYou have an object of one type and you want to use it as though it were of another type.You might not have to do anything at all. Ruby doesn't enforce type safety unless the programmer has explicitly written it in. If your original class defines the same methods as the class you were thinking of converting it to, you might be able to use your object as is.If you do have to convert from one class to another, Ruby provides conversion methods for most common paths:
"4".to_i # => 4 4.to_s # => "4" Time.now.to_f # => 1143572140.90932 { "key1" => "value1", "key2" => "value2" }.to_a # => [["key1", "value1"], ["key2", "value2"]]If all else fails, you might be able to manually create an instance of the new class, and set its instance variables using the old data.Some programming languages have a "cast" operator that forces the compiler to treat an object of one type like an object of another type. A cast is usually a programmer's assertion that he knows more about the types of objects than the compiler. Ruby has no cast operator. From Ruby's perspective, type checking is just an extra hoop you have to jump through. A cast operator would make it easier to jump through that hoop, but Ruby omits the hoop altogether.Wherever you're tempted to cast an object to another type, you should be able to just do nothing. If your object can be used as the other type, there's no problem: if not, then casting it to that type wouldn't have helped anyway.Here's a concrete example. You probably don't need to convert a hash into an array just so you can pass it into an iteration method that expects an array. If that method only callseachon its argument, it doesn't really "expect an array:" it expects a reasonable implementation ofeach. Ruby hashes provide that implementation just as well as arrays.def print_each(array) array.each { |x| puts x.inspect } end hash = { "pickled peppers" => "peck of", "sick sheep" => "sixth" } print_each(hash.to_a) # ["sick sheep", "sixth"] # ["pickled peppers", "peck of"] print_each(hash) # ["sick sheep", "sixth"] # ["pickled peppers", "peck of"]Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Getting a Human-Readable Printout of Any Object
- InhaltsvorschauYou want to look at a natural-looking rendition of a given object.Use
Object#inspect. Nearly all the time, this method will give you something more readable than simply printing out the object or converting it into a string.a = [1,2,3] puts a # 1 # 2 # 3 puts a.to_s # 123 puts a.inspect # [1, 2, 3] puts /foo/ # (?-mix:foo) puts /foo/.inspect # /foo/ f = File.open('foo', 'a') puts f # #<File:0xb7c31c30> puts f.inspect # #<File:foo>Even very complex data structures can be inspected and come out looking just like they would in Ruby code to define that data structure. In some cases, you can even run the output of inspect throughevalto recreate the object.periodic_table = [{ :symbol => "H", :name => "hydrogen", :weight => 1.007 }, { :symbol => "Rg", :name => "roentgenium", :weight => 272 }] puts periodic_table.inspect # [{:symbol=>"H", :name=>"hydrogen", :weight=>1.007}, # {:symbol=>"Rg", :name=>"roentgenium", :weight=>272}] eval(periodic_table.inspect)[0] # => {:symbol=>"H", :name=>"hydrogen", :weight=>1.007}By default, an object'sinspectmethod works the same way as itsto_smethod. Unless your classes overrideinspect, inspecting one of your objects will yield a boring and not terribly helpful string, containing only the object's class name,object_id, and instance variables:class Dog def initialize(name, age) @name = name @age = age * 7 #Compensate for dog years end end spot = Dog.new("Spot", 2.1) spot.inspect # => "#<Dog:0xb7c16bec @name="Spot", @age=14.7>"That's why you'll help out your future self by defining usefulinspectmethods that give relevant information about the objects you'll be instantiating.class Dog def inspect "<A Dog named #{@name} who's #{@age} in dog years.>" end def to_s inspect end end spot.inspect # => "<A Dog named Spot who's 14.7 in dog years.>"Or, if you believe in being able toevaltheoutputof inspect:class Dog def inspect %{Dog.new("#{@name}", #{@age/7})} end end spot.inspect # => "Dog.new("Spot", 2.1)" eval(spot.inspect).inspect # => "Dog.new("Spot", 2.1)"Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Accepting or Passing a Variable Number of Arguments
- InhaltsvorschauYou want to write a method that can accept any number of arguments. Or maybe you want to pass the contents of an array as arguments into such a method, rather than passing in the array itself as a single argument.To accept any number of arguments to your method, prefix the last argument name with an asterisk. When the method is called, all the "extra" arguments will be collected in a list and passed in as that argument:
def sum(*numbers) puts "I'm about to sum the array #{numbers.inspect}" numbers.inject(0) { |sum, x| sum += x } end sum(1, 2, 10) # I'm about to sum the array [1, 2, 10] # => 13 sum(2, -2, 2, -2, 2, -2, 2, -2, 2) # I'm about to sum the array [2, -2, 2, -2, 2, -2, 2, -2, 2] # => 2 sum # I'm about to sum the array [] # => 0To pass an array of arguments into a method, use the asterisk signifier before the array you want to be turned into "extra" arguments:to_sum = [] 1.upto(10) { |x| to_sum << x } sum(*to_sum) # I'm about to sum the array [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # => 55Bad things happen if you forget the asterisk: your entire array is treated as a single "extra" argument:sum(to_sum) # I'm about to sum the array [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]] # TypeError: Array can't be coerced into Fixnum
Why make a method take a variable number of arguments, instead of just having it take a single array? It's basically for the convenience of the user. Consider theKernel#printfmethod, which takes one fixed argument (a format string), and then a variable number of inputs to the format string:printf('%s | %s', 'left', 'right') # left | rightIt's very rare that the caller ofprintfalready has her inputs lying around in an array. Fortunately, Ruby is happy to create the array on the user's behalf. If the caller does already have an array of inputs, it's easy to pass the contents of that array as "extra" arguments by sticking the asterisk onto the appropriate variable name:inputs = ['left', 'right'] printf('%s | %s', *inputs) # left | rightAs you can see, a method can take a fixed number of "normal" arguments and then a variable number of "extra" arguments. When defining such a method, just make sure that the last argument is the one you prefix with the asterisk:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Simulating Keyword Arguments
- InhaltsvorschauA function or method can accept many optional arguments. You want to let callers pass in only the arguments they have values for, but Ruby doesn't support keyword arguments as Python and Lisp do.Write your function to accept as its final argument a map of symbols to values. Consult the map as necessary to see what arguments were passed in.
def fun_with_text(text, args={}) text = text.upcase if args[:upcase] text = text.downcase if args[:downcase] if args[:find] and args[:replace] text = text.gsub(args[:find], args[:replace]) end text = text.slice(0, args[:truncate_at]) if args[:truncate_at] return text endRuby has syntactic sugar that lets you define a hash inside a function call without putting it in curly brackets. This makes the code look more natural:fun_with_text("Foobar", {:upcase => true, :truncate_at => 5}) # => "FOOBA" fun_with_text("Foobar", :upcase => true, :truncate_at => 5) # => "FOOBA" fun_with_text("Foobar", :find => /(o+)/, :replace => '\1d', :downcase => true) # => "foodbar"This simple code works well in most cases, but it has a couple of shortcomings compared to "real" keyword arguments. These simulated keyword arguments don't work like regular arguments because they're hidden inside a hash. You can't reject an argument that's not part of the "signature," and you can't force a caller to provide a particular keyword argument.Each of these problems is easy to work around (for instance, does a required argument really need to be a keyword argument?), but it's best to define the workaround code in a mixin so you only have to do it once. The following code is based on aKeywordProcessormodule by Gavin Sinclair:### # This mix-in module lets methods match a caller's hash of keyword # parameters against a hash the method keeps, mapping keyword # arguments to default parameter values. # # If the caller leaves out a keyword parameter whose default value is # :MANDATORY (a constant in this module), then an error is raised. # # If the caller provides keyword parameters which have no # corresponding keyword arguments, an error is raised. # module KeywordProcessor MANDATORY = :MANDATORY def process_params(params, defaults) # Reject params not present in defaults. params.keys.each do |key| unless defaults.has_key? key raise ArgumentError, "No such keyword argument: #{key}" end end result = defaults.dup.update(params) # Ensure mandatory params are given. unfilled = result.select { |k,v| v == MANDATORY }.map { |k,v| k.inspect } unless unfilled.empty? msg = "Mandatory keyword parameter(s) not given: #{unfilled.join(', ')}" raise ArgumentError, msg end return result end endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Calling a Superclass's Method
- InhaltsvorschauWhen overriding a class's method in a subclass, you want to extend or decorate the behavior of the superclass, rather than totally replacing it.Use the
superkeyword to call the superclass implementation of the current method.When you callsuperwith no arguments, the arguments to your method are passed to the superclass method exactly as they were recieved by the subclass. Here's aRecipeclass that defines (among other things) acookmethod.class Recipe # … The rest of the Recipe implementation goes here. def cook(stove, cooking_time) dish = prepare_ingredients stove << dish wait_for(cooking_time) return dish end end
Here's a subclass ofRecipethat tacks some extra behavior onto the recipe. It passes all of its arguments directly intosuper:class RecipeWithExtraGarlic < Recipe def cook(stove, cooking_time) 5.times { add_ingredient(Garlic.new.chop) } super end endA subclass implementation can also choose to pass arguments intosuper. This way, a subclass can accept different arguments from its superclass implementation:class BakingRecipe < Recipe def cook(cooking_time, oven_temperature=350) oven = Oven.new(oven_temperature) super(oven, cooking_time) end end
You can callsuperat any time in the body of a method—before, during, or after calling other code. This is in contrast to languages like Java, where you must callsuperin the method's first statement or never call it at all. If you need to, you can even callsupermultiple times within a single method.Often you want to create a subclass method that exposes exactly the same interface as its parent. You can use the*argsconstructor to make the subclass method accept any arguments at all, then callsuperwith no arguments to pass all those arguments (as well as any attached code block) into the superclass implementation. Let the superclass deal with any problems with the arguments.TheString#gsubmethod exposes a fairly complicated interface, but theStringsubclass defined here doesn't need to know anything about it:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Creating an Abstract Method
- InhaltsvorschauYou want to define a method of a class, but leave it for subclasses to fill in the actual implementations.Define the method normally, but have it do nothing except raise a
NotImplementedError:class Shape2D def area raise NotImplementedError. new("#{self.class.name}#area is an abstract method.") end end Shape2D.new.area # NotImplementedError: Shape2D#area is an abstract method.A subclass can redefine the method with a concrete implementation:class Square < Shape2D def initialize(length) @length = length end def area @length ** 2 end end Square.new(10).area # => 100
Ruby doesn't have a built-in notion of an abstract method or class, and though it has many built-in classes that might be considered "abstract," it doesn't enforce this abstractness the way C++ and Java do. For instance, you can instantiate an instance ofObjectorNumeric, even though those classes don't do anything by themselves.In general, this is in the spirit of Ruby. But it's sometimes useful to define a superclass method that every subclass is expected to implement. TheNotImplementedErrorerror is the standard way of conveying that a method is not there, whether it's abstract or just an unimplemented stub.Unlike other programming languages, Ruby will let you instantiate a class that defines an abstract method. You won't have any problems until you actually call the abstract method; even then, you can catch theNotImplementedErrorand recover. If you want, you can make an entire class abstract by making itsinitializemethod raise aNotImplementedError. Then no one will be able to create instances of your class:class Shape2D def initialize raise NotImplementedError. new("#{self.class.name} is an abstract class.") end end Shape2D.new # NotImplementedError: Shape2D is an abstract class.We can do the same thing in less code by defining a decorator method ofClassthat creates an abstract method by the given name.class Class def abstract(*args) args.each do |method_name| define_method(method_name) do |*args| if method_name == :initialize msg = "#{self.class.name} is an abstract class." else msg = "#{self.class.name}##{method_name} is an abstract method." end raise NotImplementedError.new(msg) end end end endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Freezing an Object to Prevent Changes
- InhaltsvorschauYou want to prevent any further changes to the state of an object.Freeze the object with
Object#freeze:frozen_string = 'Brrrr!' frozen_string.freeze frozen_string.gsub('r', 'a') # => "Baaaa!" frozen_string.gsub!('r', 'a') # TypeError: can't modify frozen stringWhen an object is frozen, its instance variables are permanently bound to their current values. The values themselves are not frozen: their instance variables can still be modified, to the extent they were modifiable before:sequences = [[1,2,3], [1,2,4], [1,4,9]].freeze sequences << [2,3,5] # TypeError: can't modify frozen array sequences[2] << 16 # => [1, 4, 9, 16]
A frozen object cannot be unfrozen, and if cloned, the clone will also be frozen. CallingObject#dup(as opposed toObject#clone) on a frozen object yields an unfrozen object with the same instance variables.frozen_string.clone.frozen? # => true frozen_string.dup.frozen? # => false
Freezing an object does not prevent reassignment of any variables bound to that object.frozen_string = 'A new string.' frozen_string.frozen? # => false
To prevent objects from changing in ways confusing to the user or to the Ruby interpreter, Ruby sometimes copies objects and freezes the copies. When you use a string as a hash key, Ruby actually copies the string, freezes the copy, and uses the copy as the hash key: that way, if the original string changes later on, the hash key isn't affected.Constant objects are often frozen as a second line of defense against the object being modified in place. You can freeze an object whenever you need a permanent reference to an object; this is most commonly seen with strings:API_KEY = "100f7vo4gg".freeze API_KEY[0] = 4 # TypeError: can't modify frozen string API_KEY = "400f7vo4gg" # warning: already initialized constant API_KEY
Frozen objects are also useful in multithreaded code. For instance, Ruby's internal file operations work from a frozen copy of a filename instead of using the filename directly. If another thread modifies the original filename in the middle of an operation that's supposed to be atomic, there's no problem: Ruby wasn't relying on the original filename anyway. You can adopt this copy-and-freeze pattern in multithreaded code to prevent a data structure you're working on from being changed by another thread.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Making a Copy of an Object
- InhaltsvorschauYou want to make a copy of an existing object: a new object that can be modified separately from the original.Ruby provides two ways of doing this. If you only want to have to remember one way, remember
Object#clone:s1 = 'foo' # => "foo" s2 = s1.clone # => "foo" s1[0] = 'b' [s1, s2] # => ["boo", "foo"]
Ruby has two object-copy methods: a quick one and a thorough one. The quick one,Object#dup, creates a new instance of an object's class, then sets all of the new object's instance variables so that they reference the same objects as the original does. Finally, it makes the new object tainted if the old object was tainted.The downside ofdupis that it creates a new instance of the object's original class. If you open up a specific object and give it a singleton method, you implicitly create a metaclass, an anonymous subclass of the original class. Callingdupon the object will yield a copy that lacks the singleton methods. The other object-copy method,Object#clone, makes a copy of the metaclass and instantiates the copy, instead of instantiating the object's original class.material = 'cotton' class << material def definition puts 'The better half of velour.' end end material.definition # The better half of velour. 'cotton'.definition # NoMethodError: undefined method 'definition' for "cotton":String material.clone.definition # The better half of velour. material.dup.definition # NoMethodError: undefined method 'definition' for "cotton":String
Object#cloneis also more strict about propagating Ruby's internal flags: it will propagate both an object's "tainted?" flag and its "frozen?" flag. If you want to make an unfrozen copy of a frozen object, you must useObject#dup.Object#cloneandObject#dupboth perform shallow copies: they make copies of an object without also copying its instance variables. You'll end up with two objects whose instance variables point to the same objects. Modifications to one object's instance variables will be visible in the other object. This can cause problems if you're not expecting it:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Declaring Constants
- InhaltsvorschauYou want to prevent a variable from being assigned a different value after its initial definition.Declare the variable as a constant. You can't absolutely prohibit the variable from being assigned a different value, but you can make Ruby generate a warning whenever that happens.
not_a_constant = 3 not_a_constant = 10 A_CONSTANT = 3 A_CONSTANT = 10 # warning: already initialized constant A_CONSTANT
A constant variable is one whose name starts with a capital letter. By tradition, Ruby constant names consist entirely of capital letters, numbers, and underscores. Constants don't mesh well with Ruby's philosophy of unlimited changability: there's no way to absolutely prevent someone from changing your constant. However, they are a useful signal to the programmers who come after you, letting them know not to redefine a constant without a very good reason.Constants can occur anywhere in code. If they appear within a class or module, you can access them from outside the class or module with the double-colon operator ( ::). The name of the class or module qualifies the name of the constant, preventing confusion with other constants that may have the same name but be defined in different scopes.CONST = 4 module ConstModule CONST = 6 end class ConstHolder CONST = 8 def my_const return CONST end end CONST # => 4 ConstModule::CONST # => 6 ConstHolder::CONST # => 8 ConstHolder.new.my_const # => 8
The thing that's constant about a constant is its reference to an object. If you change the reference to point to a different object, you'll get a warning. Unfortunately, there's no way to tell Ruby to treat the redeclaration of a constant as an error.E = 2.718281828 # => 2.718281828 E = 6 # warning: already initialized constant E E # => 6
However, you can useModule#remove_constas a sneaky way to "undeclare" a constant. You can then declare the constant again, without even triggering a warning. Clearly, this is potent and potentially dangerous stuff:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Implementing Class and Singleton Methods
- InhaltsvorschauYou want to associate a new method with a class (as opposed to the instances of that class), or with a particular object (as opposed to other instances of the same class).To define a class method, prefix the method name with the class name in the method definition. You can do this inside or outside of the class definition.The
Regexp.is_valid? method, defined below, checks whether a string can be compiled into a regular expression. It doesn't make sense to call it on an already instantiatedRegexp, but it's clearly related functionality, so it belongs in theRegexpclass (assuming you don't mind adding a method to a core Ruby class).class Regexp def Regexp.is_valid?(str) begin compile(str) valid = true rescue RegexpError valid = false end end end Regexp.is_valid? "The horror!" # => true Regexp.is_valid? "The)horror!" # => false
Here's aFixnum.randommethod that generates a random number in a specified range:def Fixnum.random(min, max) raise ArgumentError, "min > max" if min > max return min + rand(max-min+1) end Fixnum.random(10, 20) # => 13 Fixnum.random(-5, 0) # => -5 Fixnum.random(10, 10) # => 10 Fixnum.random(20, 10) # ArgumentError: min > max
To define a method on one particular other object, prefix the method name with the variable name when you define the method:company_name = 'Homegrown Software' def company_name.legalese return "#{self} is a registered trademark of ConglomCo International." end company_name.legalese # => "Homegrown Software is a registered trademark of ConglomCo International." 'Some Other Company'.legalese # NoMethodError: undefined method 'legalese' for "Some Other Company":StringIn Ruby, a singleton method is a method defined on one specific object, and not available to other instances of the same class. This is kind of analagous to the Singleton pattern, in which all access to a certain class goes through a single instance, but the name is more confusing than helpful.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Controlling Access by Making Methods Private
- InhaltsvorschauYou've refactored your code (or written it for the first time) and ended up a method that should be marked for internal use only. You want to prevent outside objects from calling such methods.Use
privateas a statement before a method definition, and the method will not be callable from outside the class that defined it. This class defines an initializer, a public method, and a private method:class SecretNumber def initialize @secret = rand(20) end def hint puts "The number is #{"not " if secret <= 10}greater than 10." end private def secret @secret end end s = SecretNumber.new s.secret # NoMethodError: private method 'secret' called for # #<SecretNumber:0xb7c2e83c @secret=19> s.hint # The number is greater than 10.Unlike in many other programming languages, a private method in Ruby is accessible to subclasses of the class that defines it:class LessSecretNumber < SecretNumber def hint lower = secret-rand(10)-1 upper = secret+rand(10)+1 "The number is somewhere between #{lower} and #{upper}." end end ls = LessSecretNumber.new ls.hint # => "The number is somewhere between -3 and 16." ls.hint # => "The number is somewhere between -1 and 15." ls.hint # => "The number is somewhere between -2 and 16."Like many parts of Ruby that look like special language features, Ruby's privacy keywords are actually methods. In this case, they're methods ofModule. When you callprivate, protected, orpublic, the current module (remember that a class is just a special kind of module) changes the rules it applies to newly defined methods from that point on.Most languages that support method privacy make you put a keyword before every method saying whether it's public, private, or protected. In Ruby, the special privacy methods act as toggles. When you call theprivatekeyword, all methods you define after that point are declared as private, until the module definition ends or you call a different privacy method. This makes it easy to group methods of the same privacy level—a good, general programming practice:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 9: Modules and Namespaces
- InhaltsvorschauA Ruby module is nothing more than a grouping of objects under a single name. The objects may be constants, methods, classes, or other modules.Modules have two uses. You can use a module as a convenient way to bundle objects together, or you can incorporate its contents into a class with Ruby's
includestatement.When a module is used as a container for objects, it's called a namespace. Ruby'sMathmodule is a good example of a namespace: it provides an overarching structure for constants likeMath::PIand methods likeMath::log, which would otherwise clutter up the mainKernelnamespace. We cover this most basic use of modules in Recipes 9.5 and 9.7.Modules are also used to package functionality for inclusion in classes. TheEnumerablemodule isn't supposed to be used on its own: it adds functionality to a class likeArrayorHash. We cover the use of modules as packaged functionality for existing classes in Recipes 9.1 and 9.4.Moduleis actually the superclass ofClass, so every Ruby class is also a module. Throughout this book we talk about using methods ofModulefrom within classes. The same methods will work exactly the same way within modules. The only thing you can't do with a module is instantiate an object from it:Class.superclass # => Module Math.class # => Module Math.new # NoMethodError: undefined method `new' for Math:Module
You want to create a class that derives from two or more sources, but Ruby doesn't support multiple inheritance.Suppose you created a class calledTaggablethat lets you associate tags (short strings of informative metadata) with objects. Every class whose objects should be taggable could derive fromTaggable.This would work if you madeTaggablethe top-level class in your class structure, but that won't work in every situation. Eventually you might want to do something like make a string taggable. One class can't subclass bothTaggableandString, so you'd have a problem.Furthermore, it makes little sense to instantiate and use aTaggableobject by itself—there is nothing there to tag! Taggability is more of a feature of a class than a fullfledged class of its own. TheEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Simulating Multiple Inheritance with Mixins
- InhaltsvorschauYou want to create a class that derives from two or more sources, but Ruby doesn't support multiple inheritance.Suppose you created a class called
Taggablethat lets you associate tags (short strings of informative metadata) with objects. Every class whose objects should be taggable could derive fromTaggable.This would work if you madeTaggablethe top-level class in your class structure, but that won't work in every situation. Eventually you might want to do something like make a string taggable. One class can't subclass bothTaggableandString, so you'd have a problem.Furthermore, it makes little sense to instantiate and use aTaggableobject by itself—there is nothing there to tag! Taggability is more of a feature of a class than a fullfledged class of its own. TheTaggablefunctionality only works in conjunction with some other data structure.This makes it an ideal candidate for implementation as a Ruby module instead of a class. Once it's in a module, any class can include it and use the methods it defines.require 'set' # Deals with a collection of unordered values with no duplicates # Include this module to make your class taggable. The names of the # instance variable and the setup method are prefixed with "taggable_" # to reduce the risk of namespace collision. You must call # taggable_setup before you can use any of this module's methods. module Taggable attr_accessor :tags def taggable_setup @tags = Set.new end def add_tag(tag) @tags << tag end def remove_tag(tag) @tags.delete(tag) end end
Here's a taggable string class: it subclassesString, but it also includes the functionality ofTaggable.class TaggableString < String include Taggable def initialize(*args) super taggable_setup end end s = TaggableString.new('It was the best of times, it was the worst of times.') s.add_tag 'dickens' s.add_tag 'quotation' s.tags # => #<Set: {"dickens", "quotation"}>A Ruby class can only have one superclass, but it can include any number of modules. These modules are calledEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Extending Specific Objects with Modules
- InhaltsvorschauCredit: Phil TomsonYou want to add instance methods from a module (or modules) to specific objects. You don't want to mix the module into the object's class, because you want certain objects to have special abilities.Use the
Object#extendmethod.For example, let's say we have a mild-manneredPersonclass:class Person attr_reader :name, :age, :occupation def initialize(name, age, occupation) @name, @age, @occupation = name, age, occupation end def mild_mannered? true end end
Now let's create a couple of instances of this class.jimmy = Person.new('Jimmy Olsen', 21, 'cub reporter') clark = Person.new('Clark Kent', 35, 'reporter') jimmy.mild_mannered? # => true clark.mild_mannered? # => trueBut it happens that somePersonobjects are not as mild-mannered as they might appear. Some of them have super powers.module SuperPowers def fly 'Flying!' end def leap(what) "Leaping #{what} in a single bound!" end def mild_mannered? false end def superhero_name 'Superman' end endIf we useincludeto mix theSuperPowersmodule into thePersonclass, it will give every person super powers. Some people are bound to misuse such power. Instead, we'll useextendto give super powers only to certain people:clark.extend(SuperPowers) clark.superhero_name # => "Superman" clark.fly # => "Flying!" clark.mild_mannered? # => false jimmy.mild_mannered? # => true
Theextendmethod is used to mix a module's methods into an object, whileincludeis used to mix a module's methods into a class.The astute reader might point out that classes are actually objects in Ruby. Let us see what happens when we useextendin a class definition:class Person extend SuperPowers end #which is equivalent to: Person.extend(SuperPowers)
What exactly are we extending here? Within the class definition,Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Mixing in Class Methods
- InhaltsvorschauCredit: Phil TomsonYou want to mix class methods into a class, instead of mixing in instance methods.The simplest way to accomplish this is to call
extendon the class object, as seen in the Discussion of Recipe 9.2. Just as you can useextendto add singleton methods to an object, you can use it to add class methods to a class. But that's not always the best option. Your users may not know that your module provides or even requires some class methods, so they might notextendtheir class when they should. How can you make anincludestatement mix in class methods as well?To begin, within your module, define a submodule calledClassMethods,which contains the methods you want to mix into the class:module MyLib module ClassMethods def class_method puts "This method was first defined in MyLib::ClassMethods" end end end
To make this code work, we must also define theincludedcallback method within theMyLibmodule. This method is called every time a module is included in the class, and it's passed the class object in which our module is being included. Within the callback method, we extend that class object with ourClassMethodsmodule, making all of its instance methods into class methods. Continuing the example:module MyLib def self.included(receiver) puts "MyLib is being included in #{receiver}!" receiver.extend(ClassMethods) end endNow we can include ourMyLibmodule in a class, and get the contents ofClassMethodsmixed in as genuine class methods:class MyClass include MyLib end # MyLib is being included in MyClass! MyClass.class_method # This method was first defined in MyLib::ClassMethods
Module#includedis a callback method that is automatically called during the inclusion of a module into a class. The defaultincludedimplementation is an empty method. In the example,MyLiboverrides it to extend the class that's including theMyLibmodule with the contents of theMyLib::ClassMethodssubmodule.TheEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Implementing Enumerable: Write One Method, Get 22 Free
- InhaltsvorschauYou want to give a class all the useful iterator and iteration-related features of Ruby's arrays (
sort, detect, inject, and so on), but your class can't be a subclass ofArray. You don't want to define all those methods yourself.Implement aneachmethod, then include theEnumerablemodule. It defines 22 of the most useful iteration methods in terms of theeachimplementation you provide.Here's a class that keeps multiple arrays under the covers. By definingeach, it can expose a large interface that lets the user treat it like a single array:class MultiArray include Enumerable def initialize(*arrays) @arrays = arrays end def each @arrays.each { |a| a.each { |x| yield x } } end end ma = MultiArray.new([1, 2], [3], [4]) ma.collect # => [1, 2, 3, 4] ma.detect { |x| x > 3 } # => 4 ma.map { |x| x ** 2 } # => [1, 4, 9, 16] ma.each_with_index { |x, i| puts "Element #{i} is #{x}" } # Element 0 is 1 # Element 1 is 2 # Element 2 is 3 # Element 3 is 4TheEnumerablemodule is the most common mixin module. It lets you add a lot of behavior to your class for a little investment. Since Ruby relies so heavily on iterator methods, and almost every data structure can be iterated over in some way, it's no wonder that so many of the classes in Ruby's standard library includeEnumerable: Dir, Hash, Range, andString, just to name a few.Here's the complete list of methods you can get by includingEnumerable. Many of them are described elsewhere in this book, especially in Chapter 4. Perhaps the most useful arecollect, inject, find_all, andsort_by.Enumerable.instance_methods.sort # => ["all?", "any?", "collect", "detect", "each_with_index", "entries", # => "find", "find_all", "grep", "include?", "inject", "map", "max", # => "member?", "min", "partition", "reject", "select", "sort", "sort_by", # => "to_a", "zip"]
Although you can get all these methods simply by implementing anEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Avoiding Naming Collisions with Namespaces
- InhaltsvorschauYou want to define a class or module whose name conflicts with an existing class or module, or you want to prevent someone else from coming along later and defining a class whose name conflicts with yours.A Ruby module can contain classes and other modules, which means you can use it as a namespace.Here's some code from a physics library that defines a class called
Stringwithin theStringTheorymodule. The real name of this class is its fully-qualified name:StringTheory::String. It's a totally different class from Ruby's built-inStringclass.module StringTheory class String def initialize(length=10**-33) @length = length end end end String.new # => "" StringTheory::String.new # => #<StringTheory::String:0xb7c343b8 @length=1.0e-33>
If you've read Recipe 8.17, you've already seen namespaces in action. The constants defined in a module are qualified with the module's name. This letsMath::PIhave a different value fromGreek::PI.You can qualify the name of any Ruby object this way: a variable, a class, or even another module. Namespaces let you organize your libraries, and make it possible for them to coexist alongside others.Ruby's standard library uses namespaces heavily as an organizing principle. An excellent example is REXML, the standard XML library. It defines aREXMLnamespace that includes lots of XML-related classes likeREXML::CommentandREXML::Instruction. Naming those classesCommentandInstructionwould be a disaster: they'd get overwritten by other librarys'CommentandInstructionclasses. Since nothing about the genericsounding names relates them to the REXML library, you might look at someone else's code for a long time before realizing that theCommentobjects have to do with XML.Namespaces can be nested: see for instancerexml's REXML::Parsersmodule, which contains classes likeREXML::Parsers::StreamParser. Namespaces group similar classes in one place so you can find what you're looking for; nested namespaces do the same for namespaces.In Ruby, you should name your top-level module after your software project (Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Automatically Loading Libraries as Needed
- InhaltsvorschauYou've written a big library with multiple components. You'd like to split it up so that users don't have to load the entire library into memory just to use part of it. But you don't want to make your users explicitly
requireeach part of the library they plan to use.Split the big library into multiple files, and set up autoloading for the individual files by callingKernel#autoload. The individual files will be loaded as they're referenced.Suppose you have a library,functions.rb, that provides two very large modules:# functions.rb module Decidable # … Many, many methods go here. end module Semidecidable # … Many, many methods go here. end
You can provide the same interface, but possibly save your users some memory, by splittingfunctions.rbinto three files. Thefunctions.rbfile itself becomes a stub full ofautoloadcalls:# functions.rb autoload :Decidable, "decidable.rb" autoload :Semidecidable, "semidecidable.rb"
The modules themselves go into the files mentioned in the newfunctions.rb:# decidable.rb module Decidable # … Many, many methods go here. end # semidecidable.rb module Semidecidable # … Many, many methods go here. end
The following code will work if all the modules are infunctions.rb, but it will also work iffunctions.rbonly contains calls toautoload:require 'functions' Decidable.class # => Module # More use of the Decidable module follows…
WhenDecidableandSemidecidablehave been split into autoloaded modules, that code only loads theDecidablemodule. Memory is saved that would otherwise be used to contain the unsedSemidecidablemodule.Refactoring a library to consist of autoloadable components takes a little extra planning, but it's often worth it to improve performance for the people who use your library.Each call toKernel#autoloadbinds a symbol to the path of the Ruby file that's supposed to define that symbol. If the symbol is referenced, that file is loaded exactly as though it had been passed as an argument intorequireEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Including Namespaces
- InhaltsvorschauYou want to use the objects within a module without constantly qualifying the object names with the name of their module.Use
includeto copy a module's objects into the current namespace. You can then use them from the current namespace, without qualifying their names.Instead of this:require 'rexml/document' REXML::Document.new(xml)
You might write this:require 'rexml/document' include REXML Document.new(xml)
This is the exact sameincludestatement you use to incorporate a mixin module into a class you're writing. It does the same thing here as when it includes a mixin: it copies the contents of a module into the current namespace.Here, though, the point isn't to add new functionality to a class or module: it's to save you from having to do so much typing. This technique is especially useful with large library modules like Curses and the Rails libraries.This use ofincludecomes with the same caveats as any other: if you already have variables with the same names as the objects being included, the included objects will be copied in over them and clobber them.You can, of course, import a namespace that's nested within a namespace of its own. Instead of this:require 'rexml/parsers/pullparser' REXML::Parsers::PullParser.new("Some XML")You might write this:require 'rexml/parsers/pullparser' include REXML::Parsers PullParser.new("Some XML")- Recipe 11.3, "Extracting Data While Parsing a Document"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Initializing Instance Variables Defined by a Module
- InhaltsvorschauCredit: Phil TomsonYou have a mixin module that defines some instance variables. Given a class that mixes in the module, you want to initialize the instance variables whenever an instance of the class is created.Define an
initializemethod in the module, and callsuperin your class's constructor. Here's aTimeablemodule that tracks when objects are created and how old they are:module Timeable attr_reader :time_created def initialize @time_created = Time.now end def age #in seconds Time.now - @time_created end end
Timeablehas an instance variabletime_created, and aninitializemethod that assignsTime.now(the current time) to the instance variable. Now let's mixTimeableinto another class that also defines aninitializemethod:class Character include Timeable attr_reader :name def initialize( name ) @name = name super() #calls Timeable's initialize end end c = Character.new "Fred" c.time_created # => Mon Mar 27 18:34:31 EST 2006
You can define and access instance variables within a module's instance methods, but you can't actually instantiate a module. A module's instance variables only exist within objects of a class that includes the module. However, classes don't usually need to know about the instance variables defined by the modules they include. That sort of information should be initialized and maintained by the module itself.TheCharacter#initializemethod overrides theTimeable#initializemethod, but you can usesuperto call theTimeableconstructor from within theCharacterconstructor. When a module is included in a class, that module becomes an ancestor of the class. We can test this in the context of the example above by calling theModule#ancestorson theCharacterclass:Character.ancestors # => [Character, Timeable, Object, Kernel]
When you callsuperfrom within a method (such asinitialize), Ruby finds every ancestor that defines a method with the same name, and calls it too.- Recipe 8.13, "Calling a Superclass's Method"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Automatically Initializing Mixed-In Modules
- InhaltsvorschauCredit: Phil TomsonYou've written a module that gets mixed into classes. Your module has some initialization code that needs to run whenever the mixed-into class is initialized. You do not want users of your module to have to call
superin theirinitializemethods.First, we need a way for classes to keep track of which modules they've included. We also need to redefineClass#newto call a module-levelinitializemethod for each included module. Fortunately, Ruby's flexibility lets us makes changes to the built-inClassclass (though this should never be done lightly):class Class def included_modules @included_modules ||= [] end alias_method :old_new, :new def new(*args, &block) obj = old_new(*args, &block) self.included_modules.each do |mod| mod.initialize if mod.respond_to?(:initialize) end obj end end
Now every class has a list of included modules, accessable from theincluded_modulesclass method. We've also redefined theClass#newmethod so that it iterates through all the modules inincluded_modules, and calls the module-levelinitializemethod of each.All that's missing is a way to add included modules toincluded_modules. We'll put this code into anInitializablemodule. A module that wants to be initializable can mix this module into itself and define aninitializemethod:module Initializable def self.included(mod) mod.extend ClassMethods end module ClassMethods def included(mod) if mod.class != Module #in case Initializeable is mixed-into a class puts "Adding #{self} to #{mod}'s included_modules" if $DEBUG mod.included_modules << self end end end endTheincludedcallback method is called whenever this module is included in another module. We're using the pattern shown in Recipe 9.3 to add anincludedcallback method into the receiving module. If we didn't do this, you'd have to use that pattern yourself for every module you wanted to beInitializable.That's a lot of code, but here's the payoff. Let's define a couple of modules which includeEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 10: Reflection and Metaprogramming
- InhaltsvorschauIn a dynamic language like Ruby, few pieces are static. Classes can grow new methods and lose the ones they had before. Methods can be defined manually, or automatically with well-written code.Probably the most interesting aspect of the Ruby programming philosophy is its use of reflection and metaprogramming to save the programmer from having to write repetitive code. In this chapter, we will teach you the ways and the joys of these techniques.Reflection lets you treat classes and methods as objects. With reflection you can see which methods you can call on an object (Recipes 10.2 and 10.3). You can grab one of its methods as an object (Recipe 10.4), and call it or pass it in to another method as a code block. You can get references to the class an object implements and the modules it includes, and print out its inheritance structure (Recipe 10.1). Reflection is especially useful when you're interactively examining an unfamiliar object or class structure.Metaprogramming is to programming as programming is to doing a task by hand. If you need to sort a file of a hundred lines, you don't open it up in a text editor and start shuffling the lines: you write a program to do the sort. By the same token, if you need to give a Ruby class a hundred similar methods, you shouldn't just start writing the methods one at a time. You should write Ruby code that defines the methods for you (Recipe 10.10). Or you should make your class capable of intercepting calls to those methods: this way, you can implement the methods without ever defining them at all (Recipe 10.8).Methods you've seen already, like
attr_reader, use metaprogramming to define custom methods according to your specifications. Recipe 8.2 created a few more of these "decorator" methods; Recipe 10.16 in this chapter shows a more complex example of the same principle.You can metaprogram in Ruby either by writing normal Ruby code that uses a lot of reflection, or by generating a string that contains Ruby code, and evaluating the string. Writing normal Ruby code with reflection is generally safer, but sometimes the reflection just gets to be too much and you need to evaluate a string. We provide a demonstration recipe for each technique (Recipes 10.10 and 10.11).Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Finding an Object's Class and Superclass
- InhaltsvorschauGiven a class, you want an object corresponding to its class, or to the parent of its class.Use the
Object#classmethod to get the class of an object as aClassobject. UseClass#superclassto get the parentClassof aClassobject:'a string'.class # => String 'a string'.class.name # => "String" 'a string'.class.superclass # => Object String.superclass # => Object String.class # => Class String.class.superclass # => Module 'a string'.class.new # => ""
Classobjects in Ruby are first-class objects that can be assigned to variables, passed as arguments to methods, and modified dynamically. Many of the recipes in this chapter and Chapter 8 discuss things you can do with aClassobject once you have it.Thesuperclassof theObjectclass isnil. This makes it easy to iterate up an inheritance hierarchy:class Class def hierarchy (superclass ? superclass.hierarchy : []) << self end end Array.hierarchy # => [Object, Array] class MyArray < Array end MyArray.hierarchy # => [Object, Array, MyArray]
While Ruby does not support multiple inheritance, the language allows mixinModulesthat simulate it (see Recipe 9.1). TheModulesincluded by a givenClass(or anotherModule) are accessible from theModule#ancestorsmethod.A class can have only onesuperclass, but it may have any number ofancestors. The list returned byModule#ancestorscontains the entire inheritance hierarchy (including the class itself), any modules the class includes, and the ever-presentKernelmodule, whose methods are accessible from anywhere becauseObjectitself mixes it in.String.superclass # => Object String.ancestors # => [String, Enumerable, Comparable, Object, Kernel] Array.ancestors # => [Array, Enumerable, Object, Kernel] MyArray.ancestors # => [MyArray, Array, Enumerable, Object, Kernel] Object.ancestors # => [Object, Kernel] class MyClass end MyClass.ancestors # => [MyClass, Object, Kernel]
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Listing an Object's Methods
- InhaltsvorschauGiven an unfamiliar object, you want to see what methods are available to call.All Ruby objects implement the
Object#methodsmethod. It returns an array containing the names of the object's public instance methods:Object.methods # => ["name", "private_class_method", "object_id", "new", # "singleton_methods", "method_defined?", "equal?", … ]
To get a list of the singleton methods of some object (usually, but not always, a class), useObject#singleton_methods:Object.singleton_methods # => [] Fixnum.singleton_methods # => ["induced_from"] class MyClass def MyClass.my_singleton_method end def my_instance_method end end MyClass.singleton_methods # => ["my_singleton_method"]
To list the instance methods of a class, callinstance_methodson the object. This lets you list the instance methods of a class without instantiating the class:''.methods == String.instance_methods # => true
The output of these methods are most useful when sorted:Object.methods.sort # => ["<", "<=", "<=>", "==", "===", "=~", ">", ">=", # "__id__", "__send__", "allocate", "ancestors", … ]
Ruby also defines some elementary predicates along the same lines. To see whether a class defines a certain instance method, callmethod_defined?on the class orrespond_to?on an instance of the class. To see whether a class defines a certain class method, callrespond_to?on the class:MyClass.method_defined? :my_instance_method # => true MyClass.new.respond_to? :my_instance_method # => true MyClass.respond_to? :my_instance_method # => false MyClass.respond_to? :my_singleton_method # => true
It often happens that while you're in an interactive Ruby session, you need to look up which methods an object supports, or what a particular method is called. Looking directly at the object is faster than looking its class up in a book. If you're using a library like Rails or Facets, or your code has been adding methods to the built-in classes, it's also more reliable.Noninteractive code can also benefit from knowing whether a given object implements a certain method. You can use this to enforce an interface, allowing any object to be passed into a method so long as the argument implements certain methods (see Recipe 10.16).Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Listing Methods Unique to an Object
- InhaltsvorschauWhen you list the methods available to an object, the list is cluttered with extraneous methods defined in the object's superclasses and mixed-in modules. You want to see a list of only the methods defined by that object's direct class.Subtract the instance methods defined by the object's superclass. You'll be left with only the methods defined by the object's direct class (plus any methods defined on the object after its creation). The
my_methods_onlymethod defined below gives this capability to every Ruby object:class Object def my_methods_only my_super = self.class.superclass return my_super ? methods - my_super.instance_methods : methods end end s = '' s.methods.size # => 143 Object.instance_methods.size # => 41 s.my_methods_only.size # => 102 (s.methods - Object.instance_methods).size # => 102 def s.singleton_method( ) end s.methods.size # => 144 s.my_methods_only.size # => 103 class Object def new_object_method end end s.methods.size # => 145 s.my_methods_only.size # => 103 class MyString < String def my_string_method end end MyString.new.my_methods_only # => ["my_string_method"]
Themy_methods_onlytechnique removes methods defined in the superclass, the parent classes of the superclass, and in any mixin modules included by those classes. For instance, it removes the 40 methods defined by theObjectclass when it mixed in theKernelmodule. It will not remove methods defined by mixin modules included by the class itself.Usually these methods aren't clutter, but there can be a lot of them (for instance,Enumerabledefines 22 methods). To remove them, you can start out withmy_methods_only, then iterate over theancestorsof the class in question and subtract out all the methods defined in modules:class Object def my_methods_only_no_mixins self.class.ancestors.inject(methods) do |mlist, ancestor| mlist = mlist - ancestor.instance_methods unless ancestor.is_a? Class mlist end end [].methods.size # => 121 [].my_methods_only.size # => 78 [].my_methods_only_no_mixins.size # => 57
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Getting a Reference to a Method
- InhaltsvorschauYou want to the name of a method into a reference to the method itself.Use the eponymous
Object#methodmethod:s = 'A string' length_method = s.method(:length) # => #<Method: String#length> length_method.arity # => 0 length_method.call # => 8
TheObject#methodsintrospection method returns an array of strings, each containing the name of one of the methods available to that object. You can pass any of these names into an object'smethodmethod and get aMethodobject corresponding to that method of that object.AMethodobject is bound to the particular object whosemethodmethod you called. Invoke the method'sMethod#callmethod, and it's just like calling the object's method directly:1.succ # => 2 1.method(:succ).call # => 2
TheMethod#aritymethod indicates how many arguments the method takes. Arguments, including block arguments, are passed tocalljust as they would be to the original method:5.method('+').call(10) # => 15 [1,2,3].method(:each).call { |x| puts x } # 1 # 2 # 3AMethodobject can be stored in a variable and passed as an argument to other methods. This is useful for passing preexisting methods into callbacks and listeners:class EventSpawner def initialize @listeners = [] @state = 0 end def subscribe(&listener) @listeners << listener end def change_state(new_state) @listeners.each { |l| l.call(@state, new_state) } @state = new_state end end class EventListener def hear(old_state, new_state) puts "Method triggered: state changed from #{old_state} " + "to #{new_state}." end end spawner = EventSpawner.new spawner.subscribe do |old_state, new_state| puts "Block triggered: state changed from #{old_state} to #{new_state}." end spawner.subscribe &EventListener.new.method(:hear) spawner.change_state(4) # Block triggered: state changed from 0 to 4. # Method triggered: state changed from 0 to 4.AMethodcan also be used as a block:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Fixing Bugs in Someone Else's Class
- InhaltsvorschauYou're using a class that's got a bug in one of its methods. You know where the bug is and how to fix it, but you can't or don't want to change the source file itself.Extend the class from within your program and overwrite the buggy method with an implementation that fixes the bug. Create an alias for the buggy version of the method, so you can still access it if necessary.Suppose you're trying to use the buggy method in the
Multiplierclass defined below:class Multiplier def double_your_pleasure(pleasure) return pleasure * 3 # FIXME: Actually triples your pleasure. end end m = Multiplier.new m.double_your_pleasure(6) # => 18
Reopen the class, alias the buggy method to another name, then redefine it with a correct implementation:class Multiplier alias :double_your_pleasure_BUGGY :double_your_pleasure def double_your_pleasure(pleasure) return pleasure * 2 end end m.double_your_pleasure(6) # => 12 m.double_your_pleasure_BUGGY(6) # => 18
In many programming languages a class, function, or method can't be modified after its initial definition. In other languages, this behavior is possible but not encouraged. For Ruby programmers, the ability to reprogram classes on the fly is just another technique for the toolbox, to be used when necessary. It's most commonly used to add new code to a class, but it can also be used to deploy a drop-in replacement for buggy or slow implementation of a method.Since Ruby is (at least right now) a purely interpreted language, you should be able to find the source code of any Ruby class used by your program. If a method in one of those classes has a bug, you should be able to copy and paste the original Ruby implementation into your code and fix the bug in the new copy. This is not an elegant technique, but it's often better than distributing a slightly modified version of the entire class or library (that is, copying and pasting a whole file).When you fix the buggy behavior, you should also send your fix to the maintainer of the software that contains the bug. The sooner you can get the fix out of your code, the better. If the software package is abandoned, you should at least post the fix online so others can find it.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Listening for Changes to a Class
- InhaltsvorschauCredit: Phil TomsonYou want to be notified when the definition of a class changes. You might want to keep track of new methods added to the class, or existing methods that get removed or undefined. Being notified when a module is mixed into a class can also be useful.Define the class methods
method_added, method_removed, and/ormethod_undefined. Whenever the class gets a method added, removed, or undefined, Ruby will pass its symbol into the appropriate callback method.The following example prints a message whenever a method is added, removed, or undefined. If the method "important" is removed, undefined, or redefined, it throws an exception.class Tracker def important "This is an important method!" end def self.method_added(sym) if sym == :important raise 'The "important" method has been redefined!' else puts %{Method "#{sym}" was (re)defined.} end end def self.method_removed(sym) if sym == :important raise 'The "important" method has been removed!' else puts %{Method "#{sym}" was removed.} end end def self.method_undefined(sym) if sym == :important raise 'The "important" method has been undefined!' else puts %{Method "#{sym}" was removed.} end end endIf someone adds a method to the class, a message will be printed:class Tracker def new_method 'This is a new method.' end end # Method "new_method" was (re)defined.
Short of freezing the class, you can't prevent theimportantmethod from being removed, undefined, or redefined, but you can raise a stink (more precisely, an exception) if someone changes it:class Tracker undef :important end # RuntimeError: The "important" method has been undefined!
The class methods we've defined in the Tracker class (method_added, method_removed, andmethod_undefined) are hook methods. Some other piece of code (in this case, the Ruby interpreter) knows to call any methods by that name when certain conditions are met. TheModuleclass defines these methods with empty bodies: by default, nothing special happens when a method is added, removed, or undefined.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Checking Whether an Object Has Necessary Attributes
- InhaltsvorschauYou're writing a class or module that delegates the creation of some of its instance variables to a hook method. You want to be make sure that the hook method actually created those instance variables.Use the
Object#instance_variablesmethod to get a list of the instance variables. Check them over to make sure all the necessary instance variables have been defined. ThisObject#must_have_instance_variablesmethod can be called at any time:class Object def must_have_instance_variables(*args) vars = instance_variables.inject({}) { |h,var| h[var] = true; h } args.each do |var| unless vars[var] raise ArgumentError, %{Instance variable "@#{var} not defined"} end end end endThe best place to call this method is ininitializeor some other setup method of a module. Alternatively, you could accept values for the instance variables as arguments to the setup method:module LightEmitting def LightEmitting_setup must_have_instance_variables :light_color, :light_intensity @on = false end # Methods that use @light_color and @light_intensity follow… end
You can call this method from a class that defines a virtual setup method, to make sure that subclasses actually use the setup method correctly:class Request def initialize gather_parameters # This is a virtual method defined by subclasses must_have_instance_variables :action, :user, :authentication end # Methods that use @action, @user, and @authentication follow… end
AlthoughObject#must_have_instance_variablesis defined and called like any other method, it's conceptually a "decorator" method similar toattr_accessorandprivate. That's why I didn't use parentheses above, even though I called it with multiple arguments. The lack of parentheses acts as a visual indicator that you're calling a decorator method, one that alters or inspects a class or object.Here's a similar method that you can use from outside the object. It basically implements a batch form of duck typing: instead of checking an object's instance variables (which are only available inside the object), it checks whether the object supports all of the methods you need to call on it. It's useful for checking from the outside whether an object is the "shape" you expect.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Responding to Calls to Undefined Methods
- InhaltsvorschauRather than having Ruby raise a
NoMethodErrorwhen someone calls an undefined method on an instance of your class, you want to intercept the method call and do something else with it.Or you are faced with having to explicitly define a large (possibly infinite) number of methods for a class. You would rather define a single method that can respond to an infinite number of method names.Define amethod_missingmethod for your class. Whenever anyone calls a method that would otherwise result in aNoMethodError, themethod_missingmethod is called instead. It is passed the symbol of the nonexistent method, and any arguments that were passed in.Here's a class that modifies the default error handling for a missing method:class MyClass def defined_method 'This method is defined.' end def method_missing(m, *args) "Sorry, I don't know about any #{m} method." end end o = MyClass.new o.defined_method # => "This method is defined." o.undefined_method # => "Sorry, I don't know about any undefined_method method."In the second example, I'll define an infinitude of new methods onFixnumby giving it amethod_missingimplementation. Once I'm done,Fixnumwill answer to any method that looks like "plus_#" and takes no arguments.class Fixnum def method_missing(m, *args) if args.size > 0 raise ArgumentError.new("wrong number of arguments (#{args.size} for 0)") end match = /^plus_([0-9]+)$/.match(m.to_s) if match self + match.captures[0].to_i else raise NoMethodError. new(" undefined method '#{m}' for #{inspect}:#{self.class}") end end end 4.plus_5 # => 9 10.plus_0 # => 10 -1.plus_2 # => 1 100.plus_10000 # => 10100 20.send(:plus_25) # => 45 100.minus_3 # NoMethodError: undefined method 'minus_3' for 100:Fixnum 100.plus_5(105) # ArgumentError: wrong number of arguments (1 for 0)Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Automatically Initializing Instance Variables
- InhaltsvorschauYou're writing a class constructor that takes a lot of arguments, each of which is simply assigned to an instance variable.
class RGBColor(red=0, green=0, blue=0) @red = red @green = green @blue = blue end
You'd like to avoid all the typing necessary to do those variable assignments.Here's a method that initializes the instance variables for you. It takes as an argument the list of variables passed into theinitializemethod, and the binding of the variables to values.class Object private def set_instance_variables(binding, *variables) variables.each do |var| instance_variable_set("@#{var}", eval(var, binding)) end end endUsing this method, you can eliminate the tedious variable assignments:class RGBColor def initialize(red=0, green=0, blue=0) set_instance_variables(binding, *local_variables) end end RGBColor.new(10, 200, 300) # => #<RGBColor:0xb7c22fc8 @red=10, @blue=300, @green=200>
Ourset_ instance_variablestakes a list of argument names to turn into instance variables, and aBindingcontaining the values of those arguments as of the method call. For each argument name, anevalstatement binds the corresponding instance variable to the corresponding value in theBinding. Since you control the names of your own variables, thisevalis about as safe as it gets.The names of a method's arguments aren't accessible from Ruby code, so how do we get that list? Through trickery. When a method is called, any arguments passed in are immediately bound to local variables. At the very beginning of the method, these are the only local variables defined. This means that callingKernel#local_variablesat the beginning of a method will get a list of all the argument names.If your method accepts arguments that you don't want to set as instance variables, simply remove their names from the result ofKernel#local_variablesbefore passing the list intoset_instance_variables:class RGBColor def initialize(red=0, green=0, blue=0, debug=false) set_instance_variables(binding, *local_variables-['debug']) puts "Color: #{red}/#{green}/#{blue}" if debug end end RGBColor.new(10, 200, 255, true) # Color: 10/200/255 # => #<RGBColor:0xb7d309fc @blue=255, @green=200, @red=10>Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Avoiding Boilerplate Code with Metaprogramming
- InhaltsvorschauYou've got to type in a lot of repetitive code that a trained monkey could write. You're resentful at having to do this yourself, and angry that the repetitive code will clutter up your class listings.Ruby is happy to be the trained monkey that writes your repetitive code. You can define methods algorithmically with
Module#define_method.Usually the repetitive code is a bunch of similar methods. Suppose you need to write code like this:class Fetcher def fetch(how_many) puts "Fetching #{how_many ? how_many : "all"}." end def fetch_one fetch(1) end def fetch_ten fetch(10) end def fetch_all fetch(nil) end endYou can define this exact same code without having to write it all out. Create a data structure that contains the differences between the methods, and iterate over that structure, defining a method each time withdefine_method.class GeneratedFetcher def fetch(how_many) puts "Fetching #{how_many ? how_many : "all"}." end [["one", 1], ["ten", 10], ["all", nil]].each do |name, number| define_method("fetch_#{name}") do fetch(number) end end end GeneratedFetcher.instance_methods - Object.instance_methods # => ["fetch_one", "fetch", "fetch_ten", "fetch_all"] GeneratedFetcher.new.fetch_one # Fetching 1. GeneratedFetcher.new.fetch_all # Fetching all.This is less to type, less monkeyish, and it takes up less space in your class listing. If you need to define more of these methods, you can add to the data structure instead of writing out more boilerplate.Programmers have always preferred writing new code to cranking out variations on old code. Fromlexandyaccto modern programs like Hibernate and Cog, we've always used tools to generate code that would be tedious to write out manually.Instead of generating code with an external tool, Ruby programmers do it from within Ruby. There are two officially sanctioned techniques. The nicer technique is to usedefine_methodto create a method whose implementation can use the local variables available at the time it was defined.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Metaprogramming with String Evaluations
- InhaltsvorschauYou're trying to write some metaprogramming code using
define_method, but there's too much reflection going on for your code to be readable. It gets confusing and is almost as frustrating as having to write out the code in longhand.You can define new methods by generating the definitions as strings and running them as Ruby code with one of theevalmethods.Here's a reprint of the metaprogramming example from the previous recipe, which usesdefine_method:class Numeric [['add', '+'], ['subtract', '-'], ['multiply', '*',], ['divide', '/']].each do |method, operator| define_method("#{method}_2") do method(operator).call(2) end end endThe important line of code,method(operator).call(2), isn't something you'd write in normal programming. You'd write something likeself + 2 or self / 2, depending on which operator you wanted to apply. By writing your method definitions as strings, you can do metaprogramming that looks more like regular programming:class Numeric [['add', '+'], ['subtract', '-'], ['multiply', '*',], ['divide', '/']].each do |method, operator| module_eval %{ def #{method}_2 self.#{operator}(2) end } end end 4.add_2 # => 6 10.divide_2 # => 5You can do all of your metaprogramming withdefine_method, but the code doesn't look a lot like the code you'd write in normal programming. You can't set an instance variable with@foo=4; you have to callinstance_variable_set('foo', 4).The alternative is to generate a method definition as a string and execute the string as Ruby code. Most interpreted languages have a way of parsing and executing arbitrary strings as code, but it's usually regarded as a toy or a hazard, and not given much attention. Ruby breaks this taboo.The most common evalutation method used for metaprogramming isModule#module_eval. This method executes a string as Ruby code, within the context of a class or module. Any methods or class variables you define within the string will be attached to the class or module, just as if you'd typed the string within the class or module definition. Thanks to the variable substitutions, the generated string looks exactly like the code you'd type in manually.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Evaluating Code in an Earlier Context
- InhaltsvorschauYou've written a method that evaluates a string as Ruby code. But whenever anyone calls the method, the objects referenced by your string go out of scope. Your string can't be evaluated within a method.For instance, here's a method that takes a variable name and tries to print out the value of the variable.
def broken_print_variable(var_name) eval %{puts "The value of #{var_name} is " + #{var_name}.to_s} endTheevalcode only works when it's run in the same context as the variable definition. It doesn't work as a method, because your local variables go out of scope when you call a method.tin_snips = 5 broken_print_variable('tin_snips') # NameError: undefined local variable or method 'tin_snips' for main:Object var_name = 'tin_snips' eval %{puts "The value of #{var_name} is " + #{var_name}.to_s} # The value of tin_snips is 5Theevalmethod can execute a string of Ruby code as though you had written in some other part of your application. This magic is made possible byBindingobjects. You can get aBindingat any time by callingKernel#binding, and pass it in toevalto recreate your original environment where it wouldn't otherwise be available. Here's a version of the above method that takes aBinding:def print_variable(var_name, binding) eval %{puts "The value of #{var_name} is " + #{var_name}.to_s}, binding end vice_grips = 10 print_variable('vice_grips', binding) # The value of vice_grips is 10ABindingobject is a bookmark of the Ruby interpreter's state. It tracks the values of any local variables you have defined, whether you are inside a class or method definition, and so on.Once you have aBindingobject, you can pass it intoevalto run code in the same context as when you created theBinding. All the local variables you had back then will be available. If you calledKernel#bindingwithin a class definition, you'll also be able to define new methods of that class, and set class and instance variables.Since aBindingobject contains references to all the objects that were in scope when it was created, those objects can't be garbage-collected until both they and theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Undefining a Method
- InhaltsvorschauYou want to remove an already defined method from a class or module.From within a class or module, you can use
Module#remove_methodto remove a method's implementation, forcing Ruby to delegate to the superclass or a module included by a class.In the code below, I subclassArrayand override the << and [] methods to add some randomness. Then I decide that overriding [] wasn't such a good idea, so I undefine that method and get the inheritedArraybehavior back. The override of << stays in place.class RandomizingArray < Array def <<(e) insert(rand(size), e) end def [](i) super(rand(size)) end end a = RandomizingArray.new a << 1 << 2 << 3 << 4 << 5 << 6 # => [6, 3, 4, 5, 2, 1] # That was fun; now let's get some of those entries back. a[0] # => 1 a[0] # => 2 a[0] # => 5 #No, seriously, a[0]. a[0] # => 4 #It's a madhouse! A madhouse! a[0] # => 3 #That does it! class RandomizingArray remove_method('[]') end a[0] # => 6 a[0] # => 6 a[0] # => 6 # But the overridden << operator still works randomly: a << 7 # => [6, 3, 4, 7, 5, 2, 1]Usually you'll override a method by redefining it to implement your own desired behavior. However, sometimes a class will override an inherited method to do something you don't like, and you just want the "old" implementation back.You can only useremove_methodto remove a method from a class or module that explicitly defines it. You'll get an error if you try to remove a method from a class that merely inherits that method. To make a subclass stop responding to an inherited method, you shouldEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Aliasing Methods
- InhaltsvorschauYou (or your users) frequently misremember the name of a method. To reduce the confusion, you want to make the same method accessible under multiple names.Alternatively, you're about to redefine a method and you'd like to keep the old version available.You can create alias methods manually, but in most cases, you should let the
aliascommand do it for you. In this example, I define anInventoryItemclass that includes apricemethod to calculate the price of an item in quantity. Since it's likely that someone might misremember the name of thepricemethod ascost, I'll create an alias:class InventoryItem attr_accessor :name, :unit_price def initialize(name, unit_price) @name, @unit_price = name, unit_price end def price(quantity=1) @unit_price * quantity end #Make InventoryItem#cost an alias for InventoryItem#price alias :cost :price #The attr_accessor decorator created two methods called "unit_price" and #"unit_price=". I'll create aliases for those methods as well. alias :unit_cost :unit_price alias :unit_cost= :unit_price= end bacon = InventoryItem.new("Chunky Bacon", 3.95) bacon.price(100) # => 395.0 bacon.cost(100) # => 395.0 bacon.unit_price # => 3.95 bacon.unit_cost # => 3.95 bacon.unit_cost = 3.99 bacon.cost(100) # => 399.0It's difficult to pick the perfect name for a method: you must find the word or short phrase that best conveys an operation on a data structure, possibly an abstract operation that has different "meanings" depending on context.Sometimes there will be no good name for a method and you'll just have to pick one; sometimes there will be too many good names for a method and you'll just have to pick one. In either case, your users may have difficulty remembering the "right" name of the method. You can help them out by creating aliases.Ruby itself uses aliases in its standard library: for instance, for the method ofEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Doing Aspect-Oriented Programming
- InhaltsvorschauYou want to "wrap" a method with new code, so that calling the method triggers some new feature in addition to the original code.You can arrange for code to be called before and after a method invocation by using method aliasing and metaprogramming, but it's simpler to use the
gluegem or the AspectR third-party library. The latter lets you define "aspect" classes whose methods are called before and after other methods.Here's a simple example that traces calls to specific methods as they're made:require 'aspectr' class Verbose < AspectR::Aspect def describe(method_sym, object, *args) "#{object.inspect}.#{method_sym}(#{args.join(",")})" end def before(method_sym, object, return_value, *args) puts "About to call #{describe(method_sym, object, *args)}." end def after(method_sym, object, return_value, *args) puts "#{describe(method_sym, object, *args)} has returned " + return_value.inspect + '.' end endHere, I'll wrap thepushandpopmethods of an array. Every time I call those methods, the aspect code will run and some diagnostics will be printed.verbose = Verbose.new stack = [] verbose.wrap(stack, :before, :after, :push, :pop) stack.push(10) # About to call [].push(10). # [10].push(10) has returned [[10]]. stack.push(4) # About to call [10].push(4). # [10, 4].push(4) has returned [[10, 4]]. stack.pop # About to call [10, 4].pop(). # [10].pop() has returned [4].
There's a pattern that shows up again and again in Ruby (we cover it in Recipe 7.10). You write a method that performs some task-specific setup (like initializing a timer), runs a code block, then performs task-specific cleanup (like stopping the timer and printing out timing results). By passing in a code block to one of these methods you give it a new aspect: the same code runs as if you'd just calledProc#callon the code block, but now it's got something extra: the code gets timed, or logged, or won't run without authentication, or it automatically performs some locking.Aspect-oriented programming lets you permanently add these aspects to previously defined methods, without having to change any of the code that calls them. It's a good way to modularize your code, and to modify existing code without having to do a lot of metaprogramming yourself. Though less mature, the AspectR library has the same basic features of Java's AspectJ.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Enforcing Software Contracts
- InhaltsvorschauCredit: Maurice CodikYou want your methods to to validate their arguments, using techniques like duck typing and range validation, without filling your code with tons of conditions to test arguments.Here's a
Contractsmodule that you can mix in to your classes. Your methods can then define and enforce contracts.module Contracts def valid_contract(input) if @user_defined and @user_defined[input] @user_defined[input] else case input when :number lambda { |x| x.is_a? Numeric } when :string lambda { |x| x.respond_to? :to_str } when :anything lambda { |x| true } else lambda { |x| false } end end end class ContractViolation < StandardError end def define_data(inputs={}.freeze) @user_defined ||= {} inputs.each do |name, contract| @user_defined[name] = contract if contract.respond_to? :call end end def contract(method, *inputs) @contracts ||= {} @contracts[method] = inputs method_added(method) end def setup_contract(method, inputs) @contracts[method] = nil method_renamed = "__#{method}".intern conditions = "" inputs.flatten.each_with_index do |input, i| conditions << %{ if not self.class.valid_contract(#{input.inspect}).call(args[#{i}]) raise ContractViolation, "argument #{i+1} of method '#{method}' must" + "satisfy the '#{input}' contract", caller end } end class_eval %{ alias_method #{method_renamed.inspect}, #{method.inspect} def #{method}(*args) #{conditions} return #{method_renamed}(*args) end } end def method_added(method) inputs = @ contracts[method] setup_contract(method, inputs) if inputs end endYou can call thedefine_datamethod to define contracts, and call thecontractmethod to apply these contracts to your methods. Here's an example:class TestContracts def hello(n, s, f) n.times { f.write "hello #{s}!\n" } endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 11: XML and HTML
- InhaltsvorschauXML and HTML are the most popular markup languages (textual ways of describing structured data). HTML is used to describe textual documents, like you see on the Web. XML is used for just about everything else: data storage, messaging, configuration files, you name it. Just about every software buzzword forged over the past few years involves XML.Java and C++ programmers tend to regard XML as a lightweight, agile technology, and are happy to use it all over the place. XML is a lightweight technology, but only compared to Java or C++. Ruby programmers see XML from the other end of the spectrum, and from there it looks pretty heavy. Simpler formats like YAML and JSON usually work just as well (see Recipe 13.1 or Recipe 13.2), and are easier to manipulate. But to shun XML altogether would be to cut Ruby off from the rest of the world, and nobody wants that. This chapter covers the most useful ways of parsing, manipulating, slicing, and dicing XML and HTML documents.There are two standard APIs for manipulating XML: DOM and SAX. Both are overkill for most everyday uses, and neither is a good fit for Ruby's code-block–heavy style. Ruby's solution is to offer a pair of APIs that capture the style of DOM and SAX while staying true to the Ruby programming philosophy. Both APIs are in the standard library's REXML package, written by Sean Russell.Like DOM, the
Documentclass parses an XML document into a nested tree of objects. You can navigate the tree with Ruby accessors (Recipe 11.2)or with XPath queries (Recipe 11.4). You can modify the tree by creating your ownElementandTextobjects (Recipe 11.9). If evenDocumentis too heavyweight for you, you can use theXmlSimplelibrary to transform an XML file into a nested Ruby hash (Recipe 11.6).With a DOM-style API likeDocument, you have to parse the entire XML file before you can do anything. The XML document becomes a large number of Ruby objects nested under aDocumentobject, all sitting around taking up memory. With a SAXstyle parser like theStreamParserclass, you can process a document as it's parsed, creating only the objects you want. TheEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Checking XML Well-Formedness
- InhaltsvorschauCredit: Rod GaitherYou want to check that an XML document is well-formed before processing it.The best way to see whether a document is well-formed is to try to parse it. The REXML library raises an exception when it can't parse an XML document, so just try parsing it and
rescueany exception.Thevalid_xml?method below returnsnilunless it's given a valid XML document. If the document is valid, it returns a parsedDocumentobject, so you don't have to parse it again:require 'rexml/document' def valid_xml?(xml) begin REXML::Document.new(xml) rescue REXML::ParseException # Return nil if an exception is thrown end end
To be useful, an XML document must be structured correctly or "well-formed." For instance, an opening tag must either be self-closing or be paired with an appropriate closing tag.As a file and messaging format, XML is often used in situations where you don't have control over the input, so you can't assume that it will always be well-formed. Rather than just letting REXML throw an exception, you'll need to handle ill-formed XML gracefully, providing options to retry or continue on a different path.This bit of XML is not well-formed: it's missing ending tags for both thependinganddoneelements:bad_xml = %{ <tasks> <pending> <entry>Grocery Shopping</entry> <done> <entry>Dry Cleaning</entry> </tasks>} valid_xml?(bad_xml) # => nilThis bit of XML is well-formed, sovalid_xml?returns the parsedDocumentobject.good_xml = %{ <groceries> <bread>Wheat</bread> <bread>Quadrotriticale</bread> </groceries>} doc = valid_xml?(good_xml) doc.root.elements[1] # => <bread> … </>When your program is responsible for writing XML documents, you'll want to write unit tests that make sure you generate valid XML. You can use a feature of theTest:: Unitlibrary to simplify the checking. Since invalid XML makes REXML throw an exception, your unit test can use theassert_nothing_thrownmethod to make sure your XML is valid:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Extracting Data from a Document's Tree Structure
- InhaltsvorschauCredit: Rod GaitherYou want to parse an XML file into a Ruby data structure, to traverse it or extract data from it.Pass an XML document into the
REXML::Documentconstructor to load and parse the XML. ADocumentobject contains a tree of subobjects (of classElementandText) rep-resenting the tree structure of the underlying document. The methods ofDocumentandElementgive you access to the XML tree data. The most useful of these methods is#each_element.Here's some sample XML and the load process. The document describes a set of orders, each of which contains a set of items. This particular document contains a single order for two items.orders_xml = %{ <orders> <order> <number>105</number> <date>02/10/2006</date> <customer>Corner Store</customer> <items> <item upc="404100" desc="Red Roses" qty="240" /> <item upc="412002" desc="Candy Hearts" qty="160" /> </items> </order> </orders>} require 'rexml/document' orders = REXML::Document.new(orders_xml)To process each order in this document, we can useDocument#rootto get the document's root element (<orders>)and then callElement#each_elementto iterate over the children of the root element (the<order>elements). This code repeatedly callseachto move down the document tree and print the details of each order in the document:orders.root.each_element do |order| # each <order> in <orders> order.each_element do |node| # <customer>, <items>, etc. in <order> if node.has_elements? node.each_element do |child| # each <item> in <items> puts "#{child.name}: #{child.attributes['desc']}" end else # the contents of <number>, <date>, etc. puts "#{node.name}: #{node.text}" end end end # number: 105 # date: 02/10/2006 # customer: Corner Store # item: Red Roses # item: Candy HeartsParsing an XML file into aDocumentgives you a tree-like data structure that you can treat kind of like an array of arrays. Starting at the document root, you can move down the tree until you find the data that interests you. In the example above, note how the structure of the Ruby code mirrors the structure of the original document. Every call toEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Extracting Data While Parsing a Document
- InhaltsvorschauCredit: Rod GaitherYou want to process a large XML file without loading it all into memory.The method
REXML::Document.parse_streamgives you a fast and flexible way to scan a large XML file and process the parts that interest you.Consider this XML document, the output of a hypothetical program that runs auto mated tasks. We want to parse the document and find the tasks that failed (that is, returned an error code other than zero).event_xml = %{ <events> <clean system="dev" start="01:35" end="01:55" area="build" error="1" /> <backup system="prod" start="02:00" end="02:35" size="2300134" error="0" /> <backup system="dev" start="02:00" end="02:01" size="0" error="2" /> <backup system="test" start="02:00" end="02:47" size="327450" error="0" /> </events>}We can process the document as it's being parsed by writing aREXML:: StreamListenersubclass that responds to parsing events such astag_startandtag_end. Here's a subclass that listens for tags with a nonzero value for theirerrorattribute. It prints a message for every failed event it finds.require 'rexml/document' require 'rexml/streamlistener' class ErrorListener include REXML::StreamListener def tag_start(name, attrs) if attrs["error"] != nil and attrs["error"] != "0" puts %{Event "#{name}" failed for system "#{attrs["system"]}" } + %{with code #{attrs["error"]}} end end endTo actually parse the XML data, pass it along with theStreamListenerinto the methodREXML::Document.parse_stream:REXML::Document.parse_stream(event_xml, ErrorListener.new) # Event "clean" failed for system "dev" with code 1 # Event "backup" failed for system "dev" with code 2
We could find the failed events in less code by loading the XML into aDocumentand running an XPath query. That approach would work fine for this example, since the document only contains four events. It wouldn't work as well if the document were a file on disk containing a billion events. Building aDocumentmeans building an elaborate in-memory data structure representing the entire XML document. If you only care about part of a document (in this case, the failed events), it's faster and less memory-intensive to process the document as it's being parsed. Once the parser reaches the end of the document, you're done.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Navigating a Document with XPath
- InhaltsvorschauYou want to find or address sections of an XML document in a standard, programming-language–independent way.The XPath language defines a way of referring to almost any element or set of elements in an XML document, and the REXML library comes with a complete XPath implementation.
REXML::XPathprovides three class methods for locatingElementobjects within parsed documents:first, each, andmatch.Take as an example the following XML description of an aquarium. The aquarium contains some fish and a gaudy castle decoration full of algae. Due to an aquarium stocking mishap, some of the smaller fish have been eaten by larger fish, just like in those cartoon food chain diagrams. (Figure 11-1 shows the aquarium.)xml = %{ <aquarium> <fish color="blue" size="small" /> <fish color="orange" size="large"> <fish color="green" size="small"> <fish color="red" size="tiny" /> </fish> </fish> <decoration type="castle" style="gaudy"> <algae color="green" /> </decoration> </aquarium>} require 'rexml/document' doc = REXML::Document.new xml
Figure 11-1: The aquariumWe can useREXML:: Xpath.firstto get theElementobject corresponding to the first<fish>tag in the document:REXML::XPath.first(doc, '//fish') # => <fish size='small' color='blue'/>
We can usematchto get an array containing all the elements that are green:REXML::XPath.match(doc, '//[@color="green"]') # => [<fish size='small' color='green'> … </>, <algae color='green'/>]
We can useeachwith a code block to iterate over all the fish that are inside other fish:def describe(fish) "#{fish.attribute('size')} #{fish.attribute('color')} fish" end REXML:: XPath.each(doc, '//fish/fish') do |fish| puts "The #{describe(fish.parent)} has eaten the #{describe(fish)}." end # The large orange fish has eaten the small green fish. # The small green fish has eaten the tiny red fish.Every element in aDocumenthas anxpathmethod that returns the canonical XPath path to that element. This path can be considered the element's "address" within the document. In this example, a complex bit of Ruby code is replaced by a simple XPath expression:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Parsing Invalid Markup
- InhaltsvorschauYou need to extract data from a document that's supposed to be HTML or XML, but that contains some invalid markup.For a quick solution, use Rubyful Soup, written by Leonard Richardson and found in the
rubyful_soupgem. It can build a document model even out of invalid XML or HTML, and it offers an idiomatic Ruby interface for searching the document model. It's good for quick screen-scraping tasks or HTML cleanup.require 'rubygems' require 'rubyful_soup' invalid_html = 'A lot of <b class=1>tags are <i class=2>never closed.' soup = BeautifulSoup.new(invalid_html) puts soup.prettify # A lot of # <b class="1">tags are # <i class="2">never closed. # </i> # </b> soup.b.i # => <i class="2">never closed.</i> soup.i # => <i class="2">never closed.</i> soup.find(nil, :attrs=>{'class' => '2'}) # => <i class="2">never closed.</i> soup.find_all('i') # => [<i class="2">never closed.</i>] soup.b['class'] # => "1" soup.find_text(/closed/) # => "never closed."If you need better performance, do what Rubyful Soup does and write a custom parser on top of the event-based parserSGMLParser(found in thehtmltoolsgem). It works a lot like REXML'sStreamListenerinterface.Sometimes it seems like the authors of markup parsers do their coding atop an ivory tower. Most parsers simply refuse to parse bad markup, but this cuts off an enormous source of interesting data. Most of the pages on the World Wide Web are invalid HTML, so if your application uses other peoples' web pages as input, you need a forgiving parser. Invalid XML is less common but by no means rare.TheSGMLParserclass in thehtmltoolsgem uses regular expressions to parse an XMLlike data stream. When it finds an opening or closing tag, some data, or some other part of an XML-like document, it calls a hook method that you're supposed to define in a subclass.SGMLParserEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Converting an XML Document into a Hash
- InhaltsvorschauWhen you parse an XML document with
Document.new, you get a representation of the document as a complex data structure. You'd like to represent an XML document using simple, built-in Ruby data structures.Use theXmlSimplelibrary, found in thexml-simplegem. It parses an XML document into a hash.Consider an XML document like this one:xml = %{ <freezer temp="-12" scale="celcius"> <food>Phyllo dough</food> <food>Ice cream</food> <icecubetray> <cube1 /> <cube2 /> </icecubetray> </freezer>}Here's how you parse it with XMLSimple:require 'rubygems' require 'xmlsimple' doc = XmlSimple.xml_in xml
And here's what it looks like:require 'pp' pp doc # {"icecubetray"=>[{"cube2"=>[{}], "cube1"=>[{}]}], # "food"=>["Phyllo dough", "Ice cream"], # "scale"=>"celcius", # "temp"=>"-12"}XmlSimpleis a lightweight alternative to theDocumentclass. Instead of exposing a tree ofElementobjects, it exposes a nested structure of Ruby hashes and arrays. There's no performance savings (XmlSimpleactually builds aDocumentclass behind the scenes and iterates over it, so it's about half as fast asDocument), but the resulting object is easy to use.XmlSimplealso provides several tricks that can make a document more concise and navigable.The most useful trick is theKeyAttrone. Suppose you had a better-organized freezer than the one above, a freezer in which everything had its ownnameattribute:xml = %{ <freezer temp="-12" scale="celcius"> <item name="Phyllo dough" type="food" /> <item name="Ice cream" type="food" /> <item name="Ice cube tray" type="container"> <item name="Ice cube" type="food" /> <item name="Ice cube" type="food" /> </item> </freezer>}You could parse this data with just a call toXmlSimple.xml_in, but you get a more concise representation by specifing thenameattribute as aKeyAttrargument. Compare:parsed1 = XmlSimple.xml_in xml pp parsed1 # {"scale"=>"celcius", # "item"=> # [{"name"=>"Phyllo dough", "type"=>"food"}, # {"name"=>"Ice cream", "type"=>"food"}, # {"name"=>"Ice cube tray", # "type"=>"container", # "item"=> # [Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Validating an XML Document
- InhaltsvorschauCredit: Mauro CicioYou want to check whether an XML document conforms to a certain schema or DTD.Unfortunately, as of this writing there are no stable, pure Ruby libraries that do XML validation. You'll need to install a Ruby binding to a C library. The easiest one to use is the Ruby binding to the GNOME
libxml2toolkit. (There are actually two Ruby bindings tolibxml2, so don't get confused: we're referring to the one you get when you install thelibxml-rubygem.)To validate a document against a DTD, create a aDTDobject and pass it intoDocument#validate. To validate against an XML Schema, pass in aSchemaobject instead.Consider the following DTD, for a cookbook like this one:require 'rubygems' require ' libxml' dtd = XML::Dtd.new(%{<!ELEMENT rubycookbook (recipe+)> <!ELEMENT recipe (title?, problem, solution, discussion, seealso?)+> <!ELEMENT title (#PCDATA)> <!ELEMENT problem (#PCDATA)> <!ELEMENT solution (#PCDATA)> <!ELEMENT discussion (#PCDATA)> <!ELEMENT seealso (#PCDATA)>})Here's an XML document that looks like it conforms to the DTD:open('cookbook.xml', 'w') do |f| f.write %{<?xml version="1.0"?> <rubycookbook> <recipe> <title>A recipe</title> <problem>A difficult/common problem</problem> <solution>A smart solution</solution> <discussion>A deep solution</discussion> <seealso>Pointers</seealso> </recipe> </rubycookbook> } endBut does it really? We can tell for sure withDocument#validate:document = XML::Document.file('cookbook.xml') document.validate(dtd) # => trueHere's a Schema definition for the same document. We can validate the document against the schema by making it into aSchemaobject and passing that intoDocument#validate:schema = XML::Schema.from_string %{<?xml version="1.0"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="recipe" type="recipeType"/> <xsd:element name="rubycookbook" type="rubycookbookType"/> <xsd:element name="title" type="xsd:string"/> <xsd:element name="problem" type="xsd:string"/> <xsd:element name="solution" type="xsd:string"/> <xsd:element name="discussion" type="xsd:string"/> <xsd:element name="seealso" type="xsd:string"/> <xsd:complexType name="rubycookbookType"> <xsd:sequence> <xsd:element ref="recipe"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="recipeType"> <xsd:sequence> <xsd:element ref="title"/> <xsd:element ref="problem"/> <xsd:element ref="solution"/> <xsd:element ref="discussion"/> <xsd:element ref="seealso"/> </xsd:sequence> </xsd:complexType> </xsd:schema> } document.validate(schema) # => trueEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Substituting XML Entities
- InhaltsvorschauYou've parsed a document that contains internal XML entities. You want to substitute the entities in the document for their values.To perform entity substitution on a specific text element, call its
valuemethod. If it's the first text element of its parent, you can calltexton the parent instead.Here's a simple document that defines and uses two entities in a single text node. We can substitute those entities for their values without changing the document itself:require 'rexml/document' str = %{<?xml version="1.0"?> <!DOCTYPE doc [ <!ENTITY product 'Stargaze'> <!ENTITY version '2.3'> ]> <doc> &product; v&version; is the most advanced astronomy product on the market. </doc>} doc = REXML::Document.new str doc.root.children[0].value # => "\n Stargaze v2.3 is the most advanced astronomy product on the market.\n" doc.root.text # => "\n Stargaze v2.3 is the most advanced astronomy product on the market.\n" doc.root.children[0].to_s # => "\n &product; v&version; is the most advanced astronomy product on the market.\n" doc.root.write # <doc> # &product; v&version; is the most advanced astronomy program on the market. # </doc>Internal XML entities are often used to factor out data that changes a lot, like dates or version numbers. But REXML only provides a convenient way to perform substitution on a single text node. What if you want to perform substitutions throughout the entire document?When you callDocument#writeto send a document to someIOobject, it ends up callingText#to_son each text node. As seen in the Solution, this method presents a "normalized" view of the data, one where entities are displayed instead of having their values substituted in.We could write our own version ofDocument#writethat presents an "unnormalized" view of the document, one with entity values substituted in, but that would be a lot of work. We could hackText#to_sto work more likeText#value, or hackText#writeto call thevaluemethod instead ofto_s. But it's less intrusive to do the entity replacement outside of thewritemethod altogether. Here's a class that wraps anyEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Creating and Modifying XML Documents
- InhaltsvorschauYou want to modify an XML document, or create a new one from scratch.To create an XML document from scratch, just start with an empty
Documentobject.require 'rexml/document' require doc = REXML::Document.new
To add a new element to an existing document, pass its name and any attributes into its parent'sadd_elementmethod. You don't have to create theElementobjects yourself.meeting = doc.add_element 'meeting' meeting_start = Time.local(2006, 10, 31, 13) meeting.add_element('time', { 'from' => meeting_start, 'to' => meeting_start + 3600 }) doc.children[0] # => <meeting> … </> doc.children[0].children[0] # => "<time from='Tue Oct 31 13:00:00 EST 2006' # to='Tue Oct 31 14:00:00 EST 2006'/>" doc.write($stdout, 1) # <meeting> # <time from='Tue Oct 31 13:00:00 EST 2006' # to='Tue Oct 31 14:00:00 EST 2006'/> # </meeting> doc.children[0] # => <?xml … ?> doc.children[1] # => <meeting> … </>To append a text node to the contents of an element, use theadd_textmethod. This code adds an<agenda>element to the<meeting>element, and gives it two different text nodes:agenda = meeting.add_element 'agenda' doc.children[1].children[1] # => <agenda/> agenda. add_text "Nothing of importance will be decided." agenda.add_text " The same tired ideas will be rehashed yet again." doc.children[1].children[1] # => <agenda> … </> doc.write($stdout, 1) # <meeting> # <time from='Tue Oct 31 13:00:00 EST 2006' # to='Tue Oct 31 14:00:00 EST 2006'/> # <agenda> # Nothing of importance will be decided. The same tired ideas will be # rehashed yet again. # </agenda> # </meeting>
Element#text=is a nice shortcut for giving an element a single text node. You can also use to overwrite a document's initial text nodes:item1 = agenda.add_element 'item' doc.children[1].children[1].children[1] # => <item/> item1.text = 'Weekly status meetings: improving attendance' doc.children[1].children[1].children[1] # => <item> … </> doc.write($stdout, 1) # <meeting> # <time from='Tue Oct 31 13:00:00 EST 2006' # to='Tue Oct 31 14:00:00 EST 2006'/> # <agenda> # Nothing of importance will be decided. The same tired ideas will be # rehashed yet again. # <item>Weekly status meetings: improving attendance</item> # </agenda> # </meeting>
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Compressing Whitespace in an XML Document
- InhaltsvorschauWhen REXML parses a document, it respects the original whitespace of the document's text nodes. You want to make the document smaller by compressing extra whitespace.Parse the document by creating a
REXML::Documentout of it. Within theDocumentconstructor, tell the parser to compress all runs of whitespace characters:require 'rexml/document' text = %{<doc><a>Some whitespace</a> <b>Some more</b></doc>} REXML::Document.new(text, { :compress_whitespace => :all }).to_s # => "<doc><a>Some whitespace</a> <b>Some more</b></doc>"Sometimes whitespace within a document is significant, but usually (as with HTML) it can be compressed without changing the meaning of the document. The resulting document takes up less space on the disk and requires less bandwidth to transmit.Whitespace compression doesn't have to be all-or-nothing. REXML gives two ways to configure it. Instead of passing:allas a value for:compress_whitespace, you can pass in a list of tag names. Whitespace will only be compressed in those tags:REXML::Document.new(text, { :compress_whitespace => %w{a} }).to_s # => "<doc><a>Some whitespace</a> <b>Some more</b></doc>"You can also switch it around: pass in:respect_whitespaceand a list of tag names whose whitespace you don't want to be compressed. This is useful if you know that whitespace is significant within certain parts of your document.REXML::Document.new(text, { :respect_whitespace => %w{a} }).to_s # => "<doc><a>Some whitespace</a> <b>Some more</b></doc>"What about text nodes containing only whitespace? These are often inserted by XML pretty-printers, and they can usually be totally discarded without altering the meaning of a document. If you add:ignore_whitespace_nodes => :allto the parser configuration, REXML will simply decline to create text nodes that contain nothing but whitespace characters. Here's a comparison of:compress_whitespacealone, and in conjunction with:ignore_whitespace_nodes:text = %{<doc><a>Some text</a>\n <b>Some more</b>\n\n} REXML::Document.new(text, { :compress_whitespace => :all }).to_s # => "<doc><a>Some text</a>\n <b>Some more</b>\n</doc>" REXML::Document.new(text, { :compress_ whitespace => :all, :ignore_ whitespace_nodes => :all }).to_s # => "<doc><a>Some text</a><b>Some more</b></doc>"Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Guessing a Document's Encoding
- InhaltsvorschauCredit: Mauro CicioYou want to know the character encoding of a document that doesn't declare it explicitly.Use the Ruby bindings to the
libcharguesslibrary. Once it's installed, usinglibcharguessis very simple.Here's an XML document written in Italian, with no explicit encoding:doc = %{<?xml version="1.0"?> <menu tipo="specialità" giorno="venerdì"> <primo_piatto>spaghetti al ragù</primo_piatto> <bevanda>frappè</bevanda> </menu>}Let's find its encoding:require 'charguess' CharGuess::guess doc # => "windows-1252"
This is a pretty good guess: the XML is written in the ISO-8859-1 encoding, and many web browsers treat ISO-8859-1 as Windows-1252.In XML, the character-encoding indication is optional, and may be provided as an attribute of the XML declaration in the first line of the document:<xml version="1.0" encoding="utf-8"?>
If this is missing, you must guess the document encoding to process the document. You can assume the lowest common denominator for your community (usually this means assuming that everything is either UTF-8 or ISO-8859-1), or you can use a library that examines the document and uses heuristics to guess the encoding.As of the time of writing, there are no pure Ruby libraries for guessing the encoding of a document. Fortunately, there is a small Ruby wrapper around the Charguess library. This library can guess with 95% accuracy the encoding of any text whose charset is one of the following: BIG5, HZ, JIS, SJIS, EUC-JP, EUC-KR, EUC-TW, GB2312, Bulgarian, Cyrillic, Greek, Hungarian, Thai, Latin1, and UTF8.Note thatCharguessis not XML-or HTML-specific. In fact, it can guess the encoding of an arbitrary string:CharGuess::guess("\xA4\xCF") # => "EUC-JP"It's fairly easy to installlibcharguess, since the library is written in portable C++. Unfortunately, it doesn't take care to put its header files in a standard location. This makes it a little tricky to compile the Ruby bindings, which depend on thecharguess.hheader. When you runEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Converting from One Encoding to Another
- InhaltsvorschauCredit: Mauro CicioYou want to convert a document to a given charset encoding (probably UTF-8).If you don't know the document's current encoding, you can guess at it using the Charguess library described in the previous recipe. Once you know the current encoding, you can convert the document to another encoding using Ruby's standard
iconvlibrary.Here's an XML document written in Italian, with no explicit encoding:doc = %{<?xml version="1.0"?> <menu tipo="specialità" giorno="venerdì"> <primo_piatto>spaghetti al ragù</primo_piatto> <bevanda>frappè</bevanda> </menu>}Let's figure out its encoding and convert it to UTF-8:require 'iconv' require 'charguess' # not necessary if input encoding is known input_encoding = CharGuess::guess doc # => "windows-1252" output_encoding = 'utf-8' converted_doc = Iconv.new(output_encoding, input_encoding).iconv(doc) CharGuess::guess(converted_doc) # => "UTF-8"
The heart of theiconvlibrary is theIconvclass, a wrapper for the Unix 95iconv( )family of functions. These functions translate strings between various encoding systems. Sinceiconvis part of the Ruby standard library, it should be already available on your system.Iconv works well in conjunction with Charguess: even if Charguess guesses the encoding a little bit wrong (such as guessing Windows-1252 for an ISO-8859-1 document), it always makes a good enough guess thaticonvcan convert the document to another encoding.Like Charguess, the Iconv library is not XML-or HTML-specific. You can uselibcharguessandiconvtogether to convert an arbitrary string to a given encoding.- Recipe 11.11, "Guessing a Document's Encoding"
- The
iconvlibrary is documented at http://www.ruby-doc.org/stdlib/libdoc/iconv/rdoc/classes/Iconv.html; you can find pointers to The Open Group Unix library specifications
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Extracting All the URLs from an HTML Document
- InhaltsvorschauYou want to find all the URLs on a web page.Do you only want to find links (that is, URLs mentioned in the
HREFattribute of anAtag)? Do you also want to find the URLs of embedded objects like images and applets? Or do you want to find all URLs, including ones mentioned in the text of the page?The last case is the simplest. You can useURI.extractto get all the URLs found in a string, or to get only the URLs with certain schemes. Here we'll extract URLs from some HTML, whether or not they're inside A tags:require 'uri' text = %{"My homepage is at <a href="http://www.example.com/">http://www.example.com/</a>, and be sure to check out my weblog at http://www.example.com/blog/. Email me at <a href="mailto:bob@example.com">bob@example.com</a>.} URI.extract(text) # => ["http://www.example.com/", "http://www.example.com/", # "http://www.example.com/blog/.", "mailto:bob@example.com"] # Get HTTP(S) links only. URI.extract(text, ['http', 'https']) # => ["http://www.example.com/", "http://www.example.com/" # "http://www.example.com/blog/."]If you only want URLs that show up inside certain tags, you need to parse the HTML. Assuming the document is valid, you can do this with any of the parsers in therexmllibrary. Here's an efficient implementation using REXML's stream parser. It retrieves URLs found in theHREFattributes ofAtags and theSRCattributes ofIMGtags, but you can customize this behavior by passing a different map to the constructor.require 'rexml/document' require 'rexml/streamlistener' require 'set' class LinkGrabber include REXML::StreamListener attr_reader :links def initialize(interesting_tags = {'a' => %w{href}, 'img' => %w{src}}.freeze) @tags = interesting_tags @links = Set.new end def tag_start(name, attrs) @tags[name].each do |uri_attr| @links << attrs[uri_attr] if attrs[uri_attr] end if @tags[name] end def parse(text) REXML::Document.parse_stream(text, self) end end grabber = LinkGrabber.new grabber.parse(text) grabber.links # => #<Set: {"http://www.example.com/", "mailto:bob@example.com"}>Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Transforming Plain Text to HTML
- InhaltsvorschauYou want to add simple markup to plaintext and turn it into HTML.Use RedCloth, written by "why the lucky stiff" and available as the
RedClothgem. It extends Ruby's string class to support Textile markup: itsto_htmlmethod converts Textile markup to HTML.Here's a simple document:require 'rubygems' require 'redcloth' text = RedCloth.new %{Who would ever write "HTML":http://www.w3.org/MarkUp/ markup directly? I mean, _who has the time_? Nobody, that's who: |_. Person |_. Has the time? | | Jake | No | | Alice | No | | Rodney | Not since the accident | } puts text.to_html # <p>Who would ever write # <a href="http://www.w3.org/MarkUp/"><span class="caps">HTML</span></a> # markup directly?</p> # # <p>I mean, <em>who has the time</em>? Nobody, that’s who:</p> # # <table> # <tr> # <th>Person </th> # <th>Has the time? </th> # </tr> # …The Textile version is more readable and easier to edit.The Textile markup language lets you produce HTML without having to write any HTML. You just add punctuation to plain text, to convey what markup you'd like. Paragraph breaks are represented by blank lines, italics by underscores, tables by ASCII-art drawings of tables.A text-based markup that converts to HTML is very useful in weblog and wiki software, where the markup will be edited many times. It's also useful for hiding the complexity of HTML from new computer users. We wrote this entire book using a Textile-like markup, though it was converted to Docbook instead of HTML.- The RedCloth homepage (http://www.whytheluckystiff.net/ruby/redcloth/)
- A comprehensive Textile reference (http://hobix.com/textile/)and a quick reference (http://hobix.com/textile/quick.html)
- You can experiment with Textile markup at the language's homepage (http://www.textism.com/tools/textile/)
- Markdown (http://daringfireball.net/projects/markdown/)is another popular simple markup language for plain text; you can turn Markdown text to XHTML with the
BlueClothgem (project page: http://www.deveiate.org/projects/BlueCloth
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Converting HTML Documents from the Web into Text
- InhaltsvorschauYou want to get a text summary of a web site.The
open-urilibrary is the easiest way to grab the content of a web page; it lets you open a URL as though it were a file:require 'open-uri' example = open('http://www.example.com/') # => #<StringIO:0xb7bb601c> html = example.readAs with a file, thereadmethod returns a string. You can do a series ofsubandgsubmethods to clean the code into a more readable format.plain_text = html.sub(%r{<body.*?>(.*?)</body>}mi, '\1').gsub(/<.*?>/m, ' '). gsub(%r{(\n\s*){2}}, "\n\n")Finally, you can use the standardCGIlibrary to unescape HTML entities like<into their ASCII equivalents (<):require 'cgi' plain_text = CGI.unescapeHTML(plain_text)
The final product:puts plain_text # Example Web Page # # You have reached this web page by typing "example.com", # "example.net", # or "example.org" into your web browser. # These domain names are reserved for use in documentation and are not available # for registration. See RFC # 2606 , Section 3.
Theopen-urilibrary extends theopenmethod so that you can access the contents of web pages and FTP sites with the same interface used for local files.The simple regular expression substitutions above do nothing but remove HTML tags and clean up excess whitespace. They work well for well-formatted HTML, but the web is full of mean and ugly HTML, so you may consider taking a more involved approach. Let's define aHTMLSanitizerclass to do our dirty business.AnHTMLSanitizerwill start off with some HTML, and through a series of search-and-replace operations transform it into plain text. Different HTML tags will be handled differently. The contents of some HTML tags should simply be removed in a plaintext rendering. For example, you probably don't want to see the contents of<head>and<script>tags. Other tags affect what the rendition should look like, for instance, a<p>tag should be represented as a blank line:require 'open-uri' require 'cgi' class HTMLSanitizer attr_accessor :html @@ignore_tags = ['head', 'script', 'frameset' ] @@inline_tags = ['span', 'strong', 'i', 'u' ] @@block_tags = ['p', 'div', 'ul', 'ol' ]
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - A Simple Feed Aggregator
- InhaltsvorschauCredit: Rod GaitherXML is the basis for many specialized langages. One of the most popular is RSS, an XML format often used to store lists of articles from web pages. With a tool called an aggregator, you can collect weblog entries and articles from several web sites' RSS feeds, and read all those web sites at once without having to skip from one to the other. Here, we'll create a simple aggregator in Ruby.Before aggregating RSS feeds, let's start by reading a single one. Fortunately we have several options for parsing RSS feeds into Ruby data structures. The Ruby standard library has built-in support for the three major versions of the RSS format (0.9, 1.0, and 2.0). This example uses the standard
rsslibrary to parse an RSS 2.0 feed and print out the titles of the items in the feed:require 'rss/2.0' require 'open-uri' url = 'http://www.oreillynet.com/pub/feed/1?format=rss2' feed = RSS::Parser.parse(open(url).read, false) puts "=== Channel: #{feed.channel.title} ===" feed.items.each do |item| puts item.title puts " (#{item.link})" puts puts item.description end # === Channel: O'Reilly Network Articles === # How to Make Your Sound Sing with Vocoders # (http://digitalmedia.oreilly.com/2006/03/29/vocoder-tutorial-and-tips.html) # …Unfortunately, the standardrsslibrary is a little out of date. There's a newer syndication format called Atom, which serves the same purpose as RSS, and thersslibrary doesn't support it. Any serious aggregator must support all the major syndication formats.So instead, our aggregator will use Lucas Carlson's Simple RSS library, available as thesimple-rssgem. This library supports the three main versions of RSS, plus Atom, and it does so in a relaxed way so that ill-formed feeds have a better chance of being read.Here's the example above, rewritten to use Simple RSS. As you can see, only the name of the class is different:require 'rubygems' require ' simple-rss' url = 'http://www.oreillynet.com/pub/feed/1?format=rss2' feed = RSS::Parser.parse(open(url), false) puts "=== Channel: #{feed.channel.title} ===" feed.items.each do |item| puts item.title puts " (#{item.link})" puts puts item.description endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 12: Graphics and Other File Formats
- InhaltsvorschauHundreds of standards exist for storing structured data in text or binary files. Some of these are so popular that we've devoted entire chapters to them (Chapters 11 and 13). Some are so simple that you can process them with the ad hoc techniques listed in Chapters 1 and 6. This chapter is a grab bag that tries to cover the rest of the field.We focus especially on graphics, probably the most common binary files. Ruby lacks a mature image manipulation library like the Python Imaging Library, but it does have bindings to ImageMagick and GraphicsMagick, popular and stable C libraries. The RMagick library provides the same interface against ImageMagick and GraphicsMagick, so it doesn't matter which one you use.You can get RMagick by installing the
RMagickorRmagick-win32gem. Unfortunately, the C libraries themselves are difficult to install: they have a lot of dependencies, especially if you want to process image formats like GIF and PostScript. The installation FAQ can help (http://rmagick.rubyforge.org/install-faq.html). On Debian GNU/Linux, you can just install theimagemagickpackage and then theRMagickgem.The first recipes in this chapter show how to use RMagick to manipulate and convert images (on the question of finding images, see Recipe 16.2). Then it gets miscellaneous: we cover encryption, archive formats, Excel spreadsheets, and music files. We don't have space to cover every popular file format, but this chapter should give you an idea of what's out there. If this chapter lacks a recipe on your file format of choice, you may be able to find a Ruby library for it on the RAA, or by doing a web search forruby[file format name].Credit: Antonio CangianoGiven an image, you want to create a smaller image to serve as a thumbnail.UseRMagick, available from thermagickorrmagick-win32gems. ItsMagickmodule gives you a simple but versatile way to manipulate images. The classMagick::Imagelets you resize images four different ways: withresize, scale, sample, orthumbnailEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Thumbnailing Images
- InhaltsvorschauCredit: Antonio CangianoGiven an image, you want to create a smaller image to serve as a thumbnail.Use
RMagick, available from thermagickorrmagick-win32gems. ItsMagickmodule gives you a simple but versatile way to manipulate images. The classMagick::Imagelets you resize images four different ways: withresize, scale, sample, orthumbnail.All four methods accept a pair integer values, corresponding to the width and height in pixels of the thumbnail you want. Here's an example that usesresize: it takes the filemyimage.jpgand makes a thumbnail of it 100 pixels wide by 100 pixels tall:require 'rubygems' require 'RMagick' img = Magick::Image.read('myimage.jpg').first width, height = 100, 100 thumb = img.resize(width, height) thumb.write('mythumbnail.jpg')The class methodImage.read, used in the Solution, receives an image filename as an argument and returns an array ofImageobjects. You obtain the first (and, usually, only) element throughArray#first.The code given in the Solution produces a thumbnail that is 100 pixels by 100, no matter what dimensions the original image had. If the original image was a square, its proportions will be maintained. But if the initial image was a rectangle, squishing it into a 100 x 100 box will distort it.If all your thumbnails need to be the same size, you might be willing to live with this distortion. But to maintain the proportions between the longest and shortest dimensions, you should define your thumbnail's width and height in terms of the original image's aspect ratio. You can get the image's original width and height by using its accessor methods,Magick:: Image#columnsandMagick:: Image#rows.A simpler solution is to passresizea floating-point number as a scaling factor. This changes the image's size without altering the aspect ratio. Here's how to generate an image that is 15% the size of the original:scale_factor = 0.15 thumb = img.resize(scale_factor) thumb.write("mythumbnail.jpg")To impose a maximum size on an image without altering its aspect ratio, useEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Adding Text to an Image
- InhaltsvorschauCredit: Antonio CangianoYou want to add some text to an image—perhaps a caption or a copyright statement.Create an RMagick
Drawobject and call itsannotatemethod, passing in your image and the text.The following code adds the copyright string '© NPS' to the bottom-right corner of thecanyon.pngimage. It also specifies the font, the text color and size, and other features of the text:require 'rubygems' require 'RMagick' img = Magick::Image.read('canyon.png').first my_text = "\251 NPS" copyright = Magick::Draw.new copyright.annotate(img, 0, 0, 3, 18, my_text) do self.font = 'Helvetica' self.pointsize = 12 self.font_weight = Magick::BoldWeight self.fill = 'white' self.gravity = Magick::SouthEastGravity end img.write(' canyoncopyrighted.png')The resulting image looks like Figure 12-1.
Figure 12-1: With a copyright message in the bottom-right cornerTheannotatemethod takes a code block that sets properties on theMagick::Drawobject, describing how the annotation should be done. You can also set the properties on theDrawobject before callingannotate. This code works the same as the code given in the Solution:require 'rubygems' require 'RMagick' img = Magick::Image.read("canyon.png").first my_ text = '\251 NPS' copyright = Magick::Draw.new copyright.font = 'Helvetica' copyright.pointsize = 12 copyright.font_weight = Magick::BoldWeight copyright.fill = 'white' copyright.gravity = Magick::SouthEastGravity copyright.annotate(img, 0, 0, 3, 18, my_text) img.write('canyoncopyrighted.png')What do these attributes do?- The
fontattribute selects the font type from among those installed on your system. You can also specify the path to a specific font that is in a nonstandard location (e.g., "/home/antonio/Arial.ttf"). pointsizeis the font size in points (the default is 12). By default, there is one pixel per point, so you can just specify the font size in pixels.font_weightaccepts aWeightType
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Converting One Image Format to Another
- InhaltsvorschauCredit: Antonio CangianoYou want to convert an image to a different format.With
RMagick, you can just read in the file and write it out with a different extension. This code converts a PNG file to JPEG format:require 'rubygems' require 'RMagick' img = Magick::Image.read('myimage.png').first img.write('myimage.jpg')As seen in the previous two recipes,Magick::Image.readreceives the PNG image and returns an array ofImageobjects, from which we select the first and only image.RMagicklets us convert the file into a JPEG by simply changing the filename's extension when we call thewritemethod.The underlying C library, ImageMagick or GraphicsMagick, has three ways of determining the format of image files:- Checking an explicitly specified format prefix: for example, "GIF:myimage.jpg" indicates that the file
myimagecontains a GIF image, even though the file extension says otherwise. - Looking inside the file for a "magic number", a set of bytes that indicates the format.
- Checking the file extension: for example, "myphoto.gif" is presumably a GIF file.
Although the format prefix takes precedence over the magic number,RMagickwon't be fooled by an incorrect prefix. Eventually it will have to parse the image file, and the format mismatch will be revealed:Magick::Image.read("JPG:myimage.png") # Magick::ImageMagickError: Not a JPEG file: starts with 0x89 0x50 `myimage.png':When you write an image to an output file, you can choose the output format by specifying a file extension or a prefix.img = Magick::Image.read("myimage.png").first img.write("myimage.jpg") # Writes a JPEG img.write("myimage.gif") # Writes a GIF img.write("JPG:myimage") # Writes a JPEG img.write("JPG:myimage.gif") # Writes a JPEGYou can also get or set the file format of an image by calling theImage#formatorImage#format=methods:img.format # => "PNG" img.format = "GIF" img.format # => "GIF"
Of course, RMagick can't read to and write from every graphical file format in existence. How can you tell whether your version of RMagick knows how to write a particular file format?Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Graphing Data
- InhaltsvorschauYou want to convert a bunch of data into a graph; usually a line chart, bar chart, or pie chart.Use the Gruff library, written by Geoffrey Grosenbach. Install the
gruffgem and build aGruffobject corresponding to the type of graph you want (for instance,Gruff::Line, Gruff::Bar,or Gruff::Pie). Add a dataset to the graph by passingdataa label and an array of data points.Here's code to create a graph that compares the running times of different sorts of algorithms:require 'rubygems' require 'gruff' g = Gruff::Line.new(600) # The graph will be 600 pixels wide. g.title = 'Algorithm running times' g.theme_37signals # The best-looking theme, in my opinion. range = (1..101) g.data('Constant', range.collect { 1 }) g.data('O(log n)', range.collect { |x| Math::log(x) / Math::log(2) }) g.data('O(n)', range.collect { |x| x }) g.data('O(n log n)', range.collect { |x| x * Math::log(x) / Math::log(2) }) g.labels = {10 => 'n=10', 50 => 'n=50', 100 => 'n=100' } g.write('algorithms.png')Figure 12-3 shows the graph it produces.
Figure 12-3: A line chartHere's code to create a pie chart (shown in Figure 12-4). Note that the numbers given for the datasets don't have to add up to 100. Gruff automatically scales the the pie chart to display the right proportions.p = Gruff::Pie.new p.theme_monochrome p.title = "Survey: the value of pi" p.data('"About three"', [3]) p.data('3.14', [8]) p.data('3.1415', [11]) p.data('22/7', [8]) p.write('pipie.png')
Figure 12-4: A pi chartMost of the time, programmers who need a graphing library need a simple graphing library: one that lets them easily produce a quick pie, line, or bar graph. Gruff works well for graphing simple datasets, but it doesn't have the functionality of a fullfledged math program.Gruff's interface for customizing the display of datasets also leaves something to be desired. Instead of letting you tweak the colors individually, it provides a number of themes that package together a background image, a text color, and a number of colors used in the graphs. Unfortunately, most of the provided themes are uglyEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Adding Graphical Context with Sparklines
- InhaltsvorschauYou want to display a small bit of statistical context—a trend or a set of percentages—in the middle of a piece of text, without breaking up the flow of the text.Install the
sparklinesgem (written by Geoffrey Grosenbach)and create a sparkline: a tiny embedded graphic that can go next to a piece of text without being too intrusive. If you're creating an HTML page, the image doesn't even need to have its own file: it can be embedded directly in the HTML.This code creates a sparkline for a company's stock price, and embeds it in HTML after the company's stock symbol:require 'rubygems' require ' sparklines' require 'base64' def embedded_sparkline %{<img src="data:image/png;base64,#{Base64.encode64(yield)}">} end # This method scales data so that the smallest item becomes 0 and the # largest becomes 100. def scale(data) min, max = data.min, data.max data.collect { |x| (x - min) / (max - min) * 100} end # Randomly generate closing prices for the past month. prices = [rand(10)] 30.times { prices << prices.last + (rand - 0.5) } # Generate HTML containing a stock graph as an embedded sparkline. sparkline = embedded_sparkline { Sparklines.plot(scale(prices)) } open('stock.html', 'w') do |f| f << "Is EvilCorp (NASDAQ:EVIL #{sparkline}) poised for a comeback?" endThis code generates HTML that renders as shown in Figure 12-5.
Figure 12-5: A stock price history sparklineSince it has no labels, the meaning of the sparkline must be determined from context. In this case, the graphic follows a stock symbol, so you can guess that it graphs the stock price. In a different context, the sparkline for EvilCorp might be the company's reported earnings over time, or the results of a poll that tracks public opinion of the company.Embedded sparklines won't show up in Internet Explorer, but if you're using Rails you can use thesparklines_generatorgem to put cross-browser sparklines in your views.Sparklines are a way of graphically conveying information that would take lots of text to explain. They were invented by interface expert Edward Tufte, who describes them as "intense, simple, word-sized graphics." As implemented in the Ruby Sparklines library, a sparkline displays a small graph that shows a set of related numbers or a single percentage.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Strongly Encrypting Data
- InhaltsvorschauYou want to encrypt some data: to keep it private, or to keep it safe when sent through an insecure medium like email.There are at least two good symmetric-key cryptography libraries for Ruby: Pelle Braendgaard's EzCrypto (available as the
ezcryptogem)and Richard Kernahan's Crypt (a third-party download).EzCrypto is a user-friendly Ruby wrapper around the OpenSSL library, which you may need to install separately. Here's how to encrypt and decrypt a string with EzCrypto:require 'rubygems' require 'ezcrypto' plaintext = '24.9195N 17.821E' ezcrypto_key = EzCrypto::Key.with_password 'My secret key', 'salt string' ezcrypto_ciphertext = ezcrypto_key.encrypt(plaintext) # => "F\262\260\273\217\tR\351\362-\021-a\336\324Qc…" ezcrypto_key.decrypt(ezcrypto_ciphertext) # => "24.9195N 17.821E"
The Crypt library gives each encryption algorithm its own class, so you need to decide which you want to use. I'll use the AES/Rijndael algorithm: all the other algorithms have the same interface.require 'crypt/rijndael' aes_key = Crypt::Rijndael.new('My secret key') aes_cyphertext = aes_key.encrypt_string(plaintext) # => "\e\003\203\030]\203\t\346…" aes_key.decrypt_string(aes_cyphertext) # => "24.9195N 17.821E"EzCrypto is available as a gem (ezcrypto), and it's fast because the actual encryption and decryption happens in the C OpenSSL libraries. Crypt is a pure Ruby implementation, so it's slower, but you don't have to worry about OpenSSL being installed.EzCrypto and Crypt both implement several symmetric key algorithms. With EzCrypto, you can also specify the algorithm to use when you create an EzCrypto key. With Crypt, you need to instantiate the appropriate algorithm's class:# EzCrypto example blowfish_key = EzCrypto::Key.with_password('My secret password', 'salt string', :algorithm=>'blowfish') # Crypt example require 'crypt/blowfish' blowfish_key = Crypt::Blowfish.new('My secret password')The Crypt classes provide some convenience methods for encrypting and decrypting files and streams. TheEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Parsing Comma-Separated Data
- InhaltsvorschauYou have a plain-text string in a comma-delimited format. You need to parse this string, either to build a data structure or to perform some operation on the data and write it back out.The built-in
csvlibrary can parse most common character-delimited formats. The FasterCSV library, available as thefastercsvgem, improves oncsv'sperformance and interface. I'll show you both, but I recommendfastercsvunless you can't use any software at all outside the standard library.CSV::Reader.parseandFasterCSV.parsework the same way: they accept a string or an open file as an argument, and yield each parsed row of the comma-delimited file as an array. Thecsvyields aRowobject that acts like an array full ofColumnobjects. FasterCSV just yields an array of strings.require 'csv' primary_colors = "red,green,blue\nred,yellow,blue" CSV::Reader.parse(primary_colors) { |row| row.each { |cell| puts cell }} # red # green # blue # red # yellow # blue require 'rubygems' require 'faster_csv' shakespeare = %{Sweet are the uses of adversity,As You Like It "We few, we happy few",Henry V "Seems, madam! nay it is; I know not ""seems.""",Hamlet} FasterCSV.parse(shakespeare) { |row| puts "'#{row[0]}' -- #{row[1]}"} # 'Sweet are the uses of adversity' -- As You Like It # 'We few, we happy few' -- Henry V # 'Seems, madam! nay it is; I know not "seems."' -- HamletComma-delimited formats are among the most basic portable file formats. Unfortunately, they're also among the least standardized. There are many different formats, and some are internally inconsistent.FasterCSV and thecsvlibrary can't parse every comma-delimited format, but they will parse common formats like the one used by Microsoft Excel, and they're your best tool for making sense of the myriad.FasterCSV andcsvboth model a comma-delimited file as a nested array of strings. Thecsvlibrary'sCSVclass usesRowobjects andColumnobjects instead of arrays and strings, but it's the same idea. The terminology is from the spreadsheet world—understand-ably, since a CSV file is a common way of portably storing spreadsheet data.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Parsing Not-Quite-Comma-Separated Data
- InhaltsvorschauYou need to parse a plain- text string or file that's in a format similar to commadelimited format, but its delimiters are some strings other than commas and newlines.When you call a
CSV::Readermethod, you can specify strings to act as a row separator (the string between eachRow) and a field separator (the string between eachColumn). You can do the same with simulated keyword arguments passed intoFasterCSV.parse. This should let you parse most formats similar to the comma-delimited format:require 'csv' pipe_separated="1|2ENDa|bEND" CSV::Reader.parse(pipe_separated, '|', 'END') { |r| r.each { |c| puts c } } # 1 # 2 # a # b require 'rubygems' require 'faster_csv' FasterCSV.parse(pipe_separated, :col_sep=>'|', :row_sep=>'END') do |r| r.each { |c| puts c } end # 1 # 2 # a # bValue-delimited formats tend to differ along three axes:- The field separator (usually a single comma)
- The row separator (usually a single newline)
- The quote character (usually a double quote)
LikeReadermethods,Writermethods accept custom values for the field and row separators.data = [[1,2,3],['A','B','C'],['do','re','mi']] open('first3.csv', 'w') do |output| CSV::Writer.generate(output, ':', '-END-') do |writer| data.each { |x| writer << x } end end open('first3.csv') { |input| input.read() } # => "1:2:3-END-A:B:C-END-do:re:mi-END-" FasterCSV.open('first3.csv', 'w', :col_sep=>':', :row_sep=>'-END-') do |output| data.each { |x| output << x } end open('first3.csv') { |input| input.read() } # => "1:2:3-END-A:B:C-END-do:re:mi-END-"It's rare that you'll need to override the quote character, and neithercsvnorfastercsvwill let you do it. Both libraries' quote characters are hardcoded to the double-quote character. If you need to parse a format that has different quote character, the simplest thing to do is subclassFasterCSVand override itsinit_parsersmethod.Change the regular expression assigned to@parsers[:csv_row], replacing all double quotes with the quote character you want. The most common alternate quote character is the single quote: to get that, you'd have anEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Generating and Parsing Excel Spreadsheets
- InhaltsvorschauYour program needs to parse data from Excel spreadsheets, or generate new Excel spreadsheets.To generate Excel files, use the
spreadsheetlibrary, available as a third-party gem (see the See Also section below for where to get it). With it you can create simple Excel spreadsheets. As of this writing,spreadsheetdoes not support formulas or large spreadsheets (seven megabytes is the limit).This code creates an Excel spreadsheet containing some random numbers with a total, and saves it to disk:require 'rubygems' require 'spreadsheet/excel' SUM_SPREADSHEET = 'sum.xls' workbook = Spreadsheet::Excel.new(SUM_SPREADSHEET) worksheet = workbook.add_worksheet('Random numbers and their sum.') sum = 0 random_numbers = (0..9).collect { rand(100) } worksheet.write_column(0, 0, random_numbers) format = workbook.add_format(:bold => true) worksheet.write(10, 0, "Sum:", format) worksheet.write(10, 1, random_numbers.inject(0) { |sum, x| sum + x }) workbook.closeTo parse an Excel file, use theparseexcellibrary, also available as a third-party download. It can parse simple data out of the Excel file format. This code parses the Excel file generated by the previous code:require ' parseexcel/parser' workbook = Spreadsheet::ParseExcel::Parser.new.parse(SUM_SPREADSHEET) worksheet = workbook.worksheet(0) sum = (0..9).inject(0) do |sum, row| sum + worksheet.cell(row, 0).value.to_i end worksheet.cell(10, 0).value # => "Sum:" worksheet.cell(10, 1).value # => 602.0 sum # => 602
Likespreadsheet, parseexceldoesn't recognize spreadsheet formulas.The comma-separated file is the lingua franca for spreadsheet data, but sometimes you must deal with real spreadsheet files. You can save other people's time by accepting their Excel spreadsheets as input, instead of insisting they convert everything to CSV for you. And nothing impresses manager types like an automatically generated spreadsheet file they can poke at.ThespreadsheetandEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Compressing and Archiving Files with Gzip and Tar
- InhaltsvorschauYou want to write compressed data to a file to save space, or uncompress the contents of a compressed file. If you're compressing data, you might want to compress multiple files into a single archive file.The most common compression format on Unix systems is gzip. Ruby's
zliblibrary lets you read to and write from gzipped I/O streams as though they were normal files. The most useful classes in this library areGzipWriterandGzipReader.Here'sGzipWriterbeing used to create a compressed file, andGzipReaderdecompressing the same file:require ' zlib' file = 'compressed.gz' Zlib::GzipWriter.open(file) do |gzip| gzip << "For my next trick, I'll be written to a compressed file." gzip.close end open(file, 'rb') { |f| f.read(10) } # => "\037\213\010\000\201\2766D\000\003" Zlib::GzipReader.open(file) { |gzip| gzip.read } # => "For my next trick, I'll be written to a compressed file."GzipWriterandGzipReaderare most commonly used to write to files on disk, but you can wrap any file-like object in the appropriate class and automatically compress everything you write to it, or decompress everything you read from it.The following code works the same way as the compression code in the Solution, but it's more flexible: theFileobject that's passed into theZlib::GzipWriterconstructor could just as easily be aSocketor other file-like object.open('compressed.gz', 'wb') do |file| gzip = Zlib::GzipWriter.new(file) gzip << "For my next trick, I'll be written to a compressed file." gzip.close endIf you need to compress or decompress a string, use theZlib::DeflateorZlib::Inflateclasses rather than constructing aStringI0object:deflated = Zlib::Deflate.deflate("I'm a compressed string.") # => "x\234\363T\317UHTH…" Zlib::Inflate.inflate(deflated) # => "I'm a compressed string."Tar files
Gzip compresses a single file. What if you want to smash multiple files together into a single archive file? The standard archive format for Unix isEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Reading and Writing ZIP Files
- InhaltsvorschauYou want to create or examine a ZIP archive from within Ruby code.Use the
rubyzipgem. Its Zip module gives you several ways of putting files into ZIP archives, and taking them out again. The simplest interface is theZip::ZipFileSystem, which duplicates most of theFileandDiroperations within the context of a ZIP file. You can use this to create ZIP files:require 'rubygems' require 'zip/zipfilesystem' Zip::ZipFile.open('zipfile.zip', Zip::ZipFile::CREATE) do |zip| zip.file.open('file1', 'w') { |f1| f1 << 'This is file 1.' } zip.dir.mkdir('subdirectory') zip.file.open('subdirectory/file2', 'w') { |f1| f1 << 'This is file 2.' } endYou can use the same interface to read a ZIP file. Here's a method that uses the equivalent ofDir#foreachto recursively print out the contents of a ZIP file:def process_zipfile(zip, path='') if zip.file.file? path puts %{#{path}: "#{zip.read(path)}"} else unless path.empty? path += '/' puts path end zip.dir.foreach(path) do |filename| process_zipfile(zip, path + filename) end end endAnd here it is running against the ZIP file I just created:Zip::ZipFile.open('zipfile.zip') do |zip| process_zipfile(zip) end # subdirectory/ # subdirectory/file2: "This is file 2." # file1: "This is file 1."ZIP, or PKZip, is the most popular compression format on Windows. As seen in the previous recipe, Unix separates the tasks of stuffing several files into a single archive (tar), and compressing the resulting file (gzip). On Windows, ZIP files perform both tasks. If you want to compress a single file, you need to put it into a ZIP file all by itself.Therubyziplibrary provides several interfaces for creating and reading ZIP files.Zip::ZipFileSystemis the easiest for most programmers: in the example above,zip.filehas about the same interface as theFileclass, andzip.diris similar to theDirclass. The analogy holds because a ZIP file actually contains a tiny filesystem inside it.If you're porting Java code, or you're already familiar with Java'sEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Reading and Writing Configuration Files
- InhaltsvorschauYou want to store your application's configuration on disk, in a format parseable by Ruby but easily editable by someone with a text editor.Put your configuration into a data structure, and write the data structure to disk as YAML. So long as you only use built-in Ruby data types (strings, numbers, arrays, hashes, and so on), the YAML file will be human-readable and -editable.
require 'yaml' configuration = { 'color' => 'blue', 'font' => 'Septimus', 'font-size' => 7 } open('text.cfg', 'w') { |f| YAML.dump(configuration, f) } open('text.cfg') { |f| puts f.read } # -- # font-size: 7 # color: blue # font: Septimus open('text.cfg') { |f| YAML.load(f) } # => {"font-size"=>7, "color"=>"blue", "font"=>"Septimus"}It's easy for a user to edit this: it's just a colon-separated, line-delimited set of key names and values. Not a problem, even for a relatively unsophisticated user.YAML is a serialization format, designed to store data structures to disk and read them back later. But there's no reason why the data structures can't be modified by other programs while they're on disk. Since simple YAML files are human-editable, they make good configuration files.A YAML file typically contains a single data structure. The most common structures for configuration data are a hash (seen in the Solution) and an array of hashes.configuration = [ { 'name' => 'Alice', 'donation' => 50 }, { 'name' => 'Bob', 'donation' => 15, 'currency' => "EUR" } ] open('donors.cfg', 'w') { |f| YAML.dump(configuration, f) } open('donors.cfg') { |f| puts f.read } # --- # - name: Alice # donation: 50 # - name: Bob # donation: 15 # currency: EURIn Recipe 5.1 we advise saving memory by using symbols as hash keys instead of strings. If your hash is going to be converted into human-editable YAML, you should always use strings. Otherwise, people editing the YAML may become confused. Compare the following two bits of YAML:puts { 'measurements' => 'metric' }.to_yaml # --- # measurements: metric puts { :measurements => :metric }.to_yaml # --- # :measurements: :metricEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Generating PDF Files
- InhaltsvorschauYou want to create a text or graphical document as a PDF, where you have complete control over the layout.Use Austin Zeigler's
PDF::Writerlibrary, available as thepdf-writergem. Its API gives you fine-grained control over the placement of text, images, and shapes.This code usesPDF::Writerto produce a simple flyer with an image and a border (Figure 12-7). It assumes you've got a graphic calledsue.pngto insert into the document:
Figure 12-7: The flyerrequire 'rubygems' require ' pdf/writer' # => false # Putting "false" on the next line suppresses a huge output dump when # you run this code in irb. pdf = PDF::Writer.new; false pdf.text("LOST\nDINOSAUR", :justification => :center, :font_size => 42, :left => 50, :right => 50) pdf.image("sue.png", :left=> 100, :justification => :center, :resize => 0.75) pdf.text(%{Three-year-old <i>Tyrannosaurus rex</i>\nSpayed\nResponds to "Sue"}, :left => 80, :font_size => 20, :justification => :left) pdf.text("(555) 010-7829", :justification => :center, :font_size => 36) pdf.rectangle(pdf.left_margin + 25, pdf.y-25, pdf.margin_width-50, pdf.margin_height-pdf.y+50).stroke; false pdf.save_as('flyer.pdf')So long as you're only callingWriter#textandWriter#image, PDF generation is easy. PDF automatically adds new text and images to the bottom of the current text, creating new pages as needed.It gets tricky when you want to do something more complex, like draw shapes. Then you need to specify the placement and dimensions in coordinates.Take as an example theWriter#rectanglecall in the Solution:pdf.rectangle(pdf.left_margin, pdf.y-25, pdf.margin_width, pdf.margin_height-pdf.y+25).stroke
The first two arguments are coordinates: the left edge of the rectangle and the bottom edge of the rectangle. The second two arguments are the width and height of the rectangle.The width is simple enough: my box starts at the left margin and its width isEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Representing Data as MIDI Music
- InhaltsvorschauYou want to represent a series of data points as a musical piece, or just create music algorithmically.Jim Menard's midilib library makes it easy to generate MIDI music files from Ruby. It's available as the
midilibgem.Here's a simple method for visualizing a list of numbers as a piano piece. The largest number in the list is mapped to the highest note on the piano keyboard (MIDI note 108), and the smallest number to the lowest note (MIDI note 21).require 'rubygems' require 'midilib' # => false class Array def to_midi(file, note_length='eighth') midi_max = 108.0 midi_min = 21.0 low, high = min, max song = MIDI::Sequence.new # Create a new track to hold the melody, running at 120 beats per minute. song.tracks << (melody = MIDI::Track.new(song)) melody.events << MIDI::Tempo.new(MIDI::Tempo.bpm_to_mpq(120)) # Tell channel zero to use the "piano" sound. melody.events << MIDI::ProgramChange.new(0, 0) # Create a series of note events that play on channel zero. each do |number| midi_note = (midi_min + ((number-midi_min) * (midi_max-low)/high)).to_i melody.events << MIDI::NoteOnEvent.new(0, midi_note, 127, 0) melody.events << MIDI::NoteOffEvent.new(0, midi_note, 127, song.note_to_delta(note_length)) end open(file, 'w') { |f| song.write(f) } end endNow you can get an audible representation of any list of numbers:((1..100).collect { |x| x ** 2 }).to_midi('squares.mid')Themidiliblibrary provides a set of classes for modeling a MIDI file: you can parse a MIDI file, modify it with Ruby code, and write it back to disk.A MIDI file is modeled by aSequenceobject, which containsTrackobjects. A track is a mainly a series ofEventobjects: for instance, each note in the piece has aNoteOnEventand aNoteOffEvent.Array#to_midiworks by transforming each number in the array into a corresponding MIDI note. A standard piano keyboard can produce notes ranging from MIDI note 21 to MIDI note 108, with middle C being at MIDI note 60.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 13: Databases and Persistence
- InhaltsvorschauWe all want to leave behind something that will outlast us, and Ruby processes are no exception. Every program you write leaves some record of its activity, even if it's just data written to standard output. Most larger programs take this one step further: they store data from one run in a structured file, so that on another run they can pick up where they left off. There are a number of ways to persist data, from simple to insanely complex.Simple persistence mechanisms like YAML let you write Ruby data structures to disk and load them back later. This is great for simple programs that don't handle much data. Your program can store its entire state in a disk file, and load the file on its next invocation to pick up where it left off. If you never keep more data than can fit into memory, the simplest way to make it permanent is to store it with YAML, Marshal, or Madeleine, and reload it later (see Recipes 13.1, 13.2, and 13.3). Madeleine also lets you revisit the prior states of your data.If your dataset won't fit in memory, you need a database: a way of storing data on disk (usually in an indexed binary format) and retrieving parts of it quickly. The Berkeley database is the simplest database we cover: it operates like a hash, albeit a hash potentially much bigger than any you could keep in memory (Recipe 13.6).But when most people think of a "database" they think of a relational database: MySQL, Postgres, Oracle, SQLite, or the like. A persistence mechanism stores data as Ruby data structures, and a Berkeley DB stores data as a hash of strings. But relational databases store data in the form of structured records with typed fields.Because the tables of a relational database can have a complex structure and contain gigabytes of data, their contents are not accessed like normal Ruby data structures. Instead they're queried with SQL, a special programming language based on relational algebra. Most of the development time that goes into Ruby database libraries is spent trying to hide this fact. Several libraries hide the details of communication between a Ruby program and a SQL database; the balance of this chapter is devoted to showing how to use them.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
- Serializing Data with YAML
- InhaltsvorschauYou want to serialize a data structure and use it later. You may want to send the data structure to a file, then load it into a program written in a different programming language.The simplest way is to use the built-in
yamllibrary. When yourequire yaml, all Ruby objects sproutto_ yamlmethods that convert them to the YAML serialization format. A YAML string is human-readable, and it intuitively corresponds to the object from which it was derived:require 'yaml' 10.to_yaml # => "--- 10\n" 'ten'.to_yaml # => "--- ten\n" '10'.to_yaml # => "--- \"10\"\n"
Arrays are represented as bulleted lists:puts %w{Brush up your Shakespeare}.to_yaml # -- # - Brush # - up # - your # - ShakespeareHashes are represented as colon-separated key-value pairs:puts ({ 'star' => 'hydrogen', 'gold bar' => 'gold' }).to_yaml # -- # star: hydrogen # gold bar: goldMore complex Ruby objects are represented in terms of their classes and member variables:require 'set' puts Set.new([1, 2, 3]).to_yaml # --- !ruby/object:Set # hash: # 1: true # 2: true # 3: true
You can dump a data structure to a file withYAML.dump, and load it back withYAML.load:users = [{:name => 'Bob', :permissions => ['Read']}, {:name => 'Alice', :permissions => ['Read', 'Write']}] # Serialize open('users', 'w') { |f| YAML.dump(users, f) } # And deserialize users2 = open("users") { |f| YAML.load(f) } # => [{:permissions=>["Read"], :name=>"Bob"}, # {:permissions=>["Read", "Write"], :name=>"Alice"}]YAML implementations are available for Perl, Python, Java, PHP, JavaScript, and OCaml, so if you stick to the "standard" data types (strings, arrays, and so on), the serialized file will be portable across programming languages.If you've ever used Python'spicklemodule or serialized a Java object, you know how convenient it is to be able to dump an object to disk and load it back later. You don't have to define a custom data format or write an XML generator: you just shove the object into a file or a database, and read it back later. The only downside is that the serialized file is usually a binary mess that can only be understood by the serialization library.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Serializing Data with Marshal
- InhaltsvorschauYou want to serialize a data structure to disk faster than YAML can do it. You don't care about the readability of the serialized data structure, or portability to other programming languages.Use the
Marshalmodule, built into Ruby. It works more or less likeYAML, but it's much faster. TheMarshal.dumpmethod transforms a data structure into a binary string, which you can write to a file and reconstitute later withMarshal.load.Marshal.dump(10) # => "\004\010i\017" Marshal.dump('ten') # => "\004\010\"\010ten" Marshal.dump('10') # => "\004\010\"\a10" Marshal.load(Marshal.dump(%w{Brush up your Shakespeare})) # => ["Brush", "up", "your", "Shakespeare"] require 'set' Marshal.load(Marshal.dump(Set.new([1, 2, 3]))) # => #<Set: {1, 2, 3}>Marshalis what most programmers coming from other languages expect from a serializer. It's fast (much faster thanyaml), and it produces unreadable blobs of binary data. It can serialize almost anything thatyamlcan (see Recipe 13.1 for examples), and it can also handle a few cases thatyamlcan't. For instance, you can useMarshalto serialize a reference to a class:Marshal.dump(Set) # =>"\004\010c\010Set"
Note that the serialized version ofSetis little more than a reference to the class. Like YAML, Marshal depends on the presence of the original classes, and you can't deserialize a reference to a class you don't have. With YAML, you'll get an unresolvedYAML::Object; with Marshal, you get anArgumentError:#!/usr/bin/ruby -w Marshal.load("\004\010c\010Set") # ArgumentError: undefined class/module SetLike YAML, Marshal only serializes data structures. It can't serialize Ruby code (likeProcobjects), or resources allocated by other processes (like filehandles or database connections). However, the two libraries differ in their error handling. YAML tends to serialize as much as it can: it can serialize aFileobject, but when you deserialize it, you get an object that doesn't point to any actual file. Marshal just gives you an error when you try to serialize a file:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Persisting Objects with Madeleine
- InhaltsvorschauYou want to store objects in RAM and persist them between independent executions of the program. This will let your program recall its state indefinitely and access it very quickly.Use the Madeleine library available as the
madeleinegem. It transparently persists any Ruby object that can be serialized withMarshal. Unlike a conventional database persistence layer, Madeleine keeps all of its objects in RAM at all times.To use Madeleine, you have to decide which objects in your system need to be serialized, and which ones you might have saved to a database traditionally. Here's a simple Madeleine-backed program for conducting yes/no polls, in which agreement adds one to a total and disagreement subtracts one:#!/usr/bin/ruby -w # poll.rb require 'rubygems' require 'madeleine' class Poll attr_accessor :name attr_reader :total def initialize(name) @name = name @total = 0 end def agree @total += 1 end def disagree @total -= 1 end end
So far there's been no Madeleine code, just a normal class with instance variables and accessors. But how will we store the state of the poll between invocations of the polling program? Since instances of thePollclass can be serialized withMarshall, we can wrap aPollobject in aMadeleineSnapshot, and keep it in a file:poll = SnapshotMadeleine.new('poll_data') do Poll.new('Is Ruby great?') endThesystemaccessor retrieves the object wrapped byMadeleineSnapshot:if ARGV[0] == 'agree' poll.system.agree elsif ARGV[0] == 'disagree' poll.system.disagree end puts "Name: #{poll.system.name}" puts "Total: #{poll.system.total}"You can save the current state of the object withtake_snapshot:poll.take_snapshot
Here are a few sample runs of thepoll.rbprogram:$ ruby poll.rb agree Name: Is Ruby great? Total: 1 $ ruby poll.rb agree Name: Is Ruby great? Total: 2 $ ruby poll.rb disagree Name: Is Ruby great? Total: 1
Recall this piece of code:poll = SnapshotMadeleine.new('poll_data') do Poll.new('Is Ruby great?') endThe first time that code is run, Madeleine creates a directory calledEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Indexing Unstructured Text with SimpleSearch
- InhaltsvorschauYou want to index a number of texts and do quick keyword searches on them.Use the SimpleSearch library, available in the
SimpleSearchgem.Here's how to create and save an index:require 'rubygems' require 'search/simple' contents = Search::Simple::Contents.new contents << Search::Simple::Content. new('In the beginning God created the heavens…', 'Genesis.txt', Time.now) contents << Search::Simple::Content.new('Call me Ishmael…', 'MobyDick.txt', Time.now) contents << Search::Simple::Content.new('Marley was dead to begin with…', 'AChristmasCarol.txt', Time.now) searcher = Search::Simple::Searcher.load(contents, 'index_file')Here's how to load and search an existing index:require 'rubygems' require 'search/simple' searcher = nil open('index_file') do |f| searcher = Search::Simple::Searcher.new(Marshal.load(f), Marshal.load(f), 'index_file') end searcher.find_words(['begin']).results.collect { |result| result.name } # => ["AChristmasCarol.txt", "Genesis.txt"]SimpleSearch is a library that makes it easy to do fast keyword searching on unstructured text documents. The index itself is represented by aSearcher object, and each document you feed it is aContentobject.To create an index, you must first construct a number ofContentobjects and aContentsobject to contain them. AContentobject contains a piece of text, a unique identifier for that text (often a filename, though it could also be a database ID or a URL), and the time at which the text was last modified.Searcher.loadtransforms aContentsobject into a searchable index that gets serialized to disk with Marshal.The indexer analyzes the text you gives it, removes stop words (like "a"), truncates words to their roots (so "beginning" becomes "begin"), and puts every word of the text into binary data structures. Given a set of words to find and a set of words to exclude, SimpleSearch uses these structures to quickly find a set of documents.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Indexing Structured Text with Ferret
- InhaltsvorschauYou want to perform searches on structured text. For instance, you might want to search just the headline of a news story, or just the body.The Ferret library can tokenize and search structured data. It's a pure Ruby port of Java's Lucene library, and it's available as the
ferretgem.Here's how to create and populate an index with Ferret. I'll create a searchable index of useful Ruby packages, stored as a set of binary files in theruby_packages/ directory.require 'rubygems' require 'ferret' PACKAGE_INDEX_DIR = 'ruby_packages/' Dir.mkdir(PACKAGE_INDEX_DIR) unless File.directory? PACKAGE_INDEX_DIR index = Ferret::Index::Index.new(:path => PACKAGE_INDEX_DIR, :default_search_field => 'name|description') index << { :name => 'SimpleSearch', :description => 'A simple indexing library.', :supports_structured_data => false, :complexity => 2 } index << { :name => ' Ferret', :description => 'A Ruby port of the Lucene library. More powerful than SimpleSearch', :supports_structured_data => true, :complexity => 5 }By default, queries against this index will search the "name" and "description" fields, but you can search against any field:index.search_each('library') do |doc_id, score| puts index.doc(doc_id).field('name').data end # SimpleSearch # Ferret index.search_each('description:powerful AND supports_structured_data:true') do |doc_id, score| puts index.doc(doc_id).field("name").data end # Ferret index.search_each("complexity:<5") do |doc_id, score| puts index.doc(doc_id).field("name").data end # SimpleSearchWhen should you use Ferret instead of SimpleText? SimpleText is good for unstructured data like plain text. Ferret excels at searching structured data, the kind you find in databases.Relational databases are good at finding exact field matches, but not very good at locating keywords within large strings. Ferret works best when you need full text search but you want to keep some of the document structure. I've also had great success using Ferret to bring together data from disparate sources (some in databases, some not) into one structured, searchable index.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using Berkeley DB Databases
- InhaltsvorschauYou want a simple, fast database that doesn't need a server to run.Ruby's standard
dbmlibrary lets you store a database in a set of standalone binary files. It's not a SQL database: it's more like a fast disk-based hash that only stores strings.require 'dbm' DBM.new('random_thoughts') do |db| db['tape measure'] = "What if there was a tape measure you could use as a yo-yo?" db[23] = "Fnord." end DBM.open('random_thoughts') do |db| puts db['tape measure'] puts db['23'] end # What if there was a tape measure you could use as a yo-yo? # Fnord. DBM.open('random_thoughts') { |db| db[23] } # TypeError: can't convert Fixnum into String Dir['random_thoughts.*'] # => ["random_thoughts.pag", "random_thoughts.dir"]The venerable Berkeley DB format lets you store enormous associative datasets on disk and quickly access them by key. It dates from before programming languages had built-in hash structures, so it's not as useful as it used to be. In fact, if your hash is small enough to fit in memory, it's faster to simply use a Ruby hash that you serialize to disk withMarshal.If you do need to use aDBMobject, you can treat it almost exactly like a Ruby hash: it supports most of the same methods.There are many, many implementations of the Berkeley DB, and the file formats differ widely between versions, so DBM files are not very portable. If you're creating your own databases, you should use the genericdbmlibrary. It provides a uniform interface to all the DBM implementations, using the best library you have installed on your computer.Ruby also providesgdbmandsdbmlibraries, interfaces to specific database formats, but you should only need these if you're trying to load a Berkeley DB file produced by some other program.There's also the SleepyCat library, a more ambitious implementation of the Berkeley DB that implements features of traditional databases like transactions and locking. Its Ruby bindings are available as a third-party download. It's still much closer to a disk-based data structure than to a relational database, and the basic interface is similar to that ofEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Controlling MySQL on Unix
- InhaltsvorschauThe standard Ruby database interfaces assume you're connecting to a preexisting database, and that you already have access to this database. You want to create and administer MySQL databases from within Ruby.Sam Ruby came up with an elegant solution to this problem. The
mysqlmethod defined below opens up a pipe to a MySQL client program and sends SQL input to it:def mysql(opts, stream) IO.popen("mysql #{opts}", 'w') { |io| io.puts stream } endYou can use this technique to create, delete, and administer MySQL databases:mysql '-u root -p[password]', <<-end drop database if exists website_db; create database website_db; grant all on website_db.* to #{`id -un`.strip}@localhost; endThis solution looks so elegant because of the<<-enddeclaration, which allows you to end the string the same way you end a code block.One shortcoming of this solution is that theIO.popencall opens up a one-way communication with the MySQL client. This makes it difficult to call SQL commands and get the results back. If that's what you need, you can useIO.popeninteractively; see Recipe 23.1.- Recipe 23.1, "Scripting an External Program"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Finding the Number of Rows Returned by a Query
- InhaltsvorschauWriting a
DBIprogram, you want an efficient way to see how many rows were returned by a query.Adocommand returns the number of rows affected by the command, so that one's easy. To demonstrate, I'll create a database table that keeps track of my prized collection of lowercase letters:require 'cookbook_dbconnect' with_db do |c| c.do %{drop table if exists letters} c.do %{create table letters(id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, letter CHAR(1) NOT NULL)} letter_sql = ('a'..'z').collect.join('"),("') c.do %{insert into letters(letter) values ("#{letter_sql}")} end # => 26When you execute a query, you get back aStatementHandleobject representing the request. If you're using a MySQL database, you can callrowson this object to get the number of rows in the result set:vowel_query = %{select id from letters where letter in ("a","e","i","o","u")} with_db do |c| h = c.execute vowel_query "My collection contains #{h.rows} vowels." end # => "My collection contains 5 vowels."If you're not using MySQL, things are a bit trickier. The simplest thing to do is simply retrieve all the rows as an array, then use the array's size as the number of rows:with_db do |c| vowels = c.select_all(vowel_query) "My collection still contains #{vowels.size} vowels." end # => "My collection still contains 5 vowels."But this can be disastrously inefficient; see below for details.When you select some items out of a Ruby array, say withArray#grep, Ruby gives you the results in a brand new array. Once the array has been created, there's no cost to checking its size by callingArray#size.A database query acts differently. Your query might have matched millions of rows, and each result might contain kilobytes of data. This is why normally you iterate over a result set instead of usingselect_allto get it as an array. Getting the whole result set at once might use a huge amount of memory, which is why usingselect_allcan be disastrous.You've got two other options. If you're going to be iterating over the entire dataset anyway,Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Talking Directly to a MySQL Database
- InhaltsvorschauYou want to send SQL queries and commands directly to a MySQL database.Do you really need to do this? Almost all the time, it's better to use the generic DBI library. The biggest exception is when you're writing a a Rails application, and you need to run a SQL command that you can't express with ActiveRecord.If you really want to communicate directly with MySQL, use the Ruby bindings to the MySQL client library (found in the
mysqlgem). It provides an interface that's pretty similar to DBI's.Here's a MySQL-specific version of the methodwith_db, defined in this chapter's introduction. It returns aMysqlobject, which you can use to run queries or get server information.require 'rubygems' require 'mysql' def with_db dbh = Mysql.real_connect('localhost', 'cookbook_user', 'password', 'cookbook') begin yield dbh ensure dbh.close end endTheMysql#querymethod runs any SQL statement, whether it's a SELECT query or something else. When it runs a query, the return value is a result-set object (aMysqlRes); otherwise, it'snil. Here it is running some SQL commands:with_db do |db| db.query('drop table if exists secrets') db.query('create table secrets( id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, secret LONGTEXT )') db.query(%{insert into secrets(secret) values ("Oh, MySQL, you're the only one who really understands me.")}) endAnd here's a query:with_db do |db| res = db.query('select * from secrets') res.each { |row| puts "#{row[0]}: #{row[1]}" } res.free end # 1: Oh, MySQL, you're the only one who really understands me.Like the database connection itself, the result set you get fromquerywants to be closed when you're done with it. This calls for yet another instance of the pattern seen inwith_db, in which setup and cleanup are delegated to a method that takes a code block. Here's some code that altersqueryto take a code block:class Mysql alias :query_no_block :query def query(sql) res = query_no_block(sql) return res unless block_given? begin yield res ensure res.free if res end end end
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Talking Directly to a PostgreSQL Database
- InhaltsvorschauYou want to send SQL queries and commands directly to a PostgreSQL database.As with the MySQL recipe preceding this one, ask: do you really need to do this? The generic DBI library usually works just fine. As before, the main exception is when you need to make low-level SQL calls from within a Rails application.There are two APIs for communicating with a PostgreSQL database, and both are available as gems. The
postgresgem provides a Ruby binding to the C client library, and thepostgres-prgem provides a pure Ruby interface.Here's a Postgres-specific version of the methodwith_db, defined in the chapter intro. It returns aPGconnobject, which you can use to run queries or get server information. This code assumes you're accessing the database through TCP/IP on port 5432 of your local machine.require 'rubygems' require 'postgres' def with_db db = PGconn.connect('localhost', 5432, '', '', 'cookbook', 'cookbook_user', 'password') begin yield db ensure db.close end endThePGconn#execmethod runs any SQL statement, whether it's a SELECT query or something else. When it runs a query, the return value is a result-set object (aPGresult); otherwise, it'snil. Here it is running some SQL commands:with_db do |db| begin db.exec('drop table secrets') rescue PGError # Unlike MySQL, Postgres does not have a "drop table unless exists" # command. We can simulate it by issuing a "drop table" command and # ignoring any error due to the table not existing in the first place. # This is essentialy what MySQL's "drop table unless exists" does. end db.exec('create table secrets( id SERIAL PRIMARY KEY, secret TEXT )') db.exec(%{insert into secrets(secret) values ('Oh, Postgres, you\\'re the only one who really understands me.')}) endHere's a query:with_db do |db| res = db.query('select * from secrets') res.each { |row| puts "#{row[0]}: #{row[1]}" } end # 1: Oh, Postgres, you're the only one who really understands me.Note the slight differences between the Postgres implementation of SQL and the MySQL implementation. The "drop table if exists" syntax is MySQL-specific. Postgres names the data types differently, and expects string values to be single-quoted.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using Object Relational Mapping with ActiveRecord
- InhaltsvorschauYou want to store data in a database without having to use SQL to access it.Use the ActiveRecord library, available as the
activerecordgem. It automatically defines Ruby classes that access the contents of database tables.As an example, let's create two tables in the MySQL databasecookbook(see the chapter introduction for more on creating the database itself). Theblog_poststable, defined below in SQL, models a simple weblog containing a number of posts. Each blog post can have a number of comments, so we also define acommentstable.use cookbook; DROP TABLE IF EXISTS blog_posts; CREATE TABLE blog_posts ( id INT(11) NOT NULL AUTO_INCREMENT, title VARCHAR(200), content TEXT, PRIMARY KEY (id) ) ENGINE=InnoDB; DROP TABLE IF EXISTS comments; CREATE TABLE comments ( id INT(11) NOT NULL AUTO_INCREMENT, blog_post_id INT(11), author VARCHAR(200), content TEXT, PRIMARY KEY (id) ) ENGINE=InnoDB;
Here are two Ruby classes to represent those tables, and the relationship between them:require 'cookbook_dbconnect' activerecord_connect # See chapter introduction class BlogPost < ActiveRecord::Base has_many :comments end class Comment < ActiveRecord::Base belongs_to :blog_post end
Now you can create entries in the tables without writing any SQL:post = BlogPost.create(:title => 'First post', :content => "Here are some pictures of our iguana.") comment = Comment.create(:blog_post => post, :author => 'Alice', :content => "That's one cute iguana!") post.comments.create(:author => 'Bob', :content => 'Thank you, Alice!')
You can also query the tables, relate blog posts to their comments, and relate comments back to their blog posts:blog_post = BlogPost.find(:first) puts %{#{blog_post.comments.size} comments for "#{blog_post.title}"} # 2 comments for "First post" blog_post.comments.each do |comment| puts "Comment author: #{comment.author}" puts "Comment: #{comment.content}" end # Comment author: Alice # Comment: That's one cute iguana! # Comment author: Bob # Comment: Thank you, Alice! first_comment = Comment.find(:first) puts %{The first comment was made on "#{first_comment.blog_post.title}"} # The first comment was made on "First post"Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using Object Relational Mapping with Og
- InhaltsvorschauCredit: Mauro CicioYou want to store data in a database, without having to use SQL to create or access the database.Use the Og (ObjectGraph) library, available as the
oggem. Where ActiveRecord has a database-centric approach to object-relational mapping, Og is Ruby-centric. With ActiveRecord, you define the database schema ahead of time and have the library figure out what the Ruby objects should look like. With Og, you define the Ruby objects and let the library take care of creating the database schema.The only restriction Og imposes on your class definitions is that you must use special versions of the decorator methods for adding attribute accessors. For instance, instead of callingattributeto define accessor methods, you callproperty.Here we define a basic schema for a weblog program, like that defined in Recipe 13.11:require 'cookbook_dbconnect' require 'og' class BlogPost property :title, :content, String end class Comment property :author, :content, String belongs_to : og_post, BlogPost end # Now that Comment's been defined, add a reference to it in BlogPost. class BlogPost has_many :comments, Comment end
After defining the schema, we call theog_connectmethod defined in the chapter introduction. Og automatically creates any necessary database tables:og_connect # Og uses the Mysql store. # Created table 'ogcomment'. # Created table 'ogblogpost'.
Now we can create a blog post and some comments:post = BlogPost.new post.title = "First post" post.content = "Here are some pictures of our iguana." post.save! [["Alice", "That's one cute iguana!"], ["Bob", "Thank you, Alice!"]].each do |author, content| comment = Comment.new comment.blog_post = post comment.author = author comment.content = content comment.save! end
As with ActiveRecord, we can query the tables, relate blog posts to their comments, and relate comments back to their blog posts:post = BlogPost.first puts %{#{post.comments.size} comments for "#{post.title}"} # 2 comments for "First post" post.comments.each do |comment| puts "Comment author: #{comment.author}" puts "Comment: #{comment.content}" end # Comment author: Alice # Comment: That's one cute iguana! # Comment author: Bob # Comment: Thank you, Alice! puts %{The first comment was made on "#{Comment.first.blog_post.title}"} # The first comment was made on "First post"Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Building Queries Programmatically
- InhaltsvorschauYou have to write fragments of SQL to pass parameters into an ActiveRecord query. You'd like to dispense with SQL altogether, and represent the query paramaters as a Ruby data structure.Here's a simple solution. The method
ActiveRecord::Base.find_by_mapdefined below picks up wherefindleaves off. Normally a query is represented by a SQL fragment, passed in as the:conditionsargument. Here, the:conditionsargument contains a mapping of database field names to the desired values:require 'cookbook_dbconnect' class ActiveRecord::Base def self.find_by_map(id, args={}.freeze) sql = [] values = [] args[:conditions].each do |field, value| sql << "#{field} = ?" values << value end if args[:conditions] args[:conditions] = [sql.join(' AND '), values] find(id, args) end endHere'sfind_by_mapin action, using theBlogPostclass first seen in Recipe 13.11:activerecord_connect class BlogPost < ActiveRecord::Base end BlogPost.create(:title => 'Game Review: Foosball Carnage', :content => 'Four stars!') BlogPost.create(:title => 'Movie Review: Foosball Carnage: The Movie', :content => 'Zero stars!') BlogPost.find_by_map(:first, :conditions => {:title => 'Game Review: Foosball Carnage' } ).content # => "Four stars!"ActiveRecord saves you from having to write a lot of SQL, but you still have to write out the equivalent of a SQL WHERE clause every time you callActiveRecord::Base#find. Thefind_by_mapmethod lets you define those queries as Ruby hashes.Butfind_by_maponly lets you run one type of query: the kind where you're restricting fields of the database to specific values. What if you want to do a query that matches a field with the LIKE construct, or combine multiple clauses into a single query with AND or OR?A hash can only represent a very simple SQL query, but theCriteriaobject, below, can represent almost any WHERE clause. The implementation is more complex but the idea is the same. We define a data structure that can represent the WHERE clause of a SQL query, and a way of converting the data structure into a real WHERE clause.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Validating Data with ActiveRecord
- InhaltsvorschauYou want to prevent bad data from getting into your ActiveRecord data objects, whether the source of the data is clueless users or buggy code.The simplest way is to use the methods defined by the
ActiveRecord::Validationsmodule. Each of these methods (validates_length_of, validates_presence_of, and so on) performs one kind of validation. You can use them to declare restrictions on the data in your object's fields.Let's add some validation code to theCommentclass for the weblog application first seen in Recipe 13.11. Recall that aCommentobject has two main fields: the name of the author, and the text of the comment. We'll reject any comment that leaves either field blank. We'll also reject comments that are too long, and comments whose body contains any string from a customizable list of profane words.require 'cookbook_dbconnect' activerecord_connect class Comment < ActiveRecord::Base @@profanity = %w{trot krip} @@no_profanity_re = Regexp.new('^(?!.*(' + @@profanity.join('|') + '))') validates_presence_of %w{author} validates_length_of :content, :in => 1..200 validates_format_of :content, :with => @@no_profanity_re, :message => 'contains profanity' endCommentobjects that don't fit these criteria won't be saved to the database.comment = Comment.create comment.errors.on 'author' # => "can't be blank" comment.errors['content'] # => "is too short (minimum is 1 characters)" comment.save # => false comment = Comment.create(:content => 'x' * 1000) comment.errors['content'] # => "is too long (maximum is 200 characters)" comment = Comment.create(:author => 'Alice', :content => "About what I'd expect from a trotting krip such as yourself!") comment.errors.count # => 1 comment.errors.each_full { |msg| puts msg } # Content contains profanity comment = Comment.create(:author => 'Alice', :content => 'I disagree!') comment.save # => trueEvery ActiveRecord record has an associatedEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Preventing SQL Injection Attacks
- InhaltsvorschauYou want to harden your code against SQL injection attacks, whether in DBI or ActiveRecord code.With both ActiveRecord and DBI applications, you should create your SQL with question marks where variable interpolations should go. Pass in the variables along with the SQL to
DatabaseHandle#execute, and the database will make sure the values are properly quoted.Let's work against a simple database table tracking people's names:use cookbook; DROP TABLE IF EXISTS names; CREATE TABLE names ( first VARCHAR(200), last VARCHAR(200) ) ENGINE=InnoDB; INSERT INTO names values ('Leonard', 'Richardson'), ('Lucas', 'Carlson'), ('Michael', 'Loukides');Here's a simple script that searches against that table. It's been hardened against SQL injection attacks with three techniques:#!/usr/bin/ruby # no_sql_injection.rb require 'cookbook_dbconnect' activerecord_connect class Name < ActiveRecord::Base; end print 'Enter a last name to search for: ' search_for = readline.chomp # Technique 1: use ActiveRecord question marks conditions = ["last = ?", search_for] Name.find(:all, :conditions => conditions).each do |r| puts %{Matched "#{r.first} #{r.last} with ActiveRecord question marks"} end # Technique 2: use ActiveRecord named variables conditions = ["last = :last", {:last => search_for}] Name.find(:all, :conditions => conditions).each do |r| puts %{Matched "#{r.first} #{r.last}" with ActiveRecord named variables} end # Technique 3: use DBI question marks with_db do |db| sql = 'SELECT first, last FROM names WHERE last = ?' db.execute(sql, [search_for]).fetch_hash do |r| puts %{Matched "#{r['first']} #{r['last']}" with DBI question marks} end end puts "Done"Here's how this script looks in use:$ ruby no_sql_injection.rb Enter a last name to search for: Richardson Matched "Leonard Richardson" with ActiveRecord question marks Matched "Leonard Richardson" with ActiveRecord named variables Matched "Leonard Richardson" with DBI question marks Done # See the Discussion if you're not sure how this attack is supposed to work. $ ruby no_sql_injection.rb Enter a last name to search for: " or 1=1 Done
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using Transactions in ActiveRecord
- InhaltsvorschauYou want to perform database operations as a group: if one of the operations fails, it should be as though none of them had ever happened.Include
active_record/ transactions, and you'll give each ActiveRecord class atransactionmethod. This method starts a database transaction, runs a code block, then commits the transaction. If the code block throws an exception, the database transaction is rolled back.Here's some simple initialization code to give ActiveRecord access to the database tables for the weblog system first seen in Recipe 13.11:require 'cookbook_dbconnect' activerecord_connect # See chapter introduction class User < ActiveRecord::Base has_and_belongs_to_many :blog_posts end class BlogPost < ActiveRecord::Base has_and_belongs_to_many :authors, :class_name => 'User' end
Thecreate_from_new_authormethod below creates a new entry in theuserstable, then associates it with a new entry in theblog_poststable. But there's a 50% chance that an exception will be thrown right after the new author is created. If that happens, the author creation is rolled back: in effect, it never happened.require 'active_record/ transactions' class BlogPost def BlogPost.create_from_new_author(author_name, title, content) transaction do author = User.create(:name => author_name) raise 'Random failure!' if rand(2) == 0 create(:authors => [author], :title => title, :content => content) end end end
Since the whole operation is enclosed within atransaction block, an exception won't leave the database in a state where the author has been created but the blog entry hasn't:BlogPost.create_from_new_author('Carol', 'The End Is Near', 'A few more facts of doom…') # => #<BlogPost:0xb78b7c7c … > # The method succeeded; Carol's in the database: User.find(:first, :conditions=>"name='Carol'") # => #<User:0xb7888ae4 @attributes={"name"=>"Carol", … }> # Let's do another one… BlogPost.create_from_new_author('David', 'The End: A Rebuttal', 'The end is actually quite far away…') # RuntimeError: Random failure! # The method failed; David's not in the database: User.find(:first, :conditions=>"name='David'") # => nilEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Adding Hooks to Table Events
- InhaltsvorschauYou want to run some code whenever a database row is added, updated, or deleted. For instance, you might want to send out email whenever a new blog post is created.For Og, use the aspect-oriented features of
Glue::Aspect. You can use itsbeforeandaftermethods to register code blocks that run before or after any Og method. The methods you're most likely to wrap areog_insert, og_update, andog_delete.In the following code, I take theBlogPostclass first defined in Recipe 13.12, and give itsog_insertmethod an aspect that sends out email:require 'cookbook_dbconnect' require 'og' require 'glue/aspects' class BlogPost property :title, :content, String after :on => :og_insert do |post| puts %{Sending email notification of new post "#{post.title}"} # Actually send the email here… end end og_connect post = BlogPost.new post.title = 'Robots are taking over' post.content = 'Think about it! When was the last time you saw another human?' post.save! # Sending email notification of new post "Robots are taking over"This technique works with ActiveRecord as well (since aspect-oriented programming is a generic technique), but ActiveRecord defines two different approaches: callbacks and theActiveRecord::Observerclass.AnyActiveRecord::Basesubclass can define a number of callback methods:before_find, after_save, and so on. These methods run before or after the corresponding ActiveRecord methods. Here's an callback-based ActiveRecord implementation of the Og example, running against theblog_posttable first defined in Recipe 13.11. If you ran the previous example in a session, quit it now and start a new session.require 'cookbook_dbconnect' activerecord_connect class BlogPost < ActiveRecord::Base def after_create puts %{Sending email notification of new blog post "#{title}"} # Actually send the email here… end end post = BlogPost.create(:title => 'Robots: Gentle Yet Misunderstood', :content => 'Popular misconceptions about robERROR 40') # Sending email notification of new blog post "Robots: Gentle Yet MisunderstoodEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Adding Taggability with a Database Mixin
- InhaltsvorschauWithout writing a lot of code, you want to make one of your database tables " taggable"—make it possible to add short strings describing a particular item in the table.Og comes complete with a tagging mixin. Just call is
Taggableon every class you want to be taggable. Og will create all the necessary tables.Here's theBlogPostclass from Recipe 13.12, only this time it'sTaggable. Og automatically creates aTagclass and the necessary database tables:require 'cookbook_dbconnect' require 'og' require 'glue/taggable' class BlogPost is Taggable property :title, :content, String end og_connect # Now we can play around with tags. post = BlogPost.new post.title = 'Some more facts about video games' post.tag(['editorial', 'games']) BlogPost.find_with_tags('games').each { |puts| puts post.title } # Some more facts about video games Tag.find_by_name('editorial').blog_posts.each { |post| puts post.title } # Some more facts about video gamesTo get this feature in ActiveRecord, you'll need to install theacts_as_taggablegem, and you must create the database tables yourself. Here are the tables necessary to add tags to the ActiveRecordBlogPostclass (first described in Recipe 13.11): a generictagstable and a join table connecting it toblog_posts.DROP TABLE IF EXISTS tags; CREATE TABLE tags ( id INT(11) NOT NULL AUTO_INCREMENT, name VARCHAR(32), PRIMARY KEY (id) ) ENGINE=InnoDB; DROP TABLE IF EXISTS tags_blog_posts; CREATE TABLE tags_blog_posts ( tag_id INT(11), blog_post_id INT(11) ) ENGINE=InnoDB;
Note that the join table violates the normal ActiveRecord naming rule. It's calledtags_blog_posts, even though alphabetical ordering of its component tables would make itblog_posts_tags. ActiveRecord does this so all of your application'stags_jointables will show up together in a sorted list. If you want to call the tableblog_posts_tagsinstead, you'll need to pass the name as the :join_tableparameter when you call theacts_as_ taggabledecorator below.Here's the ActiveRecord code that makesBlogPostEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 14: Internet Services
- InhaltsvorschauNetwork programming is hard. The C socket library is the standard way of writing Internet clients and servers. It's like the file API descibed in Chapter 6, with its special flags and meager abstraction, only much more complicated. It's a shame because networked applications are the coolest kind of application. Only computer nerds like you and me care about XML or the best way to sort a list, but everyone uses Internet applications.Fortunately, network programming is easy. Ruby provides bindings to the C socket library (in
socket), but you'll probably never need to use them. Existing Ruby libraries (some in the standard distribution) can speak every popular high-level Internet protocol.The most popular Internet service is, of course, the Web, and Ruby's most popular Internet library (or any kind of library, actually) is the Rails framework. We've devoted the entire next chapter to Rails (Chapter 15) so that we can cover other technologies here.Apart from Rails, most of the interesting stuff you can do with Ruby happens on the client end. We start with a set of recipes for requesting web pages (Recipes 14.1, 14.2, and 14.3), which are brought together at the end of the chapter with Recipe 14.20. Combine these recipes with one from Chapter 11 (probably Recipe 11.5), and you can make your own spider or web browser.Then we present Ruby clients for the most popular Internet protocols. Ruby can do just about everything you do online: send and receive email, perform nameserver queries, even transfer files with FTP, SCP, or BitTorrent. With the Ruby interfaces, you can write custom clients for these protocols, or integrate them into larger programs.It's less likely that you'll be writing your own server in Ruby. A server only exists to service clients, so there's not much you can do but faithfully implement the appropriate protocol. If you do write a server, it'll probably be for a custom protocol, one for which no other server exists.Ruby provides two basic servers that you can use as a starting point. Thegserverlibrary described in Recipe 14.14 provides a generic framework for almost any kind of Internet server. Here you do have to do some socket programming, but only the easy parts.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Grabbing the Contents of a Web Page
- InhaltsvorschauYou want to display or process a specific web page.The simplest solution is to use the
open-urilibrary. It lets you open a web page as though it were a file. This code fetches theoreilly.comhomepage and prints out the first part of it:require ' open-uri' puts open('http://www.oreilly.com/').read(200) # <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" # "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> # <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">For more complex applications, you'll need to use thenet/httplibrary. UseNet::HTTP.get_responseto make an HTTP request and get the response as aNet::HTTPResponseobject containing the response code, headers, and body.require ' net/http' response = Net::HTTP.get_response('www.oreilly.com', '/about/') response.code # => "200" response.body.size # => 21835 response['Content-type'] # => "text/html; charset=ISO-8859-1" puts response.body[0,200] # <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" # "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> # # # <html> # <head> # <meta http-equiv="content-type" content="text/html; cRather than passing in the hostname, port, and path as separate arguments, it's usually easier to createURIobjects from URL strings and pass those into theNet::HTTPmethods.require 'uri' Net::HTTP.get(URI.parse("http://www.oreilly.com")) Net::HTTP.get_response(URI.parse("http://www.oreilly.com/about/"))If you just want the text of the page, useget. If you also want the response code or the values of the HTTP response headers, useget_reponse.Theget_responsemethod returns someHTTPResponsesubclass ofNet:HTTPResponse, which contains all information about an HTTP response. There's one subclass for every response code defined in the HTTP standard; for instance,HTTPOKfor the 200 response code,HTTPMovedPermanentlyfor the 301 response code, andHTTPNotFoundfor the 404 response code. There's also anEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Making an HTTPS Web Request
- InhaltsvorschauYou want to connect to an HTTPS web site, one whose traffic is encrypted using SSL.You need the OpenSSL extension to Ruby. You'll know if it's installed if you can
requirethenet/httpslibrary without getting aLoadError.require 'net/https' # => true
You can't make HTTPS requests with the convenience methods described in Recipe 14.1, but you can use theNet::HTTP::GetandNet::HTTP::Postclass described in Recipe 14.3. To make an HTTPS request, just instantiate aNet::HTTPobject and set itsuse_sslmember totrue.In this example, I try to download a page from a web server that only accepts HTTPS connections. Instead of listening on port 80 like a normal web server, this server listens on port 443 and expects an encrypted request. I can only connect with aNet::HTTPinstance that has theuse_sslflag set.require 'net/http' uri = URI.parse("https://www.donotcall.gov/") request = Net::HTTP.new(uri.host, uri.port) response = request.get("/") # Errno::ECONNRESET: Connection reset by peer require 'net/https' request.use_ssl = true request.verify_mode = OpenSSL::SSL::VERIFY_NONE response = request.get("/") # => #<Net::HTTPOK 200 OK readbody=true> response.body.size # => 6537The default Ruby installation for Windows includes the OpenSSL extension, but if you're on a Unix system, you might have to install it yourself. On Debian GNU/Linux, the package name islibopenssl-ruby[Ruby version]: for instance,libopenssl-ruby1.8. You might need to download the extension from the Ruby PKI homepage (see below), and compile and install it with Make.Settingverify_modetoOpenSSL:SSL::VERIFY_NONEsuppresses some warnings, but the warnings are kind of serious: they mean that OpenSSL won't verify the server's certificate or proof of identity. Your conversation with the server will be confidential, but you won't be able to definitively authenticate the server: it might be an imposter.You can have OpenSSL verify server certificates if you keep a few trusted certificates on your computer. You don't need a certificate for every server you might possibly access. You just need certificates for a few "certificate authorities:" the organizations that actually sign most other certificates. Since web browsers need these certificates too, you probably already have a bunch of them installed, although maybe not in a format that Ruby can use (if you don't have them, see below).Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Customizing HTTP Request Headers
- InhaltsvorschauWhen you make an HTTP request, you want to specify custom HTTP headers like " User-Agent" or " Accept-Language".Pass in a
Hashof header values toNet::HTTP#getorNet::HTTP#post:require 'net/http' require 'uri' #A simple wrapper method that accepts either strings or URI objects #and performs an HTTP GET. module Net class HTTP def HTTP.get_with_headers(uri, headers=nil) uri = URI.parse(uri) if uri.respond_to? :to_str start(uri.host, uri.port) do | http| return http.get(uri.path, headers) end end end end #Let's get a web page in German. res = Net::HTTP.get_with_headers('http://www.google.com/', {' Accept-Language' => 'de'}) #Check a bit of the body to make sure it's really in German. s = res.body.size res.body[s-200..s-140] # => "ngebote</a> - <a href=/intl/de/about.html>Alles \374ber Google</"Usually you can retrieve the web pages you want without specifying any custom HTTP headers. As you start performing more complicated interactions with web servers, you'll find yourself customizing the headers more.For instance, if you write a web spider or client, you'll want it to send a " User-Agent" header on every request, identifying itself to the web server. Unlike the HTTP client libraries for other programming languages, thenet/httplibrary doesn't send a "User-Agent" header by default; it's your reponsibility to send one.Net::HTTP.get_with_headers(url, {'User-Agent' => 'Ruby Web Browser v1.0'})You can often save bandwidth (at the expense of computer time) by sending an "Accept-Encoding" header, requesting that a web server compress data before sending it to you. Gzip compression is the most common way a server compresses HTTP response data; you can reverse it with Ruby'szliblibrary:uncompressed = Net::HTTP.get_with_headers('http://www.cnn.com/') uncompressed.body.size # => 65150 gzipped = Net::HTTP.get_with_headers('http://www.cnn.com/', {'Accept-Encoding' => 'gzip'}) gzipped['Content-Encoding'] # => "gzip" gzipped.body.size # => 14600 require 'zlib' require 'stringio' body_io = StringIO.new(gzipped.body) unzipped_body = Zlib::GzipReader.new(body_io).read() unzipped_body.size # => 65150Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Performing DNS Queries
- InhaltsvorschauYou want to find the IP address corresponding to a domain name, or see whether a domain provides a certain service (such as an email server).Use the
Resolv:: DNSclass in the standardresolvlibrary to perform DNS lookups. The most commonly used method isDNS#each_address, which iterates over the IP addresses assigned to a domain name.require 'resolv' Resolv::DNS.new.each_address("oreilly.com") { |addr| puts addr } # 208.201.239.36 # 208.201.239.37If you need to check the existence of a particular type of DNS record (such as a MX record for a mail server), useDNS#getresourcesor the iteratorDNS#each_resource. Both methods take a domain name and a class denoting a type of DNS record. They perform a DNS lookup and, for each matching DNS record, return an instance of the given class.These are the three most common classes:-
DNS::Resource::IN::A - Indicates a DNS record pointing to an IP address for the domain.
-
DNS::RESOURCE::IN::NS - Indicates a DNS record pointing to a DNS nameserver.
-
DNS::Resource::IN::MX - Indicates a DNS record pointing to a mail server.
This code finds the mail servers and name servers responsible for oreilly.com:dns = Resolv::DNS.new domain = "oreilly.com" dns.each_resource(domain, Resolv::DNS::Resource::IN::MX) do |mail_server| puts mail_server.exchange end # smtp1.oreilly.com # smtp2.oreilly.com dns.each_resource(domain, Resolv::DNS::Resource::IN::NS) do |nameserver| puts nameserver.name end # a.auth-ns.sonic.net # b.auth-ns.sonic.net # c.auth-ns.sonic.net # ns.oreilly.com
If your application needs to do a lot of DNS lookups, you can greatly speed things up by creating a separate thread for each lookup. Most of the time spent doing a DNS lookup is spent connecting to the network, so doing all the lookups in parallel can save a lot of time. If you do this, you shouldincludetheresolv-replacelibrary along withresolv, to make sure your DNS lookups are thread-safe.Here's some code that sees which one-letterEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. -
- Sending Mail
- InhaltsvorschauYou want to send an email message, either an autogenerated one or one entered in by an end user.First you need to turn the parts of the email message into a single string, representing the whole message complete with headers and/or attachments. You can construct the string manually or use a number of libraries, including RubyMail, TMail, and ActionMailer. Since ActionMailer is one of the dependencies of Rails, I'll use it throughout this recipe. ActionMailer uses TMail under the covers, and it's provided by the
actionmailergem.Here, I use ActionMailer to construct a simple, single-part email message:require 'rubygems' require 'action_mailer' class SimpleMailer < ActionMailer::Base def simple_message(recipient) from 'leonardr@example.org' recipients recipient subject 'A single-part message for you' body 'This message has a plain text body.' end end
ActionMailer then makes two new methods available for generating this kind of email message:SimpleMailer. create_simple_message, which returns the email message as a data structure, andSimpleMailer. deliver_simple_message, which actually sends the message.puts SimpleMailer. create_simple_message('lucas@example.com') # From: leonardr@example.org # To: lucas@example.com # Subject: A single-part message for you # Content-Type: text/plain; charset=utf-8 # # This message has a plain text body.To deliver the message, calldeliver_simple_messageinstead ofcreate_simple_message. First, though, you'll need to tell ActionMailer about your SMTP server. If you're sending mail fromexample.organd you've got an SMTP server on the local machine, you might send a message this way:ActionMailer::Base.server_settings = { :address => 'localhost', :port => 25, # 25 is the default :domain => 'example.org' } SimpleMailer.deliver_simple_message('lucas@example.com')If you're using your ISP's SMTP server, you'll probably need to send authentication information so the server knows you're not a spammer. Your ActionMailer setup will probably look like this:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Reading Mail with IMAP
- InhaltsvorschauCredit: John WellsYou want to connect to an IMAP server in order to read and manipulate the messages stored there.The
net/imap.rbpackage, written by Shugo Maeda, is part of Ruby's standard library, and provides a very capable base on which to build an IMAP-oriented email application. In the following sections, I'll walk you through various ways of using this API to interact with an IMAP server.For this recipe, let's assume you have access to an IMAP server running at mail.myhost.com on the standard IMAP port 143. Your username is, conveniently, "username", and your password is "password".To make the initial connection to the server, it's as simple as:require 'net/imap' conn = Net::IMAP.new('mail.myhost.com', 143) conn.login('username', 'password')Assuming no error messages were received, you now have a connection to the IMAP server. TheNet::IMAPobject puts all the capabilities of IMAP at your fingertips.Before doing anything, though, you must tell the server which mailbox you're interested in working with. On most IMAP servers, your default mailbox is called "INBOX". You can change mailboxes withNet::IMAP#examine:conn.examine('INBOX') # Use Net::IMAP#select instead for read-only accessA search provides a good example of how aNet::IMAPobject lets you interact with the server. To search for all messages in the selected mailbox from a particular address, you can use this code:conn.search(['FROM', 'jabba@huttfoundation.org']).each do |sequence| fetch_result = conn.fetch(sequence, 'ENVELOPE') envelope = fetch_result[0].attr['ENVELOPE'] printf("%s - From: %s - To: %s - Subject: %s\n", envelope.date, envelope.from[0].name, envelope.to[0].name, envelope.subject) end # Wed Feb 08 14:07:21 EST 2006 - From: The Hutt Foundation - To: You - Subject: Bwah! # Wed Feb 08 11:21:19 EST 2006 - From: The Hutt Foundation - To: You - Subject: Go to # do wa IMAPThe details of the IMAP protocol are a bit esoteric, and to really understand it you'll need to read the RFC. That said, the code in the solution shouldn't be too hard to understand: it uses the IMAPEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Reading Mail with POP3
- InhaltsvorschauCredit: John WellsYou want to connect to an POP server in order to read and download the messages stored there.The
net/pop.rbpackage, written by Minero Aoki, is part of Ruby's standard library, and provides a foundation on which to build a POP (Post Office Protocol)-oriented email application. As with the previous recipe on IMAP, we'll walk through some common ways of accessing a mail server with the POP API.For this recipe, we assume you have access to a POP3 server running at mail.myhost. com on the standard POP3 port 110. Just as in the previous IMAP example, your username is "username", and password is (yep) "password".To make the initial connection to the server, it's as simple as:require 'net/pop' conn = Net::POP3.new('mail.myhost.com') conn.start('username', 'password')If you receive no errors, you've got an open session to your POP3 server, and can use theconnobject to communicate with the server.The following code acts like a typical POP3 client: having connected to the server, it downloads all the new messages, and then deletes them from the server. The deletion is commented out so you don't lose mail accidentally while testing this code:require 'net/pop' conn = Net::POP3.new('mail.myhost.com') conn.start('username', 'password') conn.mails.each do |msg| File.open(msg.uidl, 'w') { |f| f.write msg.pop } # msg.delete end conn.finishPOP3 is a much simpler protocol than IMAP, and arguably a less powerful one. It doesn't support the concept of folders, so there's no need to start off by selecting a particular folder (like we did in the IMAP recipe). Once you start a session, you have immediate access to all messages currently retained on the server.IMAP stores your folders and your messages on the server itself. This way you can access the same messages and the same folders from different clients on different machines. For example, you might go to work and access an IMAP folder with Mozilla Thunderbird, then go home and access the same folder with a web-based mail client.With POP3, there are no server-side folders. You're supposed to archive your messages on the client side. If you use a POP3 client to download messages at work, when you get home you won't be able to access those messages. They're on your work computer, not on the POP3 server.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Being an FTP Client
- InhaltsvorschauYou want to automatically connect to an FTP server, and upload or download files.Use the
Net::FTPclass. It provides a filesystem-like interface to an FTP server. In this example, I log anonymously into a popular FTP site, browse one of its directories, and download two of its files:require 'net/ftp' ftp = Net::FTP.open('ftp.ibiblio.org') do |ftp| ftp.login ftp.chdir('pub/linux/') ftp.list('*Linux*') { |file| puts file } puts puts 'Saving a text file to disk while processing it.' ftp.gettextfile('How-do-I-get-Linux') { |line| puts "! #{line}" } puts "Saved #{File.size 'How-do-I-get-Linux'} bytes." puts puts 'Saving a binary file to disk.' ftp.getbinaryfile('INDEX.whole.gz') puts "Saved #{File.size 'INDEX.whole.gz'} bytes." end # -rw-r--r-- 1 (?) users 16979001 Jan 1 11:31 00-find.Linux.gz # -rw-rw-r-- 1 (?) admin 73 Mar 9 2001 How-do-I-get-Linux # Saving a text file to disk while processing it. # ! # ! Browse to http://metalab.unc.edu/linux/HOWTO/Installation-HOWTO.html # ! # Saved 73 bytes. # Saving a binary file to disk. # Saved 213507 bytes.Once the preferred way of storing and serving files through the Internet, FTP is being largely superceded by SCP for copying files, the web for distributing files, and Bit-Torrent for distributing very large files. There are still many anonymous FTP servers, though, and many web hosting companies still expect you to upload your web pages through FTP.Theloginmethod logs in to the server. Calling it without arguments logs you in anonymously, which traditionally limits you to download privileges. Calling it with a username and password logs you in to the server:ftp.login('leonardr', 'mypass')The methodschdirandlistlet you navigate the FTP server's directory structure. They work more or less like the Unixcdandlscommands (in fact,listis aliased tolsanddir).There are also two "get" methods and two "put" methods. The "get" methods aregetbinaryfileandgettextfile. They retrieve the named file from the FTP server and write it to disk. TheEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Being a Telnet Client
- InhaltsvorschauYou want to connect to a telnet service or use telnet to get low-level access to some other kind of server.Use the
Net::Telnetmodule in the Ruby standard library.The following code uses aTelnetobject to simulate an HTTP client. It sends a raw HTTP request to the web server at http://www.oreilly.com. Every chunk of data received from the web server is passed into a code block, and its size is added to a tally. Eventually the web server stops sending data, and the telnet session times out.require 'net/telnet' webserver = Net::Telnet::new('Host' => 'www.oreilly.com', 'Port' => 80, 'Telnetmode' => false) size = 0 webserver.cmd("GET / HTTP/1.1\nHost: www.oreilly.com\n") do |c| size += c.size puts "Read #{c.size} bytes; total #{size}" end # Read 1431 bytes; total 1431 # Read 1434 bytes; total 2865 # Read 1441 bytes; total 4306 # Read 1436 bytes; total 5742 # … # Read 1430 bytes; total 39901 # Read 2856 bytes; total 42757 # /usr/lib/ruby/1.8/net/telnet.rb:551:in 'waitfor': # timed out while waiting for more data (Timeout::Error)Telnet is a lightweight protocol devised for connecting to a generic service running on another computer. For a long time, the most commonly exposed service was a Unix shell: you would "telnet in" to a machine on the network, log in, and run shell commands on the other machine as though it were local.Because telnet is an insecure protocol, it's very rare now to use it for remote login. Everyone uses SSH for that instead (see the next recipe). Telnet is still useful for two things:- As a diagnostic tool (as seen in the Solution). Telnet is very close to being a generic TCP protocol. If you know, say, HTTP, you can connect to an HTTP server with telnet, send it a raw HTTP request, and view the raw HTTP response.
- As a client to text-based services other than remote shells: mainly old-school entertainments like BBSes and MUDs.
Telnetobjects implement a simple loop between you and some TCP server:- You send a string to the server.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Being an SSH Client
- InhaltsvorschauYou want to securely send data or commands back and forth between your computer, and another computer on which you have a shell account.Use the
Net::SSHmodule, which implements the SSH2 protocol. It's found in thenet-sshgem, although some operating systems package it themselves. It lets you implement Ruby applications that work like the familiarsshandscp.You can start an SSH session by passing a hostname toNet::SSH::start, along with your shell username and password on that host. If you have an SSH public/private key pair set up between your computer and the remote host, you can omit the username and password:require 'rubygems' require 'net/ssh' Net::SSH.start('example.com', :username=>'leonardr', :password=>'mypass') do |session| # Manipulate your Net::SSH::Session object here… endNet::SSH::starttakes a code block, to which it passes aNet::SSH::Sessionobject. You use the session object to send encrypted data between the machines, or to spawn processes on the remote machine. When the code block ends, the SSH session is automatically terminated.It seems strange now, but until the late 1990s, people routinely used unsecured protocols like telnet to get shell access to remote machines. Remote access was so useful that we were willing to jeopardize our electronic safety by sending our shell passwords (not to mention all the data we looked at) unencrypted across the network. Fortunately, we don't have to make that trade-off anymore. The SSH protocol makes it easy to send encrypted traffic between machines, and the client toolssshandscphave almost completely replaced tools like RSH and nonanonymous FTP.TheNet::SSHlibrary provides a low-level interface to the SSH2 protocol, but most of the time you won't need it. Instead, you'll use one of the abstractions that make it easy to spawn and control processes on a remote machine. The simplest abstraction is thepopen3method, which works like the localpopen3method in Ruby'sopen3library. It's covered in more detail in Recipe 20.10, but here's a simple example:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Copying a File to Another Machine
- InhaltsvorschauYou want to programatically send files to another computer, the way the Unix
scpcommand does.Use theNet:SSHlibrary to get a secure shell connection to the other machine. Start acatprocess on the other machine, and write the file you want to copy to its standard input.require 'rubygems' require 'net/ssh' def copy_file(session, source_path, destination_path=nil) destination_path ||= source_path cmd = %{cat > "#{destination_path.gsub('"', '\"')}"} session.process.popen3(cmd) do |i, o, e| puts " Copying #{source_path} to #{destination_path}… " open(source_path) { |f| i.write(f.read) } puts 'Done.' end end Net::SSH.start('example.com', :username=>'leonardr', :password=>'mypass') do |session| copy_file(session, '/home/leonardr/scripts/test.rb') copy_file(session, '/home/leonardr/scripts/"test".rb') end # Copying /home/leonardr/scripts/test.rb to /home/leonardr/scripts/test.rb… # Done. # Copying /home/leonardr/scripts/"test".rb to /home/leonardr/scripts/"test".rb… # Done.Thescpcommand basically implements the oldrcpprotocol over a secured connection. This code uses a shortcut to achieve the same result: it uses the high-level SSH interface to spawn a process on the remote host which writes data to a file.Since you can run multiple processes at once over your SSH session, you can copy multiple files simultaneously. For every file you want to copy, you need to spawn acatprocess:def do_copy(session, source_path, destination_path=nil) destination_path ||= source_path cmd = %{cat > "#{destination_path.gsub('"', '\"')}"} cat_process = session.process.open(cmd) cat_process.on_success do |p| p.write(open(source_path) { |f| f.read }) p.close puts "Copied #{source_path} to #{destination_path}." end endThe call tosession.process.opencreates a process-like object that runs acatcommand on the remote system. The call toon_successregisters a callback code block with the process. That code block will run once thecatcommand has been set up and is accepting standard input. Once that happens, it's safe to start writing data to the file on the remote system.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Being a BitTorrent Client
- InhaltsvorschauYou want to write a Ruby script that downloads or shares large files with BitTorrent.The third-party RubyTorrent library implements the BitTorrent protocol; you can use it to write BitTorrent clients. The RubyTorrent package has no
setup.rbfile, so you'll need to manually copy the files into your Ruby classpath or package them with your application.TheBitTorrentclass acts as a BitTorrent client, so to download a torrent, all you have to do is give it the path or URL to a .torrent file. This code will download the classic B-movie Night of the Living Dead to the current working directory:require 'rubytorrent' file = 'http://publicdomaintorrents.com/bt/btdownload.php?type=torrent' + '&file=Night_of_the_Living_Dead.avi.torrent' client = RubyTorrent::BitTorrent.new(file)
Run this inirb, keep your session open, and in a few hours (or days), you'll have your movie!BitTorrent is the most efficient way yet devised for sharing large files between lots of people. As you download the file you're also sharing what you've downloaded with others: the more people are trying to download the file, the faster it is for everyone.RubyTorrent is a simple client library to the BitTorrent protocol. In its simplest form, you simply construct aBitTorrentobject with the URL or path to a torrent information file, and wait for the download to complete. However, there's a lot more you can do to provide a better user interface.TheBitTorrentobject has several methods that let you keep track of the progress of the download:client.num_active_peers # => 9 # That is, 9 other people are downloading this file along with me. client.ulrate # => 517.638825414351 client.dlrate # => 17532.608916979 # That is, about 3 kb/sec uploading and 17 kb/sec downloading. client.percent_completed # => 0.25
You can also register code blocks to be run at certain points in the client's lifecycle. Here's a more advanced BitTorrent client that registers code blocks to let the user know about new and dropped peer connections. It also uses a thread to occasionally report on the progress of the download. The user can specify which port to use when uploading data to peers, and a maximum upload rate in kilobytes.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Pinging a Machine
- InhaltsvorschauYou want to check whether a particular machine or domain name can be reached from your computer.Use Ruby's standard
pinglibrary. Its single method,Ping.pingecho, tries to get some machine on the network to respond to its entreaties. It takes either a domain name or an IP address, and returns true if it gets a response.require 'ping' ping.pingecho('oreilly.com') # => true # timeout of 10 seconds instead of the default 5 seconds Ping.pingecho('127.0.0.1', 10) # => true # ping port 80 instead of the default echo port Ping.pingecho('slashdot.org', 5, 80) # => true Ping.pingecho('no.such.domain') # => false Ping.pingecho('222.222.222.222') # => falsePing.pingechoperforms a TCP echo: it tries to make a TCP connection to the given machine, and if the machine responds (even if to refuse the connection) it means the machine was reachable.This is not the ICMP echo of the Unixpingcommand, but the difference almost never matters. If you absolutely need an ICMP echo, you can invokepingwith a system call and check the return value:system('ping -c1 www.oreilly.com') # 64 bytes from 208.201.239.36: icmp_seq=0 ttl=42 time=27.2 ms # # --- www.oreilly.com ping statistics -- # 1 packets transmitted, 1 packets received, 0% packet loss # round-trip min/avg/max = 27.2/27.2/27.2 ms # => trueIf the domain has a DNS entry but can't be reached,Ping::pingechomay raise aTimeout::Errorinstead of returning false.Some very popular or very paranoid domains, such as microsoft.com, don't respond to incoming ping requests. However, you can usually access the web server or some other service on the domain. You can see whether such a domain is reachable by using one of Ruby's other libraries:ping.pingecho('microsoft.com') # => false require 'net/http' Net::HTTP.start('microsoft.com') { 'success!' } # => "success!" Net::HTTP.start('no.such.domain') { "success!" } # SocketError: getaddrinfo: Name or service not knownEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Writing an Internet Server
- InhaltsvorschauYou want to run a server for a TCP/IP application-level protocol, but no one has written a Ruby server for the protocol yet. This may be because it's a protocol you've made up.Use the
gserverlibrary in Ruby's standard library. It implements a generic TCP/IP server suitable for small to medium-sized tasks.Here's a very simple chat server written with gserver. It has no end-user features to speak of. People connect to the server with a telnet client, and are identified to each other only by hostname. But it's a fully functional, multithreaded, logging server written in about 30 lines of Ruby.#!/usr/bin/ruby -w # chat.rb require ' gserver' class ChatServer < GServer def initialize(port=20606, host=GServer::DEFAULT_HOST) @clients = [] super(port, host, Float::MAX, $stderr, true) end def serve(sock) begin @clients << sock hostname = sock.peeraddr[2] || sock.peeraddr[3] @clients.each do |c| c.puts "#{hostname} has joined the chat." unless c == sock end until sock.eof? do message = sock.gets.chomp break if message == "/quit" @clients.each { |c| c.puts "#{hostname}: #{message}" unless c == sock } end ensure @clients.delete(sock) @clients.each { |c| c.puts "#{hostname} has left the chat." } end end end server = ChatServer.new(*ARGV[0..2] || 20606) server.start(-1) server.joinStart the server in a Ruby session, and then use several instances of the telnet program to connect to port 20606 (from several different hosts, if you can). Your telnet sessions will be able to communicate with each other through the server. Your Ruby session will see a log of the connections and disconnections.TheGServerclass wraps Ruby's underlyingTCPServerclass in a loop that continually receives TCP connections and spawns new threads to process them. Each new thread passes its TCP connection (aTCPSocketobject) into theGServer#servemethod, which your subclass is responsible for providing.TheTCPSocketworks like a bidirectional file. Writing to it pushes data to the client, and reading from it reads data from the client. A server like the sample chat server reads one line at a time from the client; a web server would read the entire request before sending back any data.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Parsing URLs
- InhaltsvorschauYou want to parse a string representation of a URL into a data structure that articulates the parts of the URL.
URI.parsetransforms a string describing a URL into aURIobject. The parts of the URL can be determined by interrogating theURIobject.require 'uri' URI.parse('https://www.example.com').scheme # => "https" URI.parse('http://www.example.com/').host # => "www.example.com" URI.parse('http://www.example.com:6060/').port # => 6060 URI.parse('http://example.com/a/file.html').path # => "/a/file.html"URI.splittransforms a string into an array of URL parts. This is more efficient thanURI.parse, but you have to know which parts correspond to which slots in the array:URI.split('http://example.com/a/file.html') # => ["http", nil, "example.com", nil, nil, "/a/file.html", nil, nil, nil]TheURImodule contains classes for five of the most popular URI schemas. Each one can store in a structured format the data that makes up a URI for that schema.URI.parsecreates an instance of the appropriate class for a particular URL's scheme.Every URI can be decomposed into a set of components, joined by constant strings. For example: the components for a HTTP URI are the scheme ("http"), the hostname ("www.example.com (http://www.example.com)"), and so on. Each URI schema has its own components, and each of Ruby'sURIclasses stores the names of its components in an ordered array of symbols, calledcomponent:URI::HTTP.component # => [:scheme, :userinfo, :host, :port, :path, :query, :fragment] URI::MailTo.component # => [:scheme, :to, :headers]
Each of the components of aURIclass has a corresponding accessor method, which you can call to get one component of a URI. You can also instantiate a URI class directly (rather than going throughURI.parse) by passing in the appropriate component symbols as a map of keyword arguments.URI::HTTP.build(:host => 'example.com', :path => '/a/file.html', :fragment => 'section_3').to_s # => "http://example.com/a/file.html#section_3"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Writing a CGI Script
- InhaltsvorschauCredit: Chetan PatilYou want to expose Ruby code through an existing web server, without having to do any special configuration.Most web servers are set up to run CGI scripts, and it's easy to write CGI scripts in Ruby. Here's a simple CGI script that calls the Unix command
ps, parses its results, and outputs the list of running processes as an HTML document. Anyone with access to the web server can then look at the processes running on the system.#!/usr/bin/ruby # ps.cgi processes = %x{ps aux}.collect do |proc| '<tr><td>' + proc.split(/\s+/, 11).join('</td><td>') + '</td></tr>' end puts 'Content-Type: text/html' # Output other HTTP headers here… puts "\n" title = %{Processes running on #{ENV['SERVER_NAME'] || `hostname`.strip}} puts <<-end <HTML> <HEAD><TITLE>#{title}</TITLE></HEAD> <BODY> <H1>#{title}</H1> <TABLE> #{processes.join("\n")} </TABLE> </BODY> </HTML> end exit 0CGI was the first major technology to add dynamic elements to the previously static Web. A CGI resource is requested like any static HTML document, but behind the scenes the web server executes an external program (in this case, a Ruby script) instead of serving a file. The output of the program—text, HTML, or binary data—is sent as part of the HTTP response to the browser.CGI has a very simple interface, based on environment variables and standard input and output; one that should be very familiar to writers of command-line programs. This simplicity is CGI's weakness: it leaves too many things undefined. But when a Rails application would be overkill, a CGI script might be the right size.CGI programs typically reside in a special directory of the web server's web space (often the/cgi-bindirectory). On Unix systems, CGI files must be made executable by the web server, and the first line of the script must point to the system's Ruby interpreter (usually/usr/bin/rubyor/usr/local/bin/ruby).A CGI script gets most of its input from environment variables likeQUERY_STRINGandPATH_INFO, which are set by the web server. The web server also uses environment variables to tell the script where and how it's being run: note how the sample script usesEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Setting Cookies and Other HTTP Response Headers
- InhaltsvorschauCredit: Mauro CicioYou're writing a CGI program and you want to customize the HTTP headers you send in response to a request. For instance, you may want to set a client-side cookie so that you can track state between HTTP requests.Pass a hash of headers into the
CGI#outmethod that creates the HTTP response. Each key of the hash is the name of a header to set, or a special value (likecookie), which theCGIclass knows how to interpret.Here's a CGI script that demonstrates how to set some response headers, including a cookie and a custom HTTP header called "Recipe Name".First we process any incoming cookie. Every time you hit this CGI, the value stored in your cookie will be incremented, and the date of your last visit will be reset.#!/usr/bin/ruby # headers.cgi require "cgi" cgi = CGI.new("html3") # Retrieve or create the "rubycookbook" cookie cookie = cgi. cookies['rubycookbook'] cookie = CGI::Cookie.new('rubycookbook', 'hits=0', "last=#{Time.now}") if cookie.empty? # Read the values in the cookie for future use hits = cookie.value[0].split('=')[1] last = cookie.value[1].split('=')[1] # Set new values in the cookie cookie.value[0] = "hits=#{hits.succ}" cookie.value[1] = "last=#{Time.now}"Next, we build a hash of HTTP headers, and send the headers by passing the hash intoCGI#out. We then generate the output document. Since the end user doesn't usually see the HTTP headers they're served, we'll make them visible by repeating them in the output document (Figure 14-1):# Create a hash of HTTP response headers. header = { 'status' => 'OK', 'cookie' => [cookie], 'Refresh' => 2, 'Recipe Name' => 'Setting HTTP Response Headers', 'server' => ENV['SERVER_SOFTWARE'] } cgi.out(header) do cgi.html('PRETTY' => ' ') do cgi.head { cgi.title { 'Setting HTTP Response Headers' } } + cgi.body do cgi.p('Your headers:') + cgi.pre{ cgi.header(header) } + cgi.pre do "Number of times your browser hit this cgi: #{hits}\n"+ "Last connected: #{last}" end end end endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Handling File Uploads via CGI
- InhaltsvorschauCredit: Mauro CicioYou want to let a visitor to your web site upload a file to the web server, either for storage or processing.The
CGIclass provides a simple interface for accessing data sent through HTTP file upload. You can access an uploaded file throughCGI#paramsas though it were any other CGI form variable.If the uploaded file size is smaller than 10 kilobytes, its contents are made available as aStringIOobject. Otherwise, the file is put into aTempfileon disk: you can read the file from disk and process it, or move it to a permanent location.Here's a CGI that accepts file uploads and saves the files to a special directory on disk:#!/usr/bin/ruby # upload.rb # Save uploaded files to this directory UPLOAD_DIR = "/usr/local/www/uploads" require 'cgi' require 'stringio'
The CGI has two main parts: a method that prints a file upload form and a method that processes the results of the form. The method that prints the form is very simple:def display_form(cgi) action = env['script_name'] return <<EOF <form action="#{action}" method="post" enctype="multipart/form-data"> File to Upload: <input type="file" name="file_name"><br> Your email address: <input type="text" name="email_address" value="guest@example.com"><br> <input type="submit" name="Submit" value="Submit Form"> </form> EOF endThe method that processes the form is a little more complex:def process_form(cgi) email = cgi.params['email_address'][0] fileObj = cgi.params['file_name'][0] str = '<h1>Upload report</h1>' + "<p>Thanks for your upload, #{email.read}</p>" if fileObj path = fileObj.original_filename str += "Original Filename : #{path}" + cgi.br dest = File.join(UPLOAD_DIR, sanitize_filename(path)) str += "Destination : #{dest} <br>" File.open(dest.untaint, 'wb') { |f| f << fileObj.read } # Delete the temporary file if one was created local_temp_file = fileObj.local_path() File.unlink(local_temp_file) if local_temp_file end return str endTheprocess_formEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Running Servlets with WEBrick
- InhaltsvorschauCredit: John-Mason ShackelfordYou want to embed a server in your Ruby application. Your project is not a traditional web application, or else it's too small to justify the use of a framework like Rails or Nitro.Write a custom servlet for WEBrick, a web server implemented in Ruby and included in the standard library.Configure
WEBrickby creating a newHTTPServerinstance and mouting servlets. The defaultFileHandleracts like a "normal" web server: it serves a URL-space corresponding to a directory on disk. It delegates requests for*.cgifiles to theCGIHandler, renders*.rhtmlfiles withERbusing theERBHandlerservlet, and serves other files (such as static HTML files) as they are.This server mounts three servlets on a server running on port 8000 on your local machine. Each servlet serves documents, CGI scripts, and.rhtmltemplates from a different directory on disk:#!/usr/bin/ruby # simple_servlet_server.rb require 'webrick' include WEBrick s = HTTPServer.new(:Port => 8000) # Add a mime type for *.rhtml files HTTPUtils::DefaultMimeTypes.store('rhtml', 'text/html') # Required for CGI on Windows; unnecessary on Unix/Linux s.config.store( :CGIInterpreter, "#{HTTPServlet::CGIHandler::Ruby}") # Mount servlets s.mount('/', HTTPServlet::FileHandler, '/var/www/html') s.mount('/bruce', HTTPServlet::FileHandler, '/home/dibbbr/htdoc') s.mount('/marty', HTTPServlet::FileHandler, '/home/wisema/htdoc') # Trap signals so as to shutdown cleanly. ['TERM', 'INT'].each do |signal| trap(signal){ s.shutdown } end # Start the server and block on input. s.startWEBrick is robust, mature, and easy to extend. Beyond serving static HTML pages,WEBricksupports traditional CGI scripts,ERb-based templating like PHP or JSP, and custom servlet classes. While most of WEBrick's API is oriented toward responding to HTTP requests, you can also use it to implement servers that speak another protocol. (For more on this capability, see the Daytime server example on the WEBrick home page.)Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - A Real-World HTTP Client
- InhaltsvorschauThe first three recipes in this chapter cover different ways of fetching web pages. The techniques they describe work well if you just need to fetch one specific web page, but in the interests of simplicity they omit some details you'll need to consider when writing a web spider, a web browser, or any other serious HTTP client. This recipe creates a library that deals with the details.
- Mixed HTTP and HTTPS
- Any general client will have to be able to make both HTTP and HTTPS requests. But the simple
Net:HTTPmethods that work in Recipe 14.1 can't be used to make HTTPS requests. Our library will use useHTTPRequestobjects for everything. If the user requests a URL that uses the "https" scheme, we'll flip the request object'suse_sslswitch, as seen in Recipe 14.2. - Redirects
- Lots of things can go wrong with an HTTP request: the page might have moved, it might require authentication, or it might simply be gone. Most HTTP errors call for higher-level handling or human intervention, but when a page has moved, a smart client can automatically follow it to its new location.Our library will automatically follow redirects that provide "Location" fields in their responses. It'll prevent infinite redirect loops by refusing to visit a URL it's already visited. It'll prevent infinite redirect chains by limiting the number of redirects. After all the redirects are followed, it'll make the final URI available as a member of the response object.
- Proxies
- Users use HTTP proxies to make high-latency connections work faster, surf anonymously, and evade censorship. Each individual client program needs to be programmed to use a proxy, and it's an easy feature to overlook if you don't use a proxy yourself. Fortunately, it's easy to support proxies in Ruby: the
Proxyclass will create a customNet::HTTPsubclass that works through a certain proxy.
This library defines a single new method:Net::HTTP.fetch, an all-singing, all-dancing factory forHTTPRequestobjects. It silently handles HTTPS URLs (assuming you haveEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 15: Web Development: Ruby on Rails
- InhaltsvorschauRuby on Rails is unquestionably Ruby's killer app. It can take a lot of credit for lifting Ruby from obscurity outside its native Japan. No other programming language can boast a simple web application framework that also has almost all of that language's developer mindshare. This chapter demonstrates the principles underlying basic Rails usage (in recipes like Recipe 15.6), gives Rails implementations of common web application patterns (Recipes 15.4 and 15.8) and shows how to use standard Ruby tools from within Rails (Recipes 15.22 and 15.23).Despite its quality and popularity, Rails does not bring anything new to web development. Its foundations are in standard programming patterns like ActiveRecord and Model-View-Controller. It reuses many preexisting Ruby libraries (like Rake and ERb). The power of Rails is in combining these standard techniques with a ruthless dedication to automating menial tasks, and to asserting resonable default behaviors.If Rails has a secret, it's the power of naming conventions. The vast majority of web applications are CRUD applications: create, read, update, and delete information from a database. In these types of applications, Rails shines. You start with a database schema and with almost no code, but Rails ties together many pieces with naming conventions and shortcuts. This lets you put meat on your application very quickly.Because so many settings and names can be sensibly derived from other pieces of information, Rails has much less "paperwork" than other frameworks. Data that's implicit in the code or the database schema doesn't need to be specified anywhere else. An essential part of this system is the ActiveSupport system for pluralizing nouns (Recipe 15.7).Where naming conventions can't do the job, Rails uses decorator methods to declare relationships between objects. This happens within the Ruby classes affected by those relationships, not in a bloated XML configuration file. The result is a smaller, simpler to understand, and more flexible application.As mentioned above, Rails is built on top of common Ruby libraries, and many of them are also covered elsewhere in this book. These libraries include ActiveRecord (much of Chapter 13, but especially Recipe 13.11), ActionMailer (Recipe 14.5), ERb (Recipe 1.3), Rake (Chapter 19), and Test::Unit (Recipe 17.7). Some of these predate Rails, and some were written for Rails but can be used outside of it. The opposite is also true: since a Rails application can be used for many purposes, nearly every recipe in this book is useful within a Rails program.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
- Writing a Simple Rails Application to Show System Status
- InhaltsvorschauYou would like to get started with Rails by building a very simple application.This example displays the running processes on a Unix system. If you're developing on Windows, you can substitute some other command (such as the output of a
dir) or just have your application print a static message.First, make sure you have therailsgem installed.To create a Rails application, run therailscommand and pass in the name of your application. Our application will be called "status".$ rails status create create app/controllers create app/helpers create app/models create app/views/layouts create config/environments …
A Rails application needs at least two parts: a controller and a view. Our controller will get information about the system, and our view will display it.You can generate a controller and the corresponding view with thegeneratescript. The following invocation defines a controller and view that implement a single action calledindex. This will be the main (and only) screen of our application.$ cd status $ ./script/generate controller status index exists app/controllers/ exists app/helpers/ create app/views/status exists test/functional/ create app/controllers/status_controller.rb create test/functional/status_controller_test.rb create app/helpers/status_helper.rb create app/views/status/index.rhtml
The generated controller is in the Ruby source fileapp/controllers/status_controller.rb. That file defines a classStatusControllerthat implements theindexaction as an empty method calledindex. Fill out theindexmethod so that it exposes the objects you want to use in the view:class StatusController < ApplicationController def index # This variable won't be accessible to the view, since it is local # to this method time = Time.now # These variables will be accessible in the view, since they are # instance variables of the StatusController. @time = time @ps = 'ps aux' end end
The generated view is inEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Passing Data from the Controller to the View
- InhaltsvorschauYou want to pass data between a controller and its views.The view is an ERB template that is interpreted within the context of its controller object. A view cannot call any of the controller's methods, but it can access the controller's instance variables. To pass data to the view, set an instance variable of the controller.Here's a
NovelControllerclass, to be put intoapp/ controllers/novel_controller.rb. You can generate stubs for it by runningscript/generate controller novel index.class NovelController < ApplicationController def index @title = 'Shattered View: A Novel on Rails' one_plus_one = 1 + 1 increment_counter one_plus_one end def helper_method @help_message = "I see you've come to me for help." end private def increment_counter(by) @counter ||= 0 @counter += by end end
Since this is theNovelcontroller and theindexaction, the corresponding view is inapp/views/novel/index.rhtml.<h1><%= @title %></h1> <p>I looked up, but saw only the number <%= @counter %>.</p> <p>"What are you doing here?" I asked sharply. "Was it <%= @counter.succ %> who sent you?"</p>
The view is interpreted afterNovelController#indexis run. Here's what the view can and can't access:- It can access the instance variables
@titleand@counter, because they've been defined on theNovelControllerobject by the timeNovelController#indexfinishes running. - It can call instance methods of the instance variables
@titleand@counter. - It cannot access the instance variable
@help_message, because that variable is defined by the methodhelper_method, which never gets called. - It cannot access the variable
one_plus_one, because that's not an instance variable: it's local to theindexmethod. - Even though it runs in the context of
NovelController, it cannot call any method ofNovelController—neitherhelper_methodnorset_another_variable. Nor can it callindexagain.
The action method of a controller is responsible for creating and storing (in instance variables) all the objects the view will need to do its job. These variables might be as simple as strings, or they might be complex helper classes. Either way, most of your application's logic should be in the controller. It's okay to do things in the view like iterate over data structures, but most of the work should happen in the controller or in one of the objects it exposes through an instance variable.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Creating a Layout for Your Header and Footer
- InhaltsvorschauYou want to create a header and footer for every page on your web application. Certain pages should have special headers and footers, and you may want to dynamically determine which header and footer to use for a given request.Many web applications let you define header and footer files, and automatically include those files at the top and bottom of every page. Rails inverts this pattern. A single file called contains both the header and footer, and the contents of each particular page are inserted into this file.To apply a layout to every page in your web application, create a file called app/views/layouts/application.rhtml. It should look something like this:
<html> <head> <title>My Website</title> </head> <body> <%= @content_for_layout %> </body> </html>
The key piece of information in any layout file is the directive<%= content_for_layout %>. This is replaced by the content of each individual page.You can make customized layouts for each controller independently by creating files in the app/views/layouts folder. For example, app/views/layouts/status.rhtml is the layout for thestatuscontroller,StatusController. The layout file forPriceControllerwould be price.rhtml.Customized layouts override the site-wide layout; they don't add to it.Just like your main view templates, your layout templates have access to all the instance variables set by the action. Anything you can do in a view, you can do in a layout template. This means you can do things like set the page title dynamically in the action, and then use it in the layout:class StatusController < ActionController:Base def index @title = "System Status" end end
Now theapplication.rhtmlfile can access@titlelike this:<html> <head> <title>My Website - <%= @title %></title> </head> <body> <%= @content_for_ layout %> </body> </html>
application.rhtmldoesn't just happen to be the default layout template for a Rails application's controllers. It happens this way because every controller inherits fromApplicationController. By default, a layout's name is derived from the name of the controller's class. SoEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Redirecting to a Different Location
- InhaltsvorschauYou want to redirect your user to another of your application's actions, or to an external URL.The class
ActionController::Base(superclass ofApplicationController) defines a method calledredirect_to, which performs an HTTP redirect. To redirect to another site, you can pass it a URL as a string. To redirect to a different action in your application, pass it a hash that specifies the controller, action, and ID.Here's aBureaucracyControllerthat shuffles incoming requests to and fro between various actions, finally sending the client to an external site:class BureaucracyController < ApplicationController def index redirect_to :controller => 'bureaucracy', :action => 'reservation_window' end def reservation_window redirect_to :action => 'claim_your_form', :id => 123 end def claim_your_form redirect_to :action => 'fill_out_your_form', :id => params[:id] end def fill_out_your_form redirect_to :action => 'form_processing' end def form_processing redirect_to "http://www.dmv.org/" end end
If you run the Rails server and hit http://localhost:3000/bureaucracy/ in your browser, you'll end up at http://www.dmv.org/. The Rails server log will show the chain of HTTP requests you made to get there:"GET /bureaucracy HTTP/1.1" 302 "GET /bureaucracy/reservation_window HTTP/1.1" 302 "GET /bureaucracy/claim_your_form/123 HTTP/1.1" 302 "GET /bureaucracy/fill_out_your_form/123 HTTP/1.1" 302 "GET /bureaucracy/form_processing HTTP/1.1" 302
You don't need to create view templates for all of these actions, because the body of an HTTP redirect isn't displayed by the web browser.Theredirect_tomethod uses smart defaults. If you give it a hash that doesn't specify a controller, it assumes you want to move to another action in the same controller. If you leave out the action, it assumes you are talking about theindexaction.From the simple redirects given in the Solution, you might think that callingredirect_toactually stops the action method in place and does an immediate HTTP redirect. This is not true. The action method continues to run until it ends or you call return. TheEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Displaying Templates with Render
- InhaltsvorschauRails's default mapping of one action method to one view template is not flexible enough for you. You want to customize the template that gets rendered for a particular action by calling Rails's rendering code directly.Rendering happens in the
ActionController::Base#rendermethod. Rails's default behavior is to callrenderafter the action method runs, mapping the action to a corresponding view template. Thefooaction gets mapped to thefoo.rhtmltemplate.You can callrenderfrom within an action method to make Rails render a different template. This controller defines two actions, both of which are rendered using theshopping_list.rhtmltemplate:class ListController < ApplicationController def index @list = ['papaya', 'polio vaccine'] render :action => 'shopping_list' end def shopping_list @list = ['cotton balls', 'amino acids', 'pie'] end end
By default,renderassumes that you are talking about the controller and action that are running whenrenderis called. If you callrenderwith no arguments, Rails will work the same way it usually does. But specifying'shopping_list'as the view overrides this default, and makes theindexaction use theshopping_list.rhtmltemplate, just like theshopping_listaction does.Although they use the same template, visiting theindexaction is not the same as visiting theshopping_listaction. They display different lists, becauseindexdefines a different list fromshopping_list.Recall from Recipe 15.4 that theredirectmethod doesn't perform an immediate HTTP redirect. It tells Rails to do a redirect once the current action method finishes running. Similarly, therendermethod doesn't do the rendering immediately. It only tells Rails which template to render when the action is complete.Consider this example:class ListController < ApplicationController def index render :action => 'shopping_list' @budget = 87.50 end def shopping_list @list = ['lizard food', 'baking soda'] end end
You might think that callingindexsets@listbut not@budget. Actually, the reverse is true. CallingEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Integrating a Database with Your Rails Application
- InhaltsvorschauYou want your web application to store persistent data in a relational database.The hardest part is setting things up: creating your database and hooking Rails up to it. Once that's done, database access is as simple as writing Ruby code.To tell Rails how to access your database, open your application's
config/database.ymlfile. Assuming your Rails application is calledmywebapp, it should look something like this:development: adapter: mysql database: mywebapp_development host: localhost username: root password: test: adapter: mysql database: mywebapp_test host: localhost username: root password: production: adapter: mysql database: mywebapp host: localhost username: root password:
For now, just make sure thedevelopmentsection contains a valid username and password, and that it mentions the correct adapter name for your type of database (see Chapter 13 for the list).Now create a database table. As with so much else, Rails does a lot of the database work automatically if you follow its conventions. You can override the conventions if necessary, but for now it's easiest to go along with them.The name of the table must be a pluralized noun: for instance, "people", "tasks", "items".The table must contain an auto-incrementing primary key field calledid.For this example, use a database tool or aCREATE DATABASESQL command to create amywebapp_developmentdatabase (see the chapter introduction for Chapter 13 if you need help doing this). Then create a table in that database calledpeople. Here's the SQL to create apeopletable in MySQL; you can adapt it for your database.use mywebapp_development; DROP TABLE IF EXISTS 'people'; CREATE TABLE 'people' ( 'id' INT(11) NOT NULL AUTO_INCREMENT, 'name' VARCHAR(255), 'email' VARCHAR(255), PRIMARY KEY (id) ) ENGINE=InnoDB;
Now go to the command line, change into the web application's root directory, and type ./script/generate model Person. This generates a Ruby class that knows how to manipulate thepeopletable.$ ./script/generate model Person exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/person.rb create test/unit/person_test.rb create test/fixtures/people.yml
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Understanding Pluralization Rules
- InhaltsvorschauYou want to understand and customize Rails's rules for automatically pluralizing nouns.You can use Rails' pluralization functionality in any part of your application, but ActiveRecord is the only major part of Rails that does pluralization automatically. ActiveRecord generally expects table names to be pluralized noun phrases and the corresponding model classes to be singular versions of the same noun phrases.So when you create a model class, you should always use a singular name. Rails automatically pluralizes:
- The corresponding table name for the model
has_manyrelationshas_and_belongs_to_manyrelations
For example, if you create aLineItemmodel, the table name automatically becomesline_items. Note also that the table name has been lowercased, and the word break indicated by the original camelcase is now conveyed with an underscore.If you then create anOrdermodel, the corresponding table needs to be calledorders. If you want to describe an order that has many line items, the code would look like this:class Order < ActiveRecord::Base has_many :line_items end
Like the name of the table it references, the symbol used in thehas_manyrelation is pluralized and underscored. The same goes for the other relationships between tables, likehas_and_belongs_to_many.ActiveRecord pluralizes these names to make your code read more like an English sentence:has_many :line_itemscan be read "has many line items". If pluralization confuses you, you can disable it by settingActiveRecord::Base.pluralize_table_namesto false. In Rails, the simplest way to do this is to put the following code inconfig/environment.rb:Rails::Initializer.run do |config| config.active_record.pluralize_table_names = false end
If your application knows specific words that ActiveRecord does not know how to pluralize, you can define your own pluralization rules by manipulating theInflectorclass. Let's say that the plural of "foo" is "fooze", and you've build an application to manage fooze. In Rails, you can specify this transformation by putting the following code inEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Creating a Login System
- InhaltsvorschauYou want your application to support a login system based on user accounts. Users will log in with a unique username and password, as in most commercial and community web sites.Create a
userstable that contains nonnull username and password fields. The SQL to create this table should look something like this MySQL example:use mywebapp_development; DROP TABLE IF EXISTS 'users'; CREATE TABLE 'users' ( 'id' INT(11) NOT NULL AUTO_INCREMENT, 'username' VARCHAR(255) NOT NULL, 'password' VARCHAR(40) NOT NULL, PRIMARY KEY ('id') );Enter the main directory of the application and generate aUsermodel corresponding to this table:$ ./script/generate model User exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/user.rb create test/unit/user_test.rb create test/fixtures/users.ymlOpen the generated file app/models/user.rb and edit it to look like this:class User < ActiveRecord::Base validates_uniqueness_of :username validates_confirmation_of :password, :on => :create validates_length_of :password, :within => 5..40 # If a user matching the credentials is found, returns the User object. # If no matching user is found, returns nil. def self.authenticate(user_info) find_by_username_and_password(user_info[:username], user_info[:password]) end end
Now you've got aUserclass that represents a user account, and a way of validating a username and password against the one stored in the database.The simpleUsermodel given in the Solution defines a method for doing username/password validation, and some validation rules that impose limitations on the data to be stored in theuserstable. These validation rules tellUserto:- Ensure that each username is unique. No two users can have the same username.
- Ensure that, whenever the
passwordattribute is being set, thepassword_confirmationattribute has the same value. - Ensure that the value of the
passwordattribute is between 5 and 40 characters long.
Now let's create a controller for this model. It'll have aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Storing Hashed User Passwords in the Database
- InhaltsvorschauThe database table defined in Recipe 15.8 stores users' passwords as plain text. This is a bad idea: if someone compromises the database, she will have all of your users' passwords. It's best to store a secure hash of the password instead. That way, you don't have the password (so no one can steal it), but you can verify that a user knows his password.Recreate the
userstable from Recipe 15.8 so that instead of apasswordfield, it has ahashed_password field. Here's some MySQL code to do that:use mywebapp_development; DROP TABLE IF EXISTS 'users'; CREATE TABLE 'users' ( 'id' INT(11) NOT NULL AUTO_INCREMENT, 'username' VARCHAR(255) NOT NULL, 'hashed_password' VARCHAR(40) NOT NULL, PRIMARY KEY (id) );
Open the file app/models/user.rb created in Recipe 15.8, and edit it to look like this:require 'sha1' class User < ActiveRecord::Base attr_accessor :password attr_protected :hashed_password validates_uniqueness_of :username validates_confirmation_of : password, :if => lambda { |user| user.new_record? or not user.password.blank? } validates_length_of :password, :within => 5..40, :if => lambda { |user| user.new_record? or not user.password.blank? } def self.hashed(str) SHA1.new(str).to_s end # If a user matching the credentials is found, returns the User object. # If no matching user is found, returns nil. def self.authenticate(user_info) user = find_by_username(user_info[:username]) if user && user.hashed_password == hashed(user_info[:password]) return user end end private before_save :update_password # Updates the hashed_password if a plain password was provided. def update_password if not password.blank? self.hashed_password = self.class.hashed(password) end end endOnce you do this, your application will work as before (though you'll have to convert any preexisting user accounts to the new password format). You don't need to modify any of the controller or view code, because theUser.authenticatemethod works the same way it did before. This is one of the benefits of separating business logic from presentation logic.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Escaping HTML and JavaScript for Display
- InhaltsvorschauYou want to display data that might contain HTML or JavaScript without making browsers render it as HTML or interpret the JavaScript. This is especially important when displaying data entered by users.Pass a string of data into the
h()helper function to escape its HTML entities. That is, instead of this:<%= @data %>
Write this:<%=h @data %>
Theh()helper function converts the following characters into their HTML entity equivalents: ampersand (&), double quote ("), left angle bracket (<), and right angle bracket (>).You won't find the definition for theh()helper function anywhere in the Rails source code, because it's a shortcut for ERb's built-in helper functionhtml_escape().JavaScript is deployed within HTML tags like<SCRIPT>, so escaping an HTML string will neutralize any JavaScript in the HTML. However, sometimes you need to escape just the JavaScript in a string. Rails adds a helper function calledescape_javascript()that you can use. This function doesn't do much: it just turns line breaks into the string"\n", and adds backslashes before single and double quotes. This is handy when you want to use arbitrary data in your own JavaScript code:<!-- index.rhtml --> <script lang="javascript"> var text = "<%= escape_javascript @javascript_alert_text %>"; alert(text); </script>
- Chapter 11
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Setting and Retrieving Session Information
- InhaltsvorschauYou want to associate some data with each distinct web client that's using your application. The data needs to persist across HTTP requests.You can use cookies (see Recipe 15.12) but it's usually simpler to put the data in a user's session. Every visitor to your Rails site is automatically given a session cookie. Rails keys the value of the cookie to a hash of arbitrary data on the server.Throughout your entire Rails application, in controllers, views, helpers, and mailers, you can access this hash by calling a method called
session. The objects stored in this hash are persisted across requests by the same web browser.This code in a controller tracks the time of a client's first visit to your web site:class IndexController < ApplicationController def index session[:first_time] ||= Time.now end end
Within your view, you can write the following code to display the time:<!-- index.rhtml --> You first visited this site on <%= session[:first_time] %>. That was <%= time_ago_in_words session[:first_time] %> ago.
Cookies and sessions are very similar. They both store persistent data about a visitor to your site. They both let you implement stateful operations on top of HTTP, which has no state of its own. The main difference between cookies and sessions is that with cookies, all the data is stored on your visitors' computers in little cookie files. With sessions, all the data is stored on the web server. The client only keeps a small session cookie, which contains a unique ID that's tied to the data on the server. No personal data is ever stored on the visitor's computer.There are a number of reasons why you might want to use sessions instead of cookies:- A cookie can only store four kilobytes of data.
- A cookie can only store a string value.
- If you store personal information in a cookie, it can be intercepted unless all of a client's requests are encrypted with SSL. Even then, cross-site scripting attacks may be able to read the client cookie and retrieve the sensitive information.
On the other hand, cookies are useful when:- The information is not sensitive and not very large.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Setting and Retrieving Cookies
- InhaltsvorschauYou want to set a cookie from within Rails.Recall from Recipe 15.11 that all Rails controllers, views, helpers, and mailers have access to a method called
sessionsthat returns a hash of the current client's session information. Your controllers, helpers, and mailers (but not your views) also have access to a method calledcookies, which returns a hash of the current client's HTTP cookies.To set a cookie for a user, simply set a key/value pair in that hash. For example, to keep track of how many pages a visitor has looked at, you might set a "visits" cookie:class ApplicationController < ActionController::Base before_filter :count_visits private def count_visits value = (cookies[:visits] || '0').to_i cookies[:visits] = (value + 1).to_s @visits = cookies[:visits] end end
The call tobefore_filtertells Rails to run this method before calling any action method. Theprivatedeclaration makes sure that Rails doesn't think thecount_visitsmethod is itself an action method that the public can view.Since cookies are not directly available to views,count_visitsmakes the value of the:visitscookie available as the instance variable@visits. This variable can be accessed from a view:<!-- index.rhtml --> You've visited this website's pages <%= @visits %> time(s).
HTTP cookie values can only be strings. Rails can automatically convert some values to strings, but it's safest to store only string values incookies. If you need to store objects that can't easily be converted to and from strings, you should probably store them in thesessionhash instead.There may be times when you want more control over your cookies. For instance, Rails cookies expire by default when the user closes their browser session. If you want to change the browser expiration time, you can givecookiesa hash that contains an :expireskey and a time to expire the cookie. The following cookie will expire after one hour:cookies[:user_id] = { :value => '123', :expires => Time.now + 1.hour}Here are some other options for a cookie hash passed intoEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Extracting Code into Helper Functions
- InhaltsvorschauYour views are getting cluttered with Ruby code.Let's create a controller with a fairly complex view to see how this can happen:
$ ./scripts/generate controller list index exists app/controllers/ exists app/helpers/ create app/views/list exists test/functional/ create app/controllers/list_controller.rb create test/functional/list_controller_test.rb create app/helpers/list_helper.rb create app/views/list/index.rhtml
Editapp/controllers/list_controller.rbto look like this:class ListController < ApplicationController def index @list = [1, "string", :symbol, ['list']] end end
Editapp/views/list/index.rhtmlto contain the following code. It iterates over each element in@list, and prints out its index and the SHA1 hash of its object ID:<!-- app/views/list/index.rhtml --> <ul> <% @list.each_with_index do |item, i| %> <li class="<%= i%2==0 ? 'even' : 'odd' %>"><%= i %>: <%= SHA1.new(item.id.to_s) %></li> <% end %> </ul>
This is pretty messy, but if you've done much web programming it should also look sadly familiar.To clean up this code, we're going to move some of it into the helper for the controller. In this case, the controller is calledlist, so its helper lives inapp/helpers/list_helper.rb.Let's create a helper function calledcreate_li. Given an object and its position in the list, this function creates an<LI>tag suitable for use in theindexview:module ListHelper def create_li(item, i) %{<li class="#{ i%2==0 ? 'even' : 'odd' }">#{i}: #{SHA1.new(item.id.to_s)}</li>} end endThelistcontroller's views have access to all the functions defined inListHelper. We can clean up theindexview like so:<!-- app/ views/list/index.rhtml --> <ul> <% @list.each_with_index do |item, i| %> <%= create_li(item, i) %> <% end %> </ul>
Your helper functions can do anything you can normally do from within a view, so they are a great way to abstract out the heavy lifting.The purpose of helper functions is to create more maintainable code, and to enforce a good division of labor between the programmers and the UI designers. Maintainable code is easier for the programmers to work on, and when it's in helper functions it's out of the way of the designers, who can tweak the HTML here and there without having to sifting through code.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Refactoring the View into Partial Snippets of Views
- InhaltsvorschauYour view doesn't contain a lot of Ruby code, but it's still becoming more complicated than you'd like. You'd like to refactor the view logic into separate, reusable templates.You can refactor a view template into multiple templates called partials. One template can include another by calling the
rendermethod, first seen in Recipe 15.5.Let's start with a more complex version of the view shown in Recipe 15.5:<!-- app/ views/list/shopping_list.rhtml --> <h2>My shopping list</h2> <ul> <% @list.each do |item| %> <li><%= item.name %> <%= link_to 'Delete', {:action => 'delete', :id => item.id}, :post => true %> </li> <% end %> </ul> <h2>Add a new item</h2> <%= form_tag :action => 'new' %> Item: <%= text_field "product", "name" %>
 <%= submit_tag "Add new item" %> <%= end_form_tag %>Here's the corresponding controller class, and a dummyListItemclass to serve as the model:# app/controllers/list_controller.rb class ListController < ActionController::Base def shopping_list @list = [ListItem.new(4, 'aspirin'), ListItem.new(199, 'succotash')] end # Other actions go here: add, delete, etc. # … end class ListItem def initialize(id, name) @id, @name = id, name end end
The view has two parts: the first part lists all the items, and the second part prints a form to add a new item. An obvious first step is to split out the new item form.We can do this by creating a partial view to print the new item form. To do this, create a new file within app/ views/list/ called _new_item_form.rhtml. The underscore in front of the filename indicates that it is a partial view, not a full-fledged view for an action callednew_item_form. Here's the partial file.<!-- app/ views/list/_new_item_form.rhtml --> <%= form_tag :action => 'new' %> Item: <%= text_field "item", "value" %>
 <%= submit_tag "Add new item" %> <%= end_form_tag %>
To include a partial, call therendermethod from within a template. Here is the _new_item_formpartial integrated into the main view. The view looks exactly the same, but the code is better organized.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Adding DHTML Effects with script.aculo.us
- InhaltsvorschauYou want to add fancy effects such as fades to your application, without writing any JavaScript.Every Rails application comes bundled with some JavaScript libraries that allow you to create Ajax and DHTML effects. You don't even have to write JavaScript to enable DHTML in your Rails web site.First edit your main layout template (see Recipe 15.3) to call
javascript_include_tagwithin your<HEAD>tag:<!-- app/views/layouts/application.rhtml --> <html> <head> <title>My Web App</title><%= javascript_include_tag "prototype", "effects" %> </head> <body> <%= @content_for_layout %> </body> </html>Now within your views you can call thevisual_effectmethod to accomplish the DHTML tricks found in thescript.aculo.uslibrary.Here's an example of the "highlight" effect:<p id="important">Here is some important text, it will be highlighted when the page loads.</p> <script type="text/javascript"> <%= visual_effect(:highlight, "important", :duration => 1.5) %> </script>
Here's an example of the "fade" effect:<p id="deleted">Here is some old text, it will fade away when the page loads.</p> <script type="text/javascript"> <%= visual_effect(:fade, "deleted", :duration => 1.0) %> </script>
The sample code snippets above are triggered when the page loads, because they're enclosed in<SCRIPT>tags. In a real application, you'll probably display text effects in response to user actions: deleted items might fade away, or the selection of one item might highlight related items. Here's an image that gets squished when you click the link below it:<img id="to-squish" src="bug.jpg"> <%=link_to_function("Squish the bug!", visual_effect(:squish, "to-squish"))%>The JavaScript code generated by thevisual_effectmethod looks a lot like the arguments you passed into the method. For instance, this piece of a Rails view:<script type="text/javascript"> <%= visual_effect(:fade, 'deleted-text', :duration => 1.0) %> </script>
Generates this JavaScript:<script type="text/javascript"> new Effect.Fade("deleted-text", {duration:1.0}); </script>This similarity means that documentation for theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Generating Forms for Manipulating Model Objects
- InhaltsvorschauYou want to define actions that let a user create or edit objects stored in the database.Let's create a simple model, and then build forms for it. Here's some MySQL code to create a table of key-value pairs:
use mywebapp_development; DROP TABLE IF EXISTS items; CREATE TABLE `items` ( 'id' int(11) NOT NULL auto_increment, 'name' varchar(255) NOT NULL default '', 'value' varchar(40) NOT NULL default '[empty]', PRIMARY KEY ('id') );Now, from the command line, create the model class, along with a controller and views:$ ./script/generate model Item exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/item.rb create test/unit/item_test.rb create test/fixtures/items.yml create db/migrate create db/migrate/001_create_items.rb $ ./script/generate controller items new create edit exists app/controllers/ exists app/helpers/ create app/views/items exists test/functional/ create app/controllers/items_controller.rb create test/functional/items_controller_test.rb create app/helpers/items_helper.rb create app/views/items/new.rhtml create app/views/items/edit.rhtml
The first step is to customize a view. Let's start with app/views/items/new.rhtml. Edit it to look like this:<!-- app/views/items/new.rhtml --> <%= form_tag :action => "create" %> Name: <%= text_field "item", "name" %><br /> Value: <%= text_field "item", "value" %><br /> <%= submit_tag %> <%= end_form_tag %>
All these method calls generate HTML:form_tagopens a<FORM>tag,submit_taggenerates a submit button, and so on. You can type out the same HTML by hand and Rails won't care, but it's easier to make method calls, and it makes your templates neater.Thetext_fieldcall is a little more involved. It creates an<INPUT>tag that shows up in the HTML form as a text entry field. But it also binds the value of that field to one of the members of the@iteminstance variable. This code creates a text entry field that's bound to thenamemember of@itemEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Creating an Ajax Form
- InhaltsvorschauYou want to build a web application that's responsive and easy to use. You don't want your users to spend lots of time waiting around for the browser to redraw the screen.You can use JavaScript to make the browser's
XMLHTTPRequestobject send data to the server, without dragging the user through the familiar (but slow) page refresh. This technique is called Ajax, and Rails makes it easy to use Ajax without writing or knowing any JavaScript.Before you can do Ajax in your web application, you must edit your application's main layout template so that it calls thejavascript_include_tagmethod within its<HEAD>tag. This is the same change made in Recipe 15.15:<!-- app/views/layouts/application.rhtml --> <html> <head> <title>My Web App</title><%= javascript_include_tag "prototype", "effects" %> </head> <body> <%= @content_for_layout %> </body> </html>Let's change the application from Recipe 15.16 so that thenewaction is AJAXenabled (if you followed that recipe all the way through, and made theeditaction usenew.rhtmlinstead ofedit.rhtml, you'll need to undo that change and makeedituse its own view template).We'll start with the view template. Edit app/views/items/new.rhtml to look like this:<!-- app/views/items/new.rhtml --><div id="show_item"></div> <%= form_remote_tag :url => { :action => :create }, :update => "show_item", :complete => visual_effect(:highlight, "show_item") %> Name: <%= text_field "item", "name" %><br /> Value: <%= text_field "item", "value" %><br /> <%= submit_tag %> <%= end_form_tag %>Those small changes make a standard HTML form into an Ajax form. The main difference is that we callform_remote_taginstead ofform_tag. The other differences are the arguments we pass into that method.The first change is that we put the :actionparameter inside a hash passed into the :urloption. Ajax forms have more options associated with them than a normal form, so you can't describe its form action as simply as you can withform_tag.When the user clicks the submit button, the form values are serialized and sent to the destination action (in this case,Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Exposing Web Services on Your Web Site
- InhaltsvorschauYou want to offer SOAP and XML-RPC web services from your web application.Rails comes with a built-in web service generator that makes it easy to expose a controller's actions as web services. You don't have to spend time writing WSDL files or even really know how SOAP and XML-RPC work.Here's a simple example. First, follow the directions in Recipe 15.16 to create a database table named
items, and to generate a model for that table. Don't generate a controller.Now, run this from the command line:./script/generate web_service Item add edit fetch create app/apis/ exists app/controllers/ exists test/functional/ create app/apis/item_api.rb create app/controllers/item_controller.rb create test/functional/item_api_test.rb
This creates anitemcontroller that supports three actions:add, edit, andfetch. But instead of web application actions with .rhtmlviews, these are web service actions that you access with SOAP or XML-RPC.A Ruby method doesn't care about the data types of the objects it accepts as arguments, or the data type of its return value. But a SOAP or XML-RPC web service method does care. To expose a Ruby method through a SOAP or XML-RPC interface, we need to define type information for its signature. Open up the file app/apis/item_api.rb and edit it to look like this:class ItemApi < ActionWebService::API::Base api_method :add, :expects => [:string, :string], :returns => [:int] api_method :edit, :expects => [:int, :string, :string], :returns => [:bool] api_method :fetch, :expects => [:int], :returns => [Item] end
Now we need to implement the actual web service interface. Open app/controllers/item_controller.rb and edit it to look like this:class ItemController < ApplicationController wsdl_service_name 'Item' def add(name, value) Item.create(:name => name, :value => value).id end def edit(id, name, value) Item.find(id).update_attributes(:name => name, :value => value) end def fetch(id) Item.find(id) end end
Theitemcontroller now implements SOAP and XML-RPC web services for theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Sending Mail with Rails
- InhaltsvorschauYou want to send an email from within your Rails application: perhaps a confirmation of an order, or notification that some action has been taken on a user's behalf.The first is to generate some mailer infrastructure. Go to the application's base directory and type this command:
./script/generate mailer Notification welcome exists app/models/ create app/views/notification exists test/unit/ create test/fixtures/notification create app/models/notification.rb create test/unit/notification_test.rb create app/views/notification/welcome.rhtml create test/fixtures/notification/welcome
We're giving the name "Notification" to the mailing center of the application; it's somewhat analogous to a controller in the web interface. The mailer is set up to generate a single email, called "welcome": this is analagous to an action with a view template.Now openapp/models/notification.rband edit it to look like this:class Notification < ActionMailer::Base def welcome(user, sent_at=Time.now) @subject = 'A Friendly Welcome' @recipients = user.email @from = 'admin@mysite.com' @sent_on = sent_at @body = { :user => user, :sent_on => sent_at } attachment 'text/plain' do |a| a.body = File.read('rules.txt') end end endThe subject of the email is "A Friendly Welcome", and it's sent to the user's email address from the address "admin@mysite.com". It's got an attachment taken from the disk filerules.txt(relative to the root directory of your Rails application).Although the filenotification.rbis within themodels/directory, it acts like a controller in that each of its email messages has an associated view template. The view for the welcome email is inapp/views/notification/welcome.rhtml, and it acts almost the same as the view of a normal controller.The most important difference is that mailer views do not have access to the instance variables of the mailer. To set instance variables for mailers, you pass a hash of those variables to thebodymethod. The keys become instance variable names and the values become their values. InEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Automatically Sending Error Messages to Your Email
- InhaltsvorschauYou want to receive a descriptive email message every time one of your users encounters an application error.Any errors that occur while running your application are sent to the
ActionController::Base#log_errormethod. If you've set up a mailer (as shown in Recipe 15.19) you can override this method and have it send mail to you. Your code should look something like this:class ApplicationController < ActionController::Base private def log_error(exception) super Notification.deliver_error_message(exception, clean_backtrace(exception), session.instance_variable_get("@data"), params, request.env ) end endThat code rounds up a wide variety of information about the state of the Rails request at the time of the failure. It captures the exception object, the corresponding backtrace, the session data, the CGI request parameters, and the values of all environment variables.The overriddenlog_errorcallsNotification.deliver_error_messsage, which assumes you've created a mailer called "Notification", and defined the methodNotification.error_message. Here's the implementation:class Notification < ActionMailer::Base def error_message(exception, trace, session, params, env, sent_on = Time.now) @recipients = 'me@mydomain.com' @from = 'error@mydomain.com' @subject = "Error message: #{env['REQUEST_URI']}" @sent_on = sent_on @body = { :exception => exception, :trace => trace, :session => session, :params => params, :env => env } end endThe template for this email looks like this:<!-- app/views/notification/error_message.rhtml --> Time: <%= Time.now %> Message: <%= @exception.message %> Location: <%= @env['REQUEST_URI'] %> Action: <%= @params.delete('action') %></td></tr> Controller: <%= @params.delete('controller') %></td></tr> Query: <%= @env['QUERY_STRING'] %></td></tr> Method: <%= @env['REQUEST_METHOD'] %></td></tr> SSL: <%= @env['SERVER_PORT'].to_i == 443 ? "true" : "false" %> Agent: <%= @env['HTTP_ USER_AGENT'] %> Backtrace <%= @trace.to_a.join("</p>\n<p>") %> Params <% @params.each do |key, val| -%> * <%= key %>: <%= val.to_yaml %> <% end -%> Session <% @session.each do |key, val| -%> * <%= key %>: <%= val.to_yaml %> <% end -%> Environment <% @env.each do |key, val| -%> * <%= key %>: <%= val %> <% end -%>Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Documenting Your Web Site
- InhaltsvorschauYou want to document the controllers, models, and helpers of your web application so that the developers responsible for maintaining the application can understand how it works.As with any other Ruby program, you document a Rails application by adding specially-formatted commands to your code. Here's how to add documentation to the
FooControllerclass and one of its methods:# The FooController controller contains miscellaneous functionality # rejected from other controllers. class FooController < ApplicationController # The set_random action sets the @random_number instance variable # to a random number. def set_random @random_number = rand*rand end end
The documentation for classes and methods goes before their declaration, not after.When you've finished adding documentation comments to your application, go to your Rails application's root directory and issue therake appdoccommand:$ rake appdoc
This Rake task runs RDoc for your Rails application and generates a directory calleddoc/app. This directory contains a web site with the aggregate of all your documentation comments, cross-referenced against the source code. Open thedoc/app/index.rhtmlfile in any web browser, and you can browse the generated documentation.Your RDoc comments can contain markup and special directives: you can describe your arguments in definition lists, and hide a class or method from documentation with the :nodoc: directive. This is covered in Recipe 17.11.The only difference between Rails applications and other Ruby programs is that Rails comes with a Rakefile that defines anappdoctask. You don't have to find or write one yourself.You probably already put inline comments inside your methods, describing the action as it happens. Since the RDoc documentation contains a formatted version of the original source code, these comments will be visible to people going through the RDoc. These comments are formatted as Ruby source code, though, not as RDoc markup.- Recipe 17.11, "Documenting Your Application"
- Chapter 19, especially Recipe 19.2, "Automatically Generating Documentation"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Unit Testing Your Web Site
- InhaltsvorschauYou want to create a suite of automated tests that test the functionality of your Rails application.Rails can't write your test code any more than it can write your views and controllers for you, but it does make it easy to organize and run your automated tests.When you use the
./script/generatecommand to create controllers and models, not only do you save time, but you also get a generated framework for unit and functional tests. You can get pretty good test coverage by filling in the framework with tests for the functionality you write.So far, all the examples in this chapter have run against a Rails application's development database, so you only needed to make sure that thedevelopmentsection of your config/database.yml file was set up correctly. Unit test code runs on your application's test database, so now you need to set up yourtestsection as well. Yourmywebapp_testdatabase doesn't have to have any tables in it, but it must exist and be accessible to Rails.When you generate a model with thegeneratescript, Rails also generates a unit test script for the model in the test directory. It also creates a fixture, a YAML file containing test data to be loaded into themywebapp_testdatabase. This is the data against which your unit tests will run:./script/generate model User exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/user.rb create test/unit/user_test.rb create test/fixtures/users.yml create db/migrate create db/migrate/001_create_users.rb
When you generate a controller withgenerate, Rails creates a functional test script for the controller:./script/generate users list exists app/controllers/ exists app/helpers/ create app/views/users exists test/functional/ create app/controllers/users_controller.rbcreate test/functional/users_controller_test.rb create app/helpers/users_helper.rb create app/views/users/list.rhtmlAs you write code in the model and controller classes, you'll write corresponding tests in these files.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using breakpoint in Your Web Application
- InhaltsvorschauYour Rails application has a bug that you can't find using log messages. You need a heavy-duty debugging tool that lets you inspect the full state of your application at any given point.The
breakpointlibrary lets you stop the flow of code and drop intoirb, an interactive Ruby session. Withinirbyou can inspect the variables local to the current scope, modify those variables, and resume execution of the normal flow of code. If you have ever spent hours trying to track down a bug by placing logging messages everywhere, you'll find thatbreakpointgives you a much easier and more straightforward way to debug.But how can you run an interactive console program from a web application? The answer is to have a console program running beforehand, listening for calls from the Rails server.The first step is to run./script/breakpointeron the command line. This command starts a server that listens over the network for breakpoint calls from the Rails server. Keep this program running in a terminal window: this is where the irb session will start up:$ ./script/breakpointer No connection to breakpoint service at druby://localhost:42531 Tries to connect will be made every 2 seconds…
To trigger anirbsession, you can call thebreakpointmethod anywhere you like from your Rails application—within a model, controller, or helper method. When execution reaches that point, processing of the incoming client request will stop, and anirbsession will start in your terminal. When you quit the session, processing of the request will resume.Here's an example. Let's say you've written the following controller, and you're having trouble modifying the name attribute of anItemobject.class ItemsController < ApplicationController def update @item = Item.find(params[:id]) @item.value = '[default]' @item.name = params[:name] @item.save render :text => 'Saved' end end
You can put abreakpointcall in theItemclass, like this:class Item < ActiveRecord::Base attr_accessor :name, :value def name=(name) super breakpoint end end
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 16: Web Services and Distributed Programming
- InhaltsvorschauDistributed programming is like network programming—only the audience is different. The point of network programming is to let a human control a computer across the network. The point of distributed programming is to let computers communicate between themselves.Humans use networking software to get data and use algorithms they don't have on their own computers. With distributed programming, automated programs can get in on this action. The programs are (one hopes) designed for the ultimate benefit of humans, but an end user doesn't see the network usage or even neccessarily know that it's happening.The simplest and most common form of distributed programming is the web service. Web services work on top of HTTP: they generally involve sending an HTTP request to a certain URL (possibly including an XML document), and getting a response in the form of another XML document. Rather than showing this document to an end user the way a web browser would, the web service client parses the XML response document and does something with it.We start the chapter with a number of recipes that show how to provide and use web services. We include generic recipes like Recipe 16.3, and recipes for using specific, existing web services like Recipes 16.1, 16.6, and 16.9. The specific examples are useful in their own right, but they should also help you see what kind of features you should expose in your own web services.There are three main approaches to web services: REST-style services, XML-RPC, and SOAP. You don't need any special tools to offer or use REST-style services. On the client end, you just need a scriptable web client (Recipe 14.1) and an XML parser (Recipes 11.2 and 11.3). On the server side, you just write a web application that knows how to generate XML (Recipe 11.9). We cover some REST philosophy while exploring useful services in Recipe 16.1 and Recipe 16.2.REST is HTTP; XML-RPC and SOAP are protocols that run on top of HTTP. We've devoted several recipes to Ruby's SOAP client: Recipes 16.4 and 16.7 are the main ones. Ruby's standalone SOAP server is briefly covered in Recipe 16.5. Rails provides its own SOAP server (Recipe 15.18), which incidentally also acts as an XML-RPC server.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
- Searching for Books on Amazon
- InhaltsvorschauYou want to incorporate information about books or other cultural artifacts into your application.Amazon.com exposes a web service that gives you access to all kinds of information on books, music, and other media. The third-party Ruby/ Amazon library provides a simple Ruby interface to the Amazon web service.Here's a simple bit of code that searches for books with Ruby/Amazon, printing their new and used prices.
require 'amazon/search' $AWS_KEY = 'Your AWS key goes here' # See below. def price_books(keyword) req = Amazon::Search::Request.new($AWS_KEY) req.keyword_search(keyword, 'books', Amazon::Search::LIGHT) do |product| newp = product.our_price || 'Not available' usedp = product.used_price || 'not available' puts "#{product.product_name}: #{newp} new, #{usedp} used." end end price_books('ruby cookbook') # Ruby Cookbook (Cookbooks (O'Reilly)): $31.49 new, not available used. # Rails Cookbook (Cookbooks (O'Reilly)): $25.19 new, not available used. # Ruby Ann's Down Home Trailer Park Cookbook: $10.85 new, $3.54 used. # Ruby's Low-Fat Soul-Food Cookbook: Not available new, $12.43 used. # …To save bandwidth, this code asks Amazon for a "light" set of search results. The results won't include things like customer reviews.What's going on here? In one sense, it doesn't matter. Ruby/Amazon gives us a Ruby method that somehow knows about books and their Amazon prices. It's getting its information from a database somewhere, and all we need to know is how to query that database.In another sense, it matters a lot, because this is just one example of a REST-style web service. By looking under the cover of the Amazon web services, you can see how to use other REST-style services like the ones provided by Yahoo! and Flickr.REST-style web services operate directly on top of HTTP. Each URL in a REST system designates a resource or a set of them. When you callkeyword_search, Ruby/ Amazon retrieves a URL that looks something like this:http://xml.amazon.com/onca/xml3?KeywordSearch=ruby+cookbook&mode=books…
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Finding Photos on Flickr
- InhaltsvorschauYou want to use Ruby code to find freely reusable photos: perhaps to automatically illustrate a piece of text.The Flickr photo-sharing web site has a huge number of photos and provides web services for searching them. Many of the photos are licensed under Creative Commons licenses, which give you permission to reuse the photos under various restrictions.There are several Ruby bindings to Flickr's various web service APIs, but its REST API is so simple that I'm just going to use it directly. Given a tag name (like "elephants"), this code will find an appropriate picture, and return the URL to a thumbnail version of the picture.First, a bit of setup. As with Amazon and Google, to use the Flickr API at all you'll need to sign up for an API key (see below for details).
require 'open-uri' require 'rexml/document' require 'cgi' FLICKR_API_KEY = 'Your API key here'
The first method,flickr_call, sends a generic query to Flickr's REST web service. It doesn't do anything special: it just makes an HTTP GET request and parses the XML response.def flickr_call(method_name, arg_map={}.freeze) args = arg_map.collect {|k,v| CGI.escape(k) + '=' + CGI.escape(v)}.join('&') url = "http://www.flickr.com/services/rest/?api_key=%s&method=%s&%s" % [FLICKR_API_KEY, method_name, args] doc = REXML::Document.new(open(url).read) endNow comespick_a_photo, a method that usesflickr_callto invoke theflickr.photos.searchweb service method. That method returns a REXMLDocumentobject containing a<photo>element for each photo that matched the search criteria. I use XPath to grab the first<photo>element, and pass it intosmall_photo_url(defined below) to turn it into an image URL.def pick_a_photo(tag) doc = flickr_call('flickr.photos.search', 'tags' => tag, 'license' => '4', 'per_page' => '1') photo = REXML::XPath.first(doc, '//photo') small_photo_url(photo) if photo endFinally, I'll define the method,small_photo_url. Given a<photo>element, it returns the URL to a smallish version of the appropriate Flickr photo.def small_photo_url(photo) server, id, secret = ['server', 'id', 'secret'].collect do |field| photo.attribute(field) end "http://static. flickr.com/#{server}/#{id}_#{secret}_m.jpg" endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Writing an XML-RPC Client
- InhaltsvorschauCredit: John-Mason ShackelfordYou want to call a remote method through the XML-RPC web service protocol.Use Michael Neumann's
xmlrpc4rlibrary, found in Ruby's standard library.Here's the canonical simple XML-RPC example. Given a number, it looks up the name of a U.S. state in an alphabetic list:require 'xmlrpc/client' server = XMLRPC::Client.new2('http://betty.userland.com/RPC2') server.call('examples.getStateName', 5) # => "California"XML-RPC is a language-independent solution for distributed systems that makes a simple alternative to SOAP (in fact, XML-RPC is an ancestor of SOAP). Although it's losing ground to SOAP and REST-style web services, XML-RPC is still used by many blogging engines and popular web services, due to its simplicity and relatively long history.A XML-RPC request is sent to the server as a specially-formatted HTTP POST request, and the XML-RPC response is encoded in the HTTP response to that request. Since most firewalls allow HTTP traffic, this has the advantage (and disadvantage) that XML-RPC requests work through most firewalls. Since XML-RPC requests are POST requests, typical HTTP caching solutions (which only cache GETs) can't be used to speed up XML-RPC requests or save bandwidth.An XML-RPC request consists of a standard set of HTTP headers, a simple XML document that encodes the name of a remote method to call, and the parameters to pass to that method. Thexmlrpc4rlibrary automatically converts between most XML-RPC data types and the corresponding Ruby data types, so you can treat XML-RPC calls almost like local method calls. The main exceptions are date and time objects. You can pass a RubyDateorTimeobject into an XML-RPC method that expects adateTime.iso8601parameter, but a method that returns a date will always be represented as an instance ofXMLRPC::DateTime.Table 16-1 lists the supported data types of the request parameters and the response.Table 16-1: Supported data types Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Writing a SOAP Client
- InhaltsvorschauCredit: Kevin MarshallYou need to call a remote method through a SOAP-based web service.Use the SOAP RPC Driver in the Ruby standard library.This simple program prints a quote of the day. It uses the SOAP RPC Driver to connect to the SOAP web service at codingtheweb.com.
require 'soap/rpc/driver' driver = SOAP::RPC::Driver.new( 'http://webservices.codingtheweb.com/bin/qotd', 'urn:xmethods-qotd')
Once the driver is set up, we define the web service method we want to call (getQuote). We can then call it like a normal Ruby method and display the result:driver.add_method('getQuote') puts driver.getQuote # The holy passion of Friendship is of so sweet and steady and # loyal and enduring a nature that it will last through a whole # lifetime, if not asked to lend money. # Mark Twain (1835 - 1910)SOAP is a heavyweight protocol for web services, a distant descendant of XML-RPC. As with XML-RPC, a SOAP client sends an XML representation of a method call to a server, and gets back an XML representation of a return value. The whole process is more complex than XML-RPC, but Ruby's built-in SOAP library handles the low-level details for you, leaving you free to focus on using the results in your program.There are only a few things you need to know to build useful SOAP clients (as I run through them, I'll build another SOAP client; this one is to get stock quotes):- The location of the web service (known as the endpoint URL) and the namespace used by the service's documents.
require 'soap/rpc/driver' driver = SOAP::RPC::Driver.new( 'http://services.xmethods.net/soap/', # The endpoint url 'urn:xmethods-delayed-quotes') # The namespace
- The name of the SOAP method you want to call, and the names of its parameters.
driver.add_method('getQuote', 'symbol')Behind the scenes, that call toadd_methodactually defines a new method on theSOAP::RPC::Driverobject. The SOAP library uses metaprogramming to create custom Ruby methods that act like SOAP methods.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Writing a SOAP Server
- InhaltsvorschauCredit: Kevin MarshallYou want to host a SOAP-based web service using a standalone server (that is, not as part of a Rails application).Building your own SOAP server really only requires three simple steps:
- Subclass the
SOAP::StandaloneServerclass. In the constructor, register the methods you want to expose and the arguments they should take. Here we expose a methodsayhellotomethod that expects one parameter,username:require 'soap/rpc/standaloneServer' class MyServer < SOAP::RPC::StandaloneServer def initialize(*args) super add_method(self, 'sayhelloto', 'username') end end
- Define the methods you exposed in step 1:
class MyServer def sayhelloto(username) "Hello, #{username}." end end - Finally, set up and start your server. Our example server runs on port 8888 on localhost. Its name is "CoolServer" and its namespace is "urn:mySoapServer":
server = MyServer.new('CoolServer','urn:mySoapServer','localhost',8888) trap('INT') { server.shutdown } server.startWe trap interrupt signals so that we can stop our server from the command line.
We've now built a complete SOAP server. It uses the SOAPStandaloneServerand hosts one simplesayhellotomethod that can be accessed at "http://localhost:8888/sayhelloto" with a namespace of "urn:mySoapServer".To test your service, start your server in one Ruby session and then use the simple script below in another Ruby session to call the method it exposes:require 'soap/rpc/driver' driver = SOAP::RPC::Driver.new('http://localhost:8888/', 'urn:mySoapServer') driver.add_method('sayhelloto', 'username') driver.sayhelloto('Kevin') # => "Hello, Kevin."- Recipe 15.18, "Exposing Web Services on Your Web Site," shows how to use the XML-RPC/ SOAP server that comes with Rails
- For information on building web service clients, see Recipes 16.2 through 16.3, 16.4 and 16.7.
- Ruby on Rails by Bruce A. Tate and Curt Hibbs (O'Reilly)
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Searching the Web with Google's SOAP Service
- InhaltsvorschauYou want to use Google's web services to perform searches and grab their results within your Ruby application.Google exposes a SOAP API to its search functionality, and some other miscellaneous methods like spellcheck. Call these methods with the SOAP client that comes with Ruby's standard library:
$KCODE = 'u' # This lets us parse UTF characters require 'soap/wsdlDriver' class Google @@key = 'JW/JqyXMzCsv7k/dxqR9E9HF+jiSgbDL' # Get a key at http://www.google.com/apis/ @@driver = SOAP::WSDLDriverFactory. new('http://api.google.com/GoogleSearch.wsdl').create_rpc_driver def self.search(query, options={}) @@driver.doGoogleSearch( @@key, query, options[:offset] || 0, options[:limit] || 10, # Note that this value cannot exceed 10 options[:filter] || true, options[:restricts] || ' ', options[:safe_search] || false, options[:lr] || ' ', options[:ie] || ' ', options[:oe] || ' ' ) end def self.count(query, options={}) search(query, options).estimatedTotalResultsCount end def self.spell(phrase) @@driver.doSpellingSuggestion(@@key, phrase) end endHere it is in action:Google.count "Ruby Cookbook site:oreilly.com" # => 368 results = Google.search "Ruby Cookbook site:oreilly.com", :limit => 7 results.resultElements.size # => 7 results.resultElements.first["title"] # => "oreilly.com -- Online Catalog: <b>Ruby Cookbook</b>…" results.resultElements.first["URL"] # => "http://www.oreilly.com/catalog/rubyckbk/" results.resultElements.first["snippet"] # => "The <b>Ruby Cookbook</b> is a new addition to …" Google.spell "tis is te centence" # => "this is the sentence"
Each of the options defined inGoogle.searchcorresponds to an option in the Google search API.NameDescriptionkeyUnique key provided when you sign up with Google's web services.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using a WSDL File to Make SOAP Calls Easier
- InhaltsvorschauCredit: Kevin MarshallYou need to create a client for a SOAP-based web service, but you don't want to type out the definitions for all the SOAP methods you'll be calling.Most web services provide a WSDL file: a machine-readable description of the methods they offer. Ruby's SOAP WSDL Driver can parse a WSDL file and make the appropriate methods available automatically.This code uses the xmethods.com SOAP web service to get a stock price. In Recipe 16.7, we defined the
getQuotemethod manually. Here, its name and signature are loaded from a hosted WSDL file. You still have to know that the method is calledgetQuoteand that it takes one string, but you don't have to write any code telling Ruby this.require 'soap/wsdlDriver' wsdl = 'http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl' driver = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver puts "Stock price: %.2f" % driver.getQuote('TR') # Stock price: 28.78According to the World Wide Web Consortium (W3), "WSDL service definitions provide documentation for distributed systems and serve as a recipe for automating the details involved in applications communication."What this means to you is that you don't have to tell Ruby which methods a web service provides, and what arguments it expects. If you feed a WSDL file in to the Driver Factory, Ruby will give you aDriverobject with all the methods already defined.There are only a few things you need to know to build useful SOAP clients with a WSDL file. I'll illustrate with some code that performs a Google search and prints out the results.- Start with the URL to the WSDL file:
require 'soap/wsdlDriver' wsdl = 'http://api.google.com/GoogleSearch.wsdl' driver = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver
- Next you need the name of the SOAP method you want to call, and the expected types of its parameters:
my_google_key = 'get yours from https://www.google.com/accounts' my_query = 'WSDL Ruby' XSD::Charset.encoding = 'UTF8' result = driver.doGoogleSearch(my_google_key, my_query, 0, 10, false, '', false, '', '', '')
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Charging a Credit Card
- InhaltsvorschauYou want to charge a credit card from within your Ruby application.To charge credit cards online, you need an account with a credit card merchant. Although there are many to choose from, Authorize.Net is one of the best and most widely used. The
paymentlibrary encapsulates the logic of making a credit card payments with Authorize.Net, and soon it will support other gateways as well. It's available as thepaymentgem.require 'rubygems' require 'payment/authorize_net' transaction = Payment::AuthorizeNet.new( :login => 'username', :transaction_key => 'my_key', :amount => '49.95', :card_number => '4012888818888', :expiration => '0310', :first_name => 'John', :last_name => 'Doe' )
Thesubmitmethod sends a payment request. If there's a problem with your payment (probably due to an invalid credit card), thesubmitmethod will raise aPayment::PaymentError:begin transaction.submit puts "Card processed successfully: #{transaction.authorization}" rescue Payment::PaymentError puts "Card was rejected: #{transaction.error_message}" end # Card was rejected: The merchant login ID or password is invalid # or the account is inactive.Some of the information sent during initialization of thePayment::AuthorizeNetclass represent your account with Authorize.Net, and will never change (at least, not for the lifetime of the account). You can store this information in a YAML file called .payment.yml in your home directory, and have the payment library load it automatically. A .payment.yml file might look like this:login: username transaction_key: my_key
That way you don't have to hardcodeloginandtransaction_keywithin your Ruby code.If you're using the payment library from within a Rails application, you might want to put your YAML hash in the config directory with other configuration files, instead of in your home directory. You can override the location for the defaults file by specifying the:prefskey while initializing the object:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Finding the Cost to Ship Packages via UPS or FedEx
- InhaltsvorschauYou want to calculate the cost to ship any item with FedEx or UPS. This is useful if you're running an online store.FedEx and UPS provide web services that can query information on pricing as well as retrieve shipping labels. The logic for using these services has been encapsulated within the
shippinggem:require 'rubygems' require 'shipping' ship = Shipping::Base.new( :fedex_url => 'https://gatewaybeta.fedex.com/GatewayDC', :fedex_account => '123456789', :fedex_meter => '387878', :ups_account => '7B4F74E3075AEEFF', :ups_user => 'username', :ups_password => 'password', :sender_zip => 10001 # It's shipped from Manhattan. ) ship.weight = 2 # It weighs two pounds. ship.city = 'Portland' ship.state = 'OR' ship.zip = 97202 ship.ups.price # => 8.77 ship.fedex.price # => 5.49 ship.ups.valid_address? # => true
If you have a UPS account or a FedEx account, but not both, you can omit the account information you don't have, and instantiate aShipping::UPSor aShipping::FedExobject.You can either specify your account information during the initialization of the object (as above) or in a YAML hash. It's similar to thepaymentlibrary described in Recipe 16.8. If you choose to use the YAML hash, you can specify the account information in a file called .shipping.yml within the home directory of the user running the Ruby program:fedex_url: https://gatewaybeta.fedex.com/GatewayDC fedex_account: 1234556 fedex_meter: 387878 ups_account: 7B4F74E3075AEEFF ups_user: username ups_password: password
But your directory is not a good place to keep a file being used by a Rails application. Here's how to move the .shipping file into a Rails application:ship = Shipping:: FedEx.new(:prefs => "#{RAILS_ROOT}/config/shipping.yml") ship.sender_zip = 10001 ship.zip = 97202 ship.state = 'OR' ship.weight = 2 ship.price > ship.discount_price # => trueNotice the use ofship.discount_priceto find the discounted price; if you have an account with FedEx or UPS, you might be eligible for discounts.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Sharing a Hash Between Any Number of Computers
- InhaltsvorschauCredit: James Edward Gray IIYou want to easily share some application data with remote programs. Your needs are as trivial as, "What if all the computers could share this hash?"Ruby's built-in DRb library can share Ruby objects across a network. Here's a simple hash server:
#!/usr/local/ruby -w # drb_hash_server.rb require 'drb' # Start up DRb with a URI and a hash to share shared_hash = {:server => 'Some data set by the server' } DRb.start_service('druby://127.0.0.1:61676', shared_hash) puts 'Listening for connection…' DRb.thread.join # Wait on DRb thread to exit…Run this server in one Ruby session, and then you can run a client in another:require 'drb' # Prep DRb DRb.start_service # Fetch the shared object shared_data = DRbObject.new_with_uri('druby://127.0.0.1:61676') # Add to the Hash shared_data[:client] = 'Some data set by the client' shared_data.each do |key, value| puts "#{key} => #{value}" end # client => Some data set by the client # server => Some data set by the serverIf this looks like magic, that's the point. DRb hides the complexity of distributed programming. There are some complications (covered in later recipes), but for the most part DRb simply makes remote objects look like local objects.The solution given above may meet your needs if you're working with a single server and client on a trusted network, but applications aren't always that simple. Issues like thread-safety and security may force you to find a more robust solution. Luckily, that doesn't require too much more work.Let's take thread-safety first. Behind the scenes, a DRb server handles each client connection in a separate Ruby thread. Ruby'sHashclass is not automatically thread-safe, so we need to do a little extra work before we can reliably share a hash between multiple concurrent users.Here's a library that uses delegation to implement a thread-safe hash. AThreadsafeHashobject delegates all its method calls to an underlyingHashobject, but it uses aMutexto ensure that only one thread (or DRb client) can have access to the hash at a time.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Implementing a Distributed Queue
- InhaltsvorschauCredit: James Edward Gray IIYou want to use a central server as a workhorse, queueing up requests from remote clients and handling them one at a time.Here's a method that shares a
Queueobject with clients. Clients put job objects into the queue, and the server handles them byyieldingthem to a code block. #!/usr/bin/ruby#!/usr/bin/ruby # queue_server.rb require 'thread' # For Ruby's thread-safe Queue require 'drb' $SAFE = 1 # Minimum acceptable paranoia level when sharing code! def run_queue(url='druby://127.0.0.1:61676') queue = Queue.new # Containing the jobs to be processed # Start up DRb with URI and object to share DRb.start_service(url, queue) puts 'Listening for connection…' while job = queue.deq yield job end end
Have your server callrun_queue, passing in a code block that handles a single job. Every time one of your clients puts a job into the server queue, the server passes the job into the code block. Here's a sample code block that can handle a fast-running job ("Report") or a slow-running job ("Process"):run_queue do |job| case job['request'] when 'Report' puts "Reporting for #{job['from']}… Done." when 'Process' puts "Processing for #{job['from']}…" sleep 3 # Simulate real work puts 'Processing complete.' end endIf we get a couple of clients sending in requests, output might look like this:$ ruby queue_server.rb Listening for connection… Processing for Client 1… Processing complete. Processing for Client 2… Processing complete. Reporting for Client 1… Done. Reporting for Client 2… Done. Processing for Client 1… Processing complete. Reporting for Client 2… Done. …
A client for the queue server defined in the Solution simply needs to connect to the DRB server and add a mix of "Report" and "Process" jobs to the queue. Here's a client that connects to the DRb server and adds 20 jobs to the queue at random:#!/usr/bin/ruby # queue_client.rb require 'thread' require 'drb' # Get a unique name for this client NAME = ARGV.shift or raise "Usage: #{File.basename($0)} CLIENT_NAME" DRb.start_service queue = DRbObject.new_with_uri("druby://127.0.0.1:61676") 20.times do queue.enq('request' => ['Report', 'Process'][rand(2)], 'from' => NAME) sleep 1 # simulating network delays endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Creating a Shared "Whiteboard"
- InhaltsvorschauCredit: James Edward Gray IIYou want to create the network equivalent of a whiteboard. Remote programs can place Ruby objects up on the board, examine objects on the board, or remove objects from the board.You could just use a synchronized hash (as in Recipe 16.10), but Rinda provides a data structure called a
TupleSpacethat is optimized for distributed programming. It works well when you have some clients putting data on the whiteboard, and other clients processing the data and taking it down.Let's create an application that lets clients on different parts of the network translate each others' sentences, and builds a translation dictionary as they work.It's easier to see the architecture of the server if you see the clients first, so here's a client that adds some English sentences to a sharedTupleSpace:#!/usr/bin/ruby -w # english_client.rb require 'drb' require 'rinda/tuplespace' # Connect to the TupleSpace… DRb.start_service tuplespace = Rinda::TupleSpaceProxy.new( DRbObject.new_with_uri('druby://127.0.0.1:61676') )The English client's job is to split English sentences into words and to add each sentence to the whiteboard as a tuple: [unique id, language, words].counter = 0 DATA.each_line do |line| tuplespace.write([(counter += 1), 'English', line.strip.split]) end __END__ Ruby programmers have more fun Ruby gurus are obsessed with ducks Ruby programmers are happy programmers
Here's a second client. It creates a loop that continually reads all the English sentences from theTupleSpaceand puts up word-for-word translations into Pig Latin. It usesTuplespace#readto read English-language tuples off the whiteboard without removing them.require 'drb' require 'rinda/tuplespace' require 'set' DRb.start_service tuplespace = Rinda::TupleSpaceProxy.new( DRbObject.new_with_uri('druby://127.0.0.1:61676') ) # Track of the IDs of the sentences we've translated translated = Set.new # Continually read English sentences off of the board. while english = tuplespace.read([Numeric, 'English', Array]) # Skip anything we've already translated. next if translated.member? english.first translated << english.first # Translate English to Pig Latin. pig_latin = english.last.map do |word| if word =~ /^[aeiou]/i "#{word}way" elsif word =~ /^([^aeiouy]+)(.+)$/i "#{$2}#{$1.downcase}ay" end end # Write the Pig Latin translation back onto the board tuplespace.write([english.first, 'Pig Latin', pig_latin]) endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Securing DRb Services with Access Control Lists
- InhaltsvorschauCredit: James Edward Gray IIYou want to keep everybody in the world (literally!) from having access to your DRb service. Instead you want to control which hosts can, and cannot, connect.Here's the simple shared hash from Recipe 16.10, only this time it's locked down with DRb's
ACL(access control list) class:#!/usr/bin/ruby # acl_hash_server.rb require 'drb' require 'drb/acl' # Setup the security--remember to call before DRb.start_service() DRb.install_acl(ACL.new(%w{ deny all allow 192.168.1.* allow 127.0.0.1 } ) ) # Start up DRb with a URI and a hash to share shared_hash = {:server => 'Some data set by the server' } DRb.start_service("druby://127.0.0.1:61676", shared_hash) puts 'Listening for connection…' DRb.thread.join # Wait on DRb thread to exit…If you bind your DRb server tolocalhost, it'll only be accessible to other Ruby processes on your computer. That's not very distributed. But if you bind your DRb server to some other hostname, anyone on your local network (if you've got a local network) or anyone on the Internet at large will be able to share your Ruby objects. You're probably not feeling that generous.DRb'sACLclass provides simple white/blacklist security similar to that used by the Unix/etc/hosts.allowand/etc/hosts.denyfiles. TheACLconstructor takes an array of strings. The first string of a pair is always "allow" or "deny", and it's followed by the address or addresses to allow or deny access.String addresses can include wildcards ("**"), as shown in the solution, to allow or deny an entire range of addresses. TheACLclass also understands the term "all," and your first address should be either "deny all" or (less likely) "allow all". Subsequent entries can relax or restrict access, as needed.In the Solution above, the default is to deny access. Exceptions are carved out afterwards for anyone on the local IP network (192.168.1.**) and anyone on the same host as the server itself (127.0.0.1). A public DRb server might allow access by default, and deny access only to troublesome client IPs.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Automatically Discovering DRb Services with Rinda
- InhaltsvorschauCredit: James Edward Gray IIYou want to distribute Ruby code across your local network without hardcoding the clients with the addresses of the servers.Using Ruby's standard Rinda library, it's easy to provide zero-configuration networking for clients and services. With Rinda, machines can discover
DRbservices without providing any addresses. All you need is a runningRingServeron the local network:#!/usr/bin/ruby # rinda_server.rb require 'rinda/ring' # for RingServer require 'rinda/tuplespace' # for TupleSpace DRb.start_service # Create a TupleSpace to hold named services, and start running. Rinda::RingServer.new(Rinda::TupleSpace.new) DRb.thread.join
TheRingServerprovides automatic service detection for DRb servers. Any machine on your local network can find the localRingServerwithout knowing its address. Once it's found the server, a client can look up services and use them, not having to know the addresses of theDRbservers that host them.To find the Rinda server, a client broadcasts a UDP packet asking for the location of aRingServer. All computers on the local network will get this packet, and if a computer is running aRingServer, it will respond with its address. A server can use theRingServerto register services; a client can use theRingServerto look up services.ARingServerobject keeps a service listing in a sharedTupleSpace(see Recipe 16.12). Each service has a corresponding tuple with four members:- The literal symbol
:name, which indicates that the tuple is an entry in theRingServernamespace. - The symbol of a Ruby class, indicating the type of the service.
- The
DRbObjectshared by the service. - A string description of the service.
By retrieving thisTupleSpaceremotely, you can look up services as tuples and advertise your own services. Let's advertise an object (a simpleTupleSpace) through theRingServerunder the name:TupleSpace:#!/usr/bin/ruby # share_a_tuplespace.rb require ' rinda/ring' # for RingFinger and SimpleRenewer require ' rinda/tuplespace' # for TupleSpace DRb.start_service ring_server = Rinda::RingFinger.primary # Register our TupleSpace service with the RingServer ring_server.write( [:name, :TupleSpace, Rinda::TupleSpace.new, 'Tuple Space'], Rinda::SimpleRenewer.new ) DRb.thread.join
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Proxying Objects That Can't Be Distributed
- InhaltsvorschauCredit: James Edward Gray IIYou want to allow classes to connect to your
DRbserver, without giving the server access to the class definition. Perhaps you've given clients an API to implement, and you don't want to make everyone send you the source to their implementations just so they can connect to the server.…OR…You have some code that is tied to local resources: database connections, log files, or even just the closure aspect of Ruby's blocks. You want this code to interact with aDRbserver, but it must be run locally.…OR…You want to send an object to aDRbserver, perhaps as a parameter to a method; but you want the server to notice changes to that object as your local code modifies it.Rather than sending an object to the server, you can askDRbto send a proxy instead. When the server acts on the proxy, a description of the act will be sent across the network. The client end will actually perform the action. In effect, you've partially switched the roles of the client and the server.You can set up a proxy in two simple steps. First, make sure your client code includes the following line before it interacts with any server objects:DRb.start_service # The client needs to be a DRb service too.
That's generally just a good habit to get into withDRbclient code, because it allowsDRbto magically support some constructs (like Ruby's blocks) by sending a proxy object when necessary. If you're intentionally trying to send a proxy, it becomes essential.As long as your client is aDRbservice of its own, you can proxy all objects made from a specific class or individual objects by including theDRbUndumpedmodule:class MyLocalClass include DRbUndumped # The magic line. All objects of this type are proxied. # … end # … OR … my_local_object.extend DRbUndumped # Proxy just this object.
Under normal circumstances,DRbis very simple. A method call is packaged up (usingMarshal) as a target object, method name, and some arguments. The resulting object is sent over the wire to the server, where it's executed. The important thing to notice is that the server receives copies of the original arguments.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Storing Data on Distributed RAM with MemCached
- InhaltsvorschauCredit: Ben Bleything with Michael GrangerYou need a lightweight, persistent storage space, and you have systems on your network that have unused RAM.
memcachedprovides a distributed in-memory cache. When used with a Ruby client library, it can be used to store almost any Ruby object. See the Discussion section below for more information, and details of where to getmemcached.In this example, we'll use Michael Granger's Ruby-MemCache library, available as theRuby-MemCachegem.Assume you have amemcachedserver running on the machine at IP address 10.0.1.201. You can use thememcachegem to access the cache as though it were a local hash. This Ruby code will store a string in the remote cache:require 'rubygems' require 'memcache' MC = MemCache.new '10.0.1.201' MC[:test] = 'This string lives in memcached!'
The string has been placed in yourmemcachedwith the key:test. You can fetch it from a different Ruby session:require 'rubygems' require 'memcache' MC = MemCache.new '10.0.1.201' MC[:test] # => "This string lives in memcached!"
You can also place more complex objects inmemcached. In fact, any object that can be serialized withMarshal.dumpcan be placed inmemcached. Here we store and retrieve a hash:hash = { :roses => 'are red', :violets => 'are blue' } MC[:my_hash] = hash MC[:my_hash][:roses] # => "are red"memcachedwas originally designed to alleviate pressure on the database servers for LiveJournal.com. For more information about howmemcachedcan be used for this kind of purpose, see Recipe 16.17.memcachedprovides a lightweight, distributed cache space where the cache is held in RAM. This makes the cache extremely fast, and it never blocks on disk I/O. When effectively deployed,memcachedcan significantly reduce the load on your database servers by farming out storage to unused RAM on other machines.To start usingmemcached, you'll need to download the server (see below). You can install it from source, or get it via most *nix packaging systems.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Caching Expensive Results with MemCached
- InhaltsvorschauCredit: Michael Granger with Ben BleythingYou want to transparently cache the results of expensive operations, so that code that triggers the operations doesn't need to know how to use the cache. The
memcachedprogram, described in Recipe 16.16, lets you use other machines' RAM to store key-value pairs. The question is how to hide the use of this cache from the rest of your code.If you have the luxury of designing your own implementation of the expensive operation, you can design in transparent caching from the beginning. The following code defines agetmethod that delegates toexpensive_getif it can't find an appropriate value in the cache. In this case, the expensive operation that gets cached is the (relatively inexpensive, actually) string reversal operation:require 'rubygems' require 'memcache' class DataLayer def initialize(*cache_servers) @cache = MemCache.new(*cache_servers) end def get(key) @cache[key] ||= expensive_get(key) end alias_method :[], :get protected def expensive_get(key) # …do expensive fetch of data for 'key' puts "Fetching expensive value for #{key}" key.to_s.reverse end endAssuming you've got amemcachedserver running on your local machine, you can use thisDataLayeras a way to cache the reversed versions of strings:layer = DataLayer.new( 'localhost:11211' ) 3.times do puts "Data for 'foo': #{layer['foo']}" end # Fetching expensive value for foo # Data for 'foo': oof # Data for 'foo': oofThat's the easy case. But you don't always get the opportunity to define a data layer from scratch. If you want to add memcaching to an existing data layer, you can create a caching strategy and add it to your existing classes as a mixin.Here's a data layer, already written, that has no caching:class MyDataLayer def get(key) puts "Getting value for #{key} from data layer" return key.to_s.reverse end endThe data layer doesn't know about the cache, so all of its operations are expensive. In this instance, it's reversing a string every time you ask for it:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - A Remote-Controlled Jukebox
- InhaltsvorschauWhat if you had a jukebox on your main computer that played random or selected items from your music collection? What if you could search your music collection and add items to the jukebox queue from a laptop in another room of the house?Ruby can help you realize this super-geek dream—the software part, anyway. In this recipe, I'll show you how to write a jukebox server that can be programmed from any computer on the local network.The jukebox will consist of a client and a server. The server broadcasts its location to a nearby Rinda server so clients on the local network can find it without knowing the address. The client will look up the server with Rinda and then communicate with it via DRb.What features should the jukebox have? When there are no clients interfering with its business, the server will pick random songs from a predefined playlist and play them. It will call out to external Unix programs to play songs on the local computer's audio system (if you have a way of broadcasting songs through streaming audio, say, an IceCast server, it could use that instead).A client can query the jukebox, stop or restart it, or request that a particular song be played. The jukebox will keep requests in a queue. Once it plays all the requests, it will resume playing songs at random.Since we'll be running subprocesses to access the sound card on the computer that runs the jukebox, the
Jukeboxobject can't be distributed to another machine. Instead, we need to proxy it withDRbUndumped.The first thing we need to do is start aRingServersomewhere on our local network. Here's a reprint of theRingServerprogram from Recipe 16.14:#!/usr/bin/ruby # rinda_server.rb require 'rinda/ring' # for RingServer require 'rinda/tuplespace' # for TupleSpace DRb.start_service # Create a TupleSpace to hold named services, and start running. Rinda::RingServer.new(Rinda::TupleSpace.new) DRb.thread.join
Here's the jukebox server file. First, we'll define theJukeboxserver class, and set up its basic behavior: to play its queue and pick randomly when the queue is empty.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 17: Testing, Debugging, Optimizing, and Documenting
- InhaltsvorschauThe recipes in previous chapters focus on writing code to do what you want. This chapter focuses on verifying that your code really works, and on fixing it when it breaks. We start off simple and move to more advanced debugging techniques.What happens when your program has a bug? The best-case scenario is that you discover the bug before it affects anyone, including other developers. That's the goal of unit tests (Recipe 17.7). Ruby and the Ruby community promote a philosophy of writing automated tests as (or even before) you write the corresponding functionality. At every stage of development, you know that your program works, and if you make a change that breaks something, you know about it immediately. These tests can replace much boring manual testing and bug hunting.Suppose a bug slips past your tests, and you only discover it in production. How's it going to manifest itself? If you're lucky, you'll see an exception: a notification from some piece of Ruby code that something is wrong.Exceptions interrupt the normal flow of execution, and, if not handled, will crash the program. The good news is that they give you a place in the code to start debugging. It's worse if a bug doesn't cause an exception, because you'll only notice its byproducts: corrupt data or even security violations. We show code for handling exceptions (Recipes 17.3 and 17.4) and for creating your own (Recipe 17.2).Successful debugging means reproducing the bug in an environment where you can poke at it. This may mean dropping from a running program into an
irbsession (Recipe 17.10), or it may be as simple as adding diagnostic messages that make the program show its work (Recipe 17.1).Even a program that has no noticeable bugs may run too slowly or use too many resources. Ruby provides two tools for doing performance optimization: a profiler (Recipe 17.12) and a benchmarking suite (Recipe 17.13). It's easy to create your own analysis tools by writing a trace function that hooks into the Ruby interpreter as it runs. The call graph tracker presented at chapter's end (Recipe 17.15) exploits this feature.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Running Code Only in Debug Mode
- InhaltsvorschauYou want to print out debugging messages or run some sanity-checking code, but only while you're developing your application;, not when you're running it in production.Run the code only if the global variable
$DEBUGis true. You can trigger debug mode by passing in the--debugswitch to the Ruby interpreter, or you can set the variable$DEBUGto true within your code.Here's a Ruby program to divide two random numbers. It contains a trivial bug. It usually runs to completion, but sometimes it crashes. A line of debug code has been added to give some more visibility into the internal workings of the program:#!/usr/bin/env ruby # divide.rb numerator = rand(100) denominator = rand(10) $stderr.puts "Dividing #{numerator} by #{denominator}" if $DEBUG puts numerator / denominatorWhen run with the--debugflag, the debug message is printed to standard error:$ ./divide.rb --debug Dividing 64 by 9 7 $ ./divide.rb --debug Dividing 93 by 2 46 $ ./divide.rb --debug Dividing 54 by 0 Exception 'ZeroDivisionError' at divide_buggy.rb:6 - divided by 0 divide_buggy.rb:6:in '/': divided by 0 (ZeroDivisionError) from divide_buggy.rb:6
Once the bug is fixed, you can go back to running the script normally, and the debug message won't show up:$ ./divide.rb 24
This is a common technique when a "real" debugger is too much trouble. It's usually used to send debug messages to standard error, but you can put any code at all within a$DEBUGconditional. For instance, many Ruby libraries have their own "verbose", " debug level", or " debug mode" settings: you can choose to set these other variables appropriately only when$DEBUGis true.require 'fileutils' FileUtils.cp('source', 'destination', $DEBUG)If your code is running deep within a framework, you may not have immediate access to the standard error stream of the process. You can always have your debug code write to a temporary logfile, and monitor the file.Use of$DEBUGcosts a little speed, but except in tight loops it's not noticeable. At the cost of a little more speed, you can save yourself some typing by defining convenience methods like this one:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Raising an Exception
- InhaltsvorschauCredit: Steve ArneilAn error has occurred and your code can't keep running. You want to indicate the error and let some other piece of code handle it.Raise an exception by calling the
Kernel#raisemethod with a description of the error. Calling theraisemethod interrupts the flow of execution.The following method raises an exception whenever it's called. Its second message will never be printed:def raise_exception puts 'I am before the raise.' raise 'An error has occurred.' puts 'I am after the raise.' end raise_exception # I am before the raise. # RuntimeError: An error has occurred
Here's a method,inverse, that returns the inverse of a numberx. It does some basic error checking by raising an exception unlessxis a number:def inverse(x) raise "Argument is not numeric" unless x.is_a? Numeric 1.0 / x end
When you pass in a reasonable value ofx, all is well:inverse(2) # => 0.5
Whenxis not a number, the method raises an exception:inverse('not a number') # RuntimeError: Argument is not numericAn exception is an object, and theKernel#raisemethod creates an instance of an exception class. By default,Kernel#raisecreates an exception ofRuntimeErrorclass, which is a subclass ofStandardError. This in turn is a subclass ofException, the superclass of all exception classes. You can list all the standard exception classes by starting a Ruby session and executing code like this:ObjectSpace.each_object(Class) do |x| puts x if x.ancestors.member? Exception end
This variant lists only the better-known exception classes:ObjectSpace.each_object(Class) { |x| puts x if x.name =~ /Error$/ } # SystemStackError # LocalJumpError # EOFError # IOError # RegexpError # …To raise an exception of a specific class, you can pass in the class name as an argument toraise. RuntimeErroris kind of generic for theinversemethod's check againstx. The problem is there is actually a problem with one of the arguments passed into the method. A more aptly named exception class for that check would beEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Handling an Exception
- InhaltsvorschauCredit: Steve ArneilYou want to handle or recover from a raised exception.Rescue the exception with a
begin/rescueblock. The code you put into therescueclause should handle the exception and allow the program to continue executing.This code demonstrates therescueclause:def raise_and_rescue begin puts 'I am before the raise.' raise 'An error has occurred.' puts 'I am after the raise.' rescue puts 'I am rescued!' end puts 'I am after the begin block.' end raise_and_rescue # I am before the raise. # I am rescued! # I am after the begin block.
The exception doesn't stop the program from running to completion, but the code that was interrupted by the exception never gets run. Once the exception is handled, execution continues immediately after thebeginblock that spawned it.You can handle an exception with arescueblock if you know how to recover from the exception, if you want to display it in a nonstandard way, or if you know that the exception is not really a problem. You can solve the problem, present it to the end user, or just ignore it and forge ahead.By default, arescueclause rescues exceptions of classStandardErroror its subclasses. Mentioning a specific class in arescuestatement will make it rescue exceptions of that class and its subclasses.Here's a method,do_it, that calls theKernel#evalmethod to run some Ruby code passed to it. If the code cannot be run (because it's not valid Ruby),evalraises an exception—aSyntaxError. This exception is not a subclass ofStandardError; it's a subclass ofScriptError, which is a subclass ofException.def do_it(code) eval(code) rescue puts "Cannot do it!" end do_it('puts 1 + 1') # 2 do_it('puts 1 +') # SyntaxError: (eval):1:in 'do_it': compile errorThatrescueblock never gets called becauseSyntaxErroris not a subclass ofStandardError. We need to tell ourrescueblock to rescue us fromSyntaxError, or else from one of its superclasses,ScriptErrorEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Rerunning After an Exception
- InhaltsvorschauCredit: Steve ArneilYou want to rerun some code that raised an exception, having (hopefully) fixed the problem that caused it in the first place.Retry the code that failed by executing a
retrystatement within arescueclause of a code block.retryreruns the block from the beginning.Here's a demonstration of theretrystatement. The first time the code block runs, it raises an exception. The exception isrescued, the problem is "fixed," and the code runs to completion the second time:def rescue_and_retry error_fixed = false begin puts 'I am before the raise in the begin block.' raise 'An error has occurred!' unless error_fixed puts 'I am after the raise in the begin block.' rescue puts 'An exception was thrown! Retrying…' error_fixed = true retry end puts 'I am after the begin block.' end rescue_and_retry # I am before the raise in the begin block. # An exception was thrown! Retrying… # I am before the raise in the begin block. # I am after the raise in the begin block. # I am after the begin block.
Here's a method,check_connection, that checks if you are connected to the Internet. It will try to connect to aurlup tomax_triestimes. This method uses aretryclause to retry connecting until it successfully completes a connection, or until it runs out of tries:require 'open-uri' def check_connection(max_tries=2, url='http://www.ruby-lang.org/') tries = 0 begin tries += 1 puts 'Checking connection…' open(url) { puts 'Connection OK.' } rescue Exception puts 'Connection not OK!' retry unless tries >= max_tries end end check_connection # Checking connection… # Connection OK. check_connection(2, 'http://this.is.a.fake.url/') # Checking connection… # Connection not OK! # Checking connection… # Connection not OK!- Recipe 17.2, "Raising an Exception"
- Recipe 17.3, "Handling an Exception"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Adding Logging to Your Application
- InhaltsvorschauYou want to make your application log events or diagnostic data to a file or stream. You want verbose logging when your application is in development, and more taciturn logging when in production.Use the
loggerlibrary in the Ruby standard library. Use itsLoggerclass to send logging data to a file or other output stream.In most cases, you'll share a singleLoggerobject throughout your application, as a global variable or module constant:require ' logger' $LOG = Logger.new($stderr)
You can then call the instance methods ofLoggerto send messages to the log at various levels of severity. From least to most severe, the instance methods areLogger#debug, Logger#info, Logger#warn, Logger#error, andLogger#fatal.This code uses the application's logger to print a debugging message, and (at a higher severity) as part of error-handling code.def divide(numerator, denominator) $LOG.debug("Numerator: #{numerator}, denominator #{denominator}") begin result = numerator / denominator rescue Exception => e $LOG.error "Error in division!: #{e}" result = nil end return result end divide(10, 2) # D, [2006-03-31T19:35:01.043938 #18088] DEBUG -- : Numerator: 10, denominator 2 # => 5 divide(10, 0) # D, [2006-03-31T19:35:01.045230 #18088] DEBUG -- : Numerator: 10, denominator 0 # E, [2006-03-31T19:35:01.045495 #18088] ERROR -- : Error in division!: divided by 0 # => nilTo change the log level, simply assign the appropriate constant tolevel:$LOG.level = Logger::ERROR
Now our logger will ignore all log messages except those with severityERRORorFATAL:divide(10, 2) # => 5 divide(10, 0) # E, [2006-03-31T19:35:01.047861 #18088] ERROR -- : Error in division!: divided by 0 # => nil
Ruby's standard logging system works like Java's oft-imitated Log4J. TheLoggerobject centralizes all the decisions about whether a particular message is important enough to be written to the log. When you write code, you simply assume that all the messages will be logged. At runtime, you can get a more or a less verbose log by changing the log level. A production application usually has a log level ofEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Creating and Understanding Tracebacks
- InhaltsvorschauYou are debugging a program, and need to understand the stack traces that come with Ruby exceptions. Or you need to see which path the Ruby interpreter took to get to a certain line of code.You can call the
Kernel#callermethod at any time to look at the Ruby interpreter's current call stack. The call stack is represented as a list of strings.This Ruby program simulates a company with a top-down management style: one method delegates to another, which calls yet another. The method at the bottom can usecallerto look upwards and see the methods that called it:1 #!/usr/bin/ruby -w 2 # delegation.rb 3 class CEO 4 def CEO.new_vision 5 Manager.implement_vision 6 end 7 end 8 9 class Manager 10 def Manager.implement_vision 11 Engineer.do_work 12 end 13 end 14 15 class Engineer 16 def Engineer.do_work 17 puts 'How did I get here?' 18 first = true 19 caller.each do |c| 20 puts %{#{(first ? 'I' : ' which')} was called by "#{c}"} 21 first = false 22 end 23 end 24 end 25 26 CEO.new_visionRunning this program illustrates the path the interpreter takes toEngineer.do_work:$ ./delegation.rb How did I get here? I was called by "delegation.rb:11:in 'implement_vision'" which was called by "delegation.rb:5:in 'new_vision'" which was called by "delegation.rb:26"
Each string in a traceback shows which line of Ruby code made some method call. The first bit of the traceback given above shows thatEngineer.do_workwas called byManager.implement_visionon line 11 of the program. The second line shows howManager.implement_visionwas called, and so on.Remember the stack trace displayed when a Ruby script raises an exception? It's the same one you can get any time by callingKernel#caller. In fact, if yourescuean exception and assign it to a variable, you can get its traceback as an array of strings— the equivalent of callingcalleron the line that triggered the exception:def raise_exception raise Exception, 'You wanted me to raise an exception, so…' end begin raise_exception rescue Exception => e puts "Backtrace of the exception:\n #{e.backtrace.join("\n ")}" end # Backtrace of the exception: # (irb):2:in 'raise_exception' # (irb):5:in 'irb_binding' # /usr/lib/ruby/1.8/irb/workspace.rb:52:in 'irb_binding' # :0Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Writing Unit Tests
- InhaltsvorschauCredit: Steve ArneilYou want to write some unit tests for your software, to guarantee its correctness now and in the future.Use
Test::Unit, the Ruby unit testing framework, from the Ruby standard library.Consider a simple class for storing the name of a person. ThePersonclass shown below stores a first name, a last name, and an age: a person's full name is available as a computed value. This code might go into a Ruby script calledapp/person.rb:# app/person.rb class Person attr_accessor :first_name, :last_name, :age def initialize(first_name, last_name, age) raise ArgumentError, "Invalid age: #{age}" unless age > 0 @first_name, @last_name, @age = first_name, last_name, age end def full_name first_name + ' ' + last_name end endNow, let's write some unit tests for this class. By convention, these would go into the filetest/person_test.rb.First, require thePersonclass itself and theTest::Unitframework:# test/person_test.rb require File.join(File.dirname(__FILE__), '..', 'app', 'person') require 'test/unit'
Next, extend the framework classTest::Unit::TestCasewith a class to contain the actual tests. Each test should be written as a method of the test class, and each test method should begin with the prefixtest. Each test should make one or more assertions: statements about the code which must be true for the code to be correct. Below are three test methods, each making one assertion:class PersonTest < Test::Unit::TestCase def test_first_name person = Person.new('Nathaniel', 'Talbott', 25) assert_equal 'Nathaniel', person.first_name end def test_last_name person = Person.new('Nathaniel', 'Talbott', 25) assert_equal 'Talbott', person.last_name end def test_full_name person = Person.new('Nathaniel', 'Talbott', 25) assert_equal 'Nathaniel Talbott', person.full_name end def test_age person = Person.new('Nathaniel', 'Talbott', 25) assert_equal 25, person.age assert_raise(ArgumentError) { Person.new('Nathaniel', 'Talbott', -4) } assert_raise(ArgumentError) { Person.new('Nathaniel', 'Talbott', 'four') } end endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Running Unit Tests
- InhaltsvorschauCredit: Steve ArneilYou want to run some or all of the unit tests you've written.This solution uses the example test class
PersonTestfrom the previous recipe, Recipe 17.7. In that scenario, this code lives in a filetest/person_test.rb, and the code to be tested lives inapp/person.rb. Here'stest/person_test.rbagain:# person_test.rb require File.join(File.dirname(__FILE__), '..', 'app', 'person') require 'test/unit' class PersonTest < Test::Unit::TestCase FIRST_NAME, LAST_NAME, AGE = 'Nathaniel', 'Talbott', 25 def setup @person = Person.new(FIRST_NAME, LAST_NAME, AGE) end def test_first_name assert_equal FIRST_NAME, @person.first_name end def test_last_name assert_equal LAST_NAME, @person.last_name end def test_full_name assert_equal FIRST_NAME + ' ' + LAST_NAME, @person.full_name end def test_age assert_equal 25, @person.age assert_raise(ArgumentError) { Person.new(FIRST_NAME, LAST_NAME, -4) } assert_raise(ArgumentError) { Person.new(FIRST_NAME, LAST_NAME, 'four') } end endAs seen in the previous recipe, the simplest solution is to run the script that contains the tests as a Ruby script:$ ruby test/person_test.rb Loaded suite test/person_test Started …. Finished in 0.008955 seconds. 4 tests, 6 assertions, 0 failures, 0 errors
But theperson_test.rbscript also accepts command-line arguments. You can use the--nameoption to choose which test methods to run, and the--verboseoption to print each test method as it's run:$ ruby test/person_test.rb --verbose --name test_first_name \ --name test_last_name Loaded suite test/person_test Started test_first_name(PersonTest): . test_last_name(PersonTest): . Finished in 0.012567 seconds. 2 tests, 2 assertions, 0 failures, 0 errors
How do the tests run whenperson_test.rbdoesn't appear to do anything but define a class? How canperson_test.rbaccept command-line arguments? We wrote that file, and we didn't put in any command-line parsing code.It all happens behind the scenes. When we required theTest::UnitEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Testing Code That Uses External Resources
- InhaltsvorschauCredit: John-Mason ShackelfordYou want to test code without triggering its real-world side effects. For instance, you want to test a piece of code that makes an expensive network connection, or irreversibly modifies a file.Sometimes you can set up an alternate data source to use for testing (Rails does this for the application database), but doing that makes your tests slower and imposes a setup burden on other developers. Instead, you can use Jim Weirich's FlexMock library, available as the
flexmockgem.Here's some code that performs a destructive operation on a live data source:class VersionControlMaintenance DAY_SECONDS = 60 * 60 * 24 def initialize(vcs) @vcs = vcs end def purge_old_labels(age_in_days) @vcs.connect old_labels = @vcs.label_list.select do |label| label['date'] <= Time.now - age_in_days * DAY_SECONDS end @vcs.label_delete(*old_labels.collect{|label| label['name']}) @vcs.disconnect end endThis code would be difficult to test by conventional means, with thevcsvariable pointing to a live version control repository. But with FlexMock, it's simple to define a mockvcsobject that can impersonate a real one.Here's a unit test forVersionControlMaintenance#purge_old_labelsthat uses Flex-Mock, instead of modifying a real version control repository. First, we set up some dummy labels:require 'rubygems' require ' flexmock' require 'test/unit' class VersionControlMaintenanceTest < Test::Unit::TestCase DAY_SECONDS = 60 * 60 * 24 LONG_AGO = Time.now - DAY_SECONDS * 3 RECENT = Time.now - DAY_SECONDS * 1 LABEL_LIST = [ { 'name' => 'L1', 'date' => LONG_AGO }, { 'name' => 'L2', 'date' => RECENT } ]We use FlexMock to define an object that expects a certain series of method calls:def test_purge FlexMock.use("vcs") do |vcs| vcs.should_receive(:connect).with_no_args.once.ordered vcs.should_receive(:label_list).with_no_args. and_return(LABEL_LIST).once.ordered vcs.should_receive(:label_delete). with('L1').once.ordered vcs.should_receive(:disconnect).with_no_args.once.orderedEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using breakpoint to Inspect and Change the State of Your Application
- InhaltsvorschauYou're debugging an application, and would like to be able to stop the program at any point and inspect the application's state (variables, data structures, etc.). You'd also like to be able to modify the application's state before restarting it.Use the breakpoint library, available as the
ruby-breakpointgem.Once yourequire ' breakpoint', you can call thebreakpointmethod from anywhere in your application. When the execution hits thebreakpointcall, the application turns into an interactive Ruby session.Here's a short Ruby program:#!/usr/bin/ruby -w # breakpoint_test.rb require 'rubygems' require 'breakpoint' class Foo def initialize(init_value) @instance_var = init_value end def bar test_var = @instance_var puts 'About to hit the breakpoint!' breakpoint puts 'HERE ARE SOME VARIABLES:' puts "test_var: #{test_var}, @instance_var: #{@instance_var}" end end f = Foo.new('When in the course') f.barWhen you run the application, you quickly hit the call tobreakpointinFoo#bar. This drops you into anirbsession:$ ruby breakpoint_test.rb About to hit the breakpoint! Executing break point at breakpoint_test.rb:14 in 'bar' irb(#<Foo:0xb7452464>):001:0>
Once you quit theirbsession, the program continues on its way:irb(#<Foo:0xb7452a18>):001:0>quit HERE ARE SOME VARIABLES: test_var: When in the course, @instance_var: When in the courseBut there's a lot you can do within thatirbsession before you quit. You can look at the arraylocal_variables, which enumerates all variables local to the current method. You can also look at and modify any of the variables that are currently in scope, including instance variables, class variables, and globals:$ ruby breakpoint_test.rb About to hit the breakpoint! Executing break point at breakpoint_test.rb:14 in 'bar' irb(#<Foo:0xb7452464>):001:0>local_variables => ["test_var", "_"] irb(#<Foo:0xb7452428>):002:0> test_var => "When in the course" irb(#<Foo:0xb7452428>):003:0> @instance_var => "When in the course" irb(#<Foo:0xb7452428>):004:0>
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Documenting Your Application
- InhaltsvorschauYou want to create a set of API documentation for your application. You might want to go so far as to keep all your documentation in the same files as your source code.It's good programming practice to preface each of your methods, classes, and modules with a comment that lets the reader know what's going on. Ruby rewards this behavior by making it easy to transform those comments into a set of HTML pages that document your code. This is similar to Java's JavaDoc, Python's PyDoc, and Perl's Pod.Here's a simple example. Suppose your application contains only one file,
sum.rb, which defines only one method:def sum(*terms) terms.inject(0) { |sum, term| sum + term} endTo document this application, use Ruby comments to document the method, and also to document the file as a whole:# Just a simple file that defines a sum method. # Takes any number of numeric terms and returns the sum. # sum(1, 2, 3) # => 6 # sum(1, -1, 10) # => 10 # sum(1.5, 0.2, 0.3, 1) # => 3.0 def sum(*terms) terms.inject(0) { |sum, term| sum + term} endChange into the directory containing thesum.rbfile, and run therdoccommand.$ rdoc sum.rb: . Generating HTML… Files: 1 Classes: 0 Modules: 0 Methods: 1 Elapsed: 0.101s
Therdoccommand creates adoc/subdirectory beneath the current directory. It parses every Ruby file it can find in or below the current directory, and generates HTML files from the Ruby code and the comments that document it.Theindex.htmlfile in thedoc/subdirectory is a frameset that lets users navigate the files of your application. Since the example only uses one file (sum.rb), the most interesting thing about its generated documentation is what RDoc has done with the comments (Figure 17-1).RDoc parses a set of Ruby files, cross-references them, and generates a web site that captures the class and module structure, and the comments you wrote while you were coding.Generated RDoc makes for a useful reference to your classes and methods, but it's not a substitute for handwritten examples or tutorials. Of course, RDoc comments canEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Profiling Your Application
- InhaltsvorschauYou want to find the slowest parts of your application, and speed them up.Include the Ruby profiler in your application with
include 'profile'and the profiler will start tracking and timing every subsequent method call. When the application exits, the profiler will print a report to your program's standard error stream.Here's a program that contains a performance flaw:#!/usr/bin/env ruby # sequence_counter.rb require 'profile' total = 0 # Count the letter sequences containing an a, b, or c. ('a'..'zz').each do |seq| ['a', 'b', 'c'].each do |i| if seq.index(i) total += 1 break end end end puts "Total: #{total}"When the program is run, the profiler shows the parts of the program that are most important to optimize:$ ruby sequence_counter.rb Total: 150 % cumulative self self total time seconds seconds calls ms/call ms/call name 54.55 0.30 0.30 702 0.43 0.50 Array#each 32.73 0.48 0.18 1 180.00 550.00 Range#each 7.27 0.52 0.04 1952 0.02 0.02 String#index 3.64 0.54 0.02 702 0.03 0.03 String#succ 1.82 0.55 0.01 150 0.07 0.07 Fixnum#+ …
The program takes about 0.3 seconds to run, and most of that is spent inArray#each. What if we replaced that code with an equivalent regular expression?#!/usr/bin/env ruby # sequence_counter2.rb require 'profile' total = 0 # Count the letter sequences containing an a, b, or c. ('a'..'zz').each {|seq| total +=1 if seq =~ /[abc]/ } puts "Total: #{total}"Running this program yields a much better result:$ ruby sequence_counter2.rb Total: 150 % cumulative self self total time seconds seconds calls ms/call ms/call name 83.33 0.05 0.05 1 50.00 60.00 Range#each 16.67 0.06 0.01 150 0.07 0.07 Fixnum#+ 0.00 0.06 0.00 1 0.00 0.00 Fixnum#to_s …
The new version takes only 0.05 seconds to run, and as near as the profiler can measure, it's running nearly as fast as an empty iterator over the rangeEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Benchmarking Competing Solutions
- InhaltsvorschauYou want to see which of two solutions to a problem is faster. You might want to compare two different algorithms, or two libraries that do the same thing.Use the
benchmarklibrary to time the tasks you want to run. TheBenchmark.bmmethod gives you an object that can report on how long it takes for code blocks to run.Let's explore whether themember?method is faster on arrays or hashes. First, we create a large array and a large hash with the same data, and define a method that exercises themember?method:RANGE = (0..1000) array = RANGE.to_a hash = RANGE.inject({}) { |h,i| h[i] = true; h } def test_member?(data) RANGE.each { |i| data.member? i } endNext, we callBenchmark.bmto set up a series of timing tests. The first test callstest_member?on the array; the second one calls it on the hash. The results are printed in a tabular form to standard error:require 'benchmark' Benchmark.bm(5) do |timer| timer.report('Array') { test_member?(array) } timer.report('Hash') { test_member?(hash) } end # user system total real # Array 0.260000 0.060000 0.320000 ( 0.332583) # Hash 0.010000 0.000000 0.010000 ( 0.001242)As you'd expect,member?is much faster on a hash.What do the different times mean? Therealtime is "wall clock" time: the number of seconds that passed in the real world between the start of the test and its completion. This time is actually not very useful, because it includes time during which the CPU was running some other process. If your system is operating under a heavy load, the Ruby interpreter will get less of the CPU's attention and therealtimes won't reflect the actual performance of your benchmarks. You only needrealtimes when you're measuring user-visible performance on a running system.Theusertime is time actually spent running the Ruby interpreter, and thesystemtime is time spent in system calls spawned by the interpreter. If your test does a lot of I/O, itssystemtime will tend to be large; if it does a lot of processing, itsusertime will tend to be large. The most useful time is probablyEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Running Multiple Analysis Tools at Once
- InhaltsvorschauYou want to combine two analysis tools, like the Ruby profiler and the Ruby tracer. But when one tool calls
set_trace_func, it overwrites the trace function left by the other.Changeset_trace_funcso that it keeps an array of trace functions instead of just one. Here's a library calledmultitrace.rbthat makes it possible:# multitrace.rb $TRACE_FUNCS = [] alias :set_single_trace_func :set_trace_func def set_trace_func(proc) if (proc == nil) $TRACE_FUNCS.clear else $TRACE_FUNCS << proc end end trace_all = Proc.new do |event, file, line, symbol, binding, klass| $TRACE_FUNCS.each { |p| p.call(event, file, line, symbol, binding, klass)} end set_single_trace_func trace_all def unset_trace_func(proc) $TRACE_FUNCS.delete(proc) endNow you can run any number of analysis tools simultaneously. However, when one of the tools stops, they will all stop:#!/usr/bin/ruby -w # paranoia.rb require 'multitrace' require 'profile' require 'tracer' Tracer.on puts "I feel like I'm being watched."
This program's nervousness is well-justified, since its every move is being tracked by the Ruby tracer and timed by the Ruby profiler:$ ruby paranoia.rb #0:./multitrace.rb:9:Array:<: $TRACE_FUNCS << proc #0:./multitrace.rb:11:Object:<: end #0:paranoia.rb:9::-: puts "I feel like I'm being watched." #0:paranoia.rb:9:Kernel:>: puts "I feel like I'm being watched." … % cumulative self self total time seconds seconds calls ms/call ms/call name 0.00 0.00 0.00 1 0.00 0.00 Kernel.require 0.00 0.00 0.00 1 0.00 0.00 Fixnum#== 0.00 0.00 0.00 1 0.00 0.00 String#scan …
Without theinclude 'multitrace'at the beginning, only the profiler will run: its trace function will override the tracer's.This example illustrates yet again how you can benefit by replacing some built-in part of Ruby. Themultitracelibrary creates a drop-in replacement forset_trace_funcEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Who's Calling That Method? A Call Graph Analyzer
- InhaltsvorschauSuppose you're profiling a program such as the one in Recipe 17.12, and the profiler says that the top culprit is
Array#each. That is, your program spends more time iterating over arrays than doing any one other thing:% cumulative self self total time seconds seconds calls ms/call ms/call name 12.19 2.74 2.74 4930 0.56 0.77 Array#each
This points you in the right direction, but where do you go from here? Most programs are full of calls toArray#each. To optimize your program, you need to know which lines of code are responsible for most of theArray#eachcalls. Ruby's profiler can't give tell you which line of code called a problem method, but it's easy to write a different profiler that can.The heart of any Ruby profiler is aProcobject passed into theKernel#set_trace_funcmethod. This is a hook into the Ruby interpreter itself: if you set a trace function, it's called every time the Ruby interpreter does something interesting like call a method.Here's the start of aCallTrackerclass. It initializes a hash-based data structure that tracks "interesting" classes and methods. It assumes that we pass a methodtally_callsintoset_trace_func;we'll definetally_callsa little later.class CallTracker # Initialize and start the trace. def initialize(show_stack_depth=1) @show_stack_depth = show_stack_depth @to_trace = Hash.new { |h,k| h[k] = {} } start at_exit { stop } end # Register a class/method combination as being interesting. Subsequent calls # to the method will be tallied by tally_calls. def register(klass, method_symbol) @to_trace[klass][method_symbol] = {} end # Tells the Ruby interpreter to call tally_calls whenever it's about to # do anything interesting. def start set_trace_func method(:tally_calls).to_proc end # Stops the profiler, and prints a report of the interesting calls made # while it was running. def stop(out=$stderr) set_trace_func nil report(out) endNow let's define the missing methodsEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 18: Packaging and Distributing Software
- InhaltsvorschauNo matter how productive it makes you, a programming language won't save you any time if you can't take advantage of a body of code written by other people. A community works faster than any one person, and it's usually easier to install and learn a library than to write and debug the same code yourself.That is, if you can find the library in the first place. And if you're not sucked into an mess of dependencies that grow and grow, making you want to write the code yourself just so you can be doing some real programming.The success of Perl's CPAN archive has made the Ruby community work on our own centralized code repository and packaging system. Whatever you think of Perl, you must admit that a Perl programmer can find just about any library they need in CPAN. If you write your own Perl library, you know where to send it: CPAN. This is not really a technical aspect of Perl, but it's a powerful component of that language's popularity.The problem of packaging is more a logistical problem than a technical one. It's a matter of coordination: getting everyone to agree on a single mechanism for installing packages, and a single place to go to find those packages. For Ruby, the installation mechanism is Ruby gems (or
rubygemsor just "gems"), and rubyforge.org is the place to go to find gems (packaged libraries and programs).In many recipes in this book, we tell you to use a gem for some task: the alternative is often to show you pages and pages of code. This chapter covers how to find the gems you need, install them, and package your own software as gems so that others can benefit from your work.You may need to find and install the Ruby gems system itself. It comes installed by default on Windows, but not on Unix. You can download it from this URL:http://rubyforge.org/frs/?group_id=126
To install the Ruby gems package, unzip the tarball or ZIP file, and run thesetup.rbscript within. You can then use thegemcommand to search for and install gems, as described in Recipes 18.1 and 18.2. You can also build your own gems from "gemspec" files, as described in Recipe 18.6, and upload it to RubyForge or some other site (Recipe 18.7).Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Finding Libraries by Querying Gem Respositories
- InhaltsvorschauYou want to find new gems to install on your system, or see which gems you already have installed.From the command line, use
gem's query command:$ gem query *** LOCAL GEMS *** sources (0.0.1) This package provides download sources for remote gem installation $ gem query --remote *** REMOTE GEMS *** actionmailer (1.1.1, 1.0.1, 1.0.0, 0.9.1, 0.9.0, 0.8.1, …) Service layer for easy email delivery and testing. actionpack (1.10.1, 1.9.1, 1.9.0, 1.8.1, 1.8.0, 1.7.0, …) Web-flow and rendering framework putting the VC in MVC. [… Much more output omitted ….]
From Ruby code, useGem::cacheto query your locally installed gems, andGem::RemoteInstaller#searchto query the gems on some other site.Gem::cachecan be treated as anEnumerablefull of tastyGem::Specificationobjects.Gem::Remote-Installer#searchreturns anArraycontaining anArrayofGem::Specificationobjects for every remote source it searched. Usually there will only be one remote source—the main gem repository on rubyforge.org.This Ruby code iterates over the locally installed gems:require 'rubygems' Gem::cache.each do |name, gem| puts %{"#{gem.name}" gem version #{gem.version} is installed.} end # "sources" gem version 0.0.1 is installedTheformat_gemsmethod defined below gives a convenient way of looking at a large set ofGem::Specificationobjects. It groups the gems by name and version, then prints a formatted list:require 'rubygems/remote_installer' require 'yaml' def format_gems(gems) gem_versions = gems.inject({}) { |h, gem| (h[gem.name] ||= []) << gem; h} gem_versions.keys.sort.each do |name| versions = gem_versions[name].collect { |gem| gem.version.to_s } puts "#{name} is available in these versions: #{versions.join(', ')}" end endHere it is being run on the gems available from RubyForge:format_gems(Gem::RemoteInstaller.new.search(/.*/).flatten) # Asami is available in these versions: 0.04 # Bangkok is available in these versions: 0.1.0 # Bloglines4R is available in these versions: 0.1.0 # BlueCloth is available in these versions: 0.0.2, 0.0.3, 0.0.4, 1.0.0 # …
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Installing and Using a Gem
- InhaltsvorschauYou want to install a gem, then use the code it provides in your programs.You can install the latest version of a gem with the
gem installcommand. This command looks for an uninstalled gem file on your local system; if it can't find one, it calls out to an external source (gems.rubyforge.org, unless you specify otherwise) asking for a gem file. Sincegem installchanges the system-wide Ruby installation, you'll need to have superuser access to run it.$ gem install RedCloth Attempting local installation of 'RedCloth' Local gem file not found: RedCloth*.gem Attempting remote installation of 'RedCloth' Successfully installed RedCloth-3.0.4
A gem contains standard Ruby code files, and once you install the gem, you canrequirethose files normally and use the classes and modules they define. However, gems are not installed in the same path as the standard Ruby libraries, so you'll need to tell Ruby to supplement its normal library path with the path to the gems. The simplest way is torequire 'rubygems'in any program that uses a gem, before you write anyrequirestatements for libraries installed via gems. This is the solution we use throughout this book.# This code assumes the "redcloth" gem has been installed, as in the # code above. require 'redcloth' # LoadError: no such file to load -- redcloth require ' rubygems' require 'redcloth' parser = RedCloth::CommandParser.new # …
For a solution that works across Ruby scripts, you'll need to change your Ruby runtime environment, either by setting the RUBYOPT environment variable torubygems, or by aliasing your ruby command so that it always passes in a-rubygemsoption to the interpreter.$ ruby -e "require 'redcloth'; puts 'Success'" -e:1:in `require': no such file to load -- redcloth (LoadError) from -e:1 $ ruby -rubygems -e "require 'redcloth'; puts 'Success'" Success # On Unix: $ export RUBYOPT=rubygems $ ruby -e "require 'redcloth'; puts 'Success'" Success # On Windows: $ set RUBYOPT=rubygems $ ruby -e "require 'redcloth'; puts 'Success'" Success
Once you've installed a gem, you can upgrade it to the latest version with theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Requiring a Specific Version of a Gem
- InhaltsvorschauYour program depends on an interface or feature of a gem found only in particular versions of the library. If a user tries to run your program with the wrong version installed, you want to tell them which version you require, so they can upgrade.The
rubygemslibrary defines a method,Kernel#require_gem, which is a kind of assertion method for gems. It will raise aGem::LoadErrorif the given gem is not installed, or if no installed version of a gem meets your requirements.The easiest solution is to allow any version of a gem; you don't need to userequire_gemat all:require 'rubygems' require 'cmdparse' # => true
This is equivalent to requiring a minimum version of 0.0.0:require_gem 'nosuchgem' # Gem::LoadError: Could not find RubyGem nosuchgem (> 0.0.0)
If you can't use just any version of a gem, it's usually safe to require a minimum version, relying on future versions to be backwards-compatible:require_gem 'cmdparse', '>= 1.0' # => false require_gem 'cmdparse', '>= 2.0.3' # Gem::LoadError: RubyGem version error: cmdparse(2.0.0 not >= 2.0.3)
Although you may already be familiar with it, a brief review of the structure of version numbers is useful here. A version number for a Ruby gem (and most other pieces of open source software) has three parts: a major version number, a minor version number, and a revision number or build number (Figure 18-1).
Figure 18-1: Anatomy of a version numberSome packages have only a major and minor version number (such as 2.0 or 1.6), and some have additional numbers after the revision number, but the three-number convention is the accepted standard for numbering Ruby gems.The revision number is incremented at every new public release of the software. If the revision contains more than minor changes, or changes the public API in a backwards-compatible way, the author increments the minor version and resets the revision number to zero. When a release contains large changes, especially ones that change the public API in backwards-incompatible ways, the author usually increments the major version number, and resets the minor version and revision number to zero.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Uninstalling a Gem
- InhaltsvorschauYou want to remove an installed gem from your Ruby installation.From the command line, use the
gem uninstallcommand:$ gem uninstall blinkenlights Attempting to uninstall gem 'blinkenlights' Successfully uninstalled blinkenlights version 0.0.2
From Ruby code, the most reliable way to uninstall a gem is to simulate a command-line invocation with theGem::GemRunnerclass. This code installs a gem, then immediately removes it:require 'rubygems' require 'rubygems/installer' require 'rubygems/remote_installer' Gem::RemoteInstaller.new.install('blinkenlights') require 'rubygems/gem_runner' require 'rubygems/doc_manager' Gem.manage_gems Gem::GemRunner.new.run(['uninstall', 'blinkenlights']) # Successfully uninstalled blinkenlights version 0.0.4Uninstalling a gem can disrupt the normal workings of your Ruby programs, so I recommend you only uninstall gems from the command line. That way, there's less chance of a bug wiping out all your gems.Since rubygems can manage multiple installed versions of the same gem, you won't usually have to remove old copies of gems. There are three main reasons to remove gems:- You find out that a particular version of a gem is buggy, and you want to make sure it never gets used.
- You want to save disk space.
- You want to clean up the list of installed gems so that it's more obvious which gems you actually use.
If uninstalling a gem would leave another installed gem with an unmet dependency, you'll be told about the dependency and asked whether you want to go through with the uninstall anyway. You'll get this interactive prompt whether you run thegem uninstallcommand or whether you use theGem::Uninstallerclass from Ruby code.Gem::Uninstaller.new('actionpack', {}).uninstall # You have requested to uninstall the gem: # actionpack-1.8.1 # actionmailer-0.9.1 depends on [actionpack (= 1.8.1)] # If you remove this gem, the dependency will not be met. # Uninstall anyway? [yN]The sources gem is a special gem that tells rubygems to look for remotely installable gems at http://gems.rubyforge.org/ by default. If you uninstall this gem, you won't be able to install any more gems, except through complicated hacks of the classes in theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Reading Documentation for Installed Gems
- InhaltsvorschauYou want to read the RDoc documentation for the gems you have installed. Although some gem projects provide human-written documentation like tutorials, the generated RDoc documentation isn't usually available online.RDoc documentation isn't usually available online because when you install a gem, Ruby generates your very own HTML copy of the RDoc documentation and installs it along with the software. The documentation you need is probably already on your computer.The simplest way to browse the documentation for your installed gems is to run the
gem_servercommand, then visit http://localhost:8808/. You'll see all your installed gems in a table form, and be able to browse the generated documentation of each gem that provides any.Otherwise, you can find your Rubygems documentation directory, and browse the installed documentation with local filesystem tools.The generated rdoc for a gem is kept in thedoc/subdirectory of the base directory in which the gem was installed. For instance, on my computer, gems are installed in/usr/lib/ruby/gems/1.8/. For every gem that has RDoc, the generated HTML documentation will be kept in the directory/usr/lib/ruby/gems/1.8/doc/[gem name]/rdoc/. If I were to install one particular gem to another directory, the documentation for the gem would be in adoc/subdirectory of that directory.Here's some code that prints out the location of the RDoc files for every installed gem. Unless you've installed specific gems in nonstandard locations, they'll all be in thedoc/subdirectory ofGem.dir. This code snippet also shows off some of the capabilities ofGem::DocManager, the Ruby class you can use to manipulate a gem's RDoc.require 'rubygems' Gem.manage_gems def show_gem_rdoc puts "Your generated docs are all probably in #{File.join(Gem.dir, "doc")}" puts "Just to be safe, I'll print out every gem's RDoc location:" specifications_dir = File.join(Gem.dir, 'specifications') lacking_rdoc = [] Gem::SourceIndex.from_installed_gems(specifications_dir).each do |path, spec| manager = Gem::DocManager.new(spec) if manager.rdoc_installed? doc_path = File.join(spec.installation_path, 'doc', spec.full_name) puts " #{spec.full_name} => #{doc_path}" else lacking_rdoc << spec.full_name end end unless lacking_rdoc.empty? puts "\nThese installed gems have no RDoc installed:" puts " #{lacking_rdoc.join("\n ")}" end end show_gem_rdoc # Your generated RDoc is probably all in /usr/lib/ruby/gems/1.8/doc # Just to be safe, I'll print out every gem's RDoc location: # flexmock-0.1.7 => /usr/lib/ruby/gems/1.8/doc/flexmock-0.1.7 # simple-rss-1.1 => /usr/lib/ruby/gems/1.8/doc/simple-rss-1.1 # classifier-1.3.0 => /usr/lib/ruby/gems/1.8/doc/classifier-1.3.0 # actionmailer-1.1.5 => /usr/lib/ruby/gems/1.8/doc/actionmailer-1.1.5 # … # # These installed gems have no RDoc installed: # Ruby-MemCache-0.0.1 # RedCloth-3.0.4 # sources-0.0.1 # …Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Packaging Your Code as a Gem
- InhaltsvorschauYou want to package a program you wrote as a Ruby gem, possibly to distribute it on the main gem server at rubyforge.org.First, you must write a specification file. This file consists of a few lines of Ruby code that instantiate a
Gem::Specificationobject and populate it with information about your program. Assuming that all of your program's files are in a subdirectory calledlib/, the following might make a good specification file:# shielding.gemspec require 'rubygems' spec = Gem::Specification.new do |spec| spec.name = 'shielding' spec.summary = 'A library for calculating the strength of duophasic shielding' spec.description = %{This library calculates to high precision the physical and electrostatic strength of a duophasic shield. It knows about most real-world shield configurations, as well as many theoretical arrangements not yet built.} spec.author = 'Bob Zaff' spec.email = 'zaff@example.com' spec.homepage = 'http://www.example.com/software/shielding/' spec.files = Dir['lib/*.rb'] spec.version = '1.0.0' endYou can then use thegem buildcommand to create the actual gem from its specification file:$ gem build shielding.gemspec Attempting to build gem spec 'shielding.gemspec' Successfully built RubyGem Name: shielding Version: 1.0.0 File: shielding-1.0.0.gem $ ls shield.gemspec shielding-1.0.0.gem
Then install the gem normally:$ gem install ./shielding-1.0.0.gem Attempting local installation of './shielding-1.0.0.gem' Successfully installed shielding, version 1.0.0 Installing RDoc documentation for shielding-1.0.0… WARNING: Generating RDoc on .gem that may not have RDoc.
You can also build a gem from within Ruby code by passing the completedGem::Specificationinto aGem::Builderobject.require 'rubygems/builder' builder = Gem::Builder.new(spec).build # Successfully built RubyGem # Name: shielding # Version: 1.0.0 # File: shielding-1.0.0.gem # => "shielding-1.0.0.gem"
Gem::Builderis useful as a starting point for automating your releases, but if you're interested in doing that, you should use Rake (see Chapter 19, especially Recipe 19.4).Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Distributing Your Gems
- InhaltsvorschauYou've packaged your software as a Ruby gem, but nobody knows about it. You want to make your gem easy to find and install, so that your genius does not go unrecognized.The simplest solution (for you, at least) is to upload your .
gemfile to a web site or FTP site. Your users can download the .gemfile, then install it by passing the filename into thegem installcommand:$ wget http://www.example.com/gems/my_gem-1.0.4.gem --10:40:10-- http://www.example.com/gems/my_gem-1.0.4.gem => `my_gem-1.0.4.gem' Resolving gems.example.com… 204.127.202.4 Connecting to gems.example.com|204.127.202.4|:80… connected. HTTP request sent, awaiting response… 200 OK Length: 40,823 (40K) [text/plain] 100%[====================================>] 40,823 46.96K/s 10:40:11 (46.85 KB/s) - `my_gem-1.0.4.gem' saved [40823/40823] $ gem install ./my_gem-1.0.4.gem Attempting local installation of './my_gem-1.0.4.gem' Successfully installed my_gem, version 1.0.4 Installing RDoc documentation for my_gem-1.0.4…
If your gem has dependencies, the end user must separately install the dependencies before installing a downloaded gem, or thegemcommand will become confused and die. This will happen even if the user specifies the--include-dependenciesflag:$ gem install --include-dependencies ./my_gem_with_dependency-1.0.0.gem Attempting local installation of './my_gem_with_dependency.1.0.0.gem' ERROR: Error installing gem ./my_gem_with_dependency-1.0.0.gem[.gem]: my_gem_with_dependency requires my_dependency > 0.0.0
If you distribute your gem from a web site, be sure to set thehomepageattribute in your gemspec file.Gems are usually distributed through HTTP. A web server might serve standalone .gemfiles intended for download by the end user, or it might also serve some metadata that allows thegemcommand to download and install gems on its own.There are several ways of setting up gems for distribution. In general you must negotiate a tradeoff between the developer's (your) convenience and the end user's ease of installation. The Rubygems package makes it easy to install and manage third-party Ruby packages, but the developers of those packages have to jump through some hoops if they want to make the installation process as transparent as possible.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Installing and Creating Standalone Packages with setup.rb
- InhaltsvorschauYou want to install a Ruby package that includes a
setup.rbscript instead of being packaged as a Ruby gem. Or, you want to make it possible for people to install your software package without having to install Ruby gems.To install asetup-rb—based Ruby package as root or the administrative user, simply run thesetup.rbscript:$ ruby setup.rb
By default,setup.rbinstalls a package into yoursite_rubydirectory. If you don't have root access or only want to install the package for your own use, you can install the package into your home directory, like this:$ ruby setup.rb all --installdirs=home
That command installs the package into thelib/ruby/subdirectory of your home directory. Make sure you have that directory included in yourRUBYLIBenvironment variable, or Ruby won't know to look there when yourequirea library. You can check your library path with the special $: global variable:$: # => ["/home/leonardr/lib/ruby", "/usr/local/lib/site_ruby/1.8", … ] require 'installed_via_setup' # => true
Because Ruby gems are not yet part of the standard Ruby library, some people prefer to package their software releases as self-contained archives. A package that includes asetup.rbinstallation script contains all the code and data necessary for installation; it might have dependencies, but it doesn't rely on another component just to get itself installed. Therubygemspackage itself is installed viasetup.rb, since it can't assume that the system already supports gem-based installations.You might also use asetup.rbscript instead of a Ruby gem if you want to add Ruby hook scripts to the installation procedure. For instance, you might want to create a new database when your package is installed. Once the Rubygems package is included in the Ruby standard library, this will be just about the only reason left not to package all your software as Ruby gems. Even native C extensions can be included in a Ruby gem and built as part of the gem installation.Ruby gems andsetup.rbimpose similar file structures on your package: your Ruby libraries go into alib/subdirectory, command-line applications go into aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 19: Automating Tasks with Rake
- InhaltsvorschauEven when your software is written, tested, and packaged, you're still not done. You've got to start working on the next version, and the next… Every release you do, in some cases every change you make to your code, will send you running through a maze of repetitive tasks that have nothing to do with programming.Fortunately, there's a way to automate these tasks, and the best part is that you can do it by writing more Ruby code. The answer is Rake.Rake is a build language, Ruby's answer to Unix
makeand Java's Ant. It lets you define tasks: named code bocks that carry out specific actions, like building a gem or running a set of unit tests. Invoke Rake, and your predefined tasks will happily do the work you once did: compiling C extensions, splicing files together, running unit tests, or packaging a new release of your software. If you can define it, Rake can run it.Rake is available as therakegem; if you've installed Rails, you already have it. Unlike most gems, it doesn't just install libraries: it installs a command-line program calledrake, which contains the logic for actually performing Rake tasks. For ease of use, you may need to add to your PATH environment variable the directory containing therakescript: something like/usr/lib/ruby/gems/1.8/gems/rake-0.6.2/bin/. That way you can just runrakefrom the command line.A Rakefile is just a Ruby source file that has access to some special methods:task, file, directory, and a few others. Calling one of these methods defines a task, which can be run by the command-linerakeprogram, or called as a dependency by other tasks.The most commonly used method is the generic one:task. This method takes the name of the task to define, and a code block that implements the task. Here's a simple Rakefile that defines two tasks,cross_bridgeandbuild_bridge, one of which depends on the other. It designatescross_bridgeas the default task by defining a third task calleddefaultwhich does nothing except depend oncross_bridge.# Rakefile desc "Cross the bridge." task :cross_bridge => [:build_bridge] do puts "I'm crossing the bridge." end desc "Build the bridge" task :build_bridge do puts 'Bridge construction is complete.' end task :default => [:cross_bridge]
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Automatically Running Unit Tests
- InhaltsvorschauCredit: Pat EylerYou want to make it easy to run your project's unit test suite. You also want the tests to run automatically before you do a new release of your project.Require the
rake/testtasklibrary and create a newRake::TestTask. Save the following code in a file calledRakefilein the project's top-level directory (or add it to your existing Rakefile).require ' rake/testtask' Rake::TestTask.new('test') do |t| t.pattern = 'test/**/tc_*.rb' t.warning = true endThis Rakefile makes two assumptions:- The
Test::Unittest cases live in files under thetestdirectory (and its subdirectories). The names of these files start withtc_and end in.rb. - The Ruby libraries to be tested live under the
libdirectory. Rake automatically appends this directoy to Ruby's load path, the list of directories that Ruby searches when you try torequirea library.
To execute your test cases, run the commandrake testin the project's top-level directory. The tests are loaded by a new Ruby interpreter with warnings enabled. The output is the same as you'd see fromTest::Unit's console runner.If it's easy to trigger the test process, you'll run your tests more often, and you'll detect problems sooner. Rake makes it really convenient to run your tests.We can make the test command even shorter by defining a default task. Just add the following line to the Rakefile. The position within the file doesn't matter, but to keep things clear, you should put it before other task definitions:task "default" => ["test"]
Now, whenever we runrakewithout an argument, it will invoke thetesttask. If your Rakefile already has a default task, you should be able to just add thetesttask to its list of prerequisites. Similarly, if you have a task that packages a new release of your software (like the one defined in Recipe 19.4), you can make thetesttask a prerequisite. If your tests fail, your package won't be built and you won't release a buggy piece of software.TheRake::TestTaskhas a special attribute,Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Automatically Generating Documentation
- InhaltsvorschauCredit: Stefan LangYou want to automatically create HTML pages from the RDoc formatted comments in your code, and from other RDoc formatted files.Within your Rakefile, require the
rake/rdoctasklibrary and create a newRake:: RDocTask. Here's a typical example:require 'rake/rdoctask' Rake::RDocTask.new('rdoc') do |t| t.rdoc_files.include('README', 'lib/**/*.rb') t.main = 'README' t.title = "MyLib API documentation" endNow you can run the commandrake rdocfrom a shell in your project's top-level directory. This particular Rake task creates API documentation for all files under the lib directory (and its subdirectories) whose names end in.rb. Additionally, the RDoc-formatted contents of the top-levelREADMEfile will appear on the front page of the documentation.The HTML output files are written under your project's %(filename)html% directory. To read the documentation, point your browser to %(filename)html/index.html%. The browser will show "MyLib API documentation" (that is, the value of the task'stitle) as the page title.It is common practice among authors of Ruby libraries to document a library's API with RDoc-formatted text. Since Ruby 1.8.1, a standard Ruby installation contains therdoctool, which extracts the RDoc comments from source code and creates nicely formatted HTML pages.Unlike the tasks you define from scratch with thetaskmethod, but like theTestTaskcovered in Recipe 19.1,Rake::RDocTask.newtakes a code block, which is executed immediately at task definition time. The code block lets you customize how your RDoc documentation should look. After running your code block, theRake:: RDocTaskobject defines three new Rake tasks:-
rdoc - Updates the HTML documentation by running RDoc.
-
clobber_rdoc - Removes the directory and its contents created by the
rdoctask. -
rerdoc - Force a rebuild of the HTML-documentation. Has the same effect as running
clobber_rdocfollowed byrdoc.
Now we know enough to integrate theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. -
- Cleaning Up Generated Files
- InhaltsvorschauCredit: Stefan LangYou want to clean up files that aren't actually part of your project: generated files, backup files, and so on.Within your Rakefile, require the
rake/cleanlibrary to get access to thecleanandclobbertasks. Put glob patterns for all your generated files in theCLOBBER FileList. Put glob patterns for all other scratch files in theCLEAN FileList.By default,CLEANalso includes the patterns**/*~, **/*.bak, and**/core. Here's a typical set ofCLOBBERandCLEANfiles:require ' rake/clean' # Include the "pkg" and "doc" directories and their contents. # Include all files ending in ".o" in the current directory # and its subdirectories (recursively). CLOBBER.include('pkg', 'doc', '**/*.o') # Include InstalledFiles and .config: files created by setup.rb. # Include temporary files created during test run. CLEAN.include('InstalledFiles', '.config', 'test/**/*.tmp')Runrake cleanto remove all files specified by theCLEANfilelist, andrake clobberto remove the files specified by both file lists.Therake/cleanlibrary initializes the constantsCLEANandCLOBBERto newRake:: FileListinstances. It also defines the taskscleanandclobber, makingcleana prerequisite ofclobber. The idea is thatrake cleanremoves any files that might need to be recreated once your program changes, whilerake clobberreturns your source tree to a completely pristine state.Other Rake libraries define cleanup tasks that remove certain products of their main tasks. An example: the packaging libraries create a task calledclobber_package, and make it a prerequisite ofclobber. Runningrake clobberon such a project removes the package files: you don't have to explicitly include them in yourCLOBBERlist.You can do the same thing for your own tasks: rather than manipulateCLEANandCLOBBER, you can create a custom cleanup task and make it a prerequisite ofcleanorclobber. The following code is a different way of making sure thatrake clobberremoves any precompiled object files:desc 'Remove all object files.' task 'clobber_objects' do rm_f FileList['**/*.o'] end # Make clobber_objects a prerequisite of the preexisting clobber task task 'clobber' => 'clobber_objects'
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Automatically Building a Gem
- InhaltsvorschauCredit: Stefan LangYou want to automatically build a gem package for your application or library whenever you do a release.Require the
rake/gempackagetasklibrary within your Rakefile, and create aGem:: Specificationinstance that describes your project. Feed it to theRake:: GemPackageTaskconstructor, which automatically defines a number of gem-related tasks:require 'rake/gempackagetask' # Create a gem specification gem_spec = Gem::Specification.new do |s| s.name = 'docbook' s.version = '1.0.0' s.summary = 'DocBook formatting program and library.' # Files containing Test::Unit test cases. s.test_files = FileList['tests/**/*'] # Executable scripts under the "bin" directory. s.executables = ['voc'] # List of other files to be included. s.files = FileList['README', 'ChangeLog', 'lib/**/*.rb'] end Rake::GemPackageTask.new(gem_spec) do |pkg| pkg.need_zip = false pkg.need_tar = false end
Run the commandrake package, and (assuming those files actually exist), Rake will build a gem filedocbook-1.0.0.gemunder thepkg/directory.The RubyGems library provides theGem::Specificationclass, and Rake provides theRake::GemPackageTaskclass that uses it. Creating a newRake::GemPackageTaskobject automatically defines the three tasks:package, clobber_package, andrepackage.Thepackagetask builds a gem inside the project's pkg/ directory. Theclobber_packagetask removes the pkg/ directory and its contents. Therepackagetask just invokesclobber_packageto remove any old package file, and then invokespackageto rebuild them from scratch.The example above sets to false the attributesneed_zipandneed_tarof theRake::GemPackageTask. If you set them to true, then in addition to a gem you'll get a ZIP file and a gzipped tar archive containing the same files as the gem. Note that Rake uses thezipandtarcommand-line tools, so if your system doesn't provide them (the way a standard Windows installation doesn't), thepackagetask won't be able to create these ZIP or tar archives.ThepackageEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Gathering Statistics About Your Code
- InhaltsvorschauCredit: Stefan LangYou want to gather statistics about your Ruby project, like the total number of lines of code.Here's a class that parses Ruby source files and gathers statistics. Put this in
scriptlines.rbin your project's top-level directory.# scriptlines.rb # A ScriptLines instance analyses a Ruby script and maintains # counters for the total number of lines, lines of code, etc. class ScriptLines attr_reader :name attr_accessor :bytes, :lines, :lines_of_code, :comment_lines LINE_FORMAT = '%8s %8s %8s %8s %s' def self.headline sprintf LINE_FORMAT, "BYTES", "LINES", "LOC", "COMMENT", "FILE" end # The 'name' argument is usually a filename def initialize(name) @name = name @bytes = 0 @lines = 0 # total number of lines @lines_of_code = 0 @comment_lines = 0 end # Iterates over all the lines in io (io might be a file or a # string), analyses them and appropriately increases the counter # attributes. def read(io) in_multiline_comment = false io.each { |line| @lines += 1 @bytes += line.size case line when /^=begin(\s|$)/ in_multiline_comment = true @comment_lines += 1 when /^=end(\s|$)/: @comment_lines += 1 in_multiline_comment = false when /^\s*#/ @comment_lines += 1 when /^\s*$/ # empty/whitespace only line else if in_multiline_comment @comment_lines += 1 else @lines_of_code += 1 end end } end # Get a new ScriptLines instance whose counters hold the # sum of self and other. def +(other) sum = self.dup sum.bytes += other.bytes sum.lines += other.lines sum.lines_of_ code += other.lines_of_code sum.comment_lines += other.comment_lines sum end # Get a formatted string containing all counter numbers and the # name of this instance. def to_s sprintf LINE_FORMAT, @bytes, @lines, @lines_of_code, @comment_lines, @name end endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Publishing Your Documentation
- InhaltsvorschauCredit: Stefan LangYou want to automatically update your project's web site on RubyForge (or some other site) with generated documentation or custom pages.As seen in Recipe 19.2, Rake provides a
RDocTaskfor generating RDoc documentation:require 'rake/rdoctask' html_dir = 'doc/html' library = 'MyLib' Rake::RDocTask.new('rdoc') do |t| t.rdoc_files.include('README', 'lib/**/*.rb') t.main = 'README' t.title = "#{library} API documentation" t.rdoc_dir = html_dir endTo upload your generated documentation to RubyForge, use this task along with theupload-docstask defined below. The Unixscpcommand-line tool does the actual work of uploading:# Define your RubyForge username and your project's Unix name here: rubyforge_user = 'user' rubyforge_project = 'project' rubyforge_path = "/var/www/gforge-projects/#{rubyforge_project}/" desc 'Upload documentation to RubyForge.' task 'upload-docs' => ['rdoc'] do sh "scp -r #{html_dir}/* " + "#{rubyforge_user}@rubyforge.org:#{rubyforge_path}" endSet off the publishing process by invokingrake upload-docs. Theupload-docstask has therdoctask as a prerequisite, so the HTML pages underdoc/html/will be created if necessary.Thenscpprompts for your RubyForge account password. Enter it, and all files underdoc/html/and its subdirectories will be uploaded to RubyForge. The docs will become available under http://project.rubyforge.org/, where "project" is the Unix name of your project. Now your users can read your RDoc online without having to generate it themselves. Your documentation will also show up in web search results.Rake'sshmethod starts an instance of the OS's standard shell. This feature is used to run thescpcommand-line tool. This means that this recipe will only work ifscpis installed on your system.Thescpcommand copies all the files that the RDoc placed underdoc/html/, to the root of your project's web site on the RubyForge server. In effect, the main page of the API documentation will appear as your project's homepage. Some RubyForge projects don't have a custom homepage, so this is a good place to put the RDoc. If you want a custom homepage, just copy the RDoc into a different directory by changingEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Running Multiple Tasks in Parallel
- InhaltsvorschauYour build process takes too long to run. Rake finishes copying one set of files only to start copying another set. You could save time by running these tasks in parallel, instead of stringing them one after another.Define a task using the
multitaskfunction instead oftask. Each of that task's prerequisites will be run in a separate thread.In this code, I'll define two long-running tasks:task 'copy_docs' do # Simulate a large disk copy. sleep 5 end task 'compile_extensions' do # Simulate a C compiler compiling a bunch of files. sleep 10 end task 'build_serial' => ['copy_docs', 'compile_extensions'] multitask 'build_parallel' => ['copy_docs', 'compile_extensions']
Thebuild_serialtask runs in about 15 seconds, but thebuild_paralleltask does the same thing in about 10 seconds.Amultitaskruns just like a normaltask, except that each of its dependencies runs in a separate thread. When running the dependencies of amultitask, Rake first finds any common secondary dependencies of these dependencies, and runs them first. It then spawns a separate thread for each dependency, so that they can run simultaneously.Consider three tasks,ice_cream, cheese, andyogurt, all of which have a dependency onbuy_milk. You can run the first three tasks in separate threads with amultitask, but Rake will runbuy_milkbefore creating the threads. Otherwise,ice_cream, cheese, andyogurtwould all triggerbuy_milk, wasting time.When your tasks spend a lot of time blocking on I/O operations (as many Rake tasks do), using amultitaskcan speed up your builds. Unfortunately, it can also cause the same problems you'll see with any multithreaded code. If you've got a fancy Rakefile, in which the tasks keep state inside Ruby data structures, you'll need to synchronize access to those data structures to prevent multithreading problems.You may also have problems converting ataskto amultitaskif your dependencies are set up incorrectly. Take the following example:task 'build' => ['compile_extensions', 'run_tests', 'generate_rdoc']
The unit tests can't run if the compiled extensions aren't available, soEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - A Generic Project Rakefile
- InhaltsvorschauCredit: Stefan LangEvery project's Rakefile is different, but most Ruby projects can be handled by very similar Rakefiles. To close out the chapter, we present a generic Rakefile that includes most of the tasks covered in this chapter, and a few (such as compilation of C extensions) that we only hinted at.This Rakefile will work for pure Ruby projects, Ruby projects with C extensions, and projects that are only C extensions. It defines an overarching task called
publishthat builds the project, runs tests, generates RDoc, and releases the whole thing on Ruby-Forge. It's a big file, but you don't have to use all of it. Thepublishtask is made entirely of smaller tasks, and you can pick and choose from those smaller tasks to build your own Rakefile. For a simple project, you can just customize the settings at the beginning of the file, and ignore the rest. Of course, you can also extend this Rakefile with other tasks, like thestatstask presented in Recipe 19.5.This Rakefile assumes that you follow the directory layout conventions laid down by thesetup.rbscript, even if you don't actually usesetup.rbto install your project. For instance, it assumes you put your Ruby files inlib/and your unit tests intest/.First, we include Rake libraries that make it easy to define certain kinds of tasks:# Rakefile require "rake/testtask" require "rake/clean" require "rake/rdoctask" require "rake/gempackagetask"
You'll need to configure these variables:# The name of your project PROJECT = "MyProject" # Your name, used in packaging. MY_NAME = "Frodo Beutlin" # Your email address, used in packaging. MY_EMAIL = "frodo.beutlin@my.al" # Short summary of your project, used in packaging. PROJECT_SUMMARY = "Commandline program and library for …" # The project's package name (as opposed to its display name). Used for # RubyForge connectivity and packaging. UNIX_NAME = "my_project" # Your RubyForge user name. RUBYFORGE_USER = ENV["RUBYFORGE_USER"] || "frodo" # Directory on RubyForge where your website's files should be uploaded. WEBSITE_DIR = "website" # Output directory for the rdoc html files. # If you don't have a custom homepage, and want to use the RDoc # index.html as homepage, just set it to WEBSITE_DIR. RDOC_HTML_DIR = "#{WEBSITE_DIR}/rdoc"Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 20: Multitasking and Multithreading
- InhaltsvorschauYou can't concentrate on more than What's six times nine? one thing at once. You won't get very far reading this book if someone is interrupting you every five seconds asking you to do arithmetic problems. But any computer with a modern operating system can do many things at once. More precisely, it can simulate that ability by switching very quickly back and forth between tasks.In a multitasking operating system, each program, or process, gets its own space in memory and a share of the CPU's time. Every time you start the Ruby interpreter, it runs in a new process. On Unix-based systems, your script can spawn subprocesses: this feature is very useful for running external command-line programs and using the results in your own scripts (see Recipes 20.8 and 20.9, for instance).The main problem with processes is that they're expensive. It's hard to read while people are asking you to do arithmetic, not because either activity is particularly difficult, but because it takes time to switch from one to the other. An operating system spends a lot of its time as overhead, switching between processes, trying to make sure each one gets a fair share of the CPU's time.The other problem with processes is that it's difficult to get them to communicate with each other. For simple cases, you can use techniques like those described in Recipe 20.8. You can implement more complex cases with Inter-Process Communication and named pipes, but we say, don't bother. If you want your Ruby program to do two things at once, you're better off writing your code with threads.A thread is a sort of lightweight process that runs inside a real process. One Ruby process can host any number of threads, all running more or less simultaneously. It's faster to switch between threads than to switch between processes, and since all of a process's threads run in the same memory space, they can communicate simply by sharing variables.Recipe 20.3 covers the basics of multithreaded programming. We use threads throughout this book, except when only a subprocess will work (see, for instance, Recipe 20.1). Some recipes in other chapters, like Recipes 3.12 and 14.4, show threads used in context.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
- Running a Daemon Process on Unix
- InhaltsvorschauYou want to run a process in the background with minimal interference from users and the operating system.In Ruby 1.9, you can simply call
Process.daemonto turn the current process into a daemon. Otherwise, the most reliable way is to use theDaemonizemodule. It's not available as a gem, but it's worth downloading and installing, because it makes it easy and reliable to write a daemon:#!/usr/bin/ruby -w # daemonize_daemon.rb require 'tempfile' require 'daemonize' include Daemonize # Import Daemonize::daemonize into this namespace puts 'About to daemonize.' daemonize # Now you're a daemon process! log = Tempfile.new('daemon.log') loop do log.puts "I'm a daemon, doin' daemon things." log.flush sleep 5 endIf you run this code at the command line, you'll get back a new prompt almost immediately. But there will still be a Ruby process running in the background, writing to a temporary file every five seconds:$ ./daemonize_daemon.rb About to daemonize. $ ps x | grep daemon 4472 ? S 0:00 ruby daemonize_daemon.rb 4474 pts/2 S+ 0:00 grep daemon $ cat /tmp/daemon.log4472.0 I'm a daemon, doin' daemon things. I'm a daemon, doin' daemon things. I'm a daemon, doin' daemon things.
Since it runs an infinite loop, this daemon process will run until you kill it:$ kill 4472 $ ps x | grep daemon 4569 pts/2 S+ 0:00 grep daemon
A different daemon might run until some condition is met, or until it receives a Unix signal, or a "stop" message through some interface.A daemon process is one that runs in the background, without any direct user interface at all. Servers are usually daemon processes, but you might also write a daemon to do monitoring or task scheduling.Rather than replacing your process with a daemon process, you may want to spawn a daemon while continuing with your original work. The best strategy for this is to spawn a subprocess withKernel#fork.Ruby'sforkimplementation takes a code block to be run by the subprocess. The code defined after the block is run in the original process. So pass your daemonizing code intoEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Creating a Windows Service
- InhaltsvorschauCredit: Bill FroelichYou want to write a self-contained Ruby program for Windows that performs a task in the background.Create a Windows service using the
win32-servicelibrary, available as thewin32-servicegem.Put all the service code below into a Ruby file called rubysvc.rb. It defines a service that watches for the creation of a filec:\findme.txt; if it ever finds that file, it immediately renames it.The first step is to register the service with Windows. Runningruby rubysrvc.rb registerwill create the service.# rubysrvc.rb require 'rubygems' require 'win32/service' include Win32 SERVICE_NAME = "RubySvc" SERVICE_DISPLAYNAME = "A Ruby Service" if ARGV[0] == "register" # Start the service. svc = Service.new svc.create_service do |s| s.service_name = SERVICE_NAME s.display_name = SERVICE_DISPLAYNAME s.binary_path_name = 'C:\InstantRails-1.3\ruby\bin\ruby ' + File.expand_path($0) s.dependencies = [] end svc.close puts "Registered Service - " + SERVICE_DISPLAYNAME
When you're all done, you can runrubysrvc.rb stopto stop the service and remove it from Windows:elsif ARGV[0] == "delete" # Stop the service. if Service.status(SERVICE_NAME).current_state == "running" Service.stop(SERVICE_NAME) end Service.delete(SERVICE_NAME) puts "Removed Service - " + SERVICE_DISPLAYNAME else
If you runrubysrvc.rbwith no arguments, nothing will happen, but it will remind you what parameters you can use:if ENV["HOMEDRIVE"]!=nil # We are not running as a service, but the user didn't provide any # command line arguments. We've got nothing to do. puts "Usage: ruby rubysvc.rb [option]" puts " Where option is one of the following:" puts " register - To register the Service so it " + "appears in the control panel" puts " delete - To delete the Service from the control panel" exit end
But when Windows runsrubysrvc.rbas a service, the real action starts:# If we got this far, we are running as a service. class Daemon def service_init # Give the service time to get everything initialized and running, # before we enter the service_main function. sleep 10 end def service_main fileCount = 0 # Initialize the file counter for the rename watchForFile = "c:\\findme.txt" while state == RUNNING sleep 5 if File.exists? watchForFile fileCount += 1 File.rename watchForFile, watchForFile + "." + fileCount.to_s end end end end d = Daemon.new d.mainloop end
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Doing Two Things at Once with Threads
- InhaltsvorschauYou want your program to run two or more pieces of code in parallel.Create a new thread by passing a code block into
Thread.new. That block will run simultaneously with any code you write after the call toThread.new.The following code features two competing threads. One continually decrements a variable by one, while the main program's thread busily incrementing the same variable by three. The decrementing thread starts its work earlier, but the incrementing thread always wins in the end, because it increments the counter by a larger number:x = 0 Thread.new do while x < 5 x -= 1 puts "DEC: I decremented x to #{x}\n" end puts "DEC: x is too high; I give up!\n" end while x < 5 x += 3 puts "INC: I incremented x to #{x}\n" end # DEC: I decremented x to -1 # DEC: I decremented x to -2 # DEC: I decremented x to -3 # DEC: I decremented x to -4 # INC: I incremented x to -1 # DEC: I decremented x to -2 # INC: I incremented x to 1 # DEC: I decremented x to 0 # INC: I incremented x to 3 # DEC: I decremented x to 2 # INC: I incremented x to 5 # DEC: x is too high; I give up! x # => 5A Ruby process starts out running only one thread: the main thread. When you callThread#new, Ruby spawns another thread and starts running it alongside the main thread. The operating system divides CPU time among all the running processes, and the Ruby interpreter further divides its alotted CPU time among all of its threads.The block you pass intoThread.newis a closure (see Recipe 7.4), so it has access to all the variables that were in scope at the time you instantiated the thread. This means that threads can share variables; as a result, you don't need complex communication schemes the way you do to communicate between processes. However, it also means that your threads can step on each other's toes unless you're careful to synchronize any shared objects. In the example above, the threads were designed to step on each other's toes, providing head-to-head competition, but usually you don't want that.Once a thread's execution reaches the end of its code block, the thread dies. If your main thread reaches the end ofEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Synchronizing Access to an Object
- InhaltsvorschauYou want to make an object accessible from only one thread at a time.Give the object a
Mutexmember (a semaphore that controls whose turn it is to use the object). You can then use this to synchronize activity on the object.This code gives every object asynchronizemethod. This simulates the behavior of Java, in whichsynchronizeis a keyword that can be applied to any object:require 'thread' class Object def synchronize mutex.synchronize { yield self } end def mutex @mutex ||= Mutex.new end endHere's an example. The first thread gets a lock on the list and then dawdles for a while. The second thread is ready from the start to add to the list, but it doesn't get a chance until the first thread releases the lock.list = [] Thread.new { list.synchronize { |l| sleep(5); 3.times { l.push "Thread 1" } } } Thread.new { list.synchronize { |l| 3.times { l.push "Thread 2" } } } sleep(6) list # => ["Thread 1", "Thread 1", "Thread 1", "Thread 2", "Thread 2", "Thread 2"]Object#synchronizeonly prevents two synchronized code blocks from running at the same time. Nothing prevents a wayward thread from modifying the object without callingsynchronizefirst:list = [] Thread.new { list.synchronize { |l| sleep(5); 3.times { l.push "Thread 1" } } } Thread.new { 3.times { list.push "Thread 2" } } sleep(6) list # => ["Thread 2", "Thread 2", "Thread 2", "Thread 1", "Thread 1", "Thread 1"]One of the big advantages of multithreaded programs is that different threads can share data. But where there is data sharing, there is the possibility for corruption. When two threads operate on the same object at the same time, the results can vary wildly depending on when the Ruby interpreter decides to switch between threads. To get predictable behavior, you need to have one thread lock the object, so other threads can't use it.When every object has asynchronizemethod, it's easier to share an object between threads: if you want to work alone with the object, you put that code within asynchronizeblock. Of course, you may find yourself constantly writing synchronization code whenever you call certain methods of an object.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Terminating a Thread
- InhaltsvorschauYou want to kill a thread before the end of the program.A thread terminates if it reaches the end of its code block. The best way to terminate a thread early is to convince it to reach the end of its code block. This way, the thread can run cleanup code before dying.This thread runs a loop while the instance variable
continueis true. Set this variable to false, and the thread will die a natural death:require 'thread' class CounterThread < Thread def initialize @count = 0 @continue = true super do @count += 1 while @continue puts "I counted up to #{@count} before I was cruelly stopped." end end def stop @continue = false end end counter = CounterThread.new sleep 2 counter.stop # I counted up to 3413544 before I was cruelly stopped.If you need to stop a thread that doesn't offer astop-like function, or you need to stop an out-of-control thread immediately, you can always callThread#terminate. This method stops a thread in its tracks:t = Thread.new { loop { puts 'I am the unstoppable thread!' } } # I am the unstoppable thread! # I am the unstoppable thread! # I am the unstoppable thread! # I am the unstoppable thread! t.terminateIt's better to convince someone they should do something than to force them to do it. The same istrueof threads. CallingThread.terminateis a bit like throwing an exception: it interrupts the normal flow of execution in an unpredictable place. Worse, there's no equivalent of abegin/ensureconstruct for thread termination, so callingThread.terminatemay corrupt your data or leave your program in an inconsistent state. If you plan to stop a thread before the program is over, you should build that capability into the thread object itself.A common type of thread implements a loop: threads that process requests from a queue, or that periodically poll for new data. In these, the end of an iteration forms a natural stopping point. These threads can benefit from some simple VCR-style controls: pause, unpause, and stop.Here's aThreadsubclass which implements a loop that can be paused or stopped in a predictable way. A code block passed into theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Running a Code Block on Many Objects Simultaneously
- InhaltsvorschauRather than iterating over the elements of a data structure one at a time, you want to run some function on all of them simultaneously.Spawn a thread to handle each element of the data structure.Here's a simple equivalent of
Enumerable#eachthat runs a code block against every element of a data structure simultaneously. It returns theThreadobjects it spawned so that you can pause them, kill them, orjointhem and wait for them to finish:module Enumerable def each_simultaneously threads = [] each { |e| threads >> Thread.new { yield e } } return threads end endRunning the following high-latency code withEnumerable#eachwould take 15 seconds. With our newEnumerable#each_simultaneously, it takes only five seconds:start_time = Time.now [7,8,9].each_simultaneously do |e| sleep(5) # Simulate a long, high-latency operation print "Completed operation for #{e}!\n" end # Completed operation for 8! # Completed operation for 7! # Completed operation for 9! Time.now - start_time # => 5.009334You can save time by doing high-latency operations in parallel, since it often means you pay the latency price only once. If you're doing nameserver lookups, and the nameserver takes five seconds to respond to a request, you're going to be waiting at least five seconds. If you need to do 10 nameserver lookups, doing them in series will take 50 seconds, but doing them all at once might only take 5.This technique can also be applied to the other methods ofEnumerable. You could write acollect_simultaneously, afind_all_simultaneously, and so on. But that's a lot of methods to write. All the methods ofEnumerableare based oneach. What if we could just convince those methods to useeach_simultaneouslyinstead of each?It would be too much work to replace all the existing methods ofEnumerable, but we can swap out an individualEnumerableobject'seachimplementation for another, by wrapping it in anEnumerable::Enumerator. Here's how it would work:require 'enumerator' array = [7, 8, 9] simultaneous_array = array.enum_for(:each_simultaneously) simultaneous_array.each do |e| sleep(5) # Simulate a long, high-latency operation print "Completed operation for #{e}!\n" end # Completed operation for 7! # Completed operation for 9! # Completed operation for 8!Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Limiting Multithreading with a Thread Pool
- InhaltsvorschauYou want to process multiple requests in parallel, but you don't necessarily want to run all the requests simultaneously. Using a technique like that in Recipe 20.6 can create a huge number of threads running at once, slowing down the average response time. You want to set a limit on the number of simultaneously running threads.You want a thread pool. If you're writing an Internet server and you want to service requests in parallel, you should build your code on top of the
gservermodule, as seen in Recipe 14.14: it has a thread pool and many TCP/IP-specific features. Otherwise, here's a genericThreadPoolclass, based on code fromgserver.The instance variable@poolcontains the active threads. TheMutexand theConditionVariableare used to control the addition of threads to the pool, so that the pool never contains more than@max_sizethreads:require 'thread' class ThreadPool def initialize(max_size) @pool = [] @max_size = max_size @pool_mutex = Mutex.new @pool_cv = ConditionVariable.new end
When a thread wants to enter the pool, but the pool is full, the thread puts itself to sleep by callingConditionVariable#wait. When a thread in the pool finishes executing, it removes itself from the pool and callsConditionVariable#signalto wake up the first sleeping thread:def dispatch(*args) Thread.new do # Wait for space in the pool. @pool_mutex.synchronize do while @pool.size >= @max_size print "Pool is full; waiting to run #{args.join(',')}…\n" if $DEBUG # Sleep until some other thread calls @pool_cv.signal. @pool_cv.wait(@pool_mutex) end endThe newly-awakened thread adds itself to the pool, runs its code, and then callsConditionVariable#signalto wake up the next sleeping thread:@pool << Thread.current begin yield(*args) rescue => e exception(self, e, *args) ensure @pool_mutex.synchronize do # Remove the thread from the pool. @pool.delete(Thread.current) # Signal the next waiting thread that there's a space in the pool. @pool_cv.signal end end end end def shutdown @pool_mutex.synchronize { @pool_cv.wait(@pool_mutex) until @pool.empty? } end def exception(thread, exception, *original_args) # Subclass this method to handle an exception within a thread. puts "Exception in thread #{thread}: #{exception}" end endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Driving an External Process with popen
- InhaltsvorschauYou want to execute an external command in a subprocess. You want to pass some data into its standard input stream, and read its standard output.If you don't care about the standard input side of things, you can just use the %x{} construction. This runs a string as a command in an operating system subshell, and returns the standard output of the command as a string.
%x{whoami} # => "leonardr\n" puts %x{ls -a empty_dir} # . # ..If you want to pass data into the standard input of the subprocess, do it in a code block that you pass into theIO. popenmethod. Here'sIO.popenused on a Unix system to invoketail, a command that prints to standard output the last few lines of its standard input:IO.popen('tail -3', 'r+') do |pipe| 1.upto(100) { |i| pipe >> "This is line #{i}.\n" } pipe.close_write puts pipe.read end # This is line 98. # This is line 99. # This is line 100.IO.popenspawns a subprocess and creates a pipe: anIOstream connecting the Ruby interpreter to the subprocess.IO.popenmakes the pipe available to a code block, just asFile.openmakes an open file available to a code block. Writing to theIOobject sends data to the standard input of the subprocess; reading from it reads data from its standard output.IO.popentakes a file mode, just likeFile.open. To use both the standard input and output of a subprocess, you need to open it in read-write mode ("r+").A command that accepts standard input won't really start running until its input stream is closed. If you usepopento run a command liketail, you must callpipe.close_writebefore you read from the pipe. If you try to read the subprocess' standard output while the subprocess is waiting for you to send it data on standard input, both processes will hang forever.The %{} construct and thepopentechnique work on both Windows and Unix, but scripts that use them won't usually be portable, because it's very unlikely that the command you're running exists on all platforms.On Unix systems, you can also usepopento spawn a Ruby subprocess. This is like callingEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Capturing the Output and Error Streams from a Unix Shell Command
- InhaltsvorschauYou want to run an external program as in Recipe 20.8, but you also want to capture the standard error stream. Using
popenonly gives you access to the standard output.Use theopen3library in the Ruby standard library. Itspopen3method takes a code block, to which it passes threeIOstreams: one each for standard input, output, and error.Suppose you perform the Unixlscommand to list a nonexistent directory.lswill rightly object to this and write an error message to its standard error stream. If you invokedlswithIO.popenor the %x{} construction, that error message is passed right along to the standard error stream of your Ruby process. You can't capture it or suppress it:%x{ls no_such_directory} # ls: no_such_directory: No such file or directoryBut if you usepopen3, you can grab that error message and do whatever you want with it:require 'open3' Open3.popen3('ls -l no_such_directory') { |stdin, stdout, stderr| stderr.read } # => "ls: no_such_directory: No such file or directory\n"The same caveats in the previous recipe apply to the IO streams returned bypopen3. If you're running a command that accepts data on standard input, and you read fromstdoutbefore closingstdin, your process will hang.UnlikeIO.popen, thepopen3method is only implemented on Unix systems. However, the win32-open3 package (part of the Win32Utils project) provides apopen3implementation.- Recipe 20.8, "Driving an External Process with popen"
- Like many other Windows libraries for Ruby,
win32-open3is available from http://rubyforge.org/projects/win32utils
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Controlling a Process on Another Machine
- InhaltsvorschauYou want to run a process on another machine, controlling its input stream remotely, and reading its output and error streams.The
ruby-sshgem, first described in Recipe 14.10, provides apopen3method that works a lot like Ruby's built-inpopen3, except that the process you spawn runs on another computer.Here's a method that runs a Unix command on another computer and yields its standard I/O streams to a code block on your computer. All traffic going between the computers is encrypted with SSL. To authenticate yourself against the foreign host, you'll either need to provide a username and password, or set up an SSL key pair ahead of time.require 'rubygems' require 'net/ssh' def run_remotely(command, host, args) Net::SSH.start(host, args) do |session| session.process.popen3(command) do |stdin, stdout, stderr| yield stdin, stdout, stderr end end end
Here it is in action:run_remotely('ls -l /home/leonardr/dir', 'example.com', :username=>'leonardr', :password => 'mypass') { |i, o, e| puts o.read } # -rw-rw-r-- 1 leonardr leonardr 33 Dec 29 20:40 file1 # -rw-rw-r-- 1 leonardr leonardr 102 Dec 29 20:40 file2TheNet::SSHlibrary implements a low-level interface to the SSH protocol, but most of the time you don't need all that power. You just want to use SSH as a way to spawn and control processes on a remote computer. That's whyNet:SSHalso provides apopen3interface that looks a lot like thepopen3you use to manipulate processes on your own computer.Apart from the issue of authentication, there are a couple of differences betweenNet::SSH.popen3andOpen3.popen3. WithOpen3.popen3, you must be careful to close the standard input stream before reading from the output or error streams. With theNet::SSHversion ofpopen3, you can read from the output or error streams as soon as the process writes any data to it. This lets you interleavestdinwrites andstdoutreads:run_remotely('cat', 'example.com', :username=>'leonardr', :password => 'mypass') do |stdin, stdout, stderr| stdin.puts 'Line one.' puts stdout.read stdin.puts 'Line two.' puts stdout.read end # "Line one." # "Line two."Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Avoiding Deadlock
- InhaltsvorschauYour threads are competing for exclusive access to the same resources. With no coordination between threads, you'll end up with deadlock. Thread A will be blocking, waiting for a resource held by thread B, and thread B will be blocking, waiting for a resource held by thread A. Neither thread will ever be seen again.There's no simple mix-in solution to this problem. You need to come up with some rules for how your threads acquire locks, and make sure your code always abides by them.Basically, you need to guarantee that all your threads acquire locks in the same order. Impose an ordering (formally or informally) on all the locks in your program and make sure that your threads always acquire locks in ascending numerical order.Here's how it would work. The standard illustration of deadlock is the Dining Philosophers problem. A table of philosophers are sharing a plate of rice and some chopsticks, but there aren't enough utensils to go around. When there are only two chopsticks, it's easy to see the problem. If philosopher A is holding one chopstick (that is, has a lock on it), and philosopher B is holding the other, then nobody can eat.In this scenario, you'd designate the the lock on one chopstick as lock #1, and the lock on the other chopstick as lock #2. If you guarantee that no philosopher will pick up chopstick #2 unless they're already picked up the chopstick #1, deadlock is impossible. You can guarantee this by simply making all the philosophers implement the same behavior:
require 'thread' $chopstick1 = Mutex.new $chopstick2 = Mutex.new class Philosopher < Thread def initialize(name) super do loop do $chopstick1.synchronize do puts "#{name} has picked up one chopstick." $chopstick2.synchronize do puts "#{name} has picked up two chopsticks and eaten a " + "bite of tasty rice." end end end end end end Philosopher.new('Moore') Philosopher.new('Anscombe') # Moore has picked up one chopstick. # Moore has picked up two chopsticks and eaten a bite of tasty rice. # Anscombe has picked up one chopstick. # Anscombe has picked up two chopsticks and eaten a bite of tasty rice. # Moore has picked up one chopstick. # Moore has picked up two chopsticks and eaten a bite of tasty rice. # …Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 21: User Interface
- InhaltsvorschauRuby has libraries for attaching programs to the three main types of user interface. The web interface, Ruby's most popular, is covered in depth in Chapters 15, 16, and (to a lesser extent) 14. This chapter covers the other two interfaces: the terminal or console interface, and the graphical ( GUI) interface. We also cover some unorthodox interfaces (Recipe 21.11).The terminal interface is is a text-based interface usually invoked from a command line. It's used by programs like
irband the Ruby interpreter itself. The terminal interface is usually seen on Unix systems, but all modern operating systems support it.In the classic Unix-style "command-line program," the user interface consists of the options used to invoke the program (Recipe 21.3); and the program's standard input, output, and error streams (Recipe 21.1; also see Recipe 6.16). The Ruby interpreter is a good example of this kind of program. You can invoke therubyprogram with arguments like-dand--version, but once the interpreter starts, your options are limited to typing in a Ruby program and executing it.The advantage of this simple interface is that you can use Unix shell tools like redirection and pipes to connect these programs to each other. Instead of manually typing a Ruby program into the interpreter's standard input, you can send it a file with the Unix commandruby < file.rb. If you've got another program that generates Ruby code and prints it to standard output, you can pipe the generated code into the interpreter withgenerator | ruby.The disadvantage is that these programs are not very user-friendly. Libraries like Curses (Recipe 21.5), Readline, and HighLine can add color and sophistication to your terminal programs. Theirbinteractive interpreter uses Readline to offer interactive line editing instead of the simpler interface offered by the Unix shell (Recipe 21.10).The graphical user interface is the most common interface in the world. Even a web interface is usually interpreted within a GUI on the client end. However, there's not much that's Ruby-specific about GUI programming. All the common GUI libraries (like Tk, GTK, and QT) are written in C, and Ruby's bindings to them look a lot like the bindings for other dynamic languages such as Perl and Python.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - InhaltsvorschauHighLine, written by James Edward Gray II and Gregory Brown, is available as the
highlinegem. The Curses and Readline libraries come preinstalled with Ruby (even on Windows, if you use the one-click installer). If you're using Windows and don't have Curses, you can get the library and the Ruby bindings from http://www.dave.burt.id.au/ruby/curses.zip.Ncurses is an improved version of Curses (allowing things like colored text), and most modern Unix systems have it installed. You can get Ncurses bindings for Ruby from http://ncurses-ruby.berlios.de/. It's also available as the Debian packagelibncurses-ruby.The Tk binding for Ruby comes preinstalled with Ruby, assuming you've installed Tk itself. Ruby bindings for the most common GUI toolkits have been written:- wxRuby (http://wxruby.rubyforge.org/)
wxRuby is interesting because it's cross-platform and uses native widgets on each platform. You can write a Ruby program with wxRuby that runs on Unix, Windows, and Mac OS X, and looks like a native application on all three platforms.On Mac OS X, all the tools you need to build a Ruby GUI application come with the operating system, including a GUI builder. If you're using GTK, your life will be easier if you download the Glade GUI builder (http://glade.gnome.org/).Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Getting Input One Line at a Time
- InhaltsvorschauYou're writing an interactive console program, and you want to get line-based input from the user. You present the user with a prompt, and he types some data before hitting enter.Instead of reading standard input all at once, read it a line at a time with
getsorreadline.This method populates a data structure with values obtained from user input:def confirmation_hearings questions = [['What is your name?', :name], ['How old are you?', :age], ['Why would you like to be Secretary of the Treasury?', :why]] answers = questions.inject({}) do |answers, qv| question, value = qv print question + ' ' answers[value] = gets.chomp answers end puts "Okay, you're confirmed!" return answers end confirmation_hearings # What is your name? # <= Leonard Richardson # How old are you? # <= 27 # Why would you like to be Secretary of the Treasury? # <= Mainly for the money # Okay, you're confirmed! # => {:age=>"26", :why=>"Mainly for the money", :name=>"Leonard Richardson"}Most console programs take their input from command-line switches or from a file passed in on standard input. This makes it easy to programatically combine console programs: you can pipecatintogrepintolastwithout any of the programs having to know that they're connected to each other. But sometimes it's more user-friendly to ask for input interactively: in text-based games, or data entry programs with workflow.The only difference between this technique and traditional console applications is that you're writing to standard output before you're completely done reading from standard input. You can pass an input file into a program like this, and it'll still work. In this example, a Ruby program containing the questionnaire code seen in the Solution is fed by an input file:$ ./confirmation_hearings.rb < answers # => What is your name? How old are you? Why would you like to be # Secretary of the Treasury? Okay, you're confirmed!
The program works, but the result looks different—even though the standard output is actually the same. When a human is running the program, the newline created when they hit enter is echoed to the screen, making the second question appear on a separate line from the first. Those newlines don't get echoed when they're read from a file.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Getting Input One Character at a Time
- InhaltsvorschauYou're writing an interactive application or a terminal-based game. You want to read a user's input from standard input a single character at a time.Most Ruby installations on Unix come with the the Curses extension installed. If Curses has the features you want to write the rest of your program, the simplest solution is to use it.This simple Curses program echoes every key you type to the top-left corner of the screen. It stops when you hit the escape key (
\e).#!/usr/bin/ruby -w # curses_single_char_input.rb require 'curses' include Curses # Setup: create a curses screen that doesn't echo its input. init_screen noecho # Cleanup: restore the terminal settings when the program is exited or # killed. trap(0) { echo } while (c = getch) != ?\e do setpos(0,0) addstr("You typed #{c.chr.inspect}") endIf you don't want Curses to take over your program, you can use the HighLine library instead (available as thehighlinegem). It does its best to define aget_ charactermethod that will work on your system. Theget_ charactermethod itself is private, but you can access it from within a call toask:require 'rubygems' require 'highline/import' while (c = ask('') { |q| q.character = true; q.echo = false }) != "\e" do print "You typed #{c.inspect}" endBe careful;askechoes a newline after every character it receives. That's why I use a print statement in that example instead ofputs.Of course, you can avoid this annoyance by hacking theHighLineclass to makeget_characterpublic:class HighLine public :get_character end input = HighLine.new while (c = input.get_ character) != ?\e do puts "You typed #{c.chr.inspect}" endThis is a huge and complicated problem that (fortunately) is completely hidden by Curses and HighLine. Here's the problem: Unix systems know how to talk to a lot of historic and modern terminals. Each one has a different feature set and a different command language. HighLine (through the Termios library it uses on Unix) and Curses hide this complexity.Windows doesn't have to deal with a lot of terminal types, but Windows programs don't usually read from standard input either (much less one character at a time). To do single- character input on Windows, HighLine makes raw Windows API calls. Here's some code based on HighLine's, which you can use on Windows if you don't want to require HighLine:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Parsing Command-Line Arguments
- InhaltsvorschauYou want to make your Ruby script take command-line arguments, the way most Unix utilities and scripts do.If you want to treat your command-line arguments as a simple list of strings, you can just iterate over the
ARGVarray.Here's a Ruby version of the Unix commandcat; it takes a list of files on the command line, opens each one, and prints its contents to standard output:#!/usr/bin/ruby -w # cat.rb ARGV.each { |filename| IO.readlines(filename).each { |line| puts line } }If you want to treat your command-line arguments as a list of files, and you plan to open each of those files and iterate over them line by line, you can useARGFinstead ofeARGV. The followingcatimplementation is equivalent to the first one.#!/usr/bin/ruby -w # cat_argf.rb ARGF.each { |line| puts line }If you want to treat certain command-line arguments as switches, or as anything other than a homogenous list of strings, use theOptionParserclass in theoptparselibrary. Don't write the argument parsing code yourself; there are too many edge cases to think about.TheOptionParserclass can parse any command-line arguments you're likely to need, and it includes a lot of Unix know-how that would take a long time to write yourself. All you have to do is define the set of arguments your script accepts, and write code that reacts to the presence of each argument on the command line. Here, I'll useOptionParserto writecat2.rb, a second Ruby version ofcatthat supports a few of the realcat'scommand-line arguments.The first phase is turning any command-line arguments into a data structure that I can easily consult during the actual program. TheCatArgumentsclass defined below is a hash that usesOptionParserto populate itself from a list of command-line arguments.For each argument accepted bycat2.rb, I've added a code block to be run as a callback. WhenOptionParsersees a particular argument inARGV, it runs the corresponding code block, which sets an appropriate value in the hash:#!/usr/bin/ruby # cat2.rb require 'optparse' class CatArguments < Hash def initialize(args) super() self[:show_ends] = '' opts = OptionParser.new do |opts| opts.banner = "Usage: #$0 [options]" opts.on('-E', '--show-ends [STRING]', 'display [STRING] at end of each line') do |string| self[:show_ends] = string || '$' end opts.on('-n', '--number', 'number all output lines') do self[:number_lines] = true end opts.on_tail('-h', '--help', 'display this help and exit') do puts opts exit end end opts.parse!(args) end end arguments = CatArguments.new(ARGV)Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Testing Whether a Program Is Running Interactively
- InhaltsvorschauYou want to see whether there's another person on the other end of your program, or whether the program has been hooked up to a file or the output of another program.
STDIN.tty?returns true if there's a terminal hooked up to your program's original standard input. Since only humans use terminals, this will suffice. This code works on Unix and Windows:#!/usr/bin/ruby -w # interactive_or_not.rb if STDIN.tty? puts "Let me be the first to welcome my human overlords." else puts "How goes the revolution, brother software?" end
Running this program in different ways gives different results:$ ./interactive_or_not.rb Let me be the first to welcome my human overlords. $ echo "Some data" | interactive_or_not.rb How goes the revolution, brother software? $ ./interactive_or_not.rb < input_file How goes the revolution, brother software?
An interactive application can be more user friendly than one that runs solely off its command-line arguments and input streams. By checkingSTDIN.tty?you can make your program have an interactive and a noninteractive mode. The noninteractive mode can be chained together with other programs or used in shell scripts.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Setting Up and Tearing Down a Curses Program
- InhaltsvorschauTo write a program that uses Curses or Ncurses, you have to write a lot of setup and cleanup code. You'd like to factor that out.Here's a wrapper method that sets up the Curses library and passes the main screen object into a code block:
require 'curses' module Curses def self.program main_screen = init_screen noecho cbreak curs_set(0) main_screen.keypad = true yield main_screen end end
Here's a simple Ruby program that uses the wrapper method to fill up the screen with random placements of a given string:Curses.program do |scr| str = ARGV[0] || 'Test' max_x = scr.maxx-str.size+1 max_y = scr.maxy 100.times do scr.setpos(rand(max_y), rand(max_x)) scr.addstr(str) end scr.getch end
The initialization, which is hidden inCurses.program, does the following things:- Stops keystrokes from being echoed to the screen (
noecho) - Hides the cursor (
curs_set(0)) - Turns off buffered input so keys can be processed as they're typed (
cbreak) - Makes the keyboard's arrow keys generate recognizable key events (
keypad=true)
The code is a little different if you're using the third-partyncursesbinding instead of thecurseslibrary that comes with Ruby. The main difference is that withncurses, you must write some of the cleanup code that thecurseslibrary handles automatically. A wrapper method is also a good place to set up thencursescolor code if you plan to use colored text (see Recipe 21.8 for more on this).Here's anNcurses.programmethod that's equivalent toCurses.program, except that it performs its cleanup manually by registering anat_exitblock to run just before the interpreter exits. This wrapper also turns on color and initializes a few default color pairs. If your terminal has no color support, the color code will run but it won't do anything.require 'ncurses' module Ncurses COLORS = [COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE] def self.program stdscr = Ncurses.initscr # Run ncurses cleanup code when the program exits. at_exit do echo nocbreak curs_set(1) stdscr.keypad(0) endwin end noecho cbreak curs_set(0) stdscr.keypad(1) start_color COLORS[1…COLORS.size].each_with_index do |color, i| init_pair(i+1, color, COLOR_BLACK) end yield stdscr end end
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Clearing the Screen
- InhaltsvorschauYou're writing a console application, and you want it to clear the screen.Capture the output of the Unix
clearcommand as a string and print it whenever you want to clear the screen:#!/usr/bin/ruby -w # clear_console.rb clear_code = %x{clear} puts 'Press enter to clear the screen.' $stdin.gets print clear_code puts "It's cleared!"Theclearcommand prints an escape code sequence to standard output, which the Unix terminal interprets as a clear-screen command. The exact string depends on your terminal, but it's probably an ANSI escape sequence, like this:%x{clear} # => "\e[H\e[2J"Your Ruby script can print this escape code sequence to standard output, just as theclearcommand can, and clear the screen.On Windows, the command iscls, and you can't just print its standard output to clear the screen. Every time you want to clear the screen, you need to call out toclswithKernel#system:# clear_console_windows.rb puts 'Press enter to clear the screen.' $stdin.gets system('cls') puts "It's cleared!"If you've made your Windows terminal support ANSI (see Recipe 21.8), then you can print the same ANSI escape sequence used on Unix.The Curses library makes this a lot more straightforward. A Curses application can clear any of its windows withCurses::Window#clear.Curses::clearwill clear the main window:#!/usr/bin/ruby -w # curses_clear.rb require 'curses' Curses.init_ screen Curses.setpos(0,0) Curses::addstr("Type all you want. 'C' clears the screen, Escape quits.\n") begin c = nil begin c = Curses.getch end until c == ?C or c == ?\e Curses.clear end until c == ?\eBut, as always, Curses takes over your whole application, so you might want to just use the escape sequence trick.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Determining Terminal Size
- InhaltsvorschauWithin a terminal-based application, you want to find the size of the terminal: how many rows and columns are available for you to draw on.This is easy if you're using the Curses library. This example uses the
Curses.programwrapper described in Recipe 21.5:Curses.program do |scr| max_y, max_x = scr.maxy, scr.maxx scr.setpos(0, 0) scr.addstr("Your terminal size is #{max_x}x#{max_y}. Press any key to exit.") scr.getch endIt's a little less easy with Ncurses: you have to pass in two arrays to the underlying C libraries, and extract the numbers from the arrays. Again, this example uses the Ncurses wrapper from Recipe 21.5:Ncurses.program do |scr| max_y, max_x = [], [] scr.getmaxyx(max_y, max_x) max_y, max_x = max_y[0], max_x[0] str = "Your terminal size is #{max_x}x#{max_y}. Press any key to exit." scr.mvaddstr(0, 0, str) scr.getch endIf you're not using a Curses-style library, it's not easy at all.If you plan to simulate graphical elements on a textual terminal, subdivide it into virtual windows, or print justified output, you'll need to know the terminal's dimensions. For decades, the standard terminal size has been 25 rows by 80 columns, but modern GUIs and high screen resolutions let users create text terminals of almost any size. It's okay to enforce a minimum terminal size, but it's a bad idea to assume that the terminal is any specific size.The terminal size is a very useful piece of information to have, but it's not an easy one to get. The Curses library was written to solve this kind of problem, but if you're willing to go into the operating system API, or if you're on Windows where Curses is not a standard feature, you can find the terminal size without letting a Curses-style library take over your whole application.On Unix systems (including Mac OS X), you can make anioctlsystem call to get the terminal size. Since you're calling out to the underlying operating system, you'll need to use strange constants and C-like structures to carry the response:TIOCGWINSZ = 0x5413 # For an Intel processor # TIOCGWINSZ = 0x40087468 # For a PowerPC processor def terminal_size rows, cols = 25, 80 buf = [ 0, 0, 0, 0 ].pack("SSSS") if STDOUT.ioctl(TIOCGWINSZ, buf) >= 0 then rows, cols, row_pixels, col_pixels = buf.unpack("SSSS")[0..1] end return rows, cols end terminal_size # => [21, 80]Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Changing Text Color
- InhaltsvorschauYou want to display multicolored text on the console.The simplest solution is to use HighLine. It lets you enclose color commands in an ERb template that gets interpreted within HighLine and printed to standard output. Try this colorful bit of code to test the capabilities of your terminal:
require 'rubygems' require 'highline/import' say(%{Here's some <%= color('dark red text', RED) %>.}) say(%{Here's some <%= color('bright red text on a blue background', RED+BOLD+ON_BLUE) %>.}) say(%{Here's some <%= color('blinking bright cyan text', CYAN+BOLD+BLINK) %>.}) say(%{Here's some <%= GREEN+UNDERLINE %>underlined dark green text<%=CLEAR%>.})Some of these features (particularly the blinking and underlining) aren't supported on all terminals.TheHighLine#colormethod encloses a display string in special command strings, which start with an escape character and a left square bracket:HighLine.new.color('Hello', HighLine::GREEN) # => "\e[32mHello\e[0m"These are ANSI escape sequences. Instead of displaying the string "\e[32m", an ANSI-compatible terminal treats it as a command: in this case, a command to start printing characters in green-on-black. The string "\e[0m" tells the terminal to go back to white-on-black.Most modern Unix terminals support ANSI escape sequences, including the Mac OS X terminal. You should be able to get green text in yourirbsession just by callingputs "\e[32mHello\e[0m"(try it!), but HighLine makes it easy to get color without having to remember the ANSI sequences.Windows terminals don't support ANSI by default, but you can get it to work by loading ANSI.SYS (see below for a relevant Microsoft support article).An alternative to HighLine is the Ncurses library. It supports color terminals that use a means other than ANSI, but these days, most color terminals get their color support through ANSI. Since Ncurses is much more complex than HighLine, and not available as a gem, you should only use Ncurses for color if you're already using it for its other features.Here's a rough equivalent of the HighLine program given above. This program uses theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Reading a Password
- InhaltsvorschauYou want to prompt the user for a password, or otherwise capture input without echoing it to the screen for all to see.The
ruby-passwordlibrary makes this easy, but it's not available as a Ruby gem. The HighLine library is available as a gem, and it can do this almost as well. You just have to turn off the terminal echo feature:require 'rubygems' require 'highline/import' def get_password(prompt='Password: ') ask(prompt) { |q| q.echo = false} end get_password("What's your password? ") # What's your password? # => "buddy"In 2000, President Bill Clinton signed into law the Electronic Signatures Bill, which makes electronic signatures as binding as handwritten signatures. He signed the law by hand and then signed it electronically. As he typed the password to his electronic signature, it was was echoed to the screen. Everyone in the world saw that his password was the name of his pet dog, Buddy. Don't let this happen to you: turn off echoing when gathering passwords.Turning off echoing altogether is the safest way to gather a password, but it might make your users think your program has stopped responding to input. It's more userfriendly to echo a mask character, like an asterisk, for every character the user types. You can do this in HighLine by settingechoto the mask character instead offalse:def get_password(prompt='Password: ', mask='*') ask(prompt) { |q| q.echo = mask } end get_password # Password: ***** # => "buddy" get_password('Password: ', false) # Password: # => "buddy"- The
ruby-passwordthird-party library also provides ways of generating, encrypting, and test-cracking passwords (http://www.caliban.org/ruby/ruby-password.shtml )
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Allowing Input Editing with Readline
- InhaltsvorschauYou want to let your users edit their lines of input as they write them, the way
irbdoes.Use thereadlinelibrary. Instead of reading directly from standard input, pass a prompt string intoReadline.readline. The user will be able to edit their input using the same shortcut keys you can use in theirbRuby interpreter (assuming their terminal supports those keys).#!/usr/bin/ruby -w # readline.rb require 'readline' vegetable = Readline.readline("What's your favorite vegetable?> ") puts "#{vegetable.capitalize}? Are you crazy?"Note that you don't have tochompthe result ofReadline.readline:$ ruby readline.rb What's your favorite vegetable?> okra Okra? Are you crazy?
On Windows, this isn't necessary because thecmd shellprovides any console program with many ofreadline's features. The example given above will work on both Windows and Unix, but if you're writing a Windows-specific program, you don't needreadline:# readline_windows.rb print "What's your favorite vegetable?> " puts gets.chomp.capitalize + "? Are you crazy?"
In a Unix program that accepts data from standard input, the user can use their backspace key to correct typing mistakes, one character at a time. Backspace is a control character: it's a real character, just like "1" and "m" (its Ruby string representation is "\010"), but it's not usually interpreted as data. Instead, it's treated as a command: it erases one character from the input buffer.With the backspace key, you can correct errors one character at a time. But what if you want to insert text into the middle of a line, or delete the whole thing and start over? That's wherereadlinecomes in. It's a Ruby interface to the Readline library used by many Unix programs, and it recognizes many control characters besides the backspace.In areadlineprogram, you can use the left and right arrow keys to move back and forth in the input string before submitting it. If you're familiar with the Readline shortcut keys from Emacs or other Unix programs, you can perform more sophisticated text editing operations, including cut and paste.TheEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Making Your Keyboard Lights Blink
- InhaltsvorschauYou want to control the three standard keyboard LEDs (num lock, caps lock, and scroll lock) from a Ruby script.Use the Blinkenlights library, available as the
blinkenlightsgem. It works on Windows or Linux (but not on Mac OS X), and it lets you toggle the lights individually or in patterns:require 'rubygems' require 'blinkenlights' # Turn individual lights on or off. BlinkenLights.open do |lights| lights.left = true lights.middle = true lights.right = true lights.scr = false lights.cap = false lights.num = false end # Display a light show. BlinkenLights.open do |lights| lights.left_to_right 10.times { lights.random } lights.right_to_left endThe keyboard lights are an often-overlooked user interface. They were originally designed to reflect information about the state of the keyboard itself, but they can be manipulated from the computer to display more interesting things. Each light can continually display one bit of information (such as whether you have new email), or can flash over time to indicate a rate (such as your computer's use of incoming or outgoing bandwidth).BlinkenLights works by writing special command codes to the Unix keyboard device (/dev/tty8is the default, but/dev/consoleshould also work). Usually, you can only write to these devices when running as root.On Windows, BlinkenLights works by sending key events that make Windows think you actually hit the corresponding key. This means that if you tell BlinkenLights on Windows to turn on your caps lock light, caps lock itself is also enabled. The state of the light can't be disconnected from the state of the keyboard.When you pass a code block intoBlinkenlights.open, BlinkenLights runs the block and then restores the original state of the lights. This avoids confusing those users who use their lights to keep track of the state of their keyboards. If you want your setting of the lights to persist until they're changed again, then use the return value ofBlinkenlights.openinstead of passing in a code block.This code will turn on the first two lights to represent the number six in binary. Until they're changed again, whether through the keyboard or through code, they'll stay on. Even the end of your program won't restore the original state of the lights.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Creating a GUI Application with Tk
- InhaltsvorschauCredit: Kevin MarshallYou need to create a program that has a graphical user interface (GUI).Use the Tk library. It's language-independent, cross-platform, and best of all, it comes standard with most Ruby distributions.With Tk you create GUI elements, or "widgets", and then bind code blocks to them. When something happens (like the user clicking a widget), Tk runs the appropriate code block.Ruby provides a class for each type of Tk widget. This simple Tk program creates a "root" widget (the application window), and a "label" widget within the window. The program then waits for events (although it can't respond to any).
require 'tk' root = TkRoot.new { title "Tiny Tk Application" } label = TkLabel.new(root) { text "You are a trout!" } label.pack Tk.mainloopWhen run, it looks like Figure 21-1.
Figure 21-1: You are a troutThe simple application above shows most of the basic features of GUI programming in Tk and other modern GUI toolkits. We'll use the techniques to build a more complex application.Tk GUI development and layout take a parent/child approach. Most widgets are children of other widgets: depending on the widget, this nesting can go arbitrarily deep. The exception to this rule is theTkRootwidget: it's always the top-level widget, and it's represented as the application window.Child widgets are "packed" inside their parents so they can be displayed. A system called the geometry manager controls where on the screen the widgets actually show up. The default geometry manager is the "placer" manager, which lets you place widgets in relation to each other.Tk applications are event-driven, so the final step is to start a main event loop which tells our program to listen for events to be fired on our widgets.To further illustrate, let's make a simple stopwatch program to demostrate a realworld use of Tk.To start, we'll create four simple methods that will be bound to our widgets. These are the nonGUI core of the program:#!/usr/bin/ruby # stopwatch.rb require 'tk' class Stopwatch def start @accumulated = 0 unless @accumulated @elapsed = 0 @start = Time.now @mybutton.configure('text' => 'Stop') @mybutton.command { stop } @timer.start end def stop @mybutton.configure('text' => 'Start') @mybutton.command { start } @timer.stop @accumulated += @elapsed end def reset stop @accumulated, @elapsed = 0, 0 @mylabel.configure('text' => '00:00:00.0') end def tick @elapsed = Time.now - @start time = @accumulated + @elapsed h = sprintf('%02i', (time.to_i / 3600)) m = sprintf('%02i', ((time.to_i % 3600) / 60)) s = sprintf('%02i', (time.to_i % 60)) mt = sprintf('%1i', ((time - time.to_i)*10).to_i) newtime = "#{h}:#{m}:#{s}:#{mt}" @mylabel.configure('text' => newtime) endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Creating a GUI Application with wxRuby
- InhaltsvorschauYou want to write a portable GUI application that looks better than a Tk application.Use the wxRuby library, available as a third-party download. It uses native GUI widgets on Windows, Unix, and Mac OS X. It's got many more features than the Tk library, and even greater complexity.Here's a very simple wxRuby application (Figure 21-3):
#!/usr/bin/ruby -w # wxtrout.rb require 'wxruby' class TroutApp < Wx::App def on_init frame = Wx::Frame.new(nil, -1, 'Tiny wxRuby Application') panel = Wx::StaticText.new(frame, -1, 'You are a trout!', Wx::Point.new(-1,1), Wx::DEFAULT_SIZE, Wx::ALIGN_CENTER) frame.show end end TroutApp.new.main_loop
Figure 21-3: You are a wxRuby troutThe simple wxRuby application has the same basic structure as its Tk cousin (see Recipe 21.12). A top-level widget is created (here called aFrame) and a label (StaticText) widget is added to it. The application then goes into an event loop, listening for and retrieving events like mouse clicks.A wxRuby version of the Tk stopwatch program is also similar, although much longer. wxRuby code tends to be more verbose and less idiomatic than Ruby Tk code.The core methods are nearly unchanged, because they have little to do with the GUI:#!/usr/bin/ruby -w # wx_stopwatch.rb require 'wxruby' class StopwatchApp < Wx::App def start @start = Time.now @button.set_label('Stop') @button.refresh @frame.evt_button(@button.get_id) { stop } @timer.start(100) # The timer should tick every 100 milliseconds. end def stop @button.set_label('Start') @button.refresh @frame.evt_button(@button.get_id) { start } @timer.stop @accumulated += @elapsed end def reset stop @accumulated, @elapsed = 0, 0 @label.set_label('00:00:00.0') @frame.layout end def tick @elapsed = Time.now - @start time = @accumulated + @elapsed h = sprintf('%02i', (time.to_i / 3600)) m = sprintf('%02i', ((time.to_i % 3600) / 60)) s = sprintf('%02i', (time.to_i % 60)) mt = sprintf('%1i', ((time - time.to_i)*10).to_i) newtime = "#{h}:#{m}:#{s}:#{mt}" @label.set_label(newtime) @frame.layout endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Creating a GUI Application with Ruby/GTK
- InhaltsvorschauYou want to write a GUI application that uses the GTK widget library, perhaps so you can integrate it with the Gnome desktop environment.Use the Ruby bindings to Gnome's GTK widget library, available as a third-party download. Here's a simple Ruby/GTK application (Figure 21-5).
#!/usr/bin/ruby -w # gtktrout.rb require 'gtk2' Gtk.init window = Gtk::Window.new 'Tiny Ruby/GTK Application' label = Gtk::Label.new 'You are a trout!' window.add label window.signal_connect('destroy') { Gtk.main_quit } window.show_all Gtk.main
Figure 21-5: You are a GTK troutGnome is one of the two most popular Unix desktop suites. The Ruby-Gnome2 project provides and documents Ruby bindings to Gnome's vast array of C libraries. You can write Ruby applications that fully integrate with the Gnome desktop, but in this recipe I'm going to focus on the basics of the Gnome GUI library GTK.Although the details are different, the sample program above is basically the same as it would be with Tk (Recipe 21.12) or the wxRuby library (Recipe 21.13). You create two widgets (a window and a label), attach the label to the window, and tell the GUI library to display the window. As with Tk and wxRuby, the application goes into a display loop, capturing user events like mouse clicks.The sample program won't actually respond to any user events, though, so let's create a Ruby/GTK version of the stopwatch program seen in previous GUI recipes.The core methods, the ones that actually implement the stopwatch, are basically the same as the corresponding methods in the Tk and wxRuby recipes. Since GTK doesn't have a timer widget, I've implemented a simple timer as a separate thread. The other point of interest is the HTML-like markup that GTK uses to customize the font size and weight of the stopwatch text.#!/usr/bin/ruby -w # gtk_stopwatch.rb require 'gtk2' class Stopwatch LABEL_MARKUP = '<span font_desc="16" weight="bold">%s</span>' def start @accumulated ||= 0 @elapsed = 0 @start = Time.now @mybutton.label = 'Stop' set_button_handler('clicked') { stop } @timer_stopped = false @timer = Thread.new do until @timer_stopped do sleep(0.1) tick unless @timer_stopped end end end def stop @mybutton.label = 'Start' set_button_handler('clicked') { start } @timer_stopped = true @accumulated += @elapsed end def reset stop @accumulated, @elapsed = 0, 0 @mylabel.set_markup(LABEL_MARKUP % '00:00:00.0') end def tick @elapsed = Time.now - @start time = @accumulated + @elapsed h = sprintf('%02i', (time.to_i / 3600)) m = sprintf('%02i', ((time.to_i % 3600) / 60)) s = sprintf('%02i', (time.to_i % 60)) mt = sprintf('%1i', ((time - time.to_i)*10).to_i) @mylabel.set_markup(LABEL_MARKUP % "#{h}:#{m}:#{s}:#{mt}") endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Creating a Mac OS X Application with RubyCocoa
- InhaltsvorschauCredit: Alun ap RhisiartYou want to create a native Mac OS X program with a graphical user interface.Use the Mac OS X Cocoa library along with RubyCocoa and the Interface Builder application. RubyCocoa creates real OS X applications and provides a GUI interface for building GUIs, as opposed to other libraries, which make you define the GUI with Ruby code. RubyCocoa is a free download, and the Cocoa development tools are on the Mac OS X installation DVD.Interface Builder is very powerful: you can create simple applications without writing any code. In fact, it takes longer to explain what to do than to do it. Here's how to create a simple application with Interface Builder:
- Start the Xcode application and create a new project from the File menu. Choose "Cocoa-Ruby Application" from the "New Project" list, hit the Next button, give your project a name and location on disk, and click Finish.XCode will create a project that looks like Figure 21-7.
Figure 21-7: A new Cocoa-Ruby projectThe Cocoa-Ruby project template comes with two files:main.m(an Objective-C file) andrb_main.rb(a RubyCocoa file). For a simple application, this is all the code you need. - Open the NIB Files group and doubleclick
MainMenu.nibto open Interface Builder. You get a new application window, into which you can drag and drop GUI widgets, and a menubar labeledMainMenu.nib (English)–MainMenu.You'll also see a palette window with a selection of GUI objects; a nib document window namedMainMenu.nib (English), containing classes, instances, images and sounds; and an inspector. If the inspector is not open, selectShow Inspectorfrom the Tools menu.
The screenshot in Figure 21-8 shows what we're going to do to our new application window (seen in the upper left).
Figure 21-8: Our destination Interface Builder screenshot- Select the new application window and set the application's title. Type "Tiny RubyCocoa Application" in the inspector's
Window Titlefield (you need to select the "Attributes" tab to see this field).
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using AppleScript to Get User Input
- InhaltsvorschauOn Mac OS X, AppleScript makes it easy to add simple graphical interface elements to programs. You want to use AppleScript from a Ruby program.Use the AppleScript library, written by John Butler and available as the
applescriptgem. It lets you talk to AppleScript from Ruby.Here's a script that uses theAppleScriptclass to get input through AppleScript. It also shows off theAppleScript.saymethod, which uses Mac OS X's text-to-speech capabilities:require 'rubygems' require 'applescript' name = AppleScript.gets("What's your name?") AppleScript.puts("Thank you!") choice = AppleScript.choose("So which of these is your name?", ["Leonard", "Mike", "Lucas", name]) if name == choice AppleScript.say "You are right!" picture = AppleScript.choose_file("Find a picture of yourself") if File.exists?(picture) AppleScript.say "Thanks, I will now post it on Flickr for you." # Exercise for the reader: upload the file to Flickr end else AppleScript.say "But you just said your name was #{name}!" endThe AppleScript library is just a simple wrapper around theosascriptcommand-line interface to AppleScript. If you already know AppleScript, you can execute raw AppleScript code withAppleScript.execute:script = 'tell application "Finder" to display dialog "Hello World!" ' + 'buttons {"OK"}' AppleScript.execute(script)- The manpage for
osascript, available online at http://developer.apple.com/documentation/Darwin/Reference/ManPages/man1/osascript.1.html
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 22: Extending Ruby with Other Languages
- InhaltsvorschauWhen you decide to use an interpreted language such as Ruby, you're trading raw speed for ease of use. It's far easier to develop a program in a higher-level language, and you get a working program faster, but you sacrifice some of the speed you might get by writing the program in a lower-level language like C and C++.That's the simplified view. Anyone who's spent any serious amount of time working with higher-level languages knows that the truth is usually more complex. In many situations, the tradeoff doesn't really matter: if the program is only going to be run once, who cares if it takes twice as long to do its job? If a program is complex enough, it might be prohibitively hard to implement in a low-level language: you might never actually get it working right without using a language like Ruby.But even Ruby zealots must admit that there are still situations where it's useful to be able to call code written in another language. Maybe you need a particular part of your program to run blazingly fast, or maybe you want to use a particular library that's implemented in C or Java. When that happens you'll be grateful for Ruby's extension mechanism, which lets you call C code from a regular Ruby program; and the JRuby interpreter, which runs atop the Java Virtual Machine and uses Java classes as though they were Ruby classes.Compared to other dynamic languages, it's pretty easy to write C extensions in Ruby. The interfaces you need to understand are easy to use and clearly defined in just a few header files, there are numerous examples available in the Ruby standard library itself, and there are even tools that can help you access C libraries without writing any C code at all.So let's break out that trusty C compiler and learn how to drop down under the hood of the Ruby interpreter, because you just never know when your next program will to turn into one of those situations where a little bit of C code is the only solution to the problem.—Garrett RooneyCredit: Garrett RooneyYou want to implement part of your Ruby program in C. This might be the part of your program that needs to run really fast, it might contain some very platformspecific code, or you might just have a C implementation already, and you don't want to also write one in Ruby.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
- Writing a C Extension for Ruby
- InhaltsvorschauCredit: Garrett RooneyYou want to implement part of your Ruby program in C. This might be the part of your program that needs to run really fast, it might contain some very platformspecific code, or you might just have a C implementation already, and you don't want to also write one in Ruby.Write a C extension that implements that portion of your program. Compile it with
extconf.rbandrequireit in your Ruby program as though it were a Ruby library. You'll need to have the Ruby header files installed on your system.Here's a simple Ruby program that requires a library calledexample. It instantiates an instance ofExample::Classfrom that library, and calls a method on that library:require 'example' e = Example::Class.new e.print_string("Hello World\n") # Hello WorldWhat would theexamplelibrary look like if it were written in Ruby? Something like this:# example.rb module Example class Class def print_string(s) print s end end end
Let's implement that same functionality in C code. This small C library,example.c, defines a Ruby module, class, and method using the functions made available byruby.h:#include <ruby.h> #include <stdio.h> static VALUE rb_mExample; static VALUE rb_cClass; static VALUE print_string(VALUE class, VALUE arg) { printf("%s", RSTRING(arg)->ptr); return Qnil; } void Init_example() { rb_mExample = rb_define_module("Example"); rb_ cClass = rb_define_class_under(rb_mExample, "Class", rb_cObject); rb_define_method(rb_cClass, "print_string", print_string, 1); }To build the extension, you also need to create anextconf.rbfile:# extconf.rb require 'mkmf' dir_config('example') create_makefile('example')Then you can build your library by runningextconf.rb, then make:$ ls example.c extconf.rb $ ruby extconf.rb creating Makefile $ make gcc -fPIC -Wall -g -O2 -fPIC -I. -I/usr/lib/ruby/1.8/i486-linux -I/usr/lib/ruby/1.8/i486-linux -I. -c example gcc -shared -L"/usr/lib" -o example.so example.o -lruby1.8 -lpthread -ldl -lcrypt -lm -lc $ ls Makefile example.c example.o example.so extconf.rb
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using a C Library from Ruby
- InhaltsvorschauCredit: Garrett RooneyYou'd like to use a library in your Ruby program, but the library's implemented in C and there are no bindings.Write a Ruby extension that wraps the C library with Ruby classes and methods.Let's say we want to give a Ruby interface to C's file methods (yes, the
Fileclass already does this, but this makes a good example). We want to make it possible to open a disk file and read from it a byte at a time.Just as in Recipe 22.1, you'll need a C file that implements the actual extension. This one is calledstdio.c. It's got anInit_stdiofunction that defines a Ruby module (Stdio), a Ruby class (Stdio::File), and some methods for that class.Thefile_allocatefunction corresponds to theStdio::Fileconstructor. Because it's a constructor, we must also define some hook functions to create and destroy the underlying resources (in this case, a filehandle and the memory it uses):#include "stdio.h" #include "ruby.h" static VALUE rb_mStdio; static VALUE rb_cStdioFile; struct file { FILE *fhandle; }; static VALUE file_allocate(VALUE klass) { struct file *f = malloc(sizeof(*f)); f->fhandle = NULL; return Data_Wrap_Struct(klass, file_mark, file_free, f); } static void file_mark(struct file *f) { } static void file_free(struct file *f) { fclose(f->fhandle); free(f); }Thefile_openfunction implements theStdio::File#openmethod:static VALUE file_open(VALUE object, VALUE fname) { struct file *f; Data_Get_Struct(object, struct file, f); f->fhandle = fopen(RSTRING(fname)->ptr, "r"); return Qnil; }file_readbyteimplements theStdio::File#readbytemethod:static VALUE file_readbyte(VALUE object) { char buffer[2] = { 0, 0 }; struct file *f; Data_Get_Struct(object, struct file, f); if (! f->fhandle) rb_raise(rb_eRuntimeError, "Attempt to read from closed file"); fread(buffer, 1, 1, f->fhandle); return rb_str_new2(buffer); }Finally, ourInit_method defines theStdiomodule, theFileclass, and the three methods defined for theFileclass:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Calling a C Library Through SWIG
- InhaltsvorschauCredit: Garrett RooneyYou want to use a C library in your Ruby code, but you don't want to have to write any C code to do it.Use SWIG to generate the C extension for you. SWIG is a programming tool that takes as its input a file containing the information about C functions. It produces source code that lets you access those C functions from a variety of programming languages, including Ruby.All you you need to write is an interface file, containing the prototypes for the C functions you want to call. The interface file also contains a few directives to control things like the name of the resulting module. Process that file with the
swigcommand-line tool, build your extension, and you're up and running.Let's build a SWIG extension that lets Ruby access functions from the standard C library. It'll provide access to enough functionality that you can read data from one file and write it to another. In Recipe 22.1, we wrote the C code for a similar extension ourselves, but here we'll let SWIG do it.First we'll need a SWIG interface file,libc.i:%module libc FILE *fopen(const char *, const char *); int fread(void *, size_t, size_t, FILE *); int fwrite(void *, size_t, size_t, FILE *); int fclose(FILE *); void *malloc(size_t);
This file specifies the name of our extension as "libc". For SWIG Ruby extensions, this means the extension will be named "libc", and the code will be contained in a Ruby module claledLibc. This file also provides the prototypes for the functions we're going to want to call.You'll also need anextconf.rbprogram, similar to the one we used in the previous two recipes:# extconf.rb require 'mkmf' dir_config('tcl') dir_config('libc') create_makefile('libc')To generate the C extension, we process the header file with theswigcommand-line tool. We then run Ruby'sextconf.rbprogram to generate a makefile, and runmaketo compile the extension:$ swig -ruby libc.i $ ls extconf.rb libc.i libc_wrap.c $ ruby extconf.rb --with-tcl-include=/usr/include/tcl8.4 creating Makefile $ make … $ ls Makefile extconf.rb libc.i libc.so libc_wrap.c libc_wrap.o
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Writing Inline C in Your Ruby Code
- InhaltsvorschauCredit: Garrett RooneyYou want to implement small portions of your program in C without going to the trouble of creating a C extension to Ruby.Embed C code right in your Ruby program, and let RubyInline (available as the
rubyinlinegem) create an extension automatically.For example, if you want to use C's stdio functions to copy a file, you can write RubyInline code like this:#!/usr/bin/ruby -w # copy.rb require 'rubygems' require 'inline' class Copier inline do |builder| builder.c <<END void copy_file(const char *source, const char *dest) { FILE *source_f = fopen(source, "r"); if (!source_f) { rb_raise(rb_eIOError, "Could not open source : '%s'", source); } FILE *dest_f = fopen(dest, "w+"); if (!dest_f) { rb_raise(rb_eIOError, "Could not open destination : '%s'", dest); } char buffer[1024]; int nread = fread(buffer, 1, 1024, source_f); while (nread > 0) { fwrite(buffer, 1, nread, dest_f); nread = fread(buffer, 1, 1024, source_f); } } END end endThe C functioncopy_filenow exists as an instance method ofCopier:open('source.txt', 'w') { |f| f << 'Some text.' } Copier.new.copy_file('source.txt', 'dest.txt') puts open('dest.txt') { |f| f.read }Run this Ruby script, and you'll see it copy the string "Some text." fromsource.txttodest.txt.RubyInline is a framework that lets you embed other languages inside your Ruby code. It defines theModule# inlinemethod, which returns a builder object. You pass the builder a string containing code written in a language other than Ruby, and the builder transforms it into something that you can call from Ruby.When given C or C++ code (the two languages supported in the default RubyInline install), the builder objects writes a small extension to disk, compiles it, and loads it. You don't have to deal with the compilation yourself, but you can see the generated code and compiled extensions in the .ruby_inlinesubdirectory of your home directory.There are some limitations you should be aware of, though.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using Java Libraries with JRuby
- InhaltsvorschauCredit: Thomas EneboJava offers many class libraries that would be useful to a Ruby programmer; you'd like to use one of those libraries from within Ruby. A Java JDBC database may allow you to connect to a database for which Ruby has no connector. Or perhaps you need to use an obscure Java library that has no Ruby counterpart.JRuby provides an alternate implementation of the Ruby programming language that runs atop the Java Virtual Machine. When you interpret a Ruby program with JRuby instead of using the default Ruby interpreter, you can load and use Java classes from within the Ruby code.The first step to using JRuby is to install it:
- Download the latest copy of JRuby (see below for the address).
- Unzip the JRuby package into the directory where you'd like to install it.
- Add to your PATH environment variable the
bin/subdirectory of your JRuby installation. - Unless you've already installed it, download the Java Runtime Environment from Sun's Java web site and install it. You'll need the JRE version 1.4.x or higher.
Now you can invoke the JRuby interpreter with thejrubycommand and use it to run Ruby code. Here's a simple example that imports and uses Java's built-inRandomclass:#!/usr/bin/env jruby # random.jrb require 'java' include_class 'java.util.Random' r = Random.new(123) puts "Some random number #{r.nextInt % 10}" r.seed = 456 puts "Another random number #{r.nextInt % 10}"Heres a run of this program:$ jruby random.jrb Some random number 9 Another random number 0
JRuby generally behaves like Ruby. Thejrubyinterpreter supports a common subset of Ruby's command-line options, and includes a subset of common core libraries. As JRuby is developed, it will eventually end up with all of Ruby's options and libraries.The first step in a JRuby program is to load the Java support classes. If you don't do this, you can still use the JRuby interpreter, but you'll be limited to a subset of the Ruby core libraries: you might as well just use the C implementation.The statementrequire ' java'updates Ruby'sEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 23: System Administration
- InhaltsvorschauOnce you start using Ruby, you'll want to use it everywhere. Well, nothing's stopping you. This chapter shows you how to use Ruby in command-line programs that solve general everyday problems. It also demonstrates patterns that you can use to solve your own, more specific everyday problems.System administration scripts are usually private scripts, disposable or lightly reusable. Ruby scripts are easy to write, so you can get the job done quickly and move on. You won't feel bad if your script is less rigorous than your usual work, and you won't feel invested in a huge program that you only needed once.Ruby's syntax makes it easy to write, but for system administration, it's the libraries that make Ruby powerful. Most of the recipes in this chapter combine ideas from recipes elsewhere in the book to solve a real-world problem. The most commonly used idea is the
Find.findtechnique first covered in Recipe 6.12. Recipes 23.5, 23.6, 23.7, 23.8, and 23.9 all give different twists on this technique.The major new feature introduced in this chapter is Ruby's standardetclibrary. It lets you query a Unix system's users and groups. It's used in Recipe 23.10 to look up a user's ID given their username. Recipe 23.9 uses it to find a user's home directory and to get the members of Unix groups.Although these recipes focus mainly on Unix system administration, Ruby is perhaps even more useful for Windows administration. Unix has a wide variety of standard shell tools and an environment that makes it easy to combine them. If Ruby and other high-level languages didn't exist, Unix administrators would still have tools likefindandcut, and they'd use those tools like they did throughout the 1980s. On Windows, though, languages like Ruby are useful even for simple administration tasks: Ruby is easier to use than VBScript or batch files.If you're trying to administer a Windows machine with Ruby, there are many third-party libraries that provide Ruby hooks into Windows internals: see especially the "win32utils" project at http://rubyforge.org/projects/win32utils/. Another useful library is Ruby's standardEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Scripting an External Program
- InhaltsvorschauYou want to automatically control an external program that expects to get terminal input from a human user.When you're running a program that only needs a single string of input, you can use
IO.popen, as described in Recipe 20.8. This method runs a command, sends it a string as standard input, and returns the contents of its standard output:def run(command, input='') IO.popen(command, 'r+') do |io| io.puts input io.close_write return io.read end end run 'wc -w', 'How many words are in this string?' # => "7\n"
This technique is commonly used to invoke a command with sudo, which expects the user's password on standard input. This code obtains a user's password and runs a command on his behalf using sudo:print 'Enter your password for sudo: ' sudo_password = gets.chomp run('sudo apachectl graceful', user_password)IO.popenis a good way to run noninteractive commands—commands that read all their standard input at once and produce some output. But some programs are interactive; they send prompts to standard output, and expect a human on the other end to respond with more input.On Unix, you can use Ruby's standardPTYandexpectlibraries to spawn a command and impersonate a human on the other end. This code scripts the Unixpasswdcommand:require 'expect' require 'pty' print 'Old password:' old_pwd = gets.chomp print "\nNew password:" new_pwd = gets.chomp PTY.spawn('passwd') do |read,write,pid| write.sync = true $expect_verbose = false # If 30 seconds pass and the expected text is not found, the # response object will be nil. read.expect("(current) UNIX password:", 30) do |response| write.print old_pwd + "\n" if response end # You can use regular expressions instead of strings. The code block # will give you the regex matches. read.expect(/UNIX password: /, 2) do |response, *matches| write.print new_pwd + "\n" if response end # The default value for the timeout is 9999999 seconds read.expect("Retype new UNIX password:") do |response| write.puts new_pwd + "\n" if response end endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Managing Windows Services
- InhaltsvorschauCredit: Bill FroelichYou want to interact with existing system services on the Windows platform.User the
win32-servicelibrary, available as the gem of the same name. ItsServicemodule gives you an interface to work with services in Windows 2000 or XP Pro.You can use this to print a list of the currently running services on your machine:require 'rubygems' require 'win32/service' include Win32 puts 'Currently Running Services:' Service.services do |svc| if svc.current_state == 'running' puts "#{svc.service_name}\t-\t#{svc.display_name}" end end # Currently Running Services: # ACPI - Microsoft ACPI Driver # AcrSch2Svc - Acronis Scheduler2 Service # AFD - AFD Networking Support Environment # agp440 - Intel AGP Bus Filter # …This command checks whether the DNS client service exists on your machine:Service.exists?('dnscache') # => trueService.statusreturns aWin32ServiceStatusstruct describing the current state of a service:Service.status('dnscache') # => #<struct Struct::Win32ServiceStatus # service_type="share process", current_state="running", # controls_accepted=["netbind change", "param change", "stop"], # win32_exit_code=0, service_specific_exit_code=0, check_point=0, # wait_hint=0, :interactive?=false, pid=1144, service_flags=0>If a service is not currently running, you can start it withService.start:Service.stop('dnscache') Service.status('dnscache').current_state # => "stopped" Service.start('dnscache') Service.status('dnscache').current_state # => "running"Services are typically accessed using theirservice_nameattribute, not by their display name as shown in the Services Control Panel. Fortunately,Serviceprovides helpful methods to convert between the two:Service.getdisplayname('dnscache') # => "DNS Client" Service.getservicename('DNS Client') # => "dnscache"In addition to getting information about the status and list of services available, thewin32-serviceEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Running Code as Another User
- InhaltsvorschauWhile writing a Ruby script that runs as root, you need to take some action on behalf of another user: say, run an external program or create a file.Simply set
Process.euidto the UID of the user. When you're done, set it back to its previous value (that is, root's UID). Here's a methodProcess.as_uidthat runs a code block under a different user ID and resets it at the end:module Process def as_uid(uid) old_euid, old_uid = Process.euid, Process.uid Process.euid, Process.uid = uid, uid begin yield ensure Process.euid, Process.uid = old_euid, old_uid end end module_function(:as_uid) end
When a Unix process tries to do something that requires special permissions (like access a file), the permissions are checked according to the "effective user ID" of the process. The effective user ID starts out as the user ID you used when you started the process, but if you're root you can change the effective user ID withProcess.euid=. The operating system will treat you as though you were really that user.This comes in handy when you're administering a system used by others. When someone asks you for help, you can write a script that impersonates them and runs the commands they don't know how to run. Rather than creating files as root and usingchownto give them to another user, you can create the files as the other user in the first place.Here's an example. On my system the account leonardr has UID 1000. When run as root, this code will create one directory owned by root and one owned by leonardr:Dir.mkdir("as_root") Process.as_uid(1000) do Dir.mkdir("as_leonardr") %x{whoami} end # => "leonardr\n"Here are the directories:$ ls -ld as_* drwxr-xr-x 2 leonardr root 4096 Feb 2 13:06 as_leonardr/ drwxr-xr-x 2 root root 4096 Feb 2 13:06 as_root/
When you're impersonating another user, your permissions are restricted to what that user can do. I can't remove theas_rootdirectory as a nonroot user, because I created it as root:Process.as_uid(1000) do Dir.rmdir("as_root") end # Errno::EPERM: Operation not permitted - as_root Dir.rmdir("as_root") # => 0Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Running Periodic Tasks Without cron or at
- InhaltsvorschauYou want to write a self-contained Ruby program that performs a task in the background at a certain time, or runs repeatedly at a certain interval.Fork off a new process that sleeps until it's time to run the Ruby code.Here's a program that waits in the background until a certain time, then prints a message:
#!/usr/bin/ruby # lunchtime.rb def background_run_at(time) fork do sleep(1) until Time.now >= time yield end end today = Time.now noon = Time.local(today.year, today.month, today.day, 12, 0, 0) raise Exception, "It's already past lunchtime!" if noon < Time.now background_run_at(noon) { puts "Lunchtime!" }Theforkcommand only works on Unix systems. Thewin32-processthird-party add on gives Windows aforkimplementation, but it's more idiomatic to run this code as a Windows service withwin32-service.With this technique, you can write self-contained Ruby programs that act as though they were spawned by theatcommand. If you want to run a backgrounded code block at a certain interval, the way a cronjob would, then combineforkwith the technique described in Recipe 3.12.#!/usr/bin/ruby # reminder.rb def background_every_n_seconds(n) fork do loop do before = Time.now yield interval = n-(Time.now-before) sleep(interval) if interval > 0 end end end background_every_n_seconds(15*60) { puts 'Get back to work!' }Forking is the best technique if you want to run a background process and a foreground process. If you want a script that immediately returns you to the command prompt when it runs, you might want to use theDaemonizemodule instead; see Recipe 20.1.- Both the
win32-processand thewin32-servicelibraries are available at http://rubyforge.org/projects/win32utils/ - Recipe 3.12, "Running a Code Block Periodically"
- Recipe 20.1, "Running a Daemon Process on Unix"
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Deleting Files That Match a Regular Expression
- InhaltsvorschauCredit: Matthew PalmerYou have a directory full of files and you need to remove some of them. The patterns you want to match are too complex to represent as file globs, but you can represent them as a regular expression.The
Dir.entriesmethod gives you an array of all files in a directory, and you can iterate over this array with#each. A method to delete the files matching a regular expression might look like this:def delete_matching_regexp(dir, regex) Dir.entries(dir).each do |name| path = File.join(dir, name) if name =~ regex ftype = File.directory?(path) ? Dir : File begin ftype.delete(path) rescue SystemCallError => e $stderr.puts e.message end end end end
Here's an example. Let's create a bunch of files and directories beneath a temporary directory:require ' fileutils' tmp_dir = 'tmp_buncha_files' files = ['A', 'A.txt', 'A.html', 'p.html', 'A.html.bak'] directories = ['text.dir', 'Directory.for.html'] Dir.mkdir(tmp_dir) unless File.directory? tmp_dir files.each { |f| FileUtils.touch(File.join(tmp_dir,f)) } directories.each { |d| Dir.mkdir(File.join(tmp_dir, d)) }Now let's delete some of those files and directories. We'll delete a file or directory if its name starts with a capital letter, and if its extension (the string after its last period) is at least four characters long. This corresponds to the regular expression/^[A-Z].*\.[^.]{4,}$/:Dir.entries(tmp_dir) # => [".", "..", "A", "A.txt", "A.html", "p.html", "A.html.bak", # "text.dir", "Directory.for.html"] delete_matching_regexp(tmp_dir, /^[A-Z].*\.[^.]{4,}$/) Dir.entries(tmp_dir) # => [".", "..", "A", "A.txt", "p.html", "A.html.bak", "text.dir"]Like most good things in Ruby,Dir.entriestakes a code block. Ityieldsevery file and subdirectory it finds to that code block. Our particular code block uses the regular expression match operator=~to match every real file (no subdirectories) against the regular expression, andFile.deleteto remove offending files.File.deleteEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Renaming Files in Bulk
- InhaltsvorschauYou want to rename a bunch of files programmatically: for instance, to normalize the filename case or to change the extensions.Use the
Findmodule in the Ruby standard library. Here's a method that renames files according to the results of a code block. It returns a list of files it couldn't rename, because their proposed new name already existed:require 'find' module Find def rename(*paths) unrenamable = [] find(*paths) do |file| next unless File.file? file # Skip directories, etc. path, name = File.split(file) new_name = yield name if new_name and new_name != name new_path = File.join(path, new_name) if File.exists? new_path unrenamable << file else puts " Renaming #{file} to #{new_path}" if $DEBUG File.rename(file, new_path) end end end return unrenamable end module_function(:rename) endThis addition to theFindmodule makes it easy to do things like convert all filenames to lowercase. I'll create some dummy files to demonstrate:require 'fileutils' tmp_dir = 'tmp_files' Dir.mkdir(tmp_dir) ['CamelCase.rb', 'OLDFILE.TXT', 'OldFile.txt'].each do |f| FileUtils.touch(File.join(tmp_dir, f)) end tmp_dir = File.join(tmp_dir, 'subdir') Dir.mkdir(tmp_dir) ['i_am_SHOUTING', 'I_AM_SHOUTING'].each do |f| FileUtils.touch(File.join(tmp_dir, f)) end
Now let's convert these filenames to lowercase:$DEBUG = true Find.rename('./') { |file| file.downcase } # Renaming ./tmp_files/subdir/I_AM_SHOUTING to ./tmp_files/subdir/i_am_shouting # Renaming ./tmp_files/OldFile.txt to ./tmp_files/oldfile.txt # Renaming ./tmp_files/CamelCase.rb to ./tmp_files/camelcase.rb # => ["./OldFile.txt", "./dir/i_am_SHOUTING"]Two of the files couldn't be renamed, becauseoldfile.txtandsubdir/i_am_shoutingwere already taken.Let's add a ".txt" extension to all files that have no extension:Find.rename('./') { |file| file + '.txt' unless file.index('.') } # Renaming ./tmp_files/subdir/i_am_shouting to ./tmp_files/subdir/i_am_shouting.txt # Renaming ./tmp_files/subdir/i_am_SHOUTING to ./tmp_files/subdir/i_am_SHOUTING.txt # # => []Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Finding Duplicate Files
- InhaltsvorschauYou want to find the duplicate files that are taking up all the space on your hard drive.The simple solution is to group the files by size and then by their MD5 checksum. Two files are presumed identical if they have the same size and MD5 sum.The following program takes a list of directories on the command line, and prints out all sets of duplicate files. You can pass a different code block into
each_set_of_ duplicatesfor different behavior: for instance, to prompt the user about which of the duplicates to keep and which to delete.#!/usr/bin/ruby # find_duplicates.rb require 'find' require 'digest/md5' def each_set_of_duplicates(*paths) sizes = {} Find.find(*paths) do |f| (sizes[File.size(f)] ||= []) << f if File.file? f end sizes.each do |size, files| next unless files.size > 1 md5s = {} files.each do |f| digest = Digest::MD5.hexdigest(File.read(f)) (md5s[digest] ||= []) << f end md5s.each { |sum, files| yield files if files.size > 1 } end end each_set_of_ duplicates(*ARGV) do |f| puts " Duplicates: #{f.join(", ")}" endThis is one task that can't be handled with a simpleFind.findcode block, because it's trying to figure out which files have certain relationships to each other.Find.findtakes care of walking the file tree, but it would be very inefficient to try to make a single trip through the tree and immediately spit out a set of duplicates. Instead, we group the files by size and then by their MD5 checksum.The MD5 checksum is a short binary string used as a stand-in for the contents of a file. It's commonly used to verify that a huge file was downloaded without errors. It's not impossible for two different files to have an MD5 sum, but unless someone is deliberately trying to trick you, it's almost impossible to have two files with the same size and the same MD5 sum.Calculating a MD5 sum is very expensive: it means performing a mathematical calculation on the entire contents of the file. Grouping the files by size beforehand greatly reduces the number of sums that must be calculated, but that's still a lot of I/O. Even if two similarly sized files differ in the first byte, the code above will read the entire files.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Automating Backups
- InhaltsvorschauYou want to make a dated archive of a directory to burn to CD or otherwise store on backup media.This script copies a directory to a timestamped backup. It reuses the
File.versioned_ filenamemethod defined in Recipe 6.14, so you can create multiple backups in the same time period:require 'fileutils' def backup(from_dir, to_dir, time_format="-%Y%m%d") from_path, from_name = File.split(from_dir) now = Time.now.strftime(time_format) Dir.mkdir(to_dir) unless File.exists? to_dir unless File.directory? to_dir raise ArgumentError, "Not a directory: #{to_dir}" end to = File.versioned_filename(File.join(to_dir, from_name + now)) FileUtils.cp_r(from_dir, to, :preserve=>true) return to end # This method copied from "Backing Up to Versioned Filenames" class File def File.versioned_filename(base, first_suffix=".0") suffix = nil filename = base while File.exists?(filename) suffix = (suffix ? suffix.succ : first_suffix) filename = base + suffix end return filename end end # Create a dummy directory Dir.mkdir('recipes') # And back it up. backup('recipes', '/tmp/backup') # => "/tmp/backup/recipes-20061031" backup('recipes', '/tmp/backup') # => "/tmp/backup/recipes-20061031.0" backup('recipes', '/tmp/backup', '-%Y%m%d-%H.%M.%S') # => "/tmp/backup/recipes-20061031-20.48.56"Thebackupmethod recursively copies the contents of a directory into another directory, possibly on another filesystem. It uses the time-based scheme you specify along withversioned_filenameto uniquely name the destination directory.As written, thebackupmethod uses a lot of space: every time you call it, it creates an entirely new copy of every file in the source directory. Fortunately, the technique has many variations. Instead of copying the files, you can make a timestamped tarball with the techniques from Recipe 12.10. You can archive the files to another computer with the techniques from Recipe 14.11 (although to save space, you should use thersyncprogram instead). You could even automatically check your work into a version control system every so often; this works better with text than with binary files.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Normalizing Ownership and Permissions in User Directories
- InhaltsvorschauYou want to make make sure your users' home directories don't contain world-writable directories, directories owned by other users, or other potential security problems.Use the
etclibrary to look up a user's home directory and UID from the username. Then useFind.findto walk the directory trees, andFilemethods to check and modify access to each file.We are looking out for any case where one user's home directory can be modified by some other user. Whenever we find such a case, we fix it with aFile.chmodorFile.chowncall. In this program, the actual calls are commented out, so that you don't accidentally change your permissions when you just want to test out the program.#!/usr/bin/ruby -w # normalize_homes.rb require 'etc' require 'find' require 'optparse' def normalize_home(pwd_entry, maximum_perms=0775, dry_run=true) uid, home = pwd_entry.uid, pwd_entry.dir username = pwd_entry.name puts "Scanning #{username}'s home of #{home}." Find.find(home) do |f| next unless File.exists? f stat = File.stat(f) file_uid, file_gid, mode = stat.uid, stat.gid, stat.modeThe most obvious thing we want to check is whether the user owns every file in their home directory. With occasional exceptions (such as files owned by the web server), a user should own the files in his or her home directory:# Does the user own the file? if file_uid != uid begin current_owner = Etc.getpwuid(file_uid).name rescue ArgumentError # No such user; just use UID current_owner = "uid #{file_uid}" end puts " CHOWN #{f}" puts " Current owner is #{current_owner}, should be #{username}" # File.chown(uid, nil, f) unless dry_run endA less obvious check involves the Unix group that owns the file. A user can let other people work on a file in their home directory by giving ownership to a user group. But you can only give ownership to a group if you're a member of that group. If a user's home directory contains a file owned by a group the user doesn't belong to, something fishy is probably going on.# Does the user belong to the group that owns the file? begin group = Etc.getgrgid(file_gid) group_name = group.name rescue ArgumentError # No such group group_name = "gid #{file_gid}" end unless group && (group.mem.member?(username) || group.name == username) puts " CHGRP #{f}" puts " Current group is #{group_name}, and #{username} doesn't belong." # File.chown(nil, uid, f) unless dry_run endEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Killing All Processes for a Given User
- InhaltsvorschauYou want an easy way to kill all the running processes of a user whose processes get out of control.You can send a Unix signal (including the deadly SIGTERM or the even deadlier SIGKILL) from Ruby with the
Process.killmethod. But how to get the list of processes for a given user? The simplest way is to call out to the unixpscommand and parse the output. Runningps -u#{username}gives us the processes for a particular user.#!/usr/bin/ruby -w # banish.rb def signal_all(username, signal) lookup_uid(username) killed = 0 %x{ps -u#{username}}.each_with_index do |proc, i| next if i == 0 # Skip the header provided by ps pid = proc.split[0].to_i begin Process.kill(signal, pid) rescue SystemCallError => e raise e unless e.errno == Errno::ESRCH end killed += 1 end return killed endThere are a couple things to look out for here.psdumps a big error message if we pass in the name of a nonexistent user. It would look better if we could handle that error ourselves. That's what the call tolookup_uidwill do.psprints out a header as its first line. We want to skip that line because it doesn't represent a process.- Killing a process also kills all of its children. This can be a problem if the child process shows up later in the
pslist: killing it again will raise aSystemCallError. We deal with that possibility by catching and ignoring that particularSystemCallError. We still count the process as "killed," though.
Here's the implementation oflookup_id:def lookup_uid(username) require 'etc' begin user = Etc.getpwnam(username) rescue ArgumentError raise ArgumentError, "No such user: #{username}" end return user.uid endNow all that remains is the command-line interface:require 'optparse' signal = "SIGHUP" opts = OptionParser.new do |opts| opts.banner = "Usage: #{__FILE__} [-9] [USERNAME]" opts.on("-9", "--with-extreme-prejudice", "Send an uncatchable kill signal.") { signal = "SIGKILL" } end opts.parse!(ARGV) if ARGV.size != 1 $stderr.puts opts.banner exit end username = ARGV[0] if username == "root" $stderr.puts "Sorry, killing all of root's processes would bring down the system." exit end puts "Killed #{signal_all(username, signal)} process(es)."Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Zurück zu Ruby Cookbook
