Xiaopei's DokuWiki

These are the good times in your life,
so put on a smile and it'll be alright

User Tools

Site Tools


it:ruby

ruby

tips

  • String 替换
    • String.tr('from', 'to') 是 字符 替换, 与 bash 的 tr 相同
    • String.sub('from', 'to') 是 字符串 替换, 可用 regexp
  • .. Range, 不支持倒序, 有一堆方法见文档
    • ('a'..'e').to_a #⇒ [“a”, “b”, “c”, “d”, “e”]
    • ('a'…'e').to_a #⇒ [“a”, “b”, “c”, “d”]
  • if … elsif … end, 不是 else if, 不是 elif, 写错也不会报错
  • 猜数字:
    n = 100
    r = rand(10) + 1
    g = 0
     
    puts "I have a number between 1 .. #{n}"
     
    until g == r
      g = gets().to_i
      if g > r
        puts 'too big'
      elsif g < r
        puts 'too small'
      end
    end
     
    puts 'bingo'
  • 4.times { puts 'IOU' } 代码块
    • 代码块 只占一行时, 用 { .. }
    • 代码块 占多行时, 用 do .. end
    • 代码块 可带有一个或多个参数 (1..10).each { |i| puts i }
  • class Tree
    class Tree
      attr_accessor :children, :node_name
     
      def initialize(name, children = [])
        @children = children
        @node_name = name
      end
     
      def visit_all(&block)
        visit &block
        children.each { |c| c.visit_all &block }
      end
     
      def visit(&block)
        block.call self
      end
     
      def to_s
        s = "visiting entire tree\n"
        self.visit_all { |node| s += "#{node.node_name}\n" }
        s
      end    
     
    end
     
    ruby_tree = Tree.new( 'Ruby', 
                          [Tree.new('Reia'),
                           Tree.new('MacRuby')] )
     
    puts 'Visiting a node'
     
    ruby_tree.visit { |node| puts node.node_name }
    puts
     
    puts 'visiting entire tree'
    ruby_tree.visit_all { |node| puts node.node_name }
  • 开放类: 类可随时 “打开” 修改先前的类定义
    class Numeric
      def inches
        self
      end
     
      def feet
        self * 12.inches
      end
     
      def yards
        self * 3.feet
      end
     
      def miles
        self * 5280.feet
      end
     
      def back
        self * -1
      end
     
      def forward
        self
      end
    end
     
    puts 10.miles.back
    puts 2.feet.forward
  • module: 对 class 实现 mixin(混入) 功能, 类似 Java 的接口, 但 module 对 class 是不感兴趣的, 通过 duck type 对类调用:
    module ToFile
      def filename
        "object_#{self.object_id}.txt"
      end
     
      def to_f
        File.open(filename, 'w') { |f| f.write(to_s) }
      end
    end
     
    class Person
      include ToFile
      attr_accessor :name
     
      def initialize(name)
        @name = name
      end
     
      def to_s
        name
      end
    end
     
    Person.new('Xiaopei').to_f
    • 常用的 module
      • enumerable(枚举), include 后必须实现 each 方法
      • comparable(比较), include 后必须实现 ⇔ 方法
  • method_missing
    class Roman
      def self.method_missing name, *args
        puts '==='
        puts name
        roman = name.to_s
        roman.gsub!('IV', 'IIII')
        roman.gsub!('IX', 'VIII')
        roman.gsub!('XL', 'XXXX')
        roman.gsub!('XC', 'LXXX')
     
        puts roman
        (roman.count('I') + 
         roman.count('V') * 5 +
         roman.count('X') * 10 +
         roman.count('L') * 50 + 
         roman.count('C') * 100)
      end
    end
     
    puts Roman.X
    puts Roman.XC
    puts Roman.XII
    puts Roman.X
    • 缺点: 增加调试难度
  • 使用 module: 1)避免继承; 2)根据类名动态对类增加方法.
    module ActsAsCsv
      def self.included(base) 
        # Ruby will invoke the included method whenever
        # this module gets included into another
     
        # Object.extend:
        # Adds to obj the instance methods from each module 
        # given as a parameter.
        base.extend ClassMethods
      end
     
      module ClassMethods
        def acts_as_csv
          include InstanceMethods
        end 
      end
     
      module InstanceMethods
        def read
          @csv_contents = []
          filename = self.class.to_s.downcase + '.txt' 
          file = File.new(filename)
          @headers = file.gets.chomp.split(', ')
          file.each do |row|
            @csv_contents << row.chomp.split(', ')
          end 
        end
     
        attr_accessor :headers, :csv_contents
     
        def initialize
          read
        end 
     
        def each(&block)
          self.csv_contents.each do |row|
            yield CsvRow.new(@headers, row)
          end
        end
      end
    end
     
    class CsvRow
      def initialize(headers, values)
        @hash = Hash.new
        headers.each_index do |i|
          @hash[headers[i]] = values[i]
        end
      end
     
      def to_s
        @hash.to_s
      end
     
      def method_missing name, *args
        # name 默认是 symbol, 需 to_s
        @hash[name.to_s]
      end
    end
     
    class RubyCsv # no inheritance! You can mix it in include ActsAsCsv
      include ActsAsCsv
      acts_as_csv
    end
     
    m = RubyCsv.new
    puts m.headers.inspect
    puts m.csv_contents.inspect
     
     
    # 可配套 rubycsv.txt 使用
    # ruby, php
    # puts, echo
    # def, define
     
     
    # 其实上面的 module 和 class 的定义在目前功能下, 和下面的相等
    module ActsAsCsv
      def read
        @csv_contents = []
        filename = self.class.to_s.downcase + '.txt' 
        file = File.new(filename)
        @headers = file.gets.chomp.split(', ')
        file.each do |row|
          @csv_contents << row.chomp.split(', ')
        end 
      end
     
      attr_accessor :headers, :csv_contents
     
      def initialize
        read
      end 
    end
     
    class RubyCsv # no inheritance! You can mix it in include ActsAsCsv
      include ActsAsCsv
    end

