Использование Grape для создания API только с объектами Ruby вместо базы данных или Rails

Я пытаюсь использовать Grape для создания API, используя только объекты Ruby. Я не хочу использовать базу данных/Rails/ActiveSupport/и т. д. — только Rack, Ruby и Grape.

Я определил класс для Directory, с которым я хочу взаимодействовать через API. Таким образом, Directory#sort_by("last_name") возвращает данные JSON со списком объектов People в моем файле Directory. У меня также есть метод Directory#create_person(attributes), который принимает строку и использует ее для добавления Person объектов в Directory. Каталог заполняется людьми при создании.

Я новичок в работе со Rack and Grape, поэтому не знаю, где/как создать свой объект Directory и сделать его доступным через GET/POST в моем классе API Grape. Использование переменной класса внутри этого класса работает, т.е.:

module API
  class DirectoryAPI < Grape::API
    format 'json'

    @@directory = Directory.new("1.txt", "2.txt", "3.txt")

    get 'last_name' do
      @@directory.sort_by("last_name")
    end
  end
end

но использование переменных класса просто кажется неправильным. Есть ли лучший/более чистый способ создать мой объект Directory? Возможно, внутри моего файла config.ru? Или я могу как-то сделать это через метод класса внутри Directory?


person user3809888    schedule 02.08.2015    source источник


Ответы (2)


Вам нужен синглтон:

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

К сожалению, Ruby плохо работает с синглтонами. Но вы можете использовать «класс, состоящий только из методов класса», вторую стратегию, отстаиваемую в этом статья.

Я полагаю, что вы работаете над заданием по программированию, которое я выполнил несколько месяцев назад. В своем ответе я использовал «класс, состоящий только из методов класса» под названием API::Store. Вот вывод из rspec -fd:

API::Store
  ::add
    adds record to store
    appends data line to file
  ::has_line?
    instantiates a record from the data line
    without the record in the store
      should equal false
    with the record in the store
      should equal true
  ::new
    should raise NoMethodError
  ::records
    with original file
      on initial access
        should eq Records
      on subsequent access
        should eq Records
    when file replaced
      should eq OtherRecords

Finished in 0.07199 seconds (files took 2.68 seconds to load)
9 examples, 0 failures

Обратите внимание, что Store нельзя создать экземпляр; он выдает NoMethodError, если вы попытаетесь. Впрочем, это не проблема. В конечной точке Grape вы можете вызвать Store.records для доступа к данным.

Что касается сортировки записей, то это нужно делать в другом классе. Почему Store или Directory должны сортировать данные в своих файлах?

Наконец, вы спросили, где делать первоначальную подготовку (не инициализацию, конечно). Вы можете подготовить свой синглтон в config.ru, чтобы он был готов к запуску приложения:

# config.ru

# load application code here

file = File.open('data/records.txt', 'a+')
at_exit { file.close }

API::Store.file = file
run API::Base

В инструкциях говорится: "Вы можете использовать любые ресурсы, необходимые для его выполнения". на Stack Overflow разрешено. Если вы выполняете это задание для подачи заявления о приеме на работу, пожалуйста, укажите это, когда будете задавать вопросы, потому что будет справедливо, если те, кто отвечает, будут проинформированы. Было бы разумно также упомянуть на собеседовании, что вам помогли с SO. Удачи и счастливого кодирования.

person amar47shah    schedule 02.08.2015
comment
Это выглядит хорошо, но я не думаю, что это отвечает на вопрос. OP спрашивает о коде в контроллере, а не о том, как создать класс управления данными. Чего вам не хватает, так это показать, как он будет использоваться в контроллере. Возможно, строка типа Store.records вместо строки OP @@directory.sort_by("last_name")? - person Neil Slater; 02.08.2015
comment
Спасибо, @NeilSlater. Я считаю, что основной вопрос ОП был не в том, что мне делать с моим контроллером? но как мне получить доступ к моим данным? Тем не менее, я отредактировал свой ответ, чтобы сразу перейти к делу, как о синглтонах, так и о предстоящем обсуждении проблемы кодирования. - person amar47shah; 02.08.2015

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

Существует множество допустимых подходов, но я мог бы создать новый одноэлементный объект для представления вашего источника данных и связать его с вашими жестко запрограммированными данными, как если бы это были таблицы. Конечный результат здесь будет немного похож на использование Sequel (но вы можете использовать любой другой шаблон, который вы предпочитаете):

inline_data.rb

module InlineDB
  TABLES = Hash[
     :directory => Directory.new("1.txt", "2.txt", "3.txt")
  ]

  def self.[] table_name
    TABLES[table_name]
  end
end

app.rb

require_relative 'inline_data' # actual path may be more structured
module API
  class DirectoryAPI < Grape::API
    format 'json'

    get 'last_name' do
      InlineDB[:directory].sort_by("last_name")
    end
  end
end
person Neil Slater    schedule 02.08.2015