байткод • Ruby – интерпретируемый язык; • компиляция — процесс перевода кода из одного языка в другой; • интерпретация – запуск кода из исходников; • интерпретатор Ruby (>=1.9) преобразует исходный код в байткод перед исполниенем; • байткод выполняется виртуальной машиной; • виртуальная машина знает как запустить байткод на конкретной машине; • Ruby – интерпретируемый язык? 🙂 4
• инкапсуляция — данные и методы работы с ними хранятся в одной сущности; • метод — набор инструкций байткода, хранится в памяти; • копирование байткода в каждый объект класса — дорого; • объект — указатель на класс и массив переменных объекта. 6
• объект, который содержит объявления методов, имена атрибутов, указатель на суперкласс и таблицу констант; • объект класса Class. 7 irb(main) : 003 : 0> Integer.class = > Class
• инстанс методы хранятся в объекте SomeClass; • методы класса должны быть где–то в базовом классе; • мы не можем добавить их прямо в Class; • Ruby генерирует для нас singleton_class/metaclass/eigenclass. 10 class SomeClass def self.class_method; 'class_method'; end def instance_method; 'instance_method'; end end instance = SomeClass.new instance.methods # = > [:instance_method, . . . ] SomeClass.singleton_class # = > #<Class:SomeClass> instance.singleton_class.methods # = > [:class_method, . . . ]
общее для класса и его наследников 12 class SomeClass @class_var = 'value' def self.class_var @class_var end end class ChildClass < SomeClass @class_var = 'child_value' end SomeClass.class_var # = > 'value' ChildClass.class_var # = > 'child_value' class SomeClass @@class_var = 'value' def self.class_var @@class_var end end class ChildClass < SomeClass @@class_var = 'child_value' end SomeClass.class_var # = > 'child_value' ChildClass.class_var # = > 'child_value' Переменные класса
модуль — объект, который содержит объявления методов, указатель на базовый класс и таблицу констант; • при включении модуля создается новый класс и добавляется в цепочку наследования как базовый класс для текущего. 13
def name "🦖" end end class User attr_reader :name include Raptor def initialize(name) @name = name end end user = User.new("Ivan") puts user.name # => ???
метод из включенного модуля 15 module Raptor def name "🦖" end end class User attr_reader :name include Raptor def initialize(name) @name = name end end user = User.new("Ivan") puts user.name # => Ivan
в модуль 16 module Module1 def another_method 'another_method' end end module Module2 include Module1 def some_method 'some_method' end end class SomeClass include Module2 end SomeClass.new.some_method # = > "some_method" SomeClass.new.another_method # = > "another_method" module Module1 def another_method 'another_method' end end module Module2 def some_method 'some_method' end end class SomeClass include Module2 end Module2.include(Module1) SomeClass.new.some_method # = > "some_method" SomeClass.new.another_method # = > NoMethodError ⚠ разница ⚠
module Raptor def name “#{super} (🦖)” end end class User attr_reader :name prepend Raptor def initialize(name) @name = name end end user = User.new("Ivan") puts user.name # => “Ivan (🦖)" Prepend добавляет класс в цепочку наследования перед текущим
= include в singleton_class 18 class OtherClass extend M end # то же самое: class OtherClass class < < self include M end end OtherClass.new.methods # = > [ : …] OtherClass.new.singleton_class.methods # = > [:method_from_module, . . . ]
20 class User end первая область видимости вторая область видимости • секция кода внутри синтаксической структуры программы; • не принимает во внимание структуру файлов, модулей или наследование.
с помощью префикса class User def self.admins where(is_admin: true) end end 22 def+pre fi x понимает, что self — это User и добавляет метод в метакласс
< < self def admins where(is_admin: true) end end end 23 объявляем новую область видимости метод попадает в метакласс Объявление метода класса внутри новой лексической области видимости
user = User.new class < < user def full_name [f i rst_name, last_name].compact.join(" ") end end 25 Объявление метода на объекте внутри новой лексической области видимости объявляем новую область видимости
fi nement добавляет методы; • добавленные методы доступны только в текущей лексической области видимости; • полезно для портирования фич в OSS и изменения поведения классов Ruby для нужд приложения. 31 module EmailCheck ref i ne String do def is_email? include?('@') end end end using EmailCheck puts '[email protected]'.is_email? # = > true puts '123'.is_email? # = > false
EmailCheck ref i ne String do def is_email? include?('@') end end end class User using EmailCheck def has_valid_email? 'asd'.is_email? end end puts User.new.has_valid_email? # = > false puts '[email protected]'.is_email? # = > NoMethodError
везде 36 module MyAttrAccessor def my_attr_accessor(attr_name) my_attr_reader(attr_name) my_attr_writer(attr_name) end # . . . end Object.extend(MyAttrAccessor) * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-02_method_generator-rb все классы наследуются от Object
module MyAttrAccessor # . . . def my_attr_reader(attr_name) instance_variable_name = attr_to_variable_name(attr_name) def i ne_method(attr_name) do instance_variable_get(instance_variable_name) end end # . . . private def attr_to_variable_name(attr_name) "@ # { attr_name}" end end * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-02_method_generator-rb объявляем метод динамически получаем значение переменной экземпляра динамически
module MyAttrAccessor def my_attr_accessor(attr_name) class_eval < < ~RUBY def # { attr_name} @ # { attr_name} end def # { attr_name}=value @ # { attr_name} = value end RUBY end end Object.extend(MyAttrAccessor) * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-08_method_generator_class_eval-rb выполняем код в контексте класса
User include MyAttrAccessor[:email] def initialize(email: nil) @email = email end end user = User.new user.email = '[email protected]' puts user.email # = > [email protected] * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-03_module_generator-rb
MyAttrAccessor def self.[](attr_name) Module.new do instance_variable_name = "@ # { attr_name}" def i ne_method(attr_name) do instance_variable_get(instance_variable_name) end def i ne_method(" # { attr_name}=") do |value| instance_variable_set(instance_variable_name, value) end end end end * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-03_module_generator-rb dynamic module generation
50 * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-05_builder_instance_eval-rb class Router NOT_FOUND = 'NOT_FOUND' def self.draw(&block) new.tap { |router| router.instance_eval(&block) } end def route(method_and_path) method, path = method_and_path.split(' ') routes[[method.downcase, path]] | | NOT_FOUND end def get(path, * * args) routes[['get', path]] = args end def post(path, * * args) routes[['post', path]] = args end def routes @routes | | = {} end end блок будет выполнен в контексте инстанса Router все эти методы доступны внутри блока, переданного instance_eval
методы 52 * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-06_builder_generate_methods-rb class Router class < < self # . . . def generate_route_methods(*method_names) method_names.each do |method_name| def i ne_method(method_name) do |path, * * args| routes[[method_name, path]] = args end end end end generate_route_methods :get, :post, :patch, :delete # . . . end
методы экземпляра и методы класса 54 class User include CoolRecord attr_accessor :id, :email, :name def initialize(id: nil, email: nil, name: nil) @id = id @email = email @name = name end end user = User.new(email: '[email protected]', name: 'John') user.save puts User.where(name: 'John').inspect # = > [#<User:0 x 00007fa5f385c2f8 @id=1, @email="[email protected]", @name="John">] puts User.f i nd_by(name: 'John').inspect # = > #<User:0 x 00007fa5f3834398 @id=1, @email="[email protected]", @name="John"> puts User.where(name: 'Jane').inspect # = > [] * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-07_instance_and_class_methods-rb
module CoolRecord def self.included(base) base.extend(ClassMethods) end def save # . . . end module ClassMethods def where(conditions) # . . . end def f i nd_by(conditions) # . . . end end end * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-07_instance_and_class_methods-rb хук для вызова extend вместе с include модуль для статических методов этот метод будет добавлен как instance метод
56 class DB def self.instance @instance | | = new end def users @users | | = {} end end * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-07_instance_and_class_methods-rb
CoolRecord # . . . module ClassMethods # . . . def table DB.instance.public_send(table_name) end private def table_name " # { self.name.downcase}s" end end end * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36# fi le-07_instance_and_class_methods-rb вывод названия таблицы из имени класса вызов публичного метода по имени
в Ruby все является объектом и ведет себя схожим образом; • метапрограммирование — код, который пишет код; • include, prepend, extend и re fi ne помогают добавлять методы в контекст; • статические методы живут в метаклассе; • классы, модули и методы могут добавляться динамически. 60