Async_Sinatra in Rails: async actions can't write to shared session - ruby-on-rails

I have a Sinatra class in a Rails project. It uses eventmachine and async_sinatra to make asynchronous calls to external sites. I'd like to write to a session object (ideally, the same one that Rails is using), but so far I can only:
write to a separate session object from Rails' (by default, Sinatra names its session something different from Rails)
write to the same session for synchronous calls only
When I make asynchronous calls, sessions written in the async_sinatra code don't get pushed out to the client machine. I suspect one of two things is happening:
The header has already been sent to the client and the local variable storing the session (in Sinatra) will be flushed out at the end of the action. The client would never see a request from the server to save this data to a cookie.
The header is being sent to the client, but Rails immediate sends another, instructing the client to write to the cookie what Rails has stored in its session variable, overwriting what Sinatra wrote.
Either way, I'd like to just get simple session functionality in both Sinatra and Rails. An explanation of what I'm doing wrong would also be nice :)
A full working copy of the code is on github, but I believe the problem is specifically in this code:
class ExternalCall < Sinatra::Base
use ActionDispatch::Session::CookieStore
register Sinatra::Async
get '/sinatra/local' do
session[:demo] = "sinatra can write to Rails' session"
end
aget '/sinatra/goog' do
session[:async_call]="async sinatra calls cannot write to Rails' session"
make_async_req :get, "http://www.google.com/" do |http_callback|
if http_callback
session[:em_callback] = "this also isn't saving for me"
else
headers 'Status' => '422'
end
async_schedule { redirect '/' }
end
end
helpers do
def make_async_req(method, host, opts={}, &block)
opts[:head] = { 'Accept' => 'text/html', 'Connection' => 'keep-alive' }
http = EM::HttpRequest.new(host)
http = http.send(method, {:head => opts[:head], :body => {}, :query => {}})
http.callback &block
end
end
end
EDIT 7/15:
Changed code on Github to include Async-Rack. Async-sinatra can write to sessions when they are not shared with Rails. Compare the master and segmented_sessions branches for behavior difference. (Or on the master branch, change use ActionDispatch::Session::CookieStore to enable :sessions)

This is because async_sinatra uses throw :async by default, effectively skipping the session middleware logic for storing stuff. You could override async_response like that:
helpers do
def async_response
[-1, {}, []]
end
end

Related

How can I use a function defined in a controller in rails in a rake task?

I have an API function defined in my rails controller, and another to my database that I create using scaffold.
`
def function
#results = HTTParty.get( $baseurl + "/extention", :headers => {
$apikey => $apivalue,
"Content-Type" => "application/json"
})
render json: #results.body
end
`
I have defined a rake task that is executed with clockwork but to make it work in the current development environment I had to use httparty to call it internally and work with the received hash.
`
def rake_function
#results = HTTParty.get("http://localhost:3000/controller/extension/")
return #results.parsed_response
end
In addition, my task makes a post and a put when the execution is finished and I must also use a httparty for that.
if (!datExist)
#response = HTTParty.post("http://localhost:3000/controller/extension/",
body: eodData
)
else (datExist)
checkId = dbData.select {|x| x["date_time"] == yesterday}
id = checkId[0]["id"].to_s
#response = HTTParty.put("http://localhost:3000/controller/extension/" + id,
body: eodData
)
end
`
I know it's not the optimal way so I would like to be able to execute the function already defined in my controllers in my rake task
You don't.
The only public methods of your controller should be the "actions" that are called by the Rails router when it responds to HTTP requests.
Controllers are Rack applications and have a hard dependency on a incoming HTTP request - they just don't work outside of that context like in Rake task and using your controllers as a junk drawer leads to the "Fat Controller" anti-pattern.
Controllers already have tons of responsibilites - they are basically stiching your entire application together by passing user input to models and models to views. Don't give them more jobs to do.
Its a very easy problem to avoid by simply moving your API calls into their own class. One way of designing this is through client classes which are just resposible for communicating with the API:
class MyApiClient
include HTTParty
format :json
base_url $baseurl # Code smell - avoid the use of globals
attr_reader :api_key, :api_value
def initalize(api_key:, api_value:)
#api_key = api_key
#api_value = api_value
end
def get_extension
# I don't get why you think you need to dynamically set the header key
self.class.get('extension', headers: { api_key => api_value })
end
end
This lets you simply re-use the code between your controller and rake task and isolates the code touching the application boundy which avoids creating a tight coupling between your application and the external collaborator.
HTTParty also really shines when you actually use it for object oriented and not proceedural code.

