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
    
          tm_class.all
      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. кажется, пост, подоходящий под формат личного мини-блога, слишком сильно растолстел в процессе написания.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое