Json解析成对象,将属性动态定义方法

module ActsAsField
  def self.included(base_class)
    base_class.instance_variable_set(:@all_fields, [])
    base_class.include Instance_Methods
    base_class.extend Class_Methods
  end

  module Class_Methods
    # 声明变量信息,并自动生成实例方法
    def field(name, path)
      instance_variable_get(:@all_fields) << name.to_sym
      define_method(name.to_sym) do
        case path
        when String
          data.dig(*path.split('.').map(&:to_sym))
        when Proc
          path.call(self)
        end
      end
    end

    def all_fields
      instance_variable_get :@all_fields
    end
  end

  module Instance_Methods
  end
end

class Device
  include ActsAsField

  field :name, 'name'
  field :age, 'age'
  field :work_city, 'deep_info.work_city'
  field :log, lambda { |source|
    "#{source.name} - #{source.age}"
  }

  def data
    # 也可以将data通过外部传入,在initialize时声明
    {
      name: 'lucas',
      age: 24,
      sex: :male,
      deep_info: {
        work_city: :sh
      }
    }
  end
end

device = Device.new
p device.name
p device.age
p device.work_city
p device.log

p Device.all_fields

模拟 ActiveSupport::Concern

module MyConcern
  def self.extended(base)
    # 给具体的Concern设置一个类实例变量,来存储对应的依赖
    base.instance_variable_set(:@dependents, [])
  end

  # 重写 Module#append_features 方法
  # 在A模块儿被B include 时,Ruby会自动执行 A#append_features, 并传入B
  # @param base [Module] include模块儿的类.
  def append_features(base)
    if base.instance_variable_defined?(:@dependents)
      # 如果定义过说明 base 本身是一个Concern,自己是其中的一个依赖项
      base.instance_variable_get(:@dependents) << self
      # 不现在进行添加
      return false
    else
      # 如果 base 是当前类的子类,则不添加,因为已经被继承了
      return false if base < self

      @dependents.each { |dep| base.include dep }
      super
      base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
    end

    base.class_exec(&@include_block) if instance_variable_defined?(:@include_block)
  end

  def includes(&block)
    @include_block = block
  end
end

优化第一个实例中的代码

module MyConcern
  def self.extended(base)
    # 给具体的Concern设置一个类实例变量,来存储对应的依赖
    base.instance_variable_set(:@dependents, [])
  end

  def append_features(base)
    if base.instance_variable_defined?(:@dependents)
      # 如果定义过说明 base 本身是一个Concern,自己是其中的一个依赖项
      base.instance_variable_get(:@dependents) << self
      # 不现在进行添加
      return false
    else
      # 如果 base 是当前类的子类,则不添加,因为已经被继承了
      return false if base < self

      @dependents.each { |dep| base.include dep }
      super
      base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
    end

    base.class_exec(&@include_block) if instance_variable_defined?(:@include_block)
  end

  def includes(&block)
    @include_block = block
  end
end

module ActsAsField
  extend MyConcern

  includes do
    instance_variable_set(:@all_fields, [])
  end

  module ClassMethods
    # 声明变量信息,并自动生成实例方法
    def field(name, path)
      instance_variable_get(:@all_fields) << name.to_sym
      define_method(name.to_sym) do
        case path
        when String
          data.dig(*path.split('.').map(&:to_sym))
        when Proc
          path.call(self)
        end
      end
    end

    def all_fields
      instance_variable_get :@all_fields
    end
  end
end

class Device
  include ActsAsField

  field :name, 'name'
  field :age, 'age'
  field :work_city, 'deep_info.work_city'
  field :log, lambda { |source|
    "#{source.name} - #{source.age}"
  }

  def data
    # 也可以将data通过外部传入,在initialize时声明
    {
      name: 'lucas',
      age: 24,
      sex: :male,
      deep_info: {
        work_city: :sh
      }
    }
  end
end

device = Device.new
p device.name
p device.age
p device.work_city
p device.log

p Device.all_fields

灵活构造SQL查询

