Ruby Guerrilla Manuals: John Bresnik
Ruby Guerrilla Manuals: John Bresnik
Ruby
John Bresnik
Advantages are such obvious reasons such as code reduction as well some
more subtle yet powerful reasons such as Mixins (explained shortly) that
allow functionality to be shared by many classes painlessly at runtime. In
fact, Ruby supports code closure which allows you literally pass code
around and avoid having to write truckloads of Classes and methods to do
the same things in Java, etc.
The main disadvantage is that you may or may not catch type errors until
run time. Of course bear in mind that you're developing web applications
and a simple type mismatch will likely result in a BUG report in QA in the
worst case rather than sending your Mars Lander spiraling off toward the
Great Andromeda Galaxy, i.e. the advantages far outweigh the
disadvantages.
The following is a very brief introduction to Ruby that covers, first the
basics and then what are some smart, and fairly unique (compared to
current, popular languages), smart features of Ruby. You can follow along
by starting up the interactive ruby shell with the following:
irb
This provides you with an interactive shell where you can create variables,
classes, etc. All of the following examples were tested with irb.
title
Instance variables for a specific object are prefixed with an @ sign:
@title
Class variables are a kind of global variable across all instances (Objects)
of a class and unlike instance variables they have to be initialized before
they are used; they are prefixed with two @@ signs:
@@title
$title
Ruby classes have some unique features that separate them from other
languages. Some basics are as follows:
class Something
end
s = Something.new
If you intend to initialize a class with some variables you must define a
initialize method for the class:
class Something
def initialize(stuff, more_stuff)
@stuff = stuff
@more_stuff = more_stuff
end
end
Ruby classes also have their own version of accessors, for example read
and write accessors can be defined as follows:
class Address
attr_accessor :number, :street, :zip, :country
end
class Note
attr_reader :text
attr_writer :edited_by
def initialize(text)
@text = text
end
end
class Tree
attr_reader classification
private
def grow
...
end
end
class Tree
def grow
...
end
private :grow, :age
public :trim
end
One final note about Class Methods; they are static methods that available
as part of the class and don't necessarily need to be instantiated into
Objects. They are generally used as constructors or utility methods, for
example:
class TimeMachine
def TimeMachine.transcript(to)
...
end
end
class TimeMachine
class << self
def transcript(to)
...
end
end
end
And to use:
Finally, Ruby supports the ability to define operators similar to c++, for
example:
class StuffWrapper
def initialize()
@stuff = Array.new
end
def <<(append_value)
@stuff << append_value
end
def +(append_list)
@stuff += append_list
end
end
module Logger
def log_message(message)
puts message
end
end
class Person
include Logger
end
class Place
include Logger
end
This will allow me to use the module inside of the class, for example:
joe = Person.new
joe.log_message 'This is a log message..'
Strings
The String class is Ruby is one of the largest classes which contains just
about every possible method ever developed in any language, and a full
review of the methods is beyond the scope of this manual. First, there are
some basic differences from what you may be used to. For example, single
or double quotes mean different things. Single quotes mean a basic string:
puts 'A string with escaped single \'quote\'..'
Using double quotes support the expression escape for embedding the
output of a variable in a string:
require 'date'
puts "Today's date is: #{DateTime.now}"
puts <<-END_STRING
A long and multi-lined passage
that demonstrates how to create
a here document which will print
everything after the '<<-' until the
constant that is declared first..
END_STRING
You can of course, set to a variable, or use in any way you wish.
As mentioned before the String class has numerous methods, more than
we can cover here but here are a few examples of the powerful methods
for use with a String.
'a4string3with9some8numbers'.split(/[0-9]/)
=> ["a", "string", "with", "some", "numbers"]
'a4string3with9some8numbers'.scan(/[0-9]/)
["4", "3", "9", "8"]
Any many more options for string manipulation. See your favorite
reference for more.
Ranges
Ranges are a data type of their own. It is best to think of them in terms of a
sequence:
(0..9).to_a
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
zero_to_nine = 0..9
=> 0..9
zero_to_nine.max
=> 9
zero_to_nine.reject {|x| x < 7}
=> [7, 8, 9]
Finally Ranges come with their operator with three equal signs called a
case equality operator that returns true or false if a give number falls into
a range, for example:
(0..9) === 4
=> true
or
and
(0..9) === 72
=> false
This is, of course, a very significant time saver when searching a range for
the existence of a particular value. Ranges can be used in a variety of ways
from representing a range of values as well as condition expressions, etc.
Symbols
The is nothing remotely complicated about Symbols; they are simply
representations for a name. Again, just something you probably have
never came across in other languages but an important part of using Ruby.
Symbols are created by simple stating them and can be used to a variety of
purposes:
# using
s[:two]
Collection Types
Ruby supports a variety of standard collections. For example to create an
Array
a = Array.new
which will create a Set, a unique of variables in the set; or to just trim out
redundant values from an array, do it all at once:
b = Set.new(b).to_a
# or use the uniq method
b.uniq!
Briefly, the ! operator means that the method changes the original value as
opposed to simply returning it, for example:
b = b.uniq
b.uniq!
h['one']
# invert a hash
h.invert
Exceptions
Ruby handles exceptions like most OOP languages with a few additional
features. First the basics:
begin
raise 'Exception!'
rescue
# some code to handle the exception
end
begin
# some code
rescue CustomError1, CustomError2 => custom
puts "CustomError has occurred: #{boom}"
rescue StandardError => custom
puts "CustomError has occurred: #{boom}"
end
Ruby also make the global variable $! available which contains the raised
exception. Additional features include ensure and else blocks, for
example:
some_file = File.open('README')
begin
# some code
rescue
# some code to handle the exception
else
puts 'Process Successful'
ensure
some_file.close unless some_file.nil?
end
begin
remote_server.connect(login_keys[current])
rescue RemoteLoginError
if @current < @login_keys.size
@current += 1
retry
else
raise
end
end
Again, use caution when using retry; it can easily get you into a bad
situation when not used properly.
Requires
We've seen a number of different references to require so far and should
take a moment to explain how these PATHs are found, created etc.
There are set of default paths that can be seen with the following:
$: << "/tmp"
Finally, basic paths work as well. Included in the load path is a reference
to the current directory (a dot). This allows you to load modules, etc. that
are also in the current directory or reference if the files are localized (for
example in a /lib folder)
Things like:
if x.valid?
puts 'Valid!'
end
can be reduced to
or
while x < 10
x+= 2
end
can be reduced to
Now it may seem backwards, but of course it's not. It's just different from
what you're used to seeing. Like most of Ruby, once you start to use it like
Ruby, it's becomes very intuitive.
Smart Features: block iterators
Block Iterators are one of the nicest features in Ruby and liberate us from
the archaic for i = 0 loop nonsense that has plagued us now for
generations. Block iterators come in two forms, single and multiple line
iterations.
Single line iterations are likely for shorter statements and can be
performed on a single line:
This outputs a capitalized version of each element in the array. Also for
those times when you just really absolutely have to have an i:
outputs:
0: a
1: b
2: c
Multi-lined iterators are similar except for they are able to span multiple
lines clearly:
a.each |x| do
#we may have to write a number of lines
puts x.capitalize
#multi-lined iterators are good for this
puts x.swapcase
end
Why are there two options for doing the same thing? Well you may to do a
number of things inside your iteration block, and stuffing those all into a
single line (which can be done with a semi-colon separating them) would
lead to unreadable nonsense -- always keep in mind that programming
languages are for humans to communicate; machines couldn't careless
how you organize your sequence of instructions and creating cryptic
blocks of unreadable code will undermine the purpose of a language in
first place.
Smart Features: better control statements
You can now avoid the super long OR statements found in other languages
e.g.,
if ['y','x','z'].include? x
…
Again, the same result but a much cleaner, more concise and readable
format for expressing it. Also, seemingly reversed in how it is expressed
but that is only because your thinking in terms of other languages.
Imagine a scenario where you want to output a specific format type based
on the user's request. In other words, your application needs to be able to
provide HTML, XML, or Yaml accordingly.
module HtmlOutput
def custom_output(a_class)
#specifics for converting class to HTML
...
end
end
module XmlOutput
def custom_output(a_class)
#specifics for converting class to XML
...
end
end
module YamlOutput
def custom_output(a_class)
#specifics for converting class to Yaml
...
end
end
Now to use, simply extend your class with the applicable module (XML
in the following example):
@my_model.extend type_factory[:xml]
and output:
puts @my_model.custom_output
Of course since this is a Module, you can extend any class with the
functionality and transparently reuse the different Modules accordingly.
def execute
yield
end
class Engine
def start
#specifics for starting the engine
...
end
def stop
#specifics for stoping the engine
...
end
end
class EngineSwitch
def initialize(&perform_action)
perform_action.call self
end
end
Using:
my_engine = Engine.new
EngineSwitch.new { my_engine.start }
EngineSwitch.new { my_engine.stop }
This first, and likely most useful, is the map (also known as collect)
method. This allows to process each item in an enumeration, for example:
Additionally you can modify the original array with the ! version:
Don't make a get_this or set_that for every single attribute in your class.
Instead think about the purpose of encapsulation, i.e. if I need to create a
getter and a setter for this attribute that does nothing but get or set an
attribute, maybe I should just make it public or use an attr_reader
or attr_writer? Maybe there's attributes in my class that I shouldn't
be exposing via getters and setters? Also use the Ruby accessors for
accessing these attributes as opposed to a slew of get_this, set_that, etc:
class EncapsulatedThing
attr_reader :read_only_attribute
attr_accessor :public_attribute
end
Don't dig into and start creating for i = 0 loops everywhere, use the
block iterators that are provided by the language. If you absolutely need a
i value use the each_with_index method (and others) to
accomplish the same result.
Use double quotes with embedded variables instead of +ing them all
together in a massive mess, i.e don't do this:
# bad
puts '<html>'
puts ' <body>'
puts ' <h1>This is wrong</h1>'
puts ' </body>'
puts '</html>'
# good
puts <<-END_HTML
<html>
<body>
<h1>This is good</h1>
</body>
</html>
END_HTML
Don't make a Class out of everything. One of the things that still makes
me cringe when using Java is that everything must be defined as a class.
This is OOP at it's political extreme. OOP was originally meant to allow us
to model objects based on reality; but if everything has to be a object, we
are forced to create objects out of things that aren't really objects.
Principles
DRY as it sometimes abbreviated, is essentially a philosophy to avoid
excessive duplication in programming. It has many positive attributes such
decreasing the need for later changes across a code base, increased clarity,
and generally reduces inconsistency. It extends beyond simply code and
can include practices for documentation, database schema, build scripts,
and test plans as well. The core principle is that modifications of code do
not inadvertently change areas that shouldn't have been affected by such
change.
Practice
DRY implementations can be applicable in almost every application and
must determined based on it's applicability and balanced against over
obsessing the principle in the first place. A somewhat trivial example
might be:
if something == true
x = SomeClass.new
x.title = 'This is my title'
x.text = 'This is my text'
x.something = true
else
x = SomeClass.new
x.title = 'This is my title'
x.text = 'This is my text'
x.otherthing = false
end
Can be DRY'd by removing the duplicate code and reducing the condition
to the a single line:
x = SomeClass.new
x.title = 'This is my title'
x.text = 'This is my text'
x.otherthing = something ? true : false
Don't DRY-out
Keep this principle in mind but don't obsess over it. There are certainly
valid occasions when implementing DRY will take absurdly longer than
simply having duplicates. A generally good example of this is lightweight,
inexpensive views such as RHTML pages. Spending hours and hours
trying to squeeze every last drop of DRY out of your views will likely just
cut into your production schedule and in the worst case, nullify any
progress you may have made.
In my Java youth, I had a particular employee that only liked to work on
things that were exceptionally interesting and when asked to add a few,
redundant links to a small range of pages, spent an entire week
developing a 'Link Factory' to manage the 3 or 4 set of links that was
realistically about 10 minutes worth of work; and of course being Java, it
required a complete recompile of the application for the smallest change.
You get the idea, don't obsess over principles or patterns. It's more critical
that you understand when and where they are applicable than even how to
use them in the first place. DRY is meant as a means to be more
productive and clear, obsessing over it incurs the opposite.
Conventions
The following conventions are fairly universal in Ruby but of course, not
enforced by the interpreter (except class names), i.e. these are
recommended conventions.
Indentation should be two white spaces and *not a tab character, e.g.
def some_function
if true
puts 'It is true'
end
end
if x == y
y += 2
call_this x
end
All classes are camel case, all methods and variables use underscores.
module SomeModule
class SomeClass < AnotherBaseClass
attr_accessor :some_variable
def some_method(a_variable)
@some_variable = a_variable
end
end
end
Iteration block format should be based on the size of the iteration block:
# short
(0..5).each {|x| x.strip!; puts x }
# long
some_array.each do |x|
x.strip!
some_call x
puts x
puts x += 'this'
end
some_array = Array.new
some_hash = Hash.new
All comments should have a space between the comment character and the
text. Lines should not be longer than 80 characters long
Method calls should use parenthesis when the number of variables are
small
some_method_call x, y
a_long_method_call(x, y, z, a, b, c)
a_short_but_complex_call(%w[1 2 3], x, {puts 'test'})
Principles
DRY as it sometimes abbreviated, is essentially a philosophy to avoid
excessive duplication in programming. It has many positive attributes such
decreasing the need for later changes across a code base, increased clarity,
and generally reduces inconsistency. It extends beyond simply code and
can include practices for documentation, database schema, build scripts,
and test plans as well. The core principle is that modifications of code do
not inadvertently change areas that shouldn't have been affected by such
change.
Practice
DRY implementations can be applicable in almost every application and
must determined based on it's applicability and balanced against over
obsessing the principle in the first place. A somewhat trivial example
might be:
if something == true
x = SomeClass.new
x.title = 'This is my title'
x.text = 'This is my text'
x.something = true
else
x = SomeClass.new
x.title = 'This is my title'
x.text = 'This is my text'
x.otherthing = false
end
Can be DRY'd by removing the duplicate code and reducing the condition
to the a single line:
x = SomeClass.new
x.title = 'This is my title'
x.text = 'This is my text'
x.otherthing = something ? true : false
You get the idea, don't obsess over principles or patterns. It's more critical
that you understand when and where they are applicable than even how to
use them in the first place. DRY is meant as a means to be more
productive and clear, obsessing over it incurs the opposite.