0

I have written a small application in Ruby and now want to add a simple API to it to allow other applications to run some basic queries against it.

I've been looking at senatra, as it seems very lightweight and simple. But I suspect that rather than add sinatra to my app to provide the API, I should probably have written the application as a sinatra app.

My problem at the moment is that I can stand up a "thin" server with a simple "Hello!" endpoint in it, by defining it in a module that I add to my application object, like this:

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

By calling the start_server() method I can start the server as a background thread. But now I have the endpoint, I want it to refer to methods in the class I have added the module to.

So how can I access the namespace of the enclosing class?

e.g.

The class I have added the module to is called StateMachine and it has a method:

  # 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

How can I call this method from within the "get" route above?

I have found another post that seems to relate to this -

Accessing a class's containing namespace from within a module

But it's a bit above my head and I've had no luck trying to adapt the code example given.

To try and clarify this, I have a class, shown here stripped down:

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

And that class includes the module ApMessageIoModule shown above.

The StateMachine class has been instantiated, here in my unit test:

  # 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

I currently have a long sleep in there to allow me to manually confirm that the server has started by going to the endpoint in my browser. Doing so shows me the Hello message expected on the endpoint.

The query status method talks to an underlying sqlite3 db that has been created and populated by various methods called from the initialise method for StateMachine. Not shown here for brevity.

So what I want to do is cal that method in the StateMachine instance from within the ApiServer class within the ApMessageIoModule module if that makes sense.

Actually I think this scratch pad makes it clearer:

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
Kaliklipper
  • 355
  • 5
  • 19

1 Answers1

0

Your query_status is a pure function that has no dependencies on anything. Put it into the completely separated module, make it a module function and call it as a module function from everywhere:

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

now in your get:

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

Answering the question stated: since this function does not interfere with any data (is a pure function,) make it a class-level function of ApiServer and call it by it’s name:

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

  get "/" do
    "Hello, " + query_status('yes')
  end
end
Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • Hi, thanks for the reply. Not sure what you mean by a pure function. The method belongs to the enclosing class, StateMachine, and talks to an underlying database. I'll try to add the code to see if it makes that clearer – Kaliklipper Jan 12 '18 at 17:47
  • How can we access '/' endpoint when we write it in ApiServer class? – jab Oct 14 '21 at 08:43
  • @jab I am not sure I understood the question. – Aleksei Matiushkin Oct 14 '21 at 09:06
  • -- class ApiServer < Sinatra::Base def self.query_status(flag) flag.to_s end get "/" do "Hello, " + query_status('yes') end end --- Here, How can we access the Get '/' call? – jab Oct 14 '21 at 16:24