CodeShow

Ruby

raise_error

class Response
  def initialize(code = Code::SUCCESS, message = '', messages = [])
    @code = code
    @message = message
    @messages = messages
    @uuid = RequestStore.store[:request]&.uuid || SecureRandom.uuid
  end
  
  # 幽灵函数
  def method_missing(method_id, *arguments, &block)
    method_message = *arguments.join
    if (method_id.to_s =~ /^raise_[\w]+/) == 0
      error_type = method_id.to_s.split('raise_')[1].upcase!
      @code = "Response::Code::#{error_type}".constantize
      @message = method_message
      error_messages = arguments.first
      @messages = error_messages if error_messages.is_a?(Array)

      yield if block_given?

      raise StandardError.new(method_message)
    else
      super
    end
  end

  def self.rescue(catch_block = nil)
    response = self.new
    begin
      yield(response)
    rescue AtyunError => e
      log(e)
      catch_block.call if catch_block.present?
      response.code = Code::CUSTOM_ERROR
      response.message = e.message
    rescue => e
      log(e)
      catch_block.call if catch_block.present?
      response.code = Code::ERROR
      response.message = e.message
    end

    response
  end
end

rescue_error

5.times do |i|
  if_2_raise_error rescue StandardError and next
  log "run #{i}"
end

catch_error

def io
  yield
rescue TimeoutError => e1
  # Add a message to the exception without destroying the original stack
  e2 = TimeoutError.new("Connection timed out")
  e2.set_backtrace(e1.backtrace)
  raise e2
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL => e
  raise ConnectionError, "Connection lost (%s)" % [e.class.name.split("::").last]
end

def read
  io do
    value = connection.read
    @pending_reads -= 1
    value
  end
end

def write(command)
  io do
    @pending_reads += 1
    connection.write(command)
  end
end

默认参数选项

def pop(timeout = 0.5, options = {})
  options, timeout = timeout, 0.5 if Hash === timeout
  timeout = options.fetch :timeout, timeout
  ...
end

循环

# until
do_something until true_condition
# Example:
#		a = 0; b = []; b << a+=1 until (a == 3) 	#	b => [1 2 3]
  
# next
arr = ["A","2B","3C"]
arr = arr.each_with_object(Array.new) do |e, r|
  next r if (e.length == 1) && (r << e)
  next r if is_B_end? && (r << e.slice(-1))
end
# arr => ["A","B"]

循环缓存

def current_trade(id)
  return @trade_cache[id] if @trade_cache[id].present?
  @trade_cache = {}
  @trade_cache = trades.find { |t| t.id == id }

  @trade_cache
end

测量消耗

require 'benchmark'

def measure_gc_times
  yield && (return puts("GC: disabled")) if (ARGV[0] == "--no-gc")

  GC.start(full_mark: true, immediate_sweep: true, immediate_mark: false)
  c0 = GC.stat[:count]
  yield
  c1 = GC.stat[:count]

  puts "GC: #{c1 - c0} 次"
end

def measure_new_objects
  c0 = ObjectSpace.count_objects[:T_OBJECT]
  yield
  c1 = ObjectSpace.count_objects[:T_OBJECT]

  puts "new_object: #{c1 - c0}"
end

def measure_memory_usage
  # m0 = `ps -o rss= -p #{Process.pid}`.to_i
  m0 = `ps ax -o pid,rss | grep -E "^[[:space:]]*#{$$}"`.strip.split.map(&:to_i)[1]
  yield
  # m1 = `ps -o rss= -p #{Process.pid}`.to_i
  m1 = `ps ax -o pid,rss | grep -E "^[[:space:]]*#{$$}"`.strip.split.map(&:to_i)[1]

  puts("Memory: #{m1 - m0} KB")
end

def measure_time_spend
  time = Benchmark.realtime do
    yield
  end

  puts "Time: #{time.round(4)}"
end
def measure(redo_times = 1)
  measure_gc_times do
    measure_new_objects do
      measure_memory_usage do
        measure_time_spend do
          yield until (redo_times -= 1).negative?
        end
      end
    end
  end
end
  
measure(5) do
  # do something...
end
# => Time: 5.9761
# => Memory: 16684 KB
# => new_object: 6389
# => GC: 1 次

事件回调

#
# Callbacks
#

# 定义事件
EVENTS = [
  :row_success, :row_error, :row_processing, :row_skipped, :row_processed,
  :import_started, :import_finished, :import_failed, :import_aborted,
]

# sidekiq 中的事件直接保存为 hash-value 存方法,
# 这里 value 存数组,可以在一个事件上执行多个方法
def self.event_handlers
  @event_handlers ||= EVENTS.inject({}) { |hash, event| hash.merge({event => []}) }
end

# 挂载
def self.on(event, &block)
  raise "Unknown ActiveImporter event '#{event}'" unless EVENTS.include?(event)
  event_handlers[event] << block
end

# 触发事件
# 感觉这里的实例方法应该跟类方法分开,只让其中一个触发事件
# 注意这里传入的 param ,便于之后扩展
def fire_event(event, param = nil)
  self.class.send(:fire_event, self, event, param)
  unless self.class == ActiveImporter::Base
    self.class.superclass.send(:fire_event, self, event, param)
  end