How to find current abstract route in Rails middware

Rails version: '~> 4.2.7.1'
Spree version: '3.1.1'
TlDr:
How do I get route as /api/products/:id or controller and action of that route in a middleware of Rails 4 application.
Details:
I am adding a middleware in my rails app which is similar to gem scout_statsd_rack. This adds following middleware to rails app to send metrics via statsd:
def call(env)
(status, headers, body), response_time = call_with_timing(env)
statsd.timing("#{env['REQUEST_PATH']}.response", response_time)
statsd.increment("#{env['REQUEST_PATH']}.response_codes.#{status.to_s.gsub(/\d{2}$/,'xx')}")
# Rack response
[status, headers, body]
rescue Exception => exception
statsd.increment("#{env['REQUEST_PATH']}.response_codes.5xx")
raise
end
def call_with_timing(env)
start = Time.now
result = #app.call(env)
[result, ((Time.now - start) * 1000).round]
end
What I want is to find current route in the middleware so that I can send metrics specific to each route.
I tried approach described here, which tells env['PATH_INFO'] can provide path, which it does, but it gives with URL params like this: /api/products/4 but what I want is /api/products/:id as my puropose is to track performance of /api/products/:id API.
env['REQUEST_PATH'] and env['REQUEST_URI'] also gives same response.
I tried answer provided here and here:
Rails.application.routes.router.recognize({"path_info" => env['PATH_INFO']})
or like this
Rails.application.routes.router.recognize(env['PATH_INFO'])
But it gave following error:
NoMethodError (undefined method path_info' for {"path_info"=>"/api/v1/products/4"}:Hash):
vendor/bundle/gems/actionpack-4.2.7.1/lib/action_dispatch/journey/router.rb:100:infind_routes'
vendor/bundle/gems/actionpack-4.2.7.1/lib/action_dispatch/journey/router.rb:59:in recognize'
vendor/bundle/gems/scout_statsd_rack-0.1.7/lib/scout_statsd_rack.rb:27:in
call'
This answer discusses request.original_url, but How do I access variable request, I think it should be same as env but not able to get route as want from this.
Edit #1
You can see the sample repo here, with code of rails middleware here, Setup of this can be done as stated in README and than this API can be hit: http://localhost:3000/api/v1/products/1.
Edit #2
I tried approach given by #MichałMłoźniak like following:
def call(env)
(status, headers, body), response_time = call_with_timing(env)
request = ActionDispatch::Request.new(env)
request = Rack::Request.new("PATH_INFO" => env['REQUEST_PATH'], "REQUEST_METHOD" => env["REQUEST_METHOD"])
Rails.application.routes.router.recognize(request) { |route, params|
puts "I am here"
puts params.inspect
puts route.inspect
}
But I got following response:
I am here
{}
#<ActionDispatch::Journey::Route:0x007fa1833ac628 #name="spree", #app=#<ActionDispatch::Routing::Mapper::Constraints:0x007fa1833ace70 #dispatcher=false, #app=Spree::Core::Engine, #constraints=[]>, #path=#<ActionDispatch::Journey::Path::Pattern:0x007fa1833acc90 #spec=#<ActionDispatch::Journey::Nodes::Slash:0x007fa1833ad230 #left="/", #memo=nil>, #requirements={}, #separators="/.?", #anchored=false, #names=[], #optional_names=[], #required_names=[], #re=/\A\//, #offsets=[0]>, #constraints={:required_defaults=>[]}, #defaults={}, #required_defaults=nil, #required_parts=[], #parts=[], #decorated_ast=nil, #precedence=1, #path_formatter=#<ActionDispatch::Journey::Format:0x007fa1833ac588 #parts=["/"], #children=[], #parameters=[]>>
I have pushed the changes as well here.
You need to pass ActionDispatch::Request or Rack::Request to recognize method. Here is an example from another app:
main:0> req = Rack::Request.new("PATH_INFO" => "/customers/10", "REQUEST_METHOD" => "GET")
main:0> Rails.application.routes.router.recognize(req) { |route, params| puts params.inspect }; nil
{:controller=>"customers", :action=>"show", :id=>"10"}
=> nil
The same will work with ActionDispatch::Request. Inside middleware, you can easily create this object:
request = ActionDispatch::Request.new(env)
And if you need more information about recognized route, you can look into that route object that is yielded to block, by recognize method.
Update
The above solution will work for normal Rails routes, but since you only have spree engine mounted you need to use different class
request = ActionDispatch::Request.new(env)
Spree::Core::Engine.routes.router.recognize(request) { |route, params|
puts params.inspect
}
I guess the best would be find a generic solution that works with any combination of normal routes and engines, but this will work in your case.
Update #2
For more general solution you need to look at the source of Rails router, which you can find in ActionDispatch module. Look at Routing and Journey modules. What I found out is that the returned route from recognize method can be tested if this is a dispatcher or not.
request = ActionDispatch::Request.new(env)
Rails.application.routes.router.recognize(req) do |route, params|
if route.dispatcher?
# if this is a dispatcher, params should have everything you need
puts params
else
# you need to go deeper
# route.app.app will be Spree::Core::Engine
route.app.app.routes.router.recognize(request) do |route, params|
puts params.inspect
}
end
end
This approach will work in case of your app, but will not be general. For example, if you have sidekiq installed, route.app.app will be Sidekiq::Web so it needs to be handled in different way. Basically to have general solution you need to handle all possible mountable engines that Rails router supports.
I guess it is better to build something that will cover all your cases in current application. So the thing to remember is that when initial request is recognized, the value of route yielded to black can be a dispatcher or not. If it is, you have normal Rails route, if not you need to recursive check.

