7个你应该掌握的Ruby标准库

243 阅读3分钟

Ruby在其标准库中有很多有用的东西。

学习其中的内容以及如何使用它是非常有帮助的,特别是在编写小脚本时,你不想拉着bundler开始制作Gemfile等等。

这些库很强大,由核心团队维护,应该是非常可靠的,比你遇到的大多数Ruby gem都要长。

1.Net/HTTP和open-uri

我想从我建立的大多数应用程序中需要的东西开始,那就是执行HTTP请求的能力。

在过去,我选择使用HTTP.rbgem,但对于简单的任务,学习Net/http甚至open-uri对于简单的GET请求真的很有用。

require "net/http"
require "json"

# Net/HTTP GET request
uri = URI("https://swapi.dev/api/films/1/")
resp = Net::HTTP.get(uri)
json = JSON.parse(resp)
puts json["title"] # => "A New Hope"

# Net/HTTP POST request
uri = URI("https://example.org/users")
params = {
  "name" => "Bob",
  "email" => "bob@example.org",
}
res = Net::HTTP.post_form(uri, params)
puts res.body

# Net/HTTP Full form: delete/put/patch requests
uri = URI("http://example.org/some_path")

Net::HTTP.start(uri.host, uri.port) do |http|
  request = Net::HTTP::Delete.new uri
  # or request = Net::HTTP::Patch.new uri
  # or request = Net::HTTP::Put.new uri

  response = http.request(request) # Net::HTTPResponse object
  puts response.body
end

如果你只需要一个GET请求,用open-uri ,事情就更简单了。

require "open-uri"
require "json"

URI.open("https://swapi.dev/api/films/1/") do |io|
  json = JSON.parse(io.read)
  puts json["title"] # => "A New Hope"
end

# or
uri = URI("https://swapi.dev/api/films/1/")
puts uri.open.read

2.CSV

当我创建一个管理部分时,我经常需要输出某种形式的报告来衡量一个应用程序的表现。Ruby内置了这个功能,要把一些数据放到CSV中是非常简单的。

require "csv"

module CustomerExport
  module_function

  def generate(customers)
    CSV.generate do |csv|
      # Define the headers
      csv << %w[id name email country_code created_at updated_at]

      # Add a new row for each customer record
      customers.each do |customer|
        csv <<
          [
            customer.id,
            customer.name,
            customer.email,
            customer.country_code,
            customer.created_at,
            customer.updated_at
          ]
      end
    end
  end
end

CustomerExport.generate(Customer.all)

如果你想做一个Rails路线来下载这个...

module Admin
  class ExportsController < AdminBaseController
    def customers
      customers = Customer.all
      respond_to do |format|
        format.csv do
          filename = "customers-#{Time.zone.now.strftime("%Y-%m-%d")}"
          filename += ".csv"
          headers["Content-Type"] ||= "text/csv"
          headers["Content-Disposition"] =
            "attachment; filename=\"#{filename}\""
          render plain: CustomerExport.generate(customers)
        end
      end
    rescue => e
      flash[:error] = "Failed to generate export: #{e.message}"
      redirect_to admin_path
    end
  end
end

解析CSV(例如:从用户那里导入CSV文件)也是内置的,你可以阅读文档了解更多。

3.SimpleDelegator

我喜欢使用SimpleDelegator来为ActiveRecord对象添加额外的行为,例如将Stripe的特定代码包装成StripeWrappedUser 或类似的东西,这样可以避免在ActiveRecord对象上放置大量的方法,但可以用额外的功能来增强该类。

require "delegate"
require "digest"

class User < Struct.new(:id, :email)
end

class BillingUser < SimpleDelegator
  def billing_system_id
    Digest::SHA1.hexdigest(id.to_s + email)
  end
end

bob = User.new(1, "bob@example.com")
bob = BillingUser.new(bob)
puts bob.email # => bob@example.com
puts bob.billing_system_id # => 186da93f0b39990e034a80cc4b45c8ec253f2a1a

4.Struct和OpenStruct

Struct包含在Ruby核心中,它不需要,而且是这两个对象中更严格的一个,而且性能也更强。

我发现它对两件事特别有用。

  1. 作为一个值对象,给一些数据片断起一个名字,否则就是一个Hash。
  2. 在测试中,可以创建模拟对象,用来表示假的东西,比如FakeUser。
User = Struct.new(:id, :email)
alice = User.new(1, "alice@example.org")
puts alice.id # => 1
puts alice.email # => alice@example.org

# Can also be defined with additional methods
class User < Struct.new(:id, :email)
  def send_welcome_email
    UserMailer.welcome(self).deliver_later
  end
end

同时,OpenStruct更加灵活,可以作为Hash或来自API的JSON的一个整洁的包装。

require "ostruct"

data = OpenStruct.new(id: 5, name: "Jeffrey")

# New attributes can be set at runtime:
data.email = "jeff@example.org"

puts data.id # => 5
puts data.name # => "Jeffrey"
puts data.email # => "jeff@example.org"

5.PStore

PStore是一个非常简单的键值数据存储,当你需要在一个非生产系统中存储数据的地方时,PStore会很有帮助(也就是说,这不是ActiveRecord的好替代品)。

require "pstore"

store = PStore.new("my_db.pstore")

# Write some data
store.transaction do
  store["person-1"] = { name: "Bob", email: "bob@example.org" }
end

# Read some data
store.transaction(true) do
  puts store["person-1"] # => {:name=>"Bob", :email=>"bob@example.org"}
end

6.Minitest

在处理较小的脚本时,我喜欢拉入一个测试框架,在测试驱动一些代码时做一些断言。

在使用Rails时,我通常更喜欢RSpec,但它比Minitest更费力,因为它包含在Ruby中。

Minitest的好处是,它只是Ruby--没有什么神奇的事情发生,而且非常容易理解。

require "minitest/test"
require "minitest/autorun"

class APIResponse < Struct.new(:body, :error)
  def success?
    error.nil?
  end
end

class APIResponseTest < Minitest::Test
  def test_successful_responses
    resp = APIResponse.new("Well done!", nil)
    assert resp.success?
  end

  def test_error_responses
    resp = APIResponse.new(nil, StandardError.new("400 bad request"))
    refute resp.success?
  end
end

# Running:
# ..
# Finished in 0.000957s, 2089.8640 runs/s, 2089.8640 assertions/s.
# 2 runs, 2 assertions, 0 failures, 0 errors, 0 skips

7.记录器

使用logger可以在编写脚本时清理大量的调试输出,然后你可以控制日志级别来显示更多/更少的输出。注意:在Rails中使用Rails.logger

require "logger"

logger = Logger.new(STDOUT)

# Setting :warn will only show warning, error and fatal.
logger.level = :warn

# Can tweak log output to be less verbose
logger.formatter = proc do |severity, datetime, progname, msg|
  "#{severity[0...1]} #{datetime.strftime('%H:%M %b %d %Y')}: #{msg}\n"
end

logger.debug("Created logger") # wont be displayed in current log level
logger.info("Program started") # wont be displayed in current log level
logger.warn("Nothing to do!")
logger.error("Something really bad!")

# =>
# W 14:56 Oct 22 2021: Nothing to do!
# E 14:56 Oct 22 2021: Something really bad!

Ruby标准库中还有很多需要熟悉的东西,但这7个是我一次又一次回来的东西。