Ruby Hacker

Happy Hacker

Created by Bachue / @iBachue

Why are rubyists happy?

DSL

DRY

Metaprogramming

“Metaprogramming is writing code that writes code.”

Metaproramming Show

Let's start Hacking

Elegant Inquery

Rails.env


Rails.env == "production"
            

Rails.env


Rails.env.production?
            

How to Implement?

method_missing


class StringInquirer < String
  private def method_missing method_name, *arguments
    if method_name[-1] == '?'
      self == method_name[0..-2]
    else
      super
    end
  end
end
            

respond_to_missing?


class StringInquirer < String
  private def respond_to_missing? method_name, include_private = false
    method_name[-1] == '?'
  end
end

Rails.env.respond_to? :development? # => true
            

Elegant Time

Elegant Time


1.day.ago # => 2014-08-11 22:41:47 +0800
1.day - 2.hours # => 22 hours
            

How to Implement?


class Numeric
  def seconds
    self
  end
  alias second seconds

  def minutes
    self * 60
  end
  alias minute minutes

  def hours
    self * 60.minutes
  end
  alias hour hours

  def days
    self * 24.hours
  end
  alias day days

  def ago
    Time.now - self
  end
end
            

BlankSlate


class BlankSlate < BasicObject
  undef ==
  undef equal?
end
              

Proxy


class Duration < BlankSlate
  def initialize proxy
    @proxy = proxy
  end

  def + other
    ::Duration.new @proxy + other
  end

  def - other
    ::Duration.new @proxy - other
  end

  def -@
    ::Duration.new -@proxy
  end

  def ago
    ::Time.now - @proxy
  end

  private def method_missing method, *args, &block
    @proxy.send method, *args, &block
  end
end
            

Numeric


class Numeric
  def seconds
    Duration.new self
  end
  alias second seconds

  def minutes
    Duration.new self * 60.seconds
  end
  alias minute minutes

  def hours
    Duration.new self * 60.minutes
  end
  alias hour hours

  def days
    Duration.new self * 24.hours
  end
  alias day days
end
            

Question?

How to avoid 1.hour.hour?

inspect


class Duration
  def inspect
    value = @proxy
    parts = %w[days hours minutes seconds].inject([]) do |units, unit|
      if value >= 1.send(unit)
        quotient, value = value.divmod 1.send(unit)
        units + [[quotient, unit]]
      else
        units
      end
    end
    parts.map! {|value, unit| "#{value} #{value == 1 ? unit.chop : unit}" }
    parts.join(', ')
  end
end
            

Module
Include & Extend


class C
  include M
  extend M::ClassMethods
end
						

Module Include & Extend


module M
  def self.included(base)
    base.extend ClassMethods
    base.class_eval do
      scope :disabled, -> { where(disabled: true) }
    end
  end
  module ClassMethods
    # ...
  end
end
            

Concern


module M
  extend Concern
  included do
    scope :disabled, -> { where(disabled: true) }
  end
  module ClassMethods
    # ...
  end
end
            

How to Implement?


module Concern
  def self.extended base
    base.instance_variable_set(:@_dependencies, [])
  end

  def included base = nil, &block
    if base.nil?
      @_included_blocks ||= []
      @_included_blocks << block
    else
      super
    end
  end

  def append_features base
    if base.instance_variable_defined?(:@_dependencies)
      base.instance_variable_get(:@_dependencies) << self
      return false
    else
      return false if base < self
      @_dependencies.each { |dep| base.send(:include, dep) }
      super
      base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
      if instance_variable_defined?(:@_included_blocks)
        @_included_blocks.each {|block| base.class_eval(&block) }
      end
    end
  end
end
            

class_eval

module_eval

instance_eval

Aspect-oriented programming

AOP in Ruby


class Bachue
  def program code
    # ...
  end
end

class Bachue
  def program_with_coffee code
    log.info "start drinking ..."
    program_without_coffee code
    log.info "end drinking."
  end

  alias program_without_coffee program
  alias program program_with_coffee
end
            

AOP in Rails


class Bachue
  def program code
    # ...
  end
end

class Bachue
  def program_with_coffee code
    log.info "start drinking ..."
    program_without_coffee code
    log.info "end drinking."
  end

  alias_method_chain :program, :coffee
