Sinatra app as rack middleware TimesOut Rails 3 - ruby-on-rails-3

While in the Rails development environment, I am attempting to add a Sinatra app as a middleware. The Sinatra app uses the geoip gem that processes a user's ip address and returns json with their city.
I can view the returned json by going directly to the example url in the browser or using curl in the command line, http://local.fqdn.org/geoip/locate.json?ip=24.18.211.123. However when I attempt to call the url with wget from within a Rails controller, the Rails app stops responding often crashing my browser and my rails server wont exit using the control+C command.
Any clue to what is happening here? Why would going directly to the url in the browser return the proper response but my call in the controller results in a Time Out?
sinatra-geoip.rb
require 'sinatra'
require 'geoip'
require 'json'
# http://localhost/geoip/locate.json?ip=24.18.211.123
#
# {
# latitude: 47.684700012207
# country_name: "United States"
# area_code: 206
# city: "Seattle"
# region: "WA"
# longitude: -122.384803771973
# postal_code: "98117"
# country_code3: "USA"
# country_code: "US"
# dma_code: 819
# }
class GeoIPServer < Sinatra::Base
get '/geoip/locate.json' do
c = GeoIP.new('/var/www/mywebsite.org/current/GeoLiteCity.dat').city(params[:ip])
body c.to_h.to_json
end
end
routes.rb
mount GeoIPServer => "/geoip"
config/environments/development.rb
Website::Application.configure do
require "sinatra-geoip"
config.middleware.use "GeoIPServer"
...
end
controller
raw_geo_ip = Net::HTTP.get(URI.parse("http://#{geoip_server}/geoip/locate.json?ip=#{request.ip}"))
#geo_ip = JSON.parse(raw_geo_ip)

Our solution was difficult to find. We ended up finding a method in the sinatra source code call forward.
new sinatra-geoip.rb
class GeoIPServer < Sinatra::Base
if defined?(::Rails)
get '/properties.json' do
env["geo_ip.lookup"] = geo_ip_lookup(request.ip)
forward
end
end
def geo_ip_lookup(ip = nil)
ip = ip.nil? ? params[:ip] : ip
result = GeoIP.new('/var/www/mywebsite.org/current/GeoLiteCity.dat').city(ip)
result.to_h.to_json
end
end
Essentially, we removed the /geoip/locate.json route from the file and converted it to a simple method. We needed the geoip lookup to occur when the properties.json was being called, so a new param was added with the geoip information. Then we set the new param equal to #geo_ip variable in the controller.
New properties controller
if Rails.env.development? or Rails.env.test?
# Retrieves param set by sinatra-geoip middleware.
#geo_ip = JSON.parse(env["geo_ip.lookup"] || "{}")
else
# Production and staging code
end
Rather obscure problem and solution. Hopefully it will help someone out there. Cheers.

Related

How to pass sessions in Sinatra