class Element
  attr_accessor :name, :operator, :value

  def initialize(name, operator, value)
    @name = name
    @operator = operator
    @value = value
  end

  def to_sql
    case operator
    when '='
      ["#{name} = ?", value]
    when '!='
      ["#{name} != ?", value]
    when '>'
      ["#{name} > ?", value]
    when '<'
      ["#{name} < ?", value]
    when '>='
      ["#{name} >= ?", value]
    when '<='
      ["#{name} <= ?", value]
    when 'like'
      ["#{name} like ?", value]
    when 'not like'
      ["#{name} not like ?", value]
    when 'in'
      ["#{name} in (?)", value]
    when 'not in'
      ["#{name} not in (?)", value]
    else
      raise ['Unkown operator', operator].join(': ')
    end
  end
end

class Elements
  attr_accessor :childrens, :operator

  def initialize(operator)
    @operator = operator
    @childrens = []
  end

  def all_of
    element = Elements.new('and')
    yield element
    @childrens << element
  end

  def any_of
    element = Elements.new('or')
    yield element
    @childrens << element
  end

  def field(name, operator, value)
    element = Element.new(name,  operator, value)
    @childrens << element
  end

  def to_sql
    sql = []
    params = []
    @childrens.each do |e|
      r = e.to_sql
      r << '0 = 1' if r.size.zero?
      sql << r[0]
      params += r[1, r.size]
    end
    # 增减判断防止无条件时生成空括号
    params.unshift('(' + sql.join(" #{operator.upcase} ") + ')') unless sql.empty?
    params
  end
end


conditions = Elements.new('and')

# p "condition: #{conditions.to_sql}"

# conditions.all_of do |e|
#   e.field('name', '=', '张三')
#   e.field('age', '>', '18')
# end

# p "condition: #{conditions.to_sql}"

# conditions.any_of do |e|
#   e.field('name', '=', '张三')
#   e.field('age', '>', '18')
# end

# p "condition: #{conditions.to_sql}"

# conditions.field('name', '=', '张三')

# p "condition: #{conditions.to_sql}"

# conditions.all_of do |c|
#   c.any_of do |e|
#     e.field('name', '=', '张三')
#     e.field('age', '>', '18')
#   end
#   c.field('name', '=', '张三')
# end
# p "condition: #{conditions.to_sql}"

组织功能模块儿,灵活自定义扩展

# 模型
class Product
end

# 搜索框架
module Search
  # 定义类方法,对外提供入口
  def self.define(model, &block)
    # constantize 是 ActiveSupport::Inflector 中定义的
    # klass = model.to_s.constantize
    klass = model
    # 传入自己可能自定义的方法
    klass.instance_eval(&block)
    # 扩展类通用方法
    klass.extend ClassMethos
  end

  # 通用方法
  module ClassMethos
    def search
      p 'I am searching...'
    end

    def sort
      p 'I am sort...'
    end
  end
end

# 进行注册,这时候Product就拥有了Search中定义的类方法
Search.define Product do
  # 传入一个block,可以自己定义如何操作内容
  def sort
    p 'I am not sort'
  end
end

Product.search
Product.sort

这里其实更简单的方式是,定义通用Module,让别的类模块儿直接include,如果需要定义直接重写方法也能用。理解成本也低。

一定要慎用元编程,否则容易导致理解成本太高的问题

父类定义钩子,子类实现,父类调用

# 模拟Sideque::Worker,面向接口学习

require 'active_support/all'
module Worker
  extend ActiveSupport::Concern

  class_methods do
    def perform_later
      new.perform
    end
  end
end

class UserWorker
  include Worker

  def perform
    p "I want do some thing"
  end
end

UserWorker.perform_later

扩展缓存机制

# 学习通过DSL来优雅的进行扩展实现,这里实现更新,删除缓存的功能

require 'active_support/all'

# 使用DSL进行灵活扩展
module CacheExt
  extend ActiveSupport::Concern

  class_methods do
    def cache
      yield
    end

    def refresh(callback_name)
      define_method :_refresh_cache do
        p '刷新缓存。。。'
        yield(self) if block_given?
      end

      send callback_name, :_refresh_cache

      def delete(callback_name)
        define_method :_delete_cache do
          p '清除缓存。。。'
          yield(self) if block_given?
        end

        send callback_name, :_delete_cache
      end
    end
  end
end

class Product
  include CacheExt

  cache do
    refresh :after_save do |_record|
      p '保存后我刷新缓存'
    end

    delete :after_delete do |_record|
      p '删除后我刷新缓存'
    end
  end
end