markdown Ruby元编程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown Ruby元编程相关的知识,希望对你有一定的参考价值。

type = "baz"
Foo::Bar.const_get(type.capitalize).new # => new instance of Foo::Bar::Baz
# Class Extension Mixin allows you to both `include` and `extend` a class
module MyMixin
  def self.included(base) # Hook Method
    base.extend(ClassMethods)
  end

  def a
    puts "I'm A (an instance method)"
  end

  module ClassMethods # "ClassMethods" is a recognised naming pattern
    def x
      puts "I'm X (a class method)"
    end
  end
end

class Foo
  include MyMixin
end

Foo.x # => I'm X (a class method)
Foo.new.a # => I'm A (an instance method)
```rb
# Hook Methods are provided by the Ruby language and let you know about certain events
# such as when a class inherits from another class or when a method has been added to an object.
class String
  def self.inherited(subclass)
    puts "#{self} was inherited by #{subclass}"
  end
end
class MyString < String; end # => String was inherited by MyString
```

There are quite a few hooks which I've listed below.

**Method-related hooks**

```
method_missing
method_added
singleton_method_added
method_removed
singleton_method_removed
method_undefined
singleton_method_undefined
```

**Class & Module Hooks**

```
inherited
append_features
included
extend_object
extended
initialize_copy
const_missing
```

**Marshalling Hooks**

```
marshal_dump
marshal_load
```

**Coercion Hooks**

```
coerce
induced_from
to_xxx
```
# Around Alias uses the `alias` keyword to store a copy of the original method under a new name,
# allowing you to redefine the original method name and to delegate off to the previous method implementation

class String
  alias :orig_length :length
 
  def length
    "Length of string '#{self}' is: #{orig_length}"
  end  
end
 
"abc".length
#=> "Length of string 'abc' is: 3"
# Class Macros are just regular class methods that are only used in a class definition 
# e.g. not used from a new instance of the class (only at the time the class is defined)
#
# Below is an example of a Class Macro that alerts users of a publically available class
# that the methods they've been using are now deprecated and they should use the renamed version.
#
# It uses "Dynamic Method" to help performance by creating the old methods again and delegating off
# to the new methods (rather than using `method_missing` which can be quite slow as it has to spend
# time looking up the inheritance chain)
class Foo
  def get_a; puts "I'm an A" end
  def get_b; puts "I'm an B" end
  def get_c; puts "I'm an C" end

  # Defining our Class Macro
  def self.deprecate(old_method, new_method)
    define_method(old_method) do |*args, &block|
      puts "Warning: #{old_method} is deprecated! Use #{new_method} instead"
      send(new_method, *args, &block) # `self` is assumed when calling `send`
    end
  end

  # Using our Class Macro
  deprecate :a, :get_a
  deprecate :b, :get_b
  deprecate :c, :get_c
end
# Evaluate a block in the context of a class
# Similar to re-opening a class but more flexible in that it
# works on any variable that references a class, where as re-opening
# a class requires defining a constant.

# Classic class re-opening style
class String
  def m; puts "hello!" end
end

# Class eval style 
# The extra code is used to make the example a bit more re-usable/abstracted
def add_method_to_class(the_class)
  the_class.class_eval do
    def m; puts "hello!" end
  end
end

add_method_to_class String
"abc".m # => hello!
# Context Probe
# Execute a code block in the context of another object using `instance_eval`
class Foo
  def initialize
    @z = 1
  end
end
foo = Foo.new
foo.instance_eval do
  puts self # => #<Foo:0x7d15e891>
  puts @z # => 1
end
new_value = 2
foo.instance_eval { @z = new_value }
foo.instance_eval { puts @z } # => 2

# There is also `instance_exec` which works the same way but allows passing arguments to the block
class Foo
  def initialize
    @x, @y = 1, 2
  end
end
Foo.new.instance_exec(3) { |arg| (@x + @y) * arg } # => 9
# Flattening the Scope (aka Nested Lexical Scopes)
# Where you change the code in such a way that it's easier for you to pass variables through "Scope Gates".
# A scope gate is any block, where normally when you enter its scope the variables outside of it become unreachable.
# This happens in: Class definitions, Module definitions, Method definitions
# I'm not sure what the real life examples are of this, but if you ever wonder why some code does the following,
# then maybe it was that they wanted to flatten the scope so they could more easily pass around variables.
# I guess it's better to do it this way than to define a global variable?
#
# In the following code we want to access `my_var` from inside the method (inner scope gate) that's
# inside the class (outer scope gate).
my_var = "abc"
class OuterScopeGate
  puts my_var

  def inner_scope_gate
    puts my_var
  end
end

# We fix this by flattening the code into method calls (method *calls* aren't scope gates)
# So we turn the class keyword into a method call using `Class.new`
# We also turn the method inside the class from a keyword into a method call using `define_method`
my_var = "abc"
MyClass = Class.new do
  puts "Here is 'my_var' inside my class definition: #{my_var}"
  
  define_method :my_method do
    puts "Here is 'my_var' inside my class instance method: #{my_var}"
  end
end # => Here is 'my_var' inside my class definition: abc
MyClass.new.my_method # => Here is 'my_var' inside my class instance method: abc
# Kernel Method
# Add a method that gives the illusion it's a language keyword
# But really it's just added to the `Kernel` module which all other objects inherit from.
# At the top level of a Ruby program `self` is == `main`.
# `self.class` == `Object` and the `Kernel` sits above it in the hierarchy.
# You can see this by running the following code:
class Foo; end
Foo.ancestors # => [Foo, Object, Kernel, BasicObject]