I'm trying to implement sessions in my login process, but the session is not being passed from route to route while I'm using set :session_secret, SecureRandom.hex(64), but if I use set :session_secret, "secret" it works fine.
From my understanding I am not supposed to do that because it is not very secure.
Is there a way to use set :session_secret, SecureRandom.hex(64 and have it pass from route to route? Or, do I have to use set :session_secret, "secret"?
class MainController < Sinatra::Base
register Sinatra::ActiveRecordExtension
enable :sessions
#set :session_secret, SecureRandom.hex(64)
#set :session_secret, "secret"
#set :session_secret, "secretsession"
set :session_secret, "f650ed69344bab0084199bb8cc9aa5a1bd6756c3b57ad67023255af0fc3795057e"
set :views, Proc.new { File.join(root, "../views/") }
get '/sessions_set' do
session[:foo] = 'hello'
if session[:foo] == 'hello'
binding.pry
redirect '/fetch'
else
"Session value has not been set!"
end
end
get '/fetch' do
binding.pry
"You did it! session[:foo] value: #{session[:foo]}.\nMove on to Part II of this lab at '/second_exercise' "
end
end
If you run this every time it will create a different random code every time, making your session unusable essentially. You will have to run this piece of code at startup somehow for it to work.
:session_secret, SecureRandom.hex(64)
For a quick fix you could run this code in a console and just paste the output into your file as it seems you have already done. For a more secure way you could look into using environment variables.

Force Swagger UI To Load https path when hosted on Heroku

I have a rails 4 app with a Grape API and Swagger through the gem grape-swagger and grape-swagger-ui gems.
In dev everything works well, I load http://localhost:3000/api/swagger and the swagger header's text input along the top loads the expected url, http://localhost:3000/api/swagger_doc. This points properly to the file it seeks, swagger_doc.json.
I've pushed this app to heroku, which forces https connections. Unfortunately, when loading https://my-app.herokuapp.com/api/swagger the swagger header's text input along the top loads http://my-app.herokuapp.com/api/swagger_doc instead of loading https://my-app.herokuapp.com/api/swagger_doc (http vs https).
I've tried coming at this from the heroku side with things like:
routes.rb
unless Rails.env.development?
get "*path" => redirect("https://my-app.herokuapp.com%{path}"), :constraints => { :protocol => "http://" }
post "*path" => redirect("https://my-app.herokuapp.com%{path}"), :constraints => { :protocol => "http://" }
end
config/environments/production
config.force_ssl = false
config/environments/production
#config.force_ssl = false
And I've come at it with trying to set or manipulate the base_path attribute of add_swagger_documentation.
app/controllers/api/base.rb
base_path: "my-app.herokuapp.com",
app/controllers/api/base.rb
base_path: "http://my-app.herokuapp.com",
app/controllers/api/base.rb
base_path: = lambda do |request|
return "http://my-app.herokuapp.com"
end
app/controllers/api/base.rb
base_path: lambda { |request| "http://#{request.host}:#{request.port}" }
I recently clicked "view raw" on one of my resources and noticed that it was picking up my changes to base_path but that base_path isn't even used to populate the url in the text input in the swagger header. It seems to be generated from a js file. I'm unable to edit it and would happily accept a hack to do so as a solution. Here's that raw output:
https://gist.github.com/johnnygoodman/5fd246765dc5236fb8c4
The line of interest is:
"basePath":"http://localhost:3000/my-app.herokuapp.com"
Which would break the app if it was being populated and used, but it is not. I don't see an option in the grape-swagger gem that I can use to pass in this variable and change the path to https.
In conclusion:
I'd like the swagger text input box to load https://my-app.herokuapp.com/api/swagger_doc when I visit https://my-app.herokuapp.com/api/swagger.
Anyone know a hack to accomplish this on heroku?
I was able to work around this. I suggest:
Do not use + uninstall #gem 'grape-swagger-ui'
Use and install gem 'grape-swagger-rails' and follow the docs here: https://github.com/ruby-grape/grape-swagger-rails

Rails overriding default_url_options causes URL's like "localhost:2999:3000/..."

in my setup I've got nginx running on port 3000 acting as a proxy to my rails app running on port 2999. Obviously, I have to have the rails app use 3000 as the port for generated URL's (so that when I use login_url, it will generate an url like http://localhost:3000/login.
I overrid rails' default_url_options like this:
def default_url_options( options = {} )
options.merge( { :only_path => false, :port => 3000 } )
end
However, this causes URL's like http://localhost:2999:3000/login for login_path.
Only thing I found was this ticket describing a related problem: https://rails.lighthouseapp.com/projects/8994/tickets/1106-rewrite_url-adds-port-twice, but there's nothing helpful in there.
Is there any way to make rails get the URL right?
thx in advance
If you know the hostnames for your different environments, you could set them statically, which routes around the problem:
# config/environments/production.rb
DEFAULT_HOST_WITH_PORT = 'http://myrealsite.com:3000'
# config/environments/development.rb
DEFAULT_HOST_WITH_PORT = 'http://localhost:3000'
# config/environments/test.rb
DEFAULT_HOST_WITH_PORT = 'http://localhost:3000'
and then
def default_url_options( options = {} )
options.merge(:only_path => false, :host => DEFAULT_HOST_WITH_PORT)
end
(notice that you're not setting the port separately, you're just counting on it being included in host)

Error when saving a Backbone.js model with Rails

I have Backbone.js collection and model for a project object:
window.Project = Backbone.Model.extend();
window.Projects = Backbone.Collection.extend({
model: Project,
url: '/projects'
});
I have setup a rails controller to respond to the Backbone.js collection:
class ProjectsController < ApplicationController
def index
render :json => Project.all
end
def create
project = Project.create! params
render :json => project
end
end
Index works fine and I get a list of projects in my web app. The problem is if I try and create a model on the Projects collection I get a 500 error from the server.
The error message on the server is as follows:
Started POST "/projects" for 127.0.0.1 at 2011-08-21 08:27:56 +0100
Processing by ProjectsController#create as JSON
Parameters: {"title"=>"another test"}
Completed 500 Internal Server Error in 16ms
ActiveRecord::UnknownAttributeError (unknown attribute: action):
app/controllers/projects_controller.rb:8:in `create'
I am not sure what the unknown attribute: action is referring to.
For info I have set up the projects_controller as resources :projects. I have also set rails to ActiveRecord::Base.include_root_in_json = false.
Yes, Rails always adds the action and controller to params. The parameters come from ActionDispatch::Http::Parameters:
def parameters
#env["action_dispatch.request.parameters"] ||= begin
params = request_parameters.merge(query_parameters)
params.merge!(path_parameters)
encode_params(params).with_indifferent_access
end
end
And path_parameters:
Returns a hash with the parameters used to form the path of the request. Returned hash keys are strings:
{'action' => 'my_action', 'controller' => 'my_controller'}
So you shouldn't be doing project = Project.create! params. You could go the update_attributes route:
project = Project.new
project.update_attributes params[:model_name]
But this assumes that you have what you need in a sub-hash of params and it won't call your validators. Backbone won't namespace your attributes by default but you could override Backbone.sync and do it yourself. Still, you probably want your validations so update_attributes should generally be avoided.
Your best bet is to pull exactly the attributes out of params that you're expecting to be there. This is even the Backbone recommended practise:
*(In real code, never use update_attributes blindly, and always whitelist the attributes you allow to be changed.)*
You can enable parameter wrapping. Add a file in the initializer directory with:
ActiveSupport.on_load(:action_controller) do
wrap_parameters format: [:json]
end
and, for json request, you post params will now be wrapped with the model name.

How do I write a Rails 3.1 engine controller test in rspec?

I have written a Rails 3.1 engine with the namespace Posts. Hence, my controllers are found in app/controllers/posts/, my models in app/models/posts, etc. I can test the models just fine. The spec for one model looks like...
module Posts
describe Post do
describe 'Associations' do
it ...
end
... and everything works fine.
However, the specs for the controllers do not work. The Rails engine is mounted at /posts, yet the controller is Posts::PostController. Thus, the tests look for the controller route to be posts/posts.
describe "GET index" do
it "assigns all posts as #posts" do
Posts::Post.stub(:all) { [mock_post] }
get :index
assigns(:posts).should eq([mock_post])
end
end
which yields...
1) Posts::PostsController GET index assigns all posts as #posts
Failure/Error: get :index
ActionController::RoutingError:
No route matches {:controller=>"posts/posts"}
# ./spec/controllers/posts/posts_controller_spec.rb:16
I've tried all sorts of tricks in the test app's routes file... :namespace, etc, to no avail.
How do I make this work? It seems like it won't, since the engine puts the controller at /posts, yet the namespacing puts the controller at /posts/posts for the purpose of testing.
I'm assuming you're testing your engine with a dummy rails app, like the one that would be generated by enginex.
Your engine should be mounted in the dummy app:
In spec/dummy/config/routes.rb:
Dummy::Application.routes.draw do
mount Posts::Engine => '/posts-prefix'
end
My second assumption is that your engine is isolated:
In lib/posts.rb:
module Posts
class Engine < Rails::Engine
isolate_namespace Posts
end
end
I don't know if these two assumptions are really required, but that is how my own engine is structured.
The workaround is quite simple, instead of this
get :show, :id => 1
use this
get :show, {:id => 1, :use_route => :posts}
The :posts symbol should be the name of your engine and NOT the path where it is mounted.
This works because the get method parameters are passed straight to ActionDispatch::Routing::RouteSet::Generator#initialize (defined here), which in turn uses #named_route to get the correct route from Rack::Mount::RouteSet#generate (see here and here).
Plunging into the rails internals is fun, but quite time consuming, I would not do this every day ;-) .
HTH
I worked around this issue by overriding the get, post, put, and delete methods that are provided, making it so they always pass use_route as a parameter.
I used Benoit's answer as a basis for this. Thanks buddy!
module ControllerHacks
def get(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "GET")
end
# Executes a request simulating POST HTTP method and set/volley the response
def post(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "POST")
end
# Executes a request simulating PUT HTTP method and set/volley the response
def put(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "PUT")
end
# Executes a request simulating DELETE HTTP method and set/volley the response
def delete(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "DELETE")
end
private
def process_action(action, parameters = nil, session = nil, flash = nil, method = "GET")
parameters ||= {}
process(action, parameters.merge!(:use_route => :my_engine), session, flash, method)
end
end
RSpec.configure do |c|
c.include ControllerHacks, :type => :controller
end
Use the rspec-rails routes directive:
describe MyEngine::WidgetsController do
routes { MyEngine::Engine.routes }
# Specs can use the engine's routes & named URL helpers
# without any other special code.
end
– RSpec Rails 2.14 official docs.
Based on this answer I chose the following solution:
#spec/spec_helper.rb
RSpec.configure do |config|
# other code
config.before(:each) { #routes = UserManager::Engine.routes }
end
The additional benefit is, that you don't need to have the before(:each) block in every controller-spec.
Solution for a problem when you don't have or cannot use isolate_namespace:
module Posts
class Engine < Rails::Engine
end
end
In controller specs, to fix routes:
get :show, {:id => 1, :use_route => :posts_engine}
Rails adds _engine to your app routes if you don't use isolate_namespace.
I'm developing a gem for my company that provides an API for the applications we're running. We're using Rails 3.0.9 still, with latest Rspec-Rails (2.10.1). I was having a similar issue where I had defined routes like so in my Rails engine gem.
match '/companyname/api_name' => 'CompanyName/ApiName/ControllerName#apimethod'
I was getting an error like
ActionController::RoutingError:
No route matches {:controller=>"company_name/api_name/controller_name", :action=>"apimethod"}
It turns out I just needed to redefine my route in underscore case so that RSpec could match it.
match '/companyname/api_name' => 'company_name/api_name/controller_name#apimethod'
I guess Rspec controller tests use a reverse lookup based on underscore case, whereas Rails will setup and interpret the route if you define it in camelcase or underscore case.
It was already mentioned about adding routes { MyEngine::Engine.routes }, although it's possible to specify this for all controller tests:
# spec/support/test_helpers/controller_routes.rb
module TestHelpers
module ControllerRoutes
extend ActiveSupport::Concern
included do
routes { MyEngine::Engine.routes }
end
end
end
and use in rails_helper.rb:
RSpec.configure do |config|
config.include TestHelpers::ControllerRoutes, type: :controller
end