How do I modify the request object before routing in Rails in a testable way?

So, I have a situation where I need to determine something about a request before it is dispatched to any of the routes. Currently, this is implemented using several constraints that all hit the database, and I want to reduce the database hit to one. Unfortunately, doing it inline in routes.rb doesn't work, because the local variables within routes.rb don't get refreshed between requests; so if I do:
# Database work occurs here, and is then used to create comparator lambdas.
request_determinator = RequestDeterminator.new(request)
constraint(request_determinator.lambda_for(:ninja_requests)) do
# ...
end
constraint(request_determinator.lambda_for(:pirate_requests)) do
# ...
end
This works great on the first request, but then subsequent requests get routed as whatever the first request was. (D'oh.)
My next thought was to write a Rack middleware to add the "determinator" to the env hash, but there are two problems with this: first, it doesn't seem to be sticking in the hash at all, and specs don't even go through the Rack middleware, so there's no way to really test it.
Is there a simple mechanism I'm overlooking where I can insert, say, a hook for ActionDispatch to add something to the request, or just to say to Rails routing: "Do this before routing?"
I am using Rails 3.2 and Ruby 1.9.
One way to do this would be to store your determinator on the request's env object (which you have since ActionDispatch::Request is a subclass of Rack::Request):
class RequestDeterminator
def initialize(request)
#request = request
end
def self.for_request(request)
request.env[:__determinator] ||= new(request)
end
def ninja?
query_db
# Verify ninjaness with #request
end
def pirate?
query_db
# Verify piratacity with #request
end
def query_db
#result ||= begin
# Some DB lookup here
end
end
end
constraint lambda{|req| RequestDeterminator.for_request(req).ninja? } do
# Routes
end
constraint lambda{|req| RequestDeterminator.for_request(req).pirate? } do
# Routes
end
That way, you just instantiate a single determinator which caches your DB request across constraint checks.
if you really want to intercept the request,try rack as it is the first one to handle request in any Rails app...refer http://railscasts.com/episodes/151-rack-middleware to understand how rack works....
hope it helps.

Accessing a variable of one method in another in ruby on rails

I am facing an issue with accessing a particular variable of a method say A , in another method say B in the controller.. The size of the object(variable) is too big since it contains the results of a service call made.. My usecase is like on selecting an option from a drop down box, it redirects to a method B in controller and the same object(variable) should be parsed. How can I access the variable in the other method?
I tried storing in a cookie and since the size is too big I am getting Cookie Overflow exception. I am not using a DB. So I guess using memcache won't work. Also tried storing it as hidden field in view and passed its value as a data through ajax call. But I am getting it as a string. Tried to specify datatype as json and several other ways.. but of no use..Using ##var also din work..Not sure why..
Code:
On change of the drop down:
$(document).ready(function(){
$('#filter_service').change(function() {
$.ajax({type: "GET",
url: "/device_troubleshootings/query_operation",
data: { filter_service: $('# filter_service').val()},
});
});
});
Service call:
def log_results
//Service call
#get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
#dsn_result = #get_log_results.logs_result_obj_list
end
Here, I am trying to access #dsn_result object in "/device_troubleshootings/query_operation” method.
Please suggest me ways to access the variable.
MVC
I think you're getting confused with how Rails should work
Remember, Rails (which is just a framework for Ruby) is built on the "MVC" programming pattern. This means each time you send a request to your Rails application, it has to be handled by a single controller#action which you will then allow you to pull the relevant data from your models
The problem you have is you're trying to load multiple controller methods, and pass the same data to both. This might work in Ruby, but not Rails (Rails is stateless):
--
Model
The correct way to handle this type of setup is by creating another request for your application, which will load another controller#action, allowing you to access the data you need
As demonstrated by the MVC diagram above, each time you send a request to Rails, it's basically a new request. This means that unless you've persisted your data in the likes of a cookie, you'll need to load the data from the model.
The problem you have is you're trying to store an entire data-set in the front-end of your system. This issue is very bad, as not only is it inefficient, but it goes against the MVC pattern completely.
You'll be much better storing the bare-minimum data set you need in the front-end (ids or similar), which you will then be able send to your controller via ajax; building a new data-set from
--
Class Variables
You mentioned you tried to declare some ##class variables to no avail. The problem with this is that the class vars will only be available for an instance of a class.
As mentioned, since Rails is stateless, the class variables won't persist between requests (how can they?). I think you know this already, considering you've been trying to use cookies to store your data
The way to resolve this is to rebuild the data each time from the model (as detailed above)
Solution
The solution for you is to "go stateless"
Here's how:
Treat Method A and Method B as completely separate "ACTIONS"
When using these actions, you need to consider the smallest piece of data to pass between the two
To load Method B, you need to send a new request from your browser (as if you've never loaded Method A before)
Your method_a can be handled in the "standard" way:
#config/routes.rb
resources :your_controller do
collection do
get :method_a
get :method_b
end
end
This will mean that you can load method_a relatively simply:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
def method_a
#get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
#dsn_result = #get_log_results.logs_result_obj_list
end
end
As you know, the #dsn_result will not persist through to the next request.
There are two ways to resolve this (set a CONSTANT -- if you're pulling from an API, this will give you a single call -- or use a before_action to set the variable for as many actions as you need). I'll detail both for you:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
before_action :set_log_data
def method_a
end
def method_b
end
private
def set_log_data
#get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
#dsn_result = #get_log_results.logs_result_obj_list
end
end
This will work if you pull data from your own data-set (using the models), however, the better way to do this in your case will likely be to set a constant (considering, of course, that you don't want the data to change):
#config/initializers/dsn_result.rb
get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
DSN_RESULT = get_log_results.logs_result_obj_list
In my case I solved with global variable $my_global_var
So my files look like this
routes.rb
Rails.application.routes.draw do
resources :pages
root 'pages#index'
post 'pages/test'
end
pages_controller.rb
class PagesController < ApplicationController
def firstaction
$my_global_var = "My global var"
puts $my_global_var
end
def secondaction
puts $my_global_var
end
end
index.html.erb
<%= button_to 'Test', pages_test_path, method: :post %>

How to use transactional fixtures in test unit

When i run test cases, i don't want to store records in my databases. How can i achieve it.
Here is my code :-
class Sample << Test::Unit::TestCase
def setup
# code
end
def teardown
# code
end
def test_sample
# code
end
end
Am using the following gem:-
gem 'test-unit'
to run tests and api call for GET/POST/PUT/DELETE methods to create/delete records in database.
I assume what you mean is that the objects you are using are not persisted in a local database, but rather in a remote system accessed via an API. That is, when you save an objects attributes, they are sent to a remote server via an API call.
Have a look at webmock. It works well with test/unit and test/minitest (which is test/unit on steroids). Basically you define the http call that should result from an action, and pass that to webmock. Then when the action is tested webmock will intercept the http call, and return a mocked response. If the action call is different to the one you defined, web mock will generate an error that will cause the test to fail.
So say on creating a sample, you expect a POST to example.com/samples with a sample attribute :foo set to 'bar', you could write a test like this:
def test_create_sample
data = 'bar'
sample = Sample.new
sample.foo = data
stub_request(:post, "example.com/samples/1").
with(:body => {:sample => {foo: data}})
assert sample.save
end
This also assumes that your save action checks the api response, and returns true if everything is OK.

Resources