method_missing
и компания)
define_method
eval
method_missing
.
Object
и хвърля NoMethodError
.
super
.
class Hash
def method_missing(name, *args, &block)
args.empty? ? self[name] : super
end
end
things = {fish: 'Nemo', lion: 'Simba'}
things.fish # "Nemo"
things.lion # "Simba"
things.larodi # nil
things.foo(1) # error: NoMethodError
Има и коварни моменти:
class Hash
def method_missing(name, *arg, &block)
args.empty? ? self[name] : super
end
end
things = {lion: 'Simba'}
things.lion# ~> -:3: stack level too deep (SystemStackError)
arg
вместо args
.
args
всъщност е self.args
.
respond_to?
respond_to?
не работи за методите, на които "отговаряте" през method_missing
respond_to?
вика respond_to_missing?
, ако методът, за който питате, не е дефиниран
respond_to_missing?
, ако имате и method_missing
class Foo
def respond_to_missing?(symbol, include_private)
# Return true or false
end
end
class Foo
def respond_to_missing?(method_name, include_private)
puts "Looking for #{method_name}"
super
end
private
def bar() end
end
Foo.new.respond_to? :larodi # false и на екрана се извежда "Looking for bar"
Foo.new.respond_to? :bar # false и на екрана се извежда "Looking for bar"
Foo.new.respond_to? :bar, true # true
module Unicode
def self.const_missing(name)
if name.to_s =~ /^U([0-9a-fA-F]{4,5}|10[0-9a-fA-F]{4})$/
codepoint = $1.to_i(16)
utf8 = [codepoint].pack('U')
utf8.freeze
const_set(name, utf8)
utf8
else
super
end
end
end
Unicode::U20AC # "€"
Unicode::U221E # "∞"
Unicode::Baba # error: NameError
Метапрограмирането е писането на код, който пише друг код
meta- (also met- before a vowel or h)
combining form
1. denoting a change of position or condition : metamorphosis | metathesis.
2. denoting position behind, after, or beyond: : metacarpus.
3. denoting something of a higher or second-order kind : metalanguage | metonym.
4. Chemistry denoting substitution at two carbon atoms separated by one other in a benzene ring, e.g., in 1,3 positions : metadichlorobenzene. Compare with ortho- and para- 1 .
5. Chemistry denoting a compound formed by dehydration : metaphosphoric acid.
ORIGIN from Greek meta ‘with, across, or after.’
class Entity
attr_reader :table, :ident
def initialize(table, ident)
@table = table
@ident = ident
Database.sql "INSERT INTO #{@table} (id) VALUES (#{@ident})"
end
def set(col, val)
Database.sql "UPDATE #{@table} SET #{col}='#{val}' WHERE id=#{@ident}"
end
def get(col)
Database.sql("SELECT #{col} FROM #{@table} WHERE id=#{@ident}")[0][0]
end
end
class Movie < Entity
def initialize(ident)
super("movies", ident)
end
def title
get("title")
end
def title=(value)
set("title", value)
end
def director
get("director")
end
def director=(value)
set("director", value)
end
end
Тук имаше повторение.
movies
с колона title
и клас Movie
с поле @title
"title"
се повтаря
title()
и title=()
някак повтаря двойката director()
и director=()
"movies"
изглежда повтаря, че класът се казва Movie
С малко метапрограмиране, изглежда така:
class Movie < ActiveRecord::Base
end
Метапрограмирането е писането на код, което управлява конструкциите на езика по време на изпълнение
Доста просто:
class MyClass
def my_method
@v = 1
end
end
obj = MyClass.new
obj.my_method
but_what_is object
# Spoiler alert: there is no `but_what_is' method
class MyClass
def initialize
@a = 1
@b = 2
end
end
MyClass.new.instance_variables # [:@a, :@b]
class Person
def approximate_age
2011 - @birth_year
end
end
person = Person.new
person.instance_variables # []
person.instance_variable_set :@birth_year, 1989
person.approximate_age # 22
person.instance_variable_get :@birth_year # 1989
Можете да вземете класа на всеки обект с Object#class
.
"abc".class # String
"abc".class.class # Class
"abc".class.class.class # Class
String.instance_methods == "abc".methods # true
String.methods == "abc".methods # false
"abc".length # 3
String.length # error: NoMethodError
String.ancestors # [String, Comparable, Object, Kernel, BasicObject]
"abc".ancestors # error: NoMethodError
Можете да вземете родителския клас с Object#superclass
.
class A; end
class B < A; end
class C < B; end
C.superclass # B
C.superclass.superclass # A
C.superclass.superclass.superclass # Object
Class
е наследник на Module
Class.superclass == Module
Class < Module
BasicObject
идва в Ruby 1.9 и е много опростена версия на Object
.
Подходящ е за method_missing
магарии
Object.instance_methods.count # 61
BasicObject.instance_methods.count # 8
m = BasicObject.instance_methods.join(', ')
m # "==, equal?, !, !=, instance_eval, instance_exec, __send__, __id__"
Което ни навежда на следващия въпрос - instance_eval
self
self
се ползва за полета (@foo
) и търсене на методи (bar()
).
instance_eval
променя self
в рамките на един блокclass Person
private
def greeting() "I am #{@name}" end
end
mityo = Person.new
mityo.instance_eval do
@name = 'Mityo'
greeting # "I am Mityo"
self # #<Person:0x8af06c4 @name="Mityo">
end
self # main
mityo.instance_variable_get :@name # "Mityo"
instance_exec
е като instance_eval
, но позволява да давате параметри на блока.
obj = Object.new
set = ->(value) { @x = value }
obj.instance_exec(42, &set)
obj.instance_variable_get :@x # 42
obj.instance_eval { @x } # 42
Това е смислено, когато блока се подава с &
. Иначе няма нужда.
def
class
и module
го променятdef foo() end # Тук е Object
class Something
def bar() end # Тук е Something
class OrOther
def baz() end # Тук е Something::OrOther
end
end
class Something
def foo
def bar
6 * 9
end
bar - 12
end
end
something = Something.new
something.foo # 42
something.bar # 54
class_eval
променя self
и текущия клас
def monkey_patch_string
String.class_eval do
self # String
def answer
42
end
end
end
"abc".respond_to? :answer # false
monkey_patch_string
"abc".respond_to? :answer # true
Module#module_eval
е синоним на Module#class_eval
.
class Something
define_method :foo do |arg|
"!#{arg}! :)"
end
end
Something.new.foo('a') # "!a! :)"
class Something
METASYNTACTIC = %w[foo bar baz]
METASYNTACTIC.each do |name|
define_method name do |arg|
"!#{arg}! :)"
end
end
end
Something.new.bar('a') # "!a! :)"
Something.new.baz('a') # "!a! :)"
С Class.new
може да създадете анонимен клас
anonymous = Class.new do
def answer
42
end
end
instance = anonymous.new
instance.answer # 42
anonymous # #<Class:0x9089d10>
Ако присвоите Class.new
на константа, стават магии
first = Class.new {}
SECOND = Class.new {}
first # #<Class:0x8b15c30>
SECOND # SECOND
Името на класа се променя. Любопитно.
Прави същото като Class.new
, ама с модул
eval(text)
изпълнява код в низ
things = []
eval 'things << 42'
things # [42]
x = 1_024
vars = binding
vars # #<Binding:0x9298a84>
vars.eval('x') # 1024
x = 1_024
def foo
y = 42
binding
end
vars = foo
vars.eval('y') # 42
vars.eval('x') # error: NameError
module
, class
и def
секват binding-а
top_level = 1
module Something
in_module = 2
class Other
in_class = 3
def larodi
top_level # error: NameError
in_module # error: NameError
in_class # error: NameError
end
end
end
Something::Other.new.larodi
Scope gate-овете могат да се заобиколят с define_method
, Class.new
и Module.new
.
top_level = 1
module Something
in_module = 2
class Other
in_class = 3
define_method :larodi do
top_level # error: NameError
in_module # error: NameError
in_class # 3
end
end
end
Something::Other.new.larodi
top_level = 1
module Something
in_module = 2
Other = Class.new do
in_class = 3
define_method :larodi do
top_level # error: NameError
in_module # 2
in_class # 3
end
end
end
Something::Other.new.larodi
top_level = 1
Something = Module.new do
in_module = 2
Other = Class.new do
in_class = 3
define_method :larodi do
top_level # 1
in_module # 2
in_class # 3
end
end
end
Other.new.larodi
class Proxy < BasicObject
def initialize(obj)
@instance = obj
end
def method_missing(name, *args, &block)
$stdout.puts "Calling #{ name } with (#{ args.join(', ') })"
@instance.send(name, *args)
end
end
a = []
b = Proxy.new a
b.length # 0