ActiveResource, prefix и вложенные ресурсы

от автора


Предыстория

Я программист с очень небольшим стажем (недавно накопилось около года в трудовой).
Около полугода назад я начал работать с Ruby (вне Rails) и сразу же познакомился с Active Resource и Redmine.

Это был очень интересный опыт, сейчас мне кажется, что Ruby — практически идеальный язык (именно язык, я не задаюсь вопросом потребления памяти и скорости работы).

Однако в нем весьма много магии, которую бывает сложно понять, когда читаешь исходный код сколько-нибудь крупных проектов (ActiveResource я отношу к ним, хотя по сравнению с rails, частью которого он является, этот гем кажется каплей в море).

Проблема

Проект заключался в создании консольной утилиты (Thor-based), работающей с Redmine REST API и предоставляющей всякие ништяки (кстати, вдохновленный проектом, я в данный момент работаю над подобной утилитой, частично дублирующей функционал: https://github.com/Nondv/redmine_cli).

Если посмотреть на документацию по Versions или Issue Relations (http://www.redmine.org/projects/redmine/wiki/Rest_IssueRelations), то можно обратить внимание, что для получения списка отношений используется адрес вида issues/<id>/relations.xml, а для конкретного объекта — relations/<id>.xml.

Собственно, для того, чтобы получить список, мы находим решение в виде использования prefix:

class Relation < ActiveResource::Base   self.user = 'yet another apikey'   self.password = 'we dont need password when using redmine apikey'   self.site = 'www.yet-another-redmine.com'    # Вот и он!   self.prefix = '/issues/:issue_id/' end  Relation.all params: { issue_id: 1 } 

Все, вроде, шикарно и работает. Но что если нам нужно получить (ну или удалить) отдельный объект?
Relation.find(id) выдает исключение ActiveResource::MissingPrefixParam: project_id prefix_option is missing, что вполне разумно, мы ведь указали, что должны обращаться по адресу с префиксом.

Решение

Redmine REST не предусмотрел возможность получить отдельный объект во вложенных ресурсах.

Лично я, проработв чуть больше пары недель с руби, и замучив гугл вопросами, смог родить решение, в котором для получения списка использовался анонимный класс. Сейчас не смогу воспроизвести, но навскидку примерно так:

class Issue < ActiveResource::Base   self.user = 'yet another apikey'   self.password = 'we dont need password when using redmine apikey'   self.site = 'http://www.yet-another-redmine.com'    # кстати, в случае с relations, их можно получить с помощью параметра include,   # но тогда по незапомненным мною причинам пришлось изворачиваться   def relations      tmp_class = Class.new(ActiveResource::Base) do           ...           self.site = "http://www.yet-another-redmine.com/issues/#{id}/"           self.element_name = 'relation'       end   end end 

Вы уже чувствуете этот странный запах, верно?
В общем, решение работало и в общем-то было принято, т.к. альтернатив я не смог предложить.

В личном проекте (ссылка выше) я получил возможность исправить это недоразумение (надеюсь, в лучшую сторону). Решение заключается в переопределении метода ActiveResource::Base.element_path. На примере Version:

 class Version < ActiveResource::Base   ...   self.prefix = '/projects/:project_id/'    #   # Собственно, код был скопирован прямо из источника:   # https://github.com/rails/activeresource/blob/master/lib/active_resource/base.rb#L760   # и немного отредактирован   #   # Praise Open Source!   #   def self.element_path(id, _prefix_options = {}, query_options = nil)     "/versions/#{URI.parser.escape id.to_s}#{format_extension}#{query_string(query_options)}"   end end  class Project < ActiveResource::Base   ...    def versions     Version.all params: { project_id: id }   end end

Заключение

Собственно, к чему все это? Думаю, люди, которые разбираются в ActiveResource скажут, что это очевидное решение.

Суть в том, что я, будучи только погруженным во все это, столкнулся с проблемой и не смог ее решить, даже с помощью Всезнающего. В чем была моя ошибка? В том, что я побоялся немного разобраться в исходном коде и не захотел штудировать документацию (http://www.rubydoc.info просто находка!), в которой даже посмотрев summary можно было подобрать что-нибудь для решения задачи без привлечения нано-ядерно-магическо-костыльных технологий.

Надеюсь, что если кто-то окажется в моем положении, то он не станет повторять моих ошибок.
Буквально неделю назад видел утверждение, что Ларри Уолл считает лень одним из главных достоинств программиста. Не уверен, как было написано в оригинале кэмел-бука (а это именно оттуда, полагаю), но в переводе он использовал слово "добродетель", а не "достоинство".

Каким ленивым бы я ни был, это не сделает меня хорошим программистом. Лень далеко не всегда помогает находить решение.

P.S. кажется, пост, подоходящий под формат личного мини-блога, слишком сильно растолстел в процессе написания.

ссылка на оригинал статьи https://habrahabr.ru/post/279727/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *