Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Объектная модель Ruby, а также метапрограммирование и создание DSL

Объектная модель Ruby, а также метапрограммирование и создание DSL

Dmitry Tsepelev

February 24, 2022
Tweet

More Decks by Dmitry Tsepelev

Other Decks in Programming

Transcript

  1. Объектная модель Ruby
    а также метапрограммирование и создание DSL
    Дмитрий Цепелев, Backend в Evil Martians

    View Slide

  2. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Что почитать
    2

    View Slide

  3. Объектная модель Ruby

    View Slide

  4. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Виртуальная машина и байткод
    • Ruby – интерпретируемый язык;


    • компиляция — процесс перевода кода из одного языка в другой;


    • интерпретация – запуск кода из исходников;


    • интерпретатор Ruby (>=1.9) преобразует исходный код в байткод
    перед исполниенем;


    • байткод выполняется виртуальной машиной;


    • виртуальная машина знает как запустить байткод на конкретной
    машине;


    • Ruby – интерпретируемый язык? 🙂
    4

    View Slide

  5. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Виртуальная машина и байткод
    5
    code =
    < <
    CODE


    puts 2+2


    CODE


    puts RubyVM
    : :
    InstructionSequence.compile(code).disasm


    0000 putself ( 1)[Li]


    0001 putobject 2


    0003 putobject 2


    0005 opt_plus


    0007 opt_send_without_block


    0009 leave

    View Slide

  6. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Объект в Ruby
    • инкапсуляция — данные и методы работы с ними хранятся в одной
    сущности;


    • метод — набор инструкций байткода, хранится в памяти;


    • копирование байткода в каждый объект класса — дорого;


    • объект — указатель на класс и массив переменных объекта.
    6

    View Slide

  7. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Класс в Ruby
    • объект, который содержит объявления методов, имена атрибутов,
    указатель на суперкласс и таблицу констант;


    • объект класса Class.
    7
    irb(main)
    :
    003
    :
    0> Integer.class


    = >
    Class

    View Slide

  8. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Наследование
    • у каждого класса есть опциональный базовый класс;


    • методы базового класса доступны в наследнике;


    • базовый класс по умолчанию – Object.
    8

    View Slide

  9. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Наследование
    9
    class ParentClass


    def parent_method; 'parent_method'; end


    end


    class ChildClass < ParentClass


    def child_method; 'child_method'; end


    end


    ParentClass.new.methods #
    = >
    [:parent_method,
    . . .
    ]


    ChildClass.superclass #
    = >
    ParentClass


    ChildClass.new.methods #
    = >
    [:child_method, :parent_method,
    . . .
    ]


    # child has access to parent methods


    ChildClass.new.parent_method #
    = >
    'parent_method'

    View Slide

  10. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Где хранятся методы?
    • инстанс методы хранятся в объекте 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 #
    = >
    #


    instance.singleton_class.methods #
    = >
    [:class_method,
    . . .
    ]

    View Slide

  11. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Переменные класса
    Инстанс переменные создаются в указанном контексте
    11
    class SomeClass


    @class_var = 'value'


    def self.class_var


    @class_var


    end


    end


    SomeClass.new.instance_variables #
    = >
    []


    SomeClass.instance_variables #
    = >
    [
    :
    @class_var]
    class SomeClass


    @@class_var = 'value'


    def self.class_var


    @@class_var


    end


    end


    SomeClass.new.instance_variables #
    = >
    []


    SomeClass.instance_variables #
    = >
    []


    SomeClass.class_variables #
    = >
    [
    :
    @@class_var]

    View Slide

  12. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Значение переменной класса общее для класса и его наследников
    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'
    Переменные класса

    View Slide

  13. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Модули: include
    • модуль — объект, который
    содержит объявления методов,
    указатель на базовый класс и
    таблицу констант;


    • при включении модуля
    создается новый класс и
    добавляется в цепочку
    наследования как базовый
    класс для текущего.
    13

    View Slide

  14. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷 14
    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 # => ???

    View Slide

  15. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Модули: класс переопределяет метод из включенного модуля
    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

    View Slide

  16. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Модули: включение модуля в модуль
    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
    ⚠ разница ⚠

    View Slide

  17. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Модули: prepend
    17
    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 добавляет класс в цепочку наследования перед текущим

    View Slide

  18. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Модули: extend
    extend = 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,
    . . .
    ]

    View Slide

  19. Инструменты
    метапрограммирования

    View Slide

  20. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Лексическая область видимости
    20
    class User




    end
    первая область видимости
    вторая область видимости
    • секция кода внутри синтаксической структуры программы;


    • не принимает во внимание структуру файлов, модулей или
    наследование.

    View Slide

  21. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Обычное объявление метода
    class User


    def full_name


    [f
    i
    rst_name, last_name].compact.join(" ")


    end


    end
    21

    View Slide

  22. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Объявление метода класса с помощью префикса
    class User


    def self.admins


    where(is_admin: true)


    end


    end
    22
    def+pre
    fi
    x понимает, что self — это User


    и добавляет метод в метакласс

    View Slide

  23. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    class User


    class
    < <
    self


    def admins


    where(is_admin: true)


    end


    end


    end
    23
    объявляем новую область видимости
    метод попадает в метакласс
    Объявление метода класса внутри новой лексической области
    видимости

    View Slide

  24. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Объявление метода на объекте
    class User


    end


    user = User.new


    def user.full_name


    [f
    i
    rst_name, last_name].compact.join(" ")


    end
    24
    метод попадает в метакласс

    View Slide

  25. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    class User


    end


    user = User.new


    class
    < <
    user


    def full_name


    [f
    i
    rst_name, last_name].compact.join(" ")


    end


    end
    25
    Объявление метода на объекте внутри новой лексической области
    видимости
    объявляем новую область видимости

    View Slide

  26. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    define_method
    26
    class User


    def
    i
    ne_method(:full_name) do


    [f
    i
    rst_name, last_name].compact.join(" ")


    end


    end

    View Slide

  27. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    eval: выполнение кода из строки
    27
    eval "2 + 2"

    View Slide

  28. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷 28
    def build_closure(x, y)


    binding


    end


    eval "x + y", build_closure(1, 2)
    binding: замыкание без функции

    View Slide

  29. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    binding: замыкание без функции
    29
    User = Struct.new(:f
    i
    rst_name, :last_name)


    user = User.new('John', 'Doe')


    def full_name


    [f
    i
    rst_name, last_name].join(' ')


    end


    m = method(:full_name)


    m.class #
    = >
    #


    um = m.unbind


    um.class #
    = >
    UnboundMethod


    um.bind_call(user) #
    = >
    "John Doe"

    View Slide

  30. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    instance_eval: меняем получателя
    30
    class User; end


    user = User.new


    user.instance_eval do


    [f
    i
    rst_name, last_name].compact.join(' ')


    end

    View Slide

  31. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Refinements
    • re
    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

    View Slide

  32. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Refinements
    32
    module 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

    View Slide

  33. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Как доступиться до переменных?
    • instance_variables


    • instance_variable_set


    • instance_variable_get
    33

    View Slide

  34. Демо: генератор методов

    View Slide

  35. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Цель
    35
    class User


    my_attr_accessor :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-02_method_generator-rb

    View Slide

  36. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Делаем метод доступным везде
    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

    View Slide

  37. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Реализация геттера
    37
    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
    объявляем метод


    динамически
    получаем значение переменной


    экземпляра динамически

    View Slide

  38. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Реализация сеттера
    38
    module MyAttrAccessor


    #
    . . .

    def my_attr_writer(attr_name)


    instance_variable_name = attr_to_variable_name(attr_name)


    def
    i
    ne_method("
    # {
    attr_name}=") do |value|


    instance_variable_set(instance_variable_name, value)


    end


    end


    #
    . . .

    end
    * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36#
    fi
    le-02_method_generator-rb
    присваиваем значение переменной


    экземпляра динамически

    View Slide

  39. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Альтернатива: class_eval
    39
    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
    выполняем код


    в контексте класса

    View Slide

  40. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    class_eval и ошибки
    40
    class ClassThatCanRaiseError


    class_eval
    < <
    ~RUBY


    def raise_error!


    1 / 0


    end


    RUBY


    end


    ClassThatCanRaiseError.new.raise_error!


    Traceback (most recent call last)
    :

    1
    :
    from class_eval.rb:15:in `'


    (eval)
    :
    2:in `raise_error!': StandardError (StandardError)

    View Slide

  41. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷 41
    class ClassThatCanRaiseError


    class_eval
    < <
    ~RUBY,
    _ _
    FILE
    _ _
    ,
    _ _
    LINE
    _ _
    + 1


    def raise_error_with_trace!


    1 / 0


    end


    RUBY


    end


    ClassThatCanRaiseError.new.raise_error_with_trace!


    Traceback (most recent call last)
    :

    2
    :
    from class_eval.rb:16:in `'


    1
    :
    from class_eval.rb:10:in `raise_error_with_trace!'


    class_eval.rb:10:in `/': divided by 0 (ZeroDivisionError)
    class_eval и ошибки

    View Slide

  42. Демо: генератор модулей

    View Slide

  43. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Цель
    43
    class 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

    View Slide

  44. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Реализация
    44
    module 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

    View Slide

  45. Демо: роутер «как в рельсах»

    View Slide

  46. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Цель 1: делаем что работало
    46
    router = Router.draw do |builder|


    builder.get '/users', controller: :users, action: :index


    builder.post '/users', controller: :users, action: :create


    end


    puts router.route('GET /users') #
    = >
    {:controller=
    > :
    users, :action=
    > :
    index}


    puts router.route('POST /users') #
    = >
    {:controller=
    > :
    users, :action=
    > :
    create}


    puts router.route('GET /orders') #
    = >
    NOT_FOUND
    * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36#
    fi
    le-04_builder_simple-rb

    View Slide

  47. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Router
    47
    * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36#
    fi
    le-04_builder_simple-rb
    class Router


    NOT_FOUND = 'NOT_FOUND'


    def self.draw


    builder = Builder.new


    yield(builder)


    new(builder)


    end


    def initialize(builder)


    @builder = builder


    end


    def route(method_and_path)


    method, path = method_and_path.split(' ')


    @builder.routes[[method.downcase, path]]
    | |
    NOT_FOUND


    end


    end

    View Slide

  48. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Builder
    48
    * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36#
    fi
    le-04_builder_simple-rb
    class Builder


    def get(path,
    * *
    args)


    routes[['get', path]] = args


    end


    def post(path,
    * *
    args)


    routes[['post', path]] = args


    end


    def routes


    @routes
    | |
    = {}


    end


    end

    View Slide

  49. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Цель 2: блок без аргумента
    49
    router = Router.draw do


    get '/users', controller: :users, action: :index


    post '/users', controller: :users, action: :create


    end


    puts router.route('GET /users') #
    = >
    {:controller=
    > :
    users, :action=
    > :
    index}


    puts router.route('POST /users') #
    = >
    {:controller=
    > :
    users, :action=
    > :
    create}


    puts router.route('GET /orders') #
    = >
    NOT_FOUND
    * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36#
    fi
    le-05_builder_instance_eval-rb

    View Slide

  50. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Router v2: instance_eval
    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

    View Slide

  51. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Router v3: генерируем методы
    51
    * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36#
    fi
    le-06_builder_generate_methods-rb
    class Router


    class
    < <
    self


    def draw(&block)


    new.tap { |router| router.instance_eval(&block) }


    end


    def generate_route_methods(*method_names)


    #
    . . .

    end


    end


    generate_route_methods :get, :post, :patch, :delete


    NOT_FOUND = 'NOT_FOUND'


    def route(method_and_path)


    method, path = method_and_path.split(' ')


    routes[[method.downcase, path]]
    | |
    NOT_FOUND


    end


    def routes


    @routes
    | |
    = {}


    end


    end

    View Slide

  52. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Router v3: генерируем методы
    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

    View Slide

  53. Демо: CoolRecord

    View Slide

  54. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Цель: добавить и методы экземпляра и методы класса
    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 #
    = >
    [#x
    00007fa5f385c2f8 @id=1, @email="[email protected]", @name="John">]


    puts User.f
    i
    nd_by(name: 'John').inspect #
    = >
    #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

    View Slide

  55. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Хук included
    55
    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 метод

    View Slide

  56. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Мок для БД
    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

    View Slide

  57. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    #save
    57
    module CoolRecord


    #
    . . .

    def save


    record_id =


    if new_record?


    self.id = (DB.instance.users.map(&:id).max
    | |
    0) + 1


    else


    id


    end


    self.class.table[record_id] = { id: record_id, email: email, name: name }


    end


    def new_record?


    id.nil?


    end#
    . . .

    end
    * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36#
    fi
    le-07_instance_and_class_methods-rb

    View Slide

  58. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    .where/.find_by
    58
    module 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
    вывод названия таблицы


    из имени класса
    вызов публичного метода по имени

    View Slide

  59. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    .where/.find_by
    59
    module CoolRecord


    #
    . . .

    module ClassMethods


    def where(conditions)


    table.values


    .select { |attrs| conditions.all? { |(attr_name, value)| attrs[attr_name]
    = =
    value } }


    .map { |attrs| self.new(
    * *
    attrs) }


    end


    def f
    i
    nd_by(conditions)


    where(conditions).f
    i
    rst


    end


    #
    . . .

    end


    end
    * https://gist.github.com/DmitryTsepelev/ab1797e7d00b484973615ba1782e0e36#
    fi
    le-07_instance_and_class_methods-rb

    View Slide

  60. DmitryTsepelev
    Saint P Ruby Meetup Winter’22 😷
    Еще раз
    • в Ruby все является объектом и ведет себя схожим образом;


    • метапрограммирование — код, который пишет код;


    • include, prepend, extend и re
    fi
    ne помогают добавлять методы в контекст;


    • статические методы живут в метаклассе;


    • классы, модули и методы могут добавляться динамически.
    60

    View Slide

  61. @dmitrytsepelev
    Спасибо! Вопросы?
    61

    View Slide