coding conventions, style guide

排版

  • 空格
    sum = 1 + 2
    a, b = 1, 2
    1 > 2 ? true : false; puts 'Hi'
    [1, 2, 3].each { |e| puts e }
     
    e = M * c**2
     
    # No spaces after (, [ or before ], ).
    some(arg).other
    [1, 2, 3].length
  • 空行
    def some_method
      data = initialize(options)
     
      data.manipulate!
     
      data.result
    end
     
    def some_method
      result
    end
  • 过长
    # Limit lines to 80 characters.
    def send_mail(source)
      Mailer.deliver(to: 'bob@example.com',
                     from: 'us@example.com',
                     subject: 'Important message',
                     body: source.text)
    end
     
    # good
    num = 1_000_000

语法 syntax

def

  • def (with parentheses when there are arguments. Omit the parentheses when the method doesn't accept any arguments)
     def some_method
       # body omitted
     end
     
     def some_method_with_arguments(arg1, arg2)
       # body omitted
     end

判断

  • if
    # good
    if some_condition
      # body omitted
    end
     
    # Don't use parentheses around the condition of an if/unless/while, unless the **condition contains an assignment**
    if (x = self.next_value)
      # body omitted
    end
    # but it's still bad. Don't use the return value of = (an assignment) in conditional expressions.
    x = self.next_value
    if x
      # body omitted
    end
  • ternary operator(?:), Use one expression per branch in a ternary operator, inline only
    result = some_condition ? something : something_else
     
    if some_condition
      nested_condition ? nested_something : nested_something_else
    else
      something_else
    end
  • inline if (?: without :)
    # good
    do_something if some_condition
     
    # another good option
    some_condition and do_something
  • Favor unless over if for negative conditions (or control flow or). And always positive case first(Never use unless with else).
    # good
    do_something unless some_condition
     
    # another good option
    some_condition or do_something
  • case .. when .. else .. end
    case
    when song.name == 'Misty'
      puts 'Not again!'
    when song.duration > 120
      puts 'Too long!'
    when Time.now.hour > 21
      puts "It's too late"
    else
      song.play
    end
     
    kind = case year
           when 1850..1889 then 'Blues'
           when 1890..1909 then 'Ragtime'
           when 1910..1929 then 'New Orleans Jazz'
           when 1930..1939 then 'Swing'
           when 1940..1950 then 'Bebop'
           else 'Jazz'
           end

循环

  • each (不要用 for 1))
    arr = [1, 2, 3]
     
    # good
    arr.each { |elem| puts elem }
  • loop .. break
    loop do
      puts val
      val += 1
      break unless val < 0
    end
  • inline while/until
    do_something while some_condition
     
    do_something until some_condition

