[Ruby, Программирование, Ruby on Rails, Функциональное программирование] Метапрограммирование в реальной задаче
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем привет! В этой статье хочу рассказать про метапрограммирование на примере реальной часто встречающейся проблемы.Когда кто то говорит про метапрограммирование у олдскульного кодировщика случается приступ ярости.И на это есть причины так и на большом проекте может показаться безумием использовать метапрограммирование, так как код становится очень сложным для чтения. А если в проект включится специалист со стороны, то он и подавно ничего не разберет в этом мета-коде.Но все не так просто, как говорится - нет плохих инструментов. В этой статьей я постараюсь показать на реальном рабочем примере как метапрограммирование поможет сделать ваш код чище, избавит от рутинных повторений. И заставит порадоваться мета-магии.Справка из википедиМетапрограммирование — вид программирования, связанный с созданием программ, которые порождают другие программы как результат своей работы (в частности, на стадии компиляции их исходного кода), либо программ, которые меняют себя во время выполнения (самомодифицирующийся код). Первое позволяет получать программы при меньших затратах времени и усилий на кодирование, чем если бы программист писал их вручную целиком, второе позволяет улучшить свойства кода (размер и быстродействие).На этом заканчивается вступление. Теперь хочу перейти к практической части и рассказать про суть проблемыДальше речь пойдёт о языке ruby и фраймворке Rails в частности. В руби есть рефлексия и большие возможности для метапрограммирования. Большинство гемов, модулей и фреймворков созданы при помощи мощных инструментов рефлексии.
Photo by Joshua Fuller on UnsplashКто писал что то на Rails скорее всего сталкивался с таким гемом как Rails Admin, если вы в своих проектах на рельсах до сих не пробовали использовать его или аналоги категорически рекомендую это сделать, так как практически из коробки вы получите полноценную CMS для вашей системы.Так вот там есть неприятная особенность проблема с has_one association.Они не обрабатываются автоматически и у вас не будет возможности редактировать вашу has_one связь. Выглядит это примерно как на рисунке 1. Вместо селекта, просто ссылка на чертеж.
рисунок 1Посмотрим на внутренности вот так выглядит модель нашего техпроцесса. У неё есть has_one связь с чертежом (draft) и хотелось бы иметь возможность редактировать её через CMS.
class TechProcess < ApplicationRecord
include MdcSchema
has_many :executor_programs, inverse_of: :tech_process, foreign_key: :barcode_tech_process, primary_key: :barcode
validates :barcode, presence: true
validates :barcode, uniqueness: true
has_one :draft2tech_process, dependent: :destroy
has_one :draft, through: :draft2tech_process
has_many :tech_process2tech_operations
has_many :tech_operations, through: :tech_process2tech_operations
end
Ситуация удивительная но решение этой проблемы простое как палка. Как следует из вики ассоциация has_one не будет должным образом инициализирована до тех пор, пока не будут заданы способы установки и получения идентификатора. Грубо говоря сетеры и гетеры.Добавляем их в модель
def draft_id
self.draft.try :id
end
def draft_id=(id)
self.draft = Draft.find_by_id(id)
end
И в итоге получаем полноценный интерфейс редактирования техпроцесса с возможностью выбора чертежа, создания нового, или редактирования прикрепленного, в общем все прелести CMS.
Ничего сложного, но что если у нас 15 моделей с одной или даже несколькими связями has_one это что придется повторить эти гетеры сеттеры в каждом месте? Такое конечно можно сделать, но тогда нарушается очень важный принцип DRY. Ну и плюс природная лень не позволяет так расточительно писать много строк кода. Так что надо найти способ автоматизировать этот процесс и тут на помощь приходит метапрограммирование.Meta решениеЧто же приступим
self.reflect_on_all_associations(:has_one).each do |has_one_association|
define_method("#{has_one_association.name}_id") do
self.send(has_one_association.name).try :id
end
define_method("#{has_one_association.name}_id=") do |id|
self.send("#{has_one_association.name}=",has_one_association.klass.find_by_id(id))
end
end
Вот так выглядит код, который подружит has_one и Rails AdminА теперь более подробно что тут происходит. Далее детально буду останавливаться только на аспектах которые касаются рефлексии и мета программирования.В руби всё является объектом, связь также является объектом и несет полную информацию о самой себе и всех своих отношениях. Первый интересный метод reflect_on_all_associations Который возвращает массив всех связей, но может принимать параметр "macro" в примере выше я передал туда :hasone и он вернул мне только has_one связи, прекрасно, даже не пришлось дальше селектить только нужные связи.Отлично, теперь есть список всех has_one связей и нужно автоматизировано определить гетеры и сетеры. Дальше идет, простите за тавтологию, метод define_method который динамически прямо во время исполнения определяет метод. В итоге этот код создает методы необходимые для корректной инициализации has_one в rails admin.Сушим до концаВсё это задумывалось для создания "сухого" кода, так что сейчас опишу последнюю деталь. Нужно всю эту мета-магию вынести в concern
require 'active_support/concern'
module HasOneHandler
extend ActiveSupport::Concern
included do
self.reflect_on_all_associations(:has_one).each do |has_one_association|
define_method("#{has_one_association.name}_id") do
self.send(has_one_association.name).try :id
end
define_method("#{has_one_association.name}_id=") do |id|
self.send("#{has_one_association.name}=",has_one_association.klass.find_by_id(id))
end
end
end
end
И в итоге класса который в который нужно добавить гетеры и сетеры, добавляется всего одна строчка со включением консерна. Таким малословным способом удалось подружить CMS и has_one связи. Если применять стандартный подход то пришлось бы писать определять гетеры и сетеры для каждой связи в каждой модели, а их может быть немало. Итоговая версия модели
class TechProcess < ApplicationRecord
include MdcSchema
include HasOneHandler
has_many :executor_programs, inverse_of: :tech_process, foreign_key: :barcode_tech_process, primary_key: :barcode
validates :barcode, presence: true
validates :barcode, uniqueness: true
has_one :draft2tech_process, dependent: :destroy
has_one :draft, through: :draft2tech_process
has_many :tech_process2tech_operations
has_many :tech_operations, through: :tech_process2tech_operations
end
ЗаключениеНадеюсь мне удалось показать практическую ценность использования приемов метапрограммирования. Использовать его или нет решать конечно вам. Если применять его слишком часто и не к месту, то проект превратиться в абсолютно не читаемый и трудно отлаживаемый, но в случае правильного использования, напротив, уменьшает количество кода, улучшает читаемость и избавляет от рутиной работы. Спасибо всем кто дочитал!
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Java, API, Хакатоны] Тривиальная и неправильная «облачная» компиляция
- [Программирование микроконтроллеров, Производство и разработка электроники, DIY или Сделай сам, Игры и игровые приставки, Электроника для начинающих] STM32F429 + IL9341 = LVGL, DOOM1
- [C++, Системное программирование, Программирование микроконтроллеров] Запуск сложных C++ приложений на микроконтроллерах
- [Разработка веб-сайтов, JavaScript, Программирование] Углублённое руководство по JavaScript: генераторы. Часть 2, простой пример использования (перевод)
- [Программирование, .NET, C#] Провайдер логирования для Telegram (.NET 5 / .NET Core)
- [JavaScript, Программирование, Тестирование веб-сервисов] Тестирование с использованием Puppeteer
- [Программирование, Управление разработкой, Управление персоналом] Как начать программировать в парах
- [Семантика, Программирование, Prolog, Бизнес-модели] Проектируем мультипарадигменный язык программирования. Часть 6 — Заимствования из SQL
- [Программирование, Разработка мобильных приложений, Dart, Flutter] Работа с асинхронностью в Dart
- [Программирование, Машинное обучение] Распознавание речи с помощью инструментов машинного обучения
Теги для поиска: #_ruby, #_programmirovanie (Программирование), #_ruby_on_rails, #_funktsionalnoe_programmirovanie (Функциональное программирование), #_metaprogrammirovanie (метапрограммирование), #_ruby_on_rails, #_dry, #_ruby, #_metaprogramming, #_avtomatizatsija (автоматизация), #_ruby, #_programmirovanie (
Программирование
), #_ruby_on_rails, #_funktsionalnoe_programmirovanie (
Функциональное программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 21:58
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем привет! В этой статье хочу рассказать про метапрограммирование на примере реальной часто встречающейся проблемы.Когда кто то говорит про метапрограммирование у олдскульного кодировщика случается приступ ярости.И на это есть причины так и на большом проекте может показаться безумием использовать метапрограммирование, так как код становится очень сложным для чтения. А если в проект включится специалист со стороны, то он и подавно ничего не разберет в этом мета-коде.Но все не так просто, как говорится - нет плохих инструментов. В этой статьей я постараюсь показать на реальном рабочем примере как метапрограммирование поможет сделать ваш код чище, избавит от рутинных повторений. И заставит порадоваться мета-магии.Справка из википедиМетапрограммирование — вид программирования, связанный с созданием программ, которые порождают другие программы как результат своей работы (в частности, на стадии компиляции их исходного кода), либо программ, которые меняют себя во время выполнения (самомодифицирующийся код). Первое позволяет получать программы при меньших затратах времени и усилий на кодирование, чем если бы программист писал их вручную целиком, второе позволяет улучшить свойства кода (размер и быстродействие).На этом заканчивается вступление. Теперь хочу перейти к практической части и рассказать про суть проблемыДальше речь пойдёт о языке ruby и фраймворке Rails в частности. В руби есть рефлексия и большие возможности для метапрограммирования. Большинство гемов, модулей и фреймворков созданы при помощи мощных инструментов рефлексии. Photo by Joshua Fuller on UnsplashКто писал что то на Rails скорее всего сталкивался с таким гемом как Rails Admin, если вы в своих проектах на рельсах до сих не пробовали использовать его или аналоги категорически рекомендую это сделать, так как практически из коробки вы получите полноценную CMS для вашей системы.Так вот там есть неприятная особенность проблема с has_one association.Они не обрабатываются автоматически и у вас не будет возможности редактировать вашу has_one связь. Выглядит это примерно как на рисунке 1. Вместо селекта, просто ссылка на чертеж. рисунок 1Посмотрим на внутренности вот так выглядит модель нашего техпроцесса. У неё есть has_one связь с чертежом (draft) и хотелось бы иметь возможность редактировать её через CMS. class TechProcess < ApplicationRecord
include MdcSchema has_many :executor_programs, inverse_of: :tech_process, foreign_key: :barcode_tech_process, primary_key: :barcode validates :barcode, presence: true validates :barcode, uniqueness: true has_one :draft2tech_process, dependent: :destroy has_one :draft, through: :draft2tech_process has_many :tech_process2tech_operations has_many :tech_operations, through: :tech_process2tech_operations end def draft_id
self.draft.try :id end def draft_id=(id) self.draft = Draft.find_by_id(id) end Ничего сложного, но что если у нас 15 моделей с одной или даже несколькими связями has_one это что придется повторить эти гетеры сеттеры в каждом месте? Такое конечно можно сделать, но тогда нарушается очень важный принцип DRY. Ну и плюс природная лень не позволяет так расточительно писать много строк кода. Так что надо найти способ автоматизировать этот процесс и тут на помощь приходит метапрограммирование.Meta решениеЧто же приступим self.reflect_on_all_associations(:has_one).each do |has_one_association|
define_method("#{has_one_association.name}_id") do self.send(has_one_association.name).try :id end define_method("#{has_one_association.name}_id=") do |id| self.send("#{has_one_association.name}=",has_one_association.klass.find_by_id(id)) end end require 'active_support/concern'
module HasOneHandler extend ActiveSupport::Concern included do self.reflect_on_all_associations(:has_one).each do |has_one_association| define_method("#{has_one_association.name}_id") do self.send(has_one_association.name).try :id end define_method("#{has_one_association.name}_id=") do |id| self.send("#{has_one_association.name}=",has_one_association.klass.find_by_id(id)) end end end end class TechProcess < ApplicationRecord
include MdcSchema include HasOneHandler has_many :executor_programs, inverse_of: :tech_process, foreign_key: :barcode_tech_process, primary_key: :barcode validates :barcode, presence: true validates :barcode, uniqueness: true has_one :draft2tech_process, dependent: :destroy has_one :draft, through: :draft2tech_process has_many :tech_process2tech_operations has_many :tech_operations, through: :tech_process2tech_operations end =========== Источник: habr.com =========== Похожие новости:
Программирование ), #_ruby_on_rails, #_funktsionalnoe_programmirovanie ( Функциональное программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 21:58
Часовой пояс: UTC + 5