Классу Ruby внутри модуля необходим доступ к пространству имен класса

Я написал небольшое приложение на Ruby и теперь хочу добавить к нему простой API, чтобы другие приложения могли выполнять некоторые базовые запросы к нему.

Я смотрю на сенатру, так как он кажется очень легким и простым. Но я подозреваю, что вместо добавления sinatra в мое приложение для предоставления API, я, вероятно, должен был написать приложение как приложение sinatra.

Моя проблема на данный момент заключается в том, что я могу встать на "тонкий" сервер с простым "Hello!" конечная точка в нем, определяя его в модуле, который я добавляю в объект приложения, например так:

module ApMessageIoModule

  require 'sinatra/base'
  require 'thin'

  def start_server
    Thread::abort_on_exception
    Thread.new do
      ApiServer.run!
    end
  end

  class ApiServer < Sinatra::Base

    configure do
      server.threaded = settings.threaded if server.respond_to? :threaded=
    end

    get "/" do
      "Hello!"
    end

  end
end

Вызвав метод start_server(), я могу запустить сервер как фоновый поток. Но теперь у меня есть конечная точка, я хочу, чтобы она ссылалась на методы в классе, к которому я добавил модуль.

Итак, как я могу получить доступ к пространству имен включающего класса?

например

Класс, к которому я добавил модуль, называется StateMachine, и у него есть метод:

  # Query the state table for a particular state
  def query_status(flag)
    execute_sql_query(
      "select status from state where state_flag = '#{flag}';"
    )[0][0]
  end

Как я могу вызвать этот метод из маршрута "get" выше?

Я нашел другой пост, который, кажется, связан с этим -

Доступ к пространству имен, содержащему класс, из модуля

Но это немного над моей головой, и мне не повезло, пытаясь адаптировать приведенный пример кода.

Чтобы попытаться уточнить это, у меня есть класс, показанный здесь, урезанный:

class StateMachine

  # Query the state table for a particular state
  def query_status(flag)
    execute_sql_query(
      "select status from state where state_flag = '#{flag}';"
    )[0][0]
  end

end

И этот класс включает в себя модуль ApMessageIoModule, показанный выше.

Класс StateMachine был создан здесь, в моем модульном тесте:

  # Confirm that SM can load one of our modules
  def test_it_loads_user_modules
    sm = StateMachine.new
    sm.include_module('ApMessageIoModule')
    sm.start_server
    sleep(600)
    sm.stop_server
    assert_equal(sm.test_method, 'Test String')
  end

В настоящее время я долго сплю, чтобы вручную подтвердить, что сервер запущен, перейдя к конечной точке в моем браузере. Это показывает мне сообщение Hello, ожидаемое в конечной точке.

Метод состояния запроса взаимодействует с базовой базой данных sqlite3, которая была создана и заполнена различными методами, вызываемыми из метода инициализации для StateMachine. Не показано здесь для краткости.

Поэтому я хочу, чтобы этот метод вызывался в экземпляре StateMachine из класса ApiServer в модуле ApMessageIoModule, если это имеет смысл.

На самом деле я думаю, что этот блокнот делает это более ясным:

require 'sinatra/base'
require 'thin'

module MyInclude
  class SinatraServer < Sinatra::Base
    get '/' do
      test_method # This call fails with message shown below
    end
    get '/exit' do
      exit!(0)
    end
  end

  def startup
    Thread.new do
      SinatraServer.run!
    end
  end
end

class TopLevel
  include MyInclude

  def test_method
    puts "TEST"
  end

end

tl = TopLevel.new
tl.startup
sleep(600)


# Error message reads:
# NameError at /
# undefined local variable or method `test_method' for
# #<MyInclude::SinatraServer:0x00007fd002ac41d8>
# file: sinatra_server.rb location: block in <class:SinatraServer> line: 7

1 ответ

Ваш query_status это чистая функция, которая не зависит ни от чего. Поместите его в полностью отделенный модуль, сделайте его функцией модуля и вызовите его как функцию модуля отовсюду:

module Helpers
  def query_status(flag)
    ...
  end
  module_function :query_status
end

сейчас в вашем get:

get "/" do
  "Hello, " + Helpers.query_status('yes')
end

Отвечая на поставленный вопрос: поскольку эта функция не мешает каким-либо данным (является чистой функцией), сделайте ее функцией уровня класса ApiServer и назовите его по имени:

class ApiServer < Sinatra::Base
  def self.query_status(flag)
    flag.to_s
  end

  get "/" do
    "Hello, " + query_status('yes')
  end
end
Другие вопросы по тегам