end

def self.fire_event(instance, event, param = nil)
  event_handlers[event].each do |block|
    # instance_exec 执行事件代码
    # 在 sidekiq 中是 Proc 实例调用 call() 方法
    # block 也是一个 Proc 实例,也可以调用 call() 方法
    # 这里的 instance_exec() 也是一个 call() 相关的封装方法
    instance.instance_exec(param, &block)
  end
end

# 私有化
private :fire_event

class << self
  private :fire_event
end

# 执行方法
def import
  # 通过事务开启状态调用导入 module 的事务
  transaction do
    return if @book.nil?
    # 开始事件
    fire_event :import_started
    @data_row_indices.each do |index|
      @row_index = index
      @row = row_to_hash @book.row(index)
      if skip_row?
        # 跳过行事件
        fire_event :row_skipped
        next
      end
      import_row
      if aborted?
        # 放弃事件
        fire_event :import_aborted, @abort_message
        break
      end
    end
  end
rescue => e
  # 放弃事件
  fire_event :import_aborted, e.message
  raise
ensure
  # 结束事件
  fire_event :import_finished
end

def import_row
  begin
		# 保存记录
		# 这里可以扩展一下,读取数据并做自定义操作
    @model = fetch_model
    build_model
    save_model unless aborted?
  rescue => e
    @row_errors << { row_index: row_index, error_message: e.message }
    # 失败回调
    fire_event :row_error, e
    raise if transactional?
    return false
  end
  # 行保存完成回调
  fire_event :row_success
  true
ensure
  # 执行完回调
  fire_event :row_processed
end

随机等待重试

def retries(times)
  result = nil
  times.times do |i|
    yield result if block_given?

    sleep_a_while i
  end

  result
end
def sleep_a_while(i)
  # https://www.geogebra.org/graphing?lang=zh_CN
  # v1
  # f(x)=0.1 (0.3 (x-9)-3)^(2) (0.3 (x-9)+3)^(2)
  # x = i - rand
  # y = 0.1 * ((0.3 * (x - 9) - 3) ** 2) * ((0.3 * (x - 9) + 3) ** 2)

  # p "sleep #{y} seconds..."
  # sleep y
  
  # todo RangeError (NaN out of Time range)
  # v2
  # f(x)=log(1.4,x)+(0.2)/(x)
  # g(x)=log(1.6,x)+(0)/(x)
  c = 1.4 + (rand / 5)
  x = i
  y = Math.log(x, c) + (c / x)

  now = Time.now
	p "sleep #{y} seconds..."
  sleep y
  p "#{Time.now - now}"
end

向量化

i = 0
while i < 8 do
  C[i] = A[i] + B[i]
  i += 1
end
i = 0
while i < 8 do
  C[i] = A[i] + B[i]
  C[i+1] = A[i+1] + B[i+1]
  C[i+2] = A[i+2] + B[i+2]
  C[i+3] = A[i+3] + B[i+3]
  i += 4
end

Rails

懒事务

#
# Transactions
#
def self.transactional(flag = true)
  if flag
    raise "Model class does not support transactions" unless @model_class.respond_to?(:transaction)
  end
  @transactional = !!flag
end

def self.transactional?
  @transactional || false
end

def transactional?
  @transactional || self.class.transactional?
end

def transaction
  if transactional?
    model_class.transaction { yield }
  else
    yield
  end
end

private :transaction

软删除

module SoftDelete
  extend ActiveSupport::Concern

  def self.included(base)
    extended(base)
  end

  def self.extended(base)
    base.send :default_scope, -> { where(deleted_at: nil) }
  end

  def delete
    update(deleted_at: Time.now)
  end

  def delete!
    update!(deleted_at: Time.now)
  end

  def recovery
    update(deleted_at: nil)
  end

  def recovery!
    update!(deleted_at: nil)
  end

  def real_del
    ActiveRecord::Base.connection.execute("DELETE FROM `#{self.class.name.tableize}` WHERE `cooperations`.`id` = #{id}")
  end

end

大表添加索引

-- 假设需要添加索引的表为`fea_moni_res`
-- 1. 新建与表`fea_moni_res`同结构的表
CREATE TABLE fea_moni_res_tmp LIKE fea_moni_res;

-- 2. 新表上添加索引
ALTER TABLE fea_moni_res_tmp ADD INDEX idx_index_name (col_name);

-- 3. *rename*新表为原表的表名,原表换新的名称
RENAME TABLE fea_moni_res TO fea_moni_res_1, fea_moni_res_tmp TO fea_moni_res;

-- 4. 为原表新增索引,此步耗时较长
ALTER TABLE fea_moni_res_1 ADD INDEX idx_index_name (col_name);

-- 5. 待索引创建成功后,rename原表为原来的名称,并将新表里的数据导入到原表中
RENAME TABLE fea_moni_res TO fea_moni_res_tmp, fea_moni_res_1 TO fea_moni_res;
-- 需要根据业务来确定如果导入数据
INSERT INTO fea_moni_res(col_name1, col_name2) SELECT col_name1, col_name2 FROM fea_moni_res_tmp;

