Any guides for good coding in Ruby?

Is there some kind of class to format numbers? Something to let you
transform:

1 => "0001"
1234 => "1,234.00"

I needed to display the day and month in a 00 format and the way I did
it was adding a method to the Integer class

class Integer
  def addZero
    return "0" + self.to_s if self.to_s.length == 1
    return self.to_s
  end
end
t = Time.now
p "The date is: #{t.year}#{t.month.addZero}#{t.day.addZero}"

What do you guys think of this solution?

On a side note, could you guys give a few rules of thumb you follow in
order to make your code clean and readable?

Is there some kind of class to format numbers? Something to let you
transform:

1 => "0001"
1234 => "1,234.00"

I'm not aware of a core Ruby tool for commiying numbers, but it's not hard to roll a solution:

def commify( number )
   text = number.to_s.reverse
   text.gsub!(/(\d\d\d)(?=\d)(?!\ d*\.)/, '\1,')
   text.reverse
  end

I needed to display the day and month in a 00 format and the way I did
it was adding a method to the Integer class

class Integer
  def addZero
    return "0" + self.to_s if self.to_s.length == 1
    return self.to_s
  end
end
t = Time.now
p "The date is: #{t.year}#{t.month.addZero}#{t.day.addZero}"

What do you guys think of this solution?

Kernel#sprintf and it's shortcut cousin String#% will handle this for you. Here's an example:

"%02d" % 1 => "01"

Also look into Time#strftime for tasks like the above.

On a side note, could you guys give a few rules of thumb you follow in
order to make your code clean and readable?

Of course, there are loads of rules here and every person that gives you a list will be different. Common sense ideas like, "Use good variable names," apply in Ruby as they do everywhere else.

One of Ruby's great strengths is readability, I think. Play to your strengths. Give the extra five keystrokes to make things utterly clear, but don't be verbose if a concise line of code is obvious.

As always, common sense is needed.

James Edward Gray II

···

On Mar 22, 2005, at 3:09 PM, Arfin wrote:

* Arfin (Mar 22, 2005 22:10):

I needed to display the day and month in a 00 format …

printf("%02d", 1),
        nikolai

···

--
::: name: Nikolai Weibull :: aliases: pcp / lone-star / aka :::
::: born: Chicago, IL USA :: loc atm: Gothenburg, Sweden :::
::: page: minimalistic.org :: fun atm: gf,lps,ruby,lisp,war3 :::
main(){printf(&linux["\021%six\012\0"],(linux)["have"]+"fun"-97);}

Hi --

···

On Wed, 23 Mar 2005, Arfin wrote:

On a side note, could you guys give a few rules of thumb you follow in
order to make your code clean and readable?