# So we can see we can add what looks to be a language provided feature like so:
module Kernel
  def foobar
    puts "I'm not a language keyword, I'm just a fake"
  end
end

# Now from any where in our program we can call
foobar # => I'm not a language keyword, I'm just a fake
# Blank Slate
# Prevents issues when using "Dynamic Proxies"
#
# e.g. user calls a method that exists higher up the inheritance chain
# so your `method_missing` doesn't fire because the method does exist.
# 
# To work around this issue, make sure your class starts with a "Blank Slate"
# So you remove any methods you don't want to appear at all in the inheritance chain
# by using `undef_method` (there is also `remove_method` which doesn't remove the named
# method from the inheritance chain but just the current class, but that doesn't help us
# fix the "Dynamic Proxy" scenario so we use `undef_method` instead).
#
# For "Dynamic Proxy" we use the parent `method_missing` so we keep that,
# we also might use `respond_to?` so we keep that (although you can remove it if you don't).
# Also the `__` in the below regex pattern is to prevent Ruby from displaying a warning 
# about removing 'reserved' methods such as `__id__` and `__send__`
class ImBlank
  instance_methods.each do |m|
    undef_method m unless m.to_s =~ /^__|method_missing|respond_to?/
  end

  # rest of your code (such as your "Dynamic Proxy" implementation)
end
# Dynamic Proxies
# Catching "Ghost Methods" and forwarding them onto another method
# Whilst possibly adding logic around the call.
#
# For example,
# You can provide imaginary methods by utilising `method_missing` to parse
# the incoming message (e.g. `get_name`, `get_age`) and to delegate off to 
# another method such as `get(:data_type)` where `:data_type` is `:name` or `:age`.
def method_missing(message, *args, &block)
  return get($1.to_sym, *args, &block) if message.to_s =~ /^get_(.*)/
  super # if we don't find a match then we'll call the top level `BasicObject#method_missing`
end

# If (after analysis) you discover a performance issue with using `method_missing`
# you can utilise the "Dynamic Method" technique to create a real method after 
# the message has been received by `method_missing` the first time.
# Ghost Methods
# Utilises `method_missing`
class Hai
  def method_missing(method, *args)
    puts "You called: #{method}(#{args.join(', ')})"
    puts "You also passed a block" if block_given?
  end
end
Hai.new.yolo # => You called: yolo()
Hai.new.yolo "a", 123, :c # => You called: yolo(a, 123, c)
Hai.new.yolo(:a, :b, :c) { puts "a block" } # => You called: yolo(a, b, c)
                                            # => You also passed a block
# Dynamic Method
# Allows us to dynamically create methods
# define_method :method_name { block that becomes method body }
class Foo
  define_method :bar do
    puts "This is a dynamic method"
  end
end
Foo.new.bar # => "This is a dynamic method"

# Dynamic Method
# Alternative example
class Bar
  # we have to define this method on `self` (see below comment)
  def self.create_method(method)
    define_method "my_#{method}" do
      puts "Dynamic method called 'my_#{method}'"
    end
  end

  # these methods are executed within the definition of the Bar class
  create_method :foo
  create_method :bar
  create_method :baz
end
Bar.new.my_foo # => "Dynamic method called 'my_foo'"
Bar.new.my_bar # => "Dynamic method called 'my_bar'"
Bar.new.my_baz # => "Dynamic method called 'my_baz'"

# Dynamic Method
# Parse another class for data
class AnotherClass
  def get_foo_stuff; end
  def get_bar_stuff; end
  def get_baz_stuff; end
end
class Baz
  def initialize(a_class)
    a_class.methods.grep(/^get_(.*)_stuff$/) { Baz.create_method $1 }
  end
  def self.create_method(method)
    define_method "my_#{method}" do
      puts "Dynamic method called 'my_#{method}'"
    end
  end
end
another_class = AnotherClass.new
Baz.new(another_class).my_foo # => "Dynamic method called 'my_foo'"
Baz.new(another_class).my_bar # => "Dynamic method called 'my_bar'"
Baz.new(another_class).my_baz # => "Dynamic method called 'my_baz'"

class Foo
  def initialize(bar)
    self.class.send(:define_method, bar) { p "hello #{bar}!" }
  end  
end
foo = Foo.new("world")
foo.world # => "hello world!"
# Dynamic Dispatch
# Allows us to send messages to even private methods
# object.send(message, *arguments)
1.send(:+, 2) # => 3
- Dynamic Dispatch
- Dynamic Method
- Ghost Methods
- Dynamic Proxies
- Blank Slate
- Kernel Method
- Flattening the Scope (aka Nested Lexical Scopes)
- Context Probe
- Class Eval *(not really a 'spell' more just a demonstration of its usage)*
- Class Macros
- Around Alias
- Hook Methods
- Class Extension Mixin
- Module Namespace Interpolation

以上是关于markdown Ruby元编程的主要内容,如果未能解决你的问题,请参考以下文章

Ruby元编程

为啥学习 Ruby 中的元编程和特征类很重要?

3-8《Ruby元编程》

Ruby元编程

Ruby 元编程:动态实例变量名

ruby 元编程