batch_update

先记一个bug

# obj.tap {|x| block }    -> obj
# 
# Yields self to the block, and then returns self.
# The primary purpose of this method is to "tap into" a method chain,
# in order to perform operations on intermediate results within the chain.
# 
#    (1..10)                  .tap {|x| puts "original: #{x}" }
#      .to_a                  .tap {|x| puts "array:    #{x}" }
#      .select {|x| x.even? } .tap {|x| puts "evens:    #{x}" }
#      .map {|x| x*x }        .tap {|x| puts "squares:  #{x}" }
def tap
  # This is a stub implementation, used for type inference (actual method behavior may differ)
  yield self; self
end

tap会改变self

attrs = self.attribute_names.tap { |e| e.delete self.primary_key }
# => 会改变 self 中的 @attribute_names 值,应该使用拷贝值删减
attrs = self.attribute_names.dup.tap { |e| e.delete self.primary_key }

一定要注意对底层属性的更改操作,最好使用拷贝对象

def base_update(params, allow_nil = false)
  transaction do
    ids = params.require(:ids)
    attrs = check_nil(params, allow_nil).deep_symbolize_keys

    self.where(id: ids).update_all(attrs)
  end
end

# Return params with current <tt>ApplicationRecord</tt> attribute_names(except primary key).
# if allow_nil is +true+ ,passes blank value in params
def check_nil(params, allow_nil = false)
  params = permit_attr!(params, primary_key)
  params.reject { |_k, v| v.blank? } unless allow_nil == true

  params
end
# Permit Record

# Return +params+ instance that include only record attributes
# and except given +args+ , no attributes permitted return {}
def permit_attr(params, *args)
  permit_attr!(params, args) rescue nil
end

# Performance as +permit_attr+, but raise error while illegal +params+
def permit_attr!(params, *args)
  params.permit(attribute_names - args.map(&:to_s))
end

关联替换

有Record如下:

class AttachmentGroup < ApplicationRecord
  has_many :attachments, dependent: :destroy
  
  ...
end

其实例方法替换关联对象:

def replace_atch(files, &_block)
  return [] if files.nil?
  # deal files to be replace
  temp_atchs = files.inject(Array.new) do |r, file|
    atch_params = Attachment.check_file(file)
    r << Attachment.find_or_initialize_by(atch_params) do |a|
      yield(a) if block_given?
      a.attachment_group_id = self.id
    end
  end

  # return replaced records
  proxy = self.attachments
  old_atchs = proxy.load_target.dup
  new_atchs = proxy.replace(temp_atchs)

  old_atchs - new_atchs
end

多数据源

module OtherDB
  class BaseModel < ActiveRecord::Base
    self.abstract_class = true
    establish_connection "other_#{Rails.env}".to_sym
  end
end
module OtherDB
  class SomeTable < BaseModel
    # ...
  end
end

间隔下载

Rails.cache.fetch("download_interval", expires_in: 20.minutes) do
  # do download
  return render_success
end

render_fail('每次下载文件需要间隔20分钟!')

机器人指令

class Robot
  module Sign
    LEFT = :left
    RIGHT = :right
    GO = :go
    BACK = :back
  end
  DIRECTION = ['N','E','S','W']
  DefualtOptions = {dist: 1}
  
  def initialize(signs)
    @direction = 0;
    @x, @y = 0, 0
    @signs = signs
    
    @options = DefualtOptions
  end
  
  def parse(opt = {})
    @options.merge!(opt)
  end
  
  def run
    return if @signs.length <= 0
    p 'star running'
    @signs.delete_if { |e| fire(e) }
  end
  
  def add_sign(sign)
    @signs << sign
    
    self
  end

  def where_am_i
    puts "My location is: (#{@x},#{@y})"
  end
  
  def what_is_my_direction
    puts "My direction is #{DIRECTION[@direction]}"
  end
  
  def my_signs
    puts "My signs: #{@signs}"
  end
  
  private
  
  def fire(event)
    send(event)
  end
  
  def left
    @direction = (@direction + 4 - 1) % 4
  end
  
  def right
    @direction = (@direction + 4 + 1) % 4
  end
  
  def go
    move()
  end
  
  def back
    move(flag: false)
  end
  
  def move(flag: true)
    dist = @options[:dist]
    union = dist * (flag ? 1 : -1)
    
    case DIRECTION[@direction]
    when 'N'
      @y += union
    when 'E'
      @x += union
    when 'S'
      @y -= union
    when 'W'
      @x -= union
    else
      raise StandardError,'no support command'
    end
    
  end
end

signs = [:back, :back, :right]
r = Robot.new(signs)
r.add_sign(Robot::Sign::GO).add_sign(Robot::Sign::GO)
# r.parse(dist: 2)

r.my_signs
r.what_is_my_direction
r.where_am_i
r.run
r.where_am_i
r.what_is_my_direction
r.my_signs

最后更新于