I developed a Rack application based on Sinatra::Base. Now I would like to use many instances of it, each with a slightly different configuration, in a single Rack application.
My rackup should look like
use Rack::Lint
map '/mel' do
run Site.new('/home/mel/site').app
end
map '/pub' do
run Site.new('/pub').app
end
The Site class collects various parameters (in this example only the root dir) and does some preparatory work. The #app method should return a Server object that holds a reference to the served Site instance.
This is an example of the Site and Server code:
class Site
def initialize(root_dir)
#root_dir = root_dir
# ... set up things ...
end
def app
# This is where a new Server Rack application should be created
return Server.new { |server| server.set :site, self }
end
end
class Server < Sinatra::Base
before do
#content = settings.site.all_files
end
get /(.*)/ do |url_path|
# do things...
end
end
The problem with this code is that the #app method does not return a valid Rack application.
What should I do in #app to return a new, configured Server Rack application?
This is a way to make it work suggested by "carloslopes" on #sinatra.
The Site#app method becomes
class Site
def app
# This is where a new Server object should be created
return Server.new(self)
end
end
and the Server objects get their parameters via instance variables:
class Server < Sinatra::Base
def initialize(site)
super()
#site = site
end
before do
#content = #site.all_files
end
get /(.*)/ do |url_path|
# do things...
end
end
Edit: made community wiki so that other can make the solution even better and share the credit.
Related
I have the end points "/customers" and "/api/v1/customers", in html and json respectively for a list of customers. Do I have to create 2 different controllers and thus actions for them? Or can I return html or json from a single controller and action depending a requested format: html or json? Note that for "/api/v1/customers" I need authentication via an Api Key.
You can have one controller and action for both endpoints, but I would advise against it.
You mentioned that those controllers need to do different stuff, so instead of adding stuff like "if json then check api key" make two separate controllers and extract common code of getting all the customers.
There is a great talk about untangling business logic from http interface: http://www.elixirconf.eu/elixirconf2016/lance-halvorsen Getting a list of customers might be out of your controllers, so at the end you will have two controllers like this:
defmodule MyApp.Api.CustomersController do
plug MaApp.ApiAuth #plug for checking api key
def index(conn, params) do
...
customers = ActualLogic.get_customers()
...
end
end
def MyApp.CustomersController do
plug MyApp.UserAuth #for example checks if user is logged in
def index(conn, params) do
...
customers = ActualLogic.get_customers()
...
end
end
At the end your controller does not perform any logic, it calls something else to do the job and action is responsible only for web stuff like parsing params, authentication via api key, session cookies and translating end result to json/html.
There are tons of these, especially for Rails 4.0 and Rails 3.x. I am new to nested routing and I find it very difficult, especially with forms.
So I have the following:
Routes
resources :users do
resources :api_keys, path: '/developmentcenter'
end
The relationship here is: user has many api keys where api key belongs to user.
Controller
class ApiKeysController < ApplicationController
before_action :authenticate_user!
def new
#user = User.find(params[:user_id])
#api_key = ApiKey.new(:user => #user)
end
def index
#user = User.find(params[:user_id])
#api_key = #user.api_keys
end
def create
#user = User.find(params[:user_id])
#api_key = ApiKey.new(create_new_api_key)
create_api_key(#api_key, #user)
end
def destroy
#user = User.find(params[:user_id])
destroy_api_key(#user)
end
private
def create_new_api_key
params.permit(:api_key, user_attributes: [:id])
end
end
The above is pretty basic. create_api_key is a method that does something on save, or does something else on failure to save. While destroy_api_key Just find the api key based on the user id and deletes it, does something on success and something on failure.
So now lets create a form - which has a single button for creating the API key.
<h2>Create a new Key</h2>
<%= form_for ([#user, #api_keys]) do |f| %>
<p class="button"><%= f.submit "Generate API Key" %></p>
<% end %>
All we need is a single submit button that upon click, creates a new api key for the user whom is logged in.
But wait we have a an error:
First argument in form cannot contain nil or be empty
This error takes place at:
<%= form_for ([#user, #api_keys]) do |f| %>
So - I have looked at every single one of the stack questions (well most) that deal with this error, I have changed my controller based on a few, to what you see above. I have even look at the form helpers docs in the rails manual.
I cannot figure this out.
It is telling you that #user is empty or nil in the context it is using. Either this is a user who has not been created in the DB yet, or your User.find call is not working to return an actual user. The form needs to know who #user is before it can create a nested resource (#api_key) for it. Your `create_api_key' is completely wrong. You need to whitelist your params first, then find the user in the DB (or create them), then you can use the #user instance variable to create a form for that user to create a key. I think if you do it right, you shouldn't need to call #api_keys in the beginning of the form if you defined the relationships in your models (has_many or has_one, belongs_to etc.). Can you post the web server console output when you visit that page? First off you are calling
#user = User.find(params[:user_id])
every time in your controller. You should DRY that up with a before_action.
private
def set_user
#user = User.find(api_key_params[:user_id])
end
Then at the top of the controller you would have:
class ApiKeysController < ApplicationController
before_action :authenticate_user!
before_action: :set_user
Also you should make your .permit a method that returns a variable called api_key_params:
def api_key_params
params.require(:user).permit(:api_key)
end
That way you have the things you want returned in a whitelist. To access these params you just call the method. You can specify a param you want returned from the method like my set_user example. But it also gives you the ability to do things like:
def create
#api_key = Api_key.new(api_key_params)
end
That way the Api_key.new gets the user and creates the key. You don't show how or where you generate the key itself. If it gets created by some method, you should put that in the model for Api_key. There are some other things about your code that are confusing without seeing the rest of your files.
Is there a way to handle a GET request on Sinatra and make a PATCH request with a different body on the same server? User makes a request GET /clean_beautiful_api and server redirects it to PATCH /dirty/clogged_api_url_1?crap=2 "{request_body: 1}"?
I want to clean up legacy API without interfering with the functionality.
If I've understood correctly, the easiest way is to extract the block used for the patch into a helper:
patch "/dirty/clogged_api_url_1"
crap= params[:crap]
end
to:
helpers do
def patch_instead( params={} )
# whatever you want to do in here
crap= params[:crap]
end
end
get "/clean_beautiful_api" do
patch_instead( params.merge(request_body: 1) )
end
patch "/dirty/clogged_api_url_1"
patch_instead( params )
end
Or you could use a lambda…
Patch_instead = ->( params={} ) {
# whatever you want to do in here
crap= params[:crap]
}
get "/clean_beautiful_api" do
Patch_instead.call( params.merge(request_body: 1) )
end
# you get the picture
the main thing is to extract the method to somewhere else and then call it.
Edit: You can also trigger another route internally using the Rack interface via call.
This question has been asked in various permutations, but I haven't found the right combination that answers my particular question.
The configuration
Rails 3.1 (allowing me to use force_ssl in my ApplicationController)
Hosted on Heroku Cedar (so I can't touch the middleware)
My SSL certs are registered for secure.example.com
I've already added force_ssl to my ApplicationController, like this:
# file: controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
force_ssl
end
The problem
Currently, if a user navigates to http://example.com, force_ssl switches to SSL, but since it's NOT secure.example.com, it presents a warning about an unverified security cert because it's using the default Heroku cert.
(I've verified that navigating to http://secure.example.com properly redirects to https://secure.example.com and uses the proper security cert. That's good.)
The question
How do I force http://www.example.com/anything and http://example.com/anything to redirect to http://secure.example.com/anything? (I'm assuming that force_ssl will handle the switch from http to https.) Since I cannot touch the middleware (recall that this is Heroku hosting), I assume I can do something like:
# file: controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
force_ssl
before_filter :force_secure_subdomain
private
def force_secure_subdomain
redirect_to(something...) unless request.ssl?
end
end
... but I haven't sufficiently grokked redirect_to and the request object to know what to write for something.... (I want to be sure that it handles query params, etc.)
you can redirect to a different hostname by doing the following:
# file: controllers/application_controller.rb
class ApplicationController < ActionController::Base
force_ssl :host => "secure.example.com"
end
see: rails force_ssl source for more info
You should have a look at rack-rewrite - it's essentially Apache re-write but in Ruby form, and usable on Heroku.
This will allow you to create all sorts of Rack level rules and what redirections etc should occur and when.
I have the following code:
require 'rubygems'
require 'eventmachine'
require 'em-http'
require 'sinatra/base'
require 'sinatra/async'
class Api < Sinatra::Base
register Sinatra::Async
aget '/1' do
EventMachine.run {
http = EventMachine::HttpRequest.new( "http://www.google.com").get(:timeout => 5)
http.callback { puts "h2" ;ret_val = http.response; EventMachine.stop}
http.errback {puts "was h2ere1" ;ret_val = nil; EventMachine.stop}
}
body "done processing 1"
end
aget '/2' do
body "done processing 2"
end
end
When I issue the following, it works well:
curl http://localhost:3000/2
But, when I issue the following request, it prints "h2" and the application silently quits:
curl http://localhost:3000/1
Any help will be appreciated. Thanks!
If your web server (Eg. thin) is based on EventMachine, then the EventMachine.stop line will actually stop the webserver as well as the EventMachine instance created by EventMachine.run.
I can't find a way to stop nested EventMachines like this. My advice - use Weary or another non-blocking HTTP request library.
Sinatra::Async provides it's own body helper that needs to be called from within the EventMachine loop. Also worth noting: if you're running Sinatra through Thin, you shouldn't call EM.run explicitly, as Sinatra is already operating within an EventMachine loop.