(Sorry to answer only the side note :slight_smile:

I try to follow the style that is predominant in the Ruby parts of the
Ruby source. Also, see:

http://pub.cozmixng.org/~the-rwiki/rw-cgi.rb?cmd=view;name=RubyCodingConvention

which summarizes a lot of the traditional practices.

David

--
David A. Black
dblack@wobblini.net

Is there some kind of class to format numbers? Something to let you
transform:

1 => "0001"
1234 => "1,234.00"

I needed to display the day and month in a 00 format and the way I did
it was adding a method to the Integer class

class Integer
        def addZero
                return "0" + self.to_s if self.to_s.length == 1
                return self.to_s
        end
end
t = Time.now
p "The date is: #{t.year}#{t.month.addZero}#{t.day.addZero}"

What do you guys think of this solution?

Might be easier with "printf" - although if you're not familiar with
it, might take a bit of work to get the format right.

On a side note, could you guys give a few rules of thumb you follow in
order to make your code clean and readable?

Some helpful stuff here:
http://www.caliban.org/ruby/rubyguide.shtml

Happy Rubying

···

On Wed, 23 Mar 2005 06:09:50 +0900, Arfin <arfinmail@yahoo.com> wrote:
--
Bill Guindon (aka aGorilla)

Arfin wrote:

Is there some kind of class to format numbers? Something to let you
transform:

1 => "0001"

"%04d" % 1

This is pretty much the same as sprintf() in C.

But in this case you can also do:

"1".rjust(4, "0")

1234 => "1,234.00"

I'd do that like this, even though there's other versions:

def number_format(number, padding_char = ",")
   result = number.to_s
   while pos = result.rindex(/\d{4}(?=\D|\Z)/)
     result[pos + 1, 0] = padding_char
   end
   return result
end

irb(main):001:0> number_format 100_000_000
=> "100,000,000"
irb(main):002:0> number_format 10_000_000_000
=> "10,000,000,000"
irb(main):003:0> number_format "foo 10000000000 bar 50 qux 5000"
=> "foo 10,000,000,000 bar 50 qux 5,000"

* Arfin (Mar 22, 2005 22:10):

Is there some kind of class to format numbers? Something to let you
transform:

1 => "0001" 1234 => "1,234.00"

Btw, the ruby-lisp module (check http://raa.ruby-lang.org/\) has a method
for outputting numbers in this format,
        nikolai

···

--
::: name: Nikolai Weibull :: aliases: pcp / lone-star / aka :::
::: born: Chicago, IL USA :: loc atm: Gothenburg, Sweden :::
::: page: minimalistic.org :: fun atm: gf,lps,ruby,lisp,war3 :::
main(){printf(&linux["\021%six\012\0"],(linux)["have"]+"fun"-97);}

On a side note, could you guys give a few rules of thumb you follow in
order to make your code clean and readable?

An rpa guide to decent API design:

http://rpa-base.rubyforge.org/wiki/wiki.cgi?GoodAPIDesign

cheers,
                        vruz

Is there some kind of class to format numbers? Something to let you
transform:

1 => "0001"

"%04d" % 1

class Integer
  def addZero
    return "0" + self.to_s if self.to_s.length == 1
    return self.to_s
  end
end
t = Time.now
p "The date is: #{t.year}#{t.month.addZero}#{t.day.addZero}"

Time.strftime "%Y%m%d"

Bertram

···

Am Mittwoch, 23. Mär 2005, 06:09:50 +0900 schrieb Arfin:

--
Bertram Scharpf
Stuttgart, Deutschland/Germany
http://www.bertram-scharpf.de

Arfin wrote:

1234 => "1,234.00"

I only handle the non-obvious "1234.00" -> "1,234.00" part of the task
but that one for float and integer regardless of length and sign. The
only minor trick is using positive lookahead to extract the right digits
in the case of a string that represents a float. Note that the argument
is left unchanged if

- it is no string
- it neither matches /^[-+]?\d+\.\d+$/ nor /^[-+]?\d+$/

The latter means that no modification is done if exponential notation is
used. This is done on purpose because one would not expect colons in
such a notation.

Extending the algorithm to numbers with leading zeroes is left as an
exercise to the reader :->

Here is the HOW IT IS DONE:

def threepack(x)
   return x unless x.class == String
   if /^[-+]?\d+\.\d+$/.match(x)
     match = /((\d\d\d)+)(?=\.)/.match(x)
   elsif /^[-+]?\d+$/.match(x)
     match = /((\d\d\d)+)$/.match(x)
   end
   return x if match.nil?
   result = match.pre_match
   temp = match[1]
   until temp.empty?
     result << ',' unless ['', '-', '+'].member?(result)
     result << temp[0..2]
     temp = temp[3..-1]
   end
   result << match.post_match unless match.post_match.nil?
   result
end

testdata = [

               12345678,
               '08/15',
               '4711 Eau de Cologne',
               'Beethoven',
               '%d' % 1,
               '%d' % 12,
               '%d' % 123,
               '%d' % 1_234,
               '%d' % 12_345,
               '%d' % 123_456,
               '%d' % 1_234_567,

               '%d' % -1,
               '%d' % -12,
               '%d' % -123,
               '%d' % -1_234,
               '%d' % -12_345,
               '%d' % -123_456,
               '%d' % -1_234_567,

                '%4.2f' % 1.0,
                '%5.2f' % 12.0,
                '%6.2f' % 123.0,
                '%7.2f' % 1_234.0,
                '%8.2f' % 12_345.0,
                '%9.2f' % 123_456.0,
               '%10.2f' % 1_234_567.0,

                '%5.2f' % -1.0,
                '%6.2f' % -12.0,
                '%7.2f' % -123.0,
                '%8.2f' % -1_234.0,
                '%9.2f' % -12_345.0,
               '%10.2f' % -123_456.0,
               '%11.2f' % -1_234_567.0,

             ]

testdata.each{|x| puts threepack(x)}

Here is the WHAT IS DONE:

12345678
08/15
4711 Eau de Cologne
Beethoven
1
12
123
1,234
12,345
123,456
1,234,567
-1
-12
-123
-1,234
-12,345
-123,456
-1,234,567
1.00
12.00
123.00
1,234.00
12,345.00
123,456.00
1,234,567.00
-1.00
-12.00
-123.00
-1,234.00
-12,345.00
-123,456.00
-1,234,567.00

Before someone asks: The examples were not chosen by chance; I live in
the former FRG capital of Bonn where Beethoven was born that is located
near Cologne.

Josef 'Jupp' Schugt

···

--
This space is intentionally left void.

Hi!

From Arfin's question on formatting numbers I conclude that there is a need for a means to precisely control the display of numbers.

As long as (natural) science is concerned, printf serves its purpose
very well. What seemingly is missing is a powerful commercial number
formatting like the one of COBOL.

After a small archeological excavation campaign I used some COBOL book to write a first draft of such a formatting. I use the COBOL notation because it has shown its commercial strength for DECADES. Moreover I do not know of any other equally powerful format string language for this kind of application. The EBNF for the full formatting string still needs to be written but that takes some time.

Currently planned features:

   Fixnum#initialize(format, value)
   Fixnum#to_s(format)
   Fixnum#decimalPointIsComma
   Fixnum#decimalPoint
   Fixnum#groupingCharacter
   Fixnum#currencySymbol

This is EBNF for Fixnum format.

   PositiveInteger ::= '1' | '2' | '3' | '4' |
                               '5' | '6' | '7' | '8' | '9'.

   Integer ::= '0' | PositiveInteger.

   RepetitionFactor ::= '(' PositiveInteger [Integer+] ')'.

   DecimalPointPlaceholder ::= 'v'.

   SignPlaceholder ::= 's'.

   DigitPlaceholder ::= '9'.

   DigitsPlaceholder ::= Digitplaceholder+ |
                               Digitplaceholder RepetitionFactor.

   UnsignedPlaceholder ::= DigitsPlaceholder
                               [
                                 DecimalPointPlaceholder
                                 DigitsPlaceholder
                               ].

   Fixnum ::= Sign Unsigned.

This is EBNF for Fixnum format string.

   PositiveInteger ::= '1' | '2' | '3' | '4' |
                             '5' | '6' | '7' | '8' | '9'.

   Integer ::= '0' | PositiveInteger.

   RepetitionFactor ::= '(' PositiveInteger [Integer+] ')'.

   ShowLeadingZero ::= '9'.
   SuppressedLeadingZero ::= 'Z'.
   StarLeadingZero ::= '*'.
   DecimalPoint ::= '.'.
   GroupingSymbol ::= ','.
   NegativeSign ::= '-'.
   AnySign ::= '+'.
   Blank ::= 'B'.
   Zero ::= '0'.
   CurrencySymbol ::= '$'.
   Credit ::= 'CR'.
   Debit ::= 'DB'.
   Slash ::= '/'.

'9', 'Z', and '*' are all used to display a digit. They only differn in
their handling of leading zeroes.

   '9' displays any digit as is even if it is a leading zero

   'Z' displays any digit as is with the exception of replacing a
       leading zero by a blank character '.'

   '*' displays any digit as if with the exception of replacing a
       leading zero by an asterisk character '*'.

'.' displays a 'decimal point' (i.e. the character that separates the
       integer and fractional part of a number). In many countries this
       character is indeed a point '.' but in some (e.g. Germany) it is
       ','[1, 2, 5].

',' displays a grouping character (i.e. the character that is used to
       group large numbers). In many countries people tend to use ',',
       while in other ones '.' is preferred[1, 3, 4, 5].

'-' displays a minus sign '-' for negative numbers and a blank
       character ' ' otherwise.

'+' displays a minus sign '-' for negative numbers and a plus sign
       '+' otherwise.

'B' displays a blank character ' ' at the given position.
'0' displays a zero '0' at the given position

'$' displays a currency symbol[6] at the given position

'CR' displays 'CR' in the case of credit (i.e. a value smaller than
       zero) and two blank characters otherwise.

'DB' displays 'DB' in the case of debit (i.e. a value not smaller than
       zero) and two blank characters otherwise.

'/' displays a slash at the given position.

[1] The Boolean decimalPointIsComma property controls the defaults for
     the decimalPoint[2] and groupingCharacter[3]. It defaults to

         fixnum.decimalPointIsComma = false

[2] The actual character is defined by the decimalPoint property.
     Default (comments show value of decimalPointIsComma):

         fixnum.decimalPoint = '.' # false
         fixnum.decimalPoint = ',' # true

     The value must be a single character.

[3] The actual character is defined by the groupingCharacter property.
     Defaults (comments show value of decimalPointIsComma):

         fixnum.groupingCharacter = ',' # false
         fixnum.groupingCharacter = '.' # true

[4] It is strongly recommended NEITHER to use the default setting NOR
     '.' but a blank character:

         fixnum.groupingCharacter = ' '

     The value must be a single character.

[5] If an assignment sets decimalPoint and groupingCharacter to the
     same character an ArgumentError will be risen.

[6] The currency symbol is defined by the currencySymbol property.
     Default:

         fixnum.currencySymbol = '$'

     The currency symbol can be longer than one character. This allows
     you to use ISO 4217 currency symbols if a given currency symbol is
     inaccessible (you may e.g. wish to use 'EUR' instead of '€' for
     euro, 'GBP' instead of '£' for british pound sterling, 'JPY'
     instead of '¥' for japanese yen, and 'KRW' instead of '₩' for
     korean won).

Comments?

Over and out,

Josef 'Jupp' Schugt

···

--
Mi estas la releviĝo kaj la vivo; kiu kredas al mi, eĉ se li estos
mortinta, tiu vivos, kaj ĉiu, kiu vivas kaj kredas al mi, por ĉiam ne
mortos. -- Johaneo 11, 25f

I try to follow the style that is predominant in the Ruby parts of the
Ruby source. Also, see:

I also do this, but there is one thing I cannot stand: every ruby lib uses 2 spaces for indentation! IMHO it should be tabs, and *only* tabs. This is much better, because in almost every editor it is possible to set the tab width to whatever one likes. I hate 2 spaced sourcecode, because I usually set one tab == 8 spaces, and use a non-fixed font.

martinus

An rpa guide to decent API design:

http://rpa-base.rubyforge.org/wiki/wiki.cgi?GoodAPIDesign

I just read this collection of very good tips. Thanks for the link!

One section does bother me there though:

Put methods where they belong

It's not a Ruby API design idiom, but anyway (perhaps we should split this page in two: Ruby API idioms and design advices).

Classes are supposed to reflect reality. Thus, you should generally put your methods where they belong, to reduce coupling. For example, typical "data classes" should not have "actions", but methods to access their data.

Example:
  #
  # Bad API
  #
  class GraphicElement
     def draw(canvas)
        raise "Implement me!"
     end
  end
  class Circle < GraphicElement
     def draw(canvas)
        # Draw directly into the canvas
        # (couples Circle with the canvas, as Circle
        # follows this particular canvas API)
     end
  end

  #
  # Good API (definitely better than above, perhaps not the best)
  #
  class GraphicElement
     def to_canvas(resolution)
        raise "Implement me!"
     end
  end
  class Circle < GraphicElement
     def to_canvas(resolution)
        # Convert circle to an array of arrays of pixels,
        # taking into account the resolution parameter
     end
  end
  class CanvasPainter
     def paint(elmt, canvas, color=Color::BLACK,
                             centerX=0, centerY=0)
        elmt.to_canvas.each do |x,y|
           canvas.put(centerX+x, centerY+y, color)
        end
     end
  end

I can't decide if it's just the example or the whole section, but this one just doesn't feel right to me. I would much rather have a class rendering itself (who better qualified?), then providing accessor like data for others to do it. Push, don't pull, right?

An excellent book on this topic is Holub on Patterns, if you're interested.

Anyway, just wanted to share. I really did love the page.

James Edward Gray II

···

On Mar 22, 2005, at 4:41 PM, vruz wrote:

Josef 'Jupp' Schugt wrote:

Here is the HOW IT IS DONE:

def threepack(x)
  return x unless x.class == String
  if /^[-+]?\d+\.\d+$/.match(x)
    match = /((\d\d\d)+)(?=\.)/.match(x)
  elsif /^[-+]?\d+$/.match(x)
    match = /((\d\d\d)+)$/.match(x)
  end
  return x if match.nil?
  result = match.pre_match
  temp = match[1]
  until temp.empty?
    result << ',' unless ['', '-', '+'].member?(result)
    result << temp[0..2]
    temp = temp[3..-1]
  end
  result << match.post_match unless match.post_match.nil?
  result
end

Nifty code, though I wonder if it could be simplified by using String#rindex? It's a nice method that's not commonly used, but very nifty when needing to work from the end to the beginning.

It's not core Ruby, but Gavin Sinclair has added a number formatting
class that I wrote last year to his extensions project; this is one
of the most comprehensive number formatting classes you'll find,
IMO.

And I'm not biased at all :wink:

-austin

···

On Wed, 23 Mar 2005 06:26:12 +0900, James Edward Gray II <james@grayproductions.net> wrote:

On Mar 22, 2005, at 3:09 PM, Arfin wrote:
I'm not aware of a core Ruby tool for commiying numbers, but it's
not hard to roll a solution:

--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

Josef 'Jupp' Schugt wrote:

As long as (natural) science is concerned, printf serves its purpose
very well. What seemingly is missing is a powerful commercial number
formatting like the one of COBOL.

....

Comments?

This would make an excellent addition and possibly foundation for Ruby report writing applications.

As a somewhat related aside, in my own little language, I implement a fixed decimal type in addition to float and binary along the same lines of IBM's C extension which was submitted to ANSI many years ago.

decimal(15,2) x;

which is equivalent to Cobol's

77 X PIC S(13)V99 COMP-3.

Fixed point decimal is actually present in all the 3xx architectures and has proven to be very useful for financial applications. While this is not architecturally optimal for arithmetic on x86, maybe there's some interest in a fixed point decimal type as a class library?

···

--
J. Lambert

Looks good.

One small nit; "Debit" is usually indicated (counter-intuitively) by
"DR", not "DB" (though I have seen both.) No idea if there is a
standard for this, or what it says.

···

On 3/29/05, Josef 'Jupp' Schugt <jupp@gmx.de> wrote:

Hi!

From Arfin's question on formatting numbers I conclude that there is a
need for a means to precisely control the display of numbers.

Martin Ankerl wrote:

I try to follow the style that is predominant in the Ruby parts of the
Ruby source. Also, see:

I also do this, but there is one thing I cannot stand: every ruby lib uses 2 spaces for indentation! IMHO it should be tabs, and *only* tabs. This is much better, because in almost every editor it is possible to set the tab width to whatever one likes. I hate 2 spaced sourcecode, because I usually set one tab == 8 spaces, and use a non-fixed font.

I dislike 2 spaces as well, but tabs are worse. It's *extremely* and I mean *extremely* rare for me to see a source file that uses tabs correctly.
Tabs can *never* be used for anything other than indenting the first non-tab character on a line. That's it. As soon as they're used anywhere else on a line following non-tab characters to 'line up' code, then you've broken the ability for other users to use different tab settings and view your code the same way. The *only* way of assuring a consistent representation of source code is to use spaces and only spaces for indentation and column alignment. Modern editors make using spaces for tabs a non-issue anyway.

Patrick Bennett

Amen! I don't know who thought up the two space thing, but they were out of line. :slight_smile: In Ruby, you don't even need the extra characters to keep lines short, generally. I'm set tabs to four spaces wide, but it's definitely tabs all the way.

And while we're getting things off our chest, it really bugs me when people don't keep their code within the 80 character boundary guideline. I've been reading all the links posted in this thread and they've all recommended it, but I can sure tell you from running Ruby Quiz that not everyone is listening. :wink:

James Edward Gray II

···

On Mar 22, 2005, at 4:24 PM, Martin Ankerl wrote:

I also do this, but there is one thing I cannot stand: every ruby lib uses 2 spaces for indentation! IMHO it should be tabs, and *only* tabs. This is much better, because in almost every editor it is possible to set the tab width to whatever one likes. I hate 2 spaced sourcecode, because I usually set one tab == 8 spaces, and use a non-fixed font.

Martin Ankerl wrote:

I try to follow the style that is predominant in the Ruby parts of the
Ruby source. Also, see:

I also do this, but there is one thing I cannot stand: every ruby lib uses 2 spaces for indentation! IMHO it should be tabs, and *only* tabs. This is much better, because in almost every editor it is possible to set the tab width to whatever one likes. I hate 2 spaced sourcecode, because I usually set one tab == 8 spaces, and use a non-fixed font.

Yay! Let's start a tabs vs. spaces flame war!!

My view? Tabs should never, ever be used in a file. (Ever) Can you have a source code file without spaces? I doubt it. Can you have a file without tabs? Certainly. Is mixing the two the main problem? Yup. Use only spaces.

It's funny though. I think this eternal flame war is related to the other eternal flame war: vi vs. emacs. In Emacs, the 'tab' button is most often bound to 'indent-command'. This and 'indent-region' makes it really easy to use spaces for indentation, and to adjust/fix someone else's indentation when it's bad. On the other hand, I think vi makes it much harder to use spaces for indentation, so tabs seem more attractive.

Anyhow, before throwing in your views, at least read up on other people's arguments. :slight_smile:

http://www.google.com/search?hl=en&ie=UTF-8&oe=UTF-8&q="tabs%20vs%20spaces"

Ben