methods

  • Omit parentheses around parameters for methods that are part of an internal DSL (e.g. Rake, Rails, RSpec), methods that have “keyword” status in Ruby (e.g. attr_reader, puts) and attribute access methods. Use parentheses around the arguments of all other method invocations. FIXME
    class Person
      attr_reader :name, :age
     
      # omitted
    end
     
    temperance = Person.new('Temperance', 30)
    temperance.name
     
    puts temperance.age
     
    x = Math.sin(y)
    array.delete(e)
  • Avoid return where not required for flow of control.
    # good
    def some_method(some_arr)
      some_arr.size # no return
    end
  • If the first argument to a method begins with an open parenthesis, always use parentheses in the method invocation. For example, write f((3 + 2) + 1).
  • Avoid self where not required. (It is only required when calling a self write accessor.)
    # good
    def ready?
      if last_reviewed_at > last_updated_at # self omitted
        worker.update(content, options)
        self.status = :in_progress
      end
      status == :verified
    end
  • As a corollary, avoid shadowing methods with local variables unless they are both equivalent. :?:
    class Foo
      attr_accessor :options
     
      # ok
      def initialize(options)
        self.options = options
        # both options and self.options are equivalent here
      end
     
      # bad
      def do_something(options = {})
        unless options[:when] == :later
          output(self.options[:message])
        end
      end
     
      # good
      def do_something(params = {})
        unless params[:when] == :later
          output(options[:message])
        end
      end
    end
  • Use ||= freely to initialize variables, but don't use ||= to initialize boolean variables. (Consider what would happen if the current value happened to be false.)
    # set name to Bozhidar, only if it's nil or false
    name ||= 'Bozhidar'
     
    # bad - would set enabled to true even if it was false
    enabled ||= true
     
    # good
    enabled = true if enabled.nil?
  • lambda
    # good
    lambda = ->(a, b) { a + b }
    lambda.(1, 2)

block

  • { … }for single-line blocks.
    names = ['Bozhidar', 'Steve', 'Sarah']
     
    # good
    names.each { |name| puts name }
     
     
    # good
    names.select { |name| name.start_with?('S') }.map { |name| name.upcase }
  • do…end for multi-line blocks.
  • Use _ for unused block parameters.
    # good
    result = hash.map { |_, v| v + 1 }

异常处理

  • Signal exceptions using the fail method. Use raise only when catching an exception and re-raising it (because here you're not failing, but explicitly and purposefully raising an exception).
    begin
      fail 'Oops';
    rescue => error
      raise if error.message != 'Oops'
    end

Classes

详见文档, 这里记几条容易忘的规则:

  • Always supply a proper to_s method for classes that represent domain objects.
  • Consider using Struct.new, which defines the trivial accessors, constructor and comparison operators for you.
  • Consider adding factory methods to provide additional sensible ways to create instances of a particular class.
  • Prefer duck-typing over inheritance.
  • Assign proper visibility levels to methods (private, protected) in accordance with their intended usage. Don't go off leaving everything public (which is the default).

flavors 语言特性

  • 完全 OO
  • 判断和循环都能单行写
    > puts 'This appears to be true.' if x == 4
    This appears to be true.
     => nil
     
    > x = x + 1 while x < 10
     => nil
    > x
     => 10

regexp

recipes

  • 替换标签
'[BOLD]Strategy[\BOLD]'.gsub(/\[BOLD\](.*?)\[\\BOLD\]/, '<em>\\1</em>'  #=> <em>Strategy</em>

glossary

  • rvm: ruby version manager
  • gem: a package manager

gem

功能命令
查看当前源gem source
删除源gem source -r http://rubygems.org/
增加源gem source -a http://ruby.taobao.org
Installationgem install mygem
Uninstallationgem uninstall mygem
Listing installed gemsgem list - -local
Listing available gemsgem list - -remote
Create RDoc documentation for all gemsgem rdoc - -all
Download but do not install a gemgem fetch mygem
Search available gemsgem search STRING - -remote

web

json

Ruby 自 1.9 起加入了 json, 之前需要额外安装(apt-get install ruby-json). Ruby 更推荐的格式是 yaml. 而 php 需装扩展才能支持 yaml.

# encode
data = { name: 'dave', address: [ 'tx', 'usa' ], age: 17 } serialized = data.to_json
serialized # => {"name":"dave","address":["tx","usa"],"age":17}
File.open("data", "w") {|f| f.puts serialized}
 
# decode
serialized = File.read("data")
data = JSON.parse(serialized)
data # => {"name"=>"dave", "address"=>["tx", "usa"], "age"=>17}
 
# j 和 jj 分别是普通和美化输出 json 的方法 

sysadmin

Command Expansion 运行操作系统命令 exec

可使用 `%x( FIXME 区别?):

`date`                  # => "Mon Apr 13 13:25:58 CDT 2009\n"
`ls`.split[34]          # => "ext_c_win32ole.tip"
%x{echo "Hello there"}  # => "Hello there\n"
1)
for doesn't introduce a new scope (unlike each) and variables defined in its block will be visible outside it
it/ruby.txt · Last modified: 2013/08/19 07:22 (external edit)