end
            

How to Implement?


class Class
  def alias_method_chain(target, feature)
    aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
    yield aliased_target, punctuation if block_given?

    with_method = "#{aliased_target}_with_#{feature}#{punctuation}"
    without_method = "#{aliased_target}_without_#{feature}#{punctuation}"

    alias_method without_method, target
    alias_method target, with_method

    case
    when public_method_defined?(without_method)
      public target
    when protected_method_defined?(without_method)
      protected target
    when private_method_defined?(without_method)
      private target
    end
  end
end
            

Do more


class Bachue
  hook def before_program code
    puts 'start drinking ...'
  end

  hook def after_program code
    puts 'end drinking.'
  end

  hook def around_program code
    puts 'start coding ...'
    yield code
    puts 'end coding.'
  end
end
            

How to Implement?


class Class
  def hook hook_method
    unless hook_method.to_s =~ /^(before|around|after)_(.+)$/
      raise 'Hook method must start with before, around or after'
    end
    type, method_name = $1.to_sym, $2
    feature = "#{type}_#{SecureRandom.hex 8}__"

    alias_method_chain method_name, feature do |method_name, punctuation|
      define_method "#{method_name}_with_#{feature}#{punctuation}" do |*args, &block|
        old_method = "#{method_name}_without_#{feature}#{punctuation}"
        case type
        when :before
          send hook_method, *args, &block
          send old_method, *args, &block
        when :after
          send old_method, *args, &block
          send hook_method, *args, &block
        when :around
          send(hook_method, *args) {|*args, &block| send old_method, *args, &block }
        end
      end
    end
  end
end
            

Dependency

Rails convension


Companies::CloudComputing::EMC <=> "companies/cloud_computing/emc.rb"
            

$LOAD_PATH

autoload_paths


  paths.add "app",                 eager_load: true, glob: "*"
  paths.add "app/assets",          glob: "*"
  paths.add "app/controllers",     eager_load: true
  paths.add "app/helpers",         eager_load: true
  paths.add "app/models",          eager_load: true
  paths.add "app/mailers",         eager_load: true
  paths.add "app/views"

  paths.add "app/controllers/concerns", eager_load: true
  paths.add "app/models/concerns",      eager_load: true

  paths.add "lib",                 load_path: true
  paths.add "lib/assets",          glob: "*"
  paths.add "lib/tasks",           glob: "**/*.rake"

  paths.add "config"
  paths.add "config/environments", glob: "#{Rails.env}.rb"
  paths.add "config/initializers", glob: "**/*.rb"
  paths.add "config/locales",      glob: "*.{rb,yml}"
  paths.add "config/routes.rb"

  paths.add "db"
  paths.add "db/migrate"
  paths.add "db/seeds.rb"

  paths.add "vendor",              load_path: true
  paths.add "vendor/assets",       glob: "*"
            

Constant lookup


class SuperClass
  FIND_ME = "Found in SuperClass"
end

module ParentLexicalScope
  FIND_ME = "Found in ParentLexicalScope"

  class SubClass < SuperClass
    p FIND_ME
  end
end
            

const_missing


def const_missing(const_name)
  Dependencies.load_missing_constant(self, const_name)
end
            

load_missing_constant


def load_missing_constant(from_mod, const_name)
  qualified_name = qualified_name_for from_mod, const_name
  path_suffix = qualified_name.underscore

  file_path = search_for_file path_suffix

  if file_path
    expanded = File.expand_path(file_path).sub(/\.rb\z/, '')
    require_or_load(expanded, qualified_name)
    unless from_mod.const_defined?(const_name, false)
      raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it"
    end
    return from_mod.const_get(const_name)
  elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix)
    return mod
  elsif (parent = from_mod.parent) && parent != from_mod &&
        ! from_mod.parents.any? { |p| p.const_defined?(const_name, false) }
    begin
      return parent.const_missing(const_name)
    rescue NameError => e
      raise unless e.missing_name? qualified_name_for(parent, const_name)
    end
  end

  raise NameError, "uninitialized constant #{qualified_name}"
end
            
“There’s no such thing as metaprogramming. It’s just programming all the way through.”

Thanks!