I have a few Rack apps mounted within my routes.rb like so:
mount ImageVise, at: '/ivise'
mount ShaderApiV0, at: '/api/v0'
I am currently migrating from a spaghetti custom Rack stack to Rails, so it makes sense for me to keep those apps. I am noticing that the entire Rails middleware stack that is defined on the application (including my Rack::Cache setup) does function when I call a controller, but does not function when I call URLs controlled by those mini-apps. I am almost certain in Rails 4 it worked however.
For example, if I introduce a custom middleware like so:
class Mittel < Struct.new(:app)
def call(env)
a, b, c = app.call(env)
b['X-Kustom'] = 'olala'
[a,b,c]
end
end
config.middleware.insert_before Rack::Head, Mittel
I do see the X-Kustom response header when I request a URL that is driven by the Rails controllers, but I do not get it when I request one of the URLs controlled by the mounted mini-apps. Consequently conditional GET and things like that do not work etc. How can I make it work aside from replicating a third of the Rails middleware stack in config.rb and moving these apps mounts there?
Found it. Turns out I was mounting one of these apps in config.ru as well, under the same URL - and then of course the entire Rails stack gets bypassed, as it should be. Lesson learned.
Related
I'm working on a Rails app that uses an engine. I'm using an initializer to configure one of my engine's controllers so that it will trigger an action in the host app. The code looks something like this:
# config/initializers/my_engine.rb
MyEngine::SomeController.after_filter proc {
# Do something in the host app
}, :only => :update
This works fine in production, but in development mode, the proc is only called on the first request. This is because the classes are getting reloaded and this configuration is lost, because it was stored in a class variable. (For example, MyEngine::SomeController is reloaded from the file it's in, and since the after_filter isn't declared there, it isn't added back on.)
Some Rails background
In development mode, Rails uses the following load strategy:
Code in the app directory is reloaded on each request, on the assumption that you're actively changing it.
Code in the lib directory, along with config/initializer files, are loaded once, when the application boots.
Initializer files are generally used for configuring gems. In the past, gems have mostly had code in the lib directory, so running their configuration once was sufficient.
How engines change things
However, Rails engines have code in the app directory: controllers, models, etc. These files are reloaded in development mode on each request. Therefore, configuration like my example above is lost.
Enter to_prepare
Rails provides config.to_prepare specifically to solve this problem: it run once in production, and on every request in development.
For example, we have this in application.rb, which works fine:
config.to_prepare do
# set up class variables (after_filters, etc)
end
However, if I have to put all my engines' configuration in application.rb, this defeats the point of config/initializers in keeping things organized.
So, for any configuration of classes in my engines' app directories, I want to put that code in files under config/initializers.
Here are my questions.
I'm unclear how to get config into scope in an initializer file. I'm thinking it would be Rails.application.config. Is that right?
Can I add add multiple to_prepare blocks? I'm afraid that calling it multiple times will overwrite previous blocks.
Update
As #Frederick Cheung mentioned, Rails.application.config.to_prepare does work in config/initializer files, and one can use as many of these as needed in the various files; each call appends its block to an array, so nothing is overwritten.
So the solution to this problem is:
# config/initializers/my_engine.rb
Rails.application.config.to_prepare do
MyEngine::SomeController.after_filter proc {
# Do something in the host app
}, :only => :update
end
One thing that still seems odd: I expected the to_prepare block to be called on every request in development mode, but instead it seems to be called randomly every 3rd request or so. I added block:
Rails.application.config.to_prepare do
Rails.logger.info "Running the prepare block!"
end
... restarted my app, and refreshed the page nine times. I only saw the message on the 1st, 5th, 7th and 9th requests. I'm not sure what explains this behavior, but it does explain why my code without the to_prepare worked intermittently in development.
You can add as many to_prepare blocks as you want - when you do config.to_prepare, Rails is doing (in configuration.rb in railties)
def to_prepare(&blk)
to_prepare_blocks << blk if blk
end
and then iterates over those blocks handing them over to ActionDispatch::Reloader, where to_prepare is implemented using ActiveSupport::Callbacks (i.e. the same thing that is used for before_save and so on). Multiple to_prepare blocks are fine.
Currently it looks like Rails iterates over to_prepare_blocks after reading application initialisers so adding to Rails.application.configuration.to_prepare should work. You may prefer to use ActionDispatch::Reloader.to_prepare.
There's nothing to stop you from doing initializer code in a file that lives in app/models.
for example
class MyClass
def self.run_me_when_the_class_is_loaded
end
end
MyClass.run_me_when_the_class_is_loaded
MyClass.run_me... will run when the class is loaded .... which is what we want, right?
Not sure if its the Rails way.... but its extremely straightforward, and does not depend on the shifting winds of Rails.
I would like to mount a sinatra application in my rails app.
But I would like this one to share the same layout.
The iframe could work but do you have any other idea ?
Thanks
You basically need to do two things:
You need to tell the Rails router that a certain URL path is to be handled by another Rack app (in your case a Sinata app). This can be done by adding this to your routes.rb:
match "/sinatra" => MySinatraApp, :anchor => false
Having done that, you can create your app like so:
class MySinatraApp < Sinatra::Base
get "/" do
"Hello Sinatra World"
end
end
The second step now is to tell your Sinatra app to use the rails layout which by default lives in app/views/layouts/application.html.erb for Rails 3.1. by default, Sinatra uses ./views/layout.ext (with ext being the extension of your chosen template system). So you basically, have to tell Sinatra to
use another directory to find views and layouts instead of the default ./views
use another template file as the default layout.
Both can be achieved by setting the following in your sinatra app:
set :views, "/path/to/your/railsapp/views"
set :erb, layout => :"layout/application" # or whatever rendering engine you chose
to share the same layout, you can point sinatra to the folder where the layout is in your rails app:
(taken from here: http://www.sinatrarb.com/configuration.html)
:views - view template directory A string specifying the directory
where view templates are located. By default, this is assumed to be a
directory named “views” within the application’s root directory (see
the :root setting). The best way to specify an alternative directory
name within the root of the application is to use a deferred value
that references the :root setting:
set :views, Proc.new { File.join(root, "templates") }
From your Rails app you can build a method which you can call from the action where the sinatra app should be included in the view.
(given you want to use the index action for this)
def index
#sinatra_content = get_sinatra
end
# use #sinatra_content in your views for rendering
def get_sinatra
sinatra_ip = 127.0.0.1;
sinatra_port = 4567;
#start a request here
RestClient.get 'http://#{sinatra_ip}:{sinatra_port}/', {:params => {:id => 50, 'foo' => 'bar'}}
end
see how rest-client works here: https://github.com/archiloque/rest-client and don't forget to include the gem in your rails app.
To use links in your sinatra app you should decide if sinatra should handle this (point to sinatra app (with port) or build links in your sinatra app which are handled by your rails app)
I think that using the append_view_path in your rails application will work a little bit better. Just append the Sinatra views to your Rails app and it will look there after looking in app/views.
The Crafting Rails Applications book by José Valim has a lot of documentation on that topic (rendering views from other sources), you may want to look at that.
Also, this Railscasts can help: http://railscasts.com/episodes/222-rack-in-rails-3
Is it possible to get the value of named route from with in a custom rack app when the app is mounted in rails 3 (in my case a Sinatra app)?
Simply using the route, (login_path) is throwing an exception for an undefined local variable.
UPDATE:
Here is an example, of what I am trying to do:
before do
redirect login_path unless some_condition
end
The app is mounted with
mount App.new, :at => '/path'
This part works as expected.
Thanks,
Scott
Accessing the hosting rails app's routes in the mounted Sinatra might not be very elegant, since the hosted Sinatra should not have knowledge of the app that hosts it.
So instead, it'd better to do this in the rails app.
If you use devise, you can surround your mount block as this:
authenticate "user" do
mount App.new, :at => '/path'
end
This can be done because devise itself is a middleware added before route.
Devise implements this as:
def authenticate(scope)
constraint = lambda do |request|
request.env["warden"].authenticate!(:scope => scope)
end
constraints(constraint) do
yield
end
end
If you don't use devise, you might need to implement something similar.
I think this is not possible because they have separate code. You're just telling to rails what to do with certain paths, an it routes those requests to another rack app, they don't share anything about internal code.
maybe you can write some code to tell Sinatra how to read rails routes. a good place to start from:
http://apidock.com/rails/ActionDispatch
Most of a web application is pretty standard CRUD. I need some data to be pushed to clients live as it's created. Is it worthwhile to run a separate, more lightweight stack (such as Sinatra or EventMachine) for the AJAX?
If I run one stack, I'll have Rails' overhead. This might require more Rails processes / servers, so how do I communicate between these without database queries? ZeroMQ? Finally, is Rails suited to handle hundreds of concurrent connections?
If I run two stacks, I'll need to duplicate authorization logic.
Which methods have been successful for you?
In Rails 3 you can pretty easily hook in Rack apps using the new routing syntax and inheriting your controllers from ActionController::Metal or just defining self.call.
Have a read (or watch) of Railscast #222 and check out wycats' blog (see below for example excerpts).
You're already aware of the additional cases you'll need to handle if you go down this track, so make sure it's necessary before getting stuck into it. It's usually cheaper to drop $ on servers than on programmers - hardware and bandwidth is pretty cheap.
I personally feel like this falls under 'premature optimization' unless you app is already running and choking up with too much traffic.
A few nice examples:
# config/routes.rb
# Hook in Sinatra
root :to => HomeApp
# Write your own barebones Rack compatible code
match "/processes" => ProcessesApp
# Even specify an inline proc
match "/heartbeat", :to => proc {|env| [200, {}, ["App is running"]] }
# /lib/home_app.rb
class HomeApp < Sinatra::Base
get "/" do
"Hello from Sinatra"
end
end
# lib/processes_app.rb
class ProcessesApp
def self.call(env)
[200, {}, [`ps -axcr -o "pid,pcpu, pmem, time, comm"`]]
end
end
I've got a couple Engine plugins with metal endpoints that implement some extremely simple web services I intend to share across multiple applications. They work just fine as they are, but obviously, while loading them locally for development and testing, sending Net::HTTP a get_response message to ask localhost for another page from inside the currently executing controller object results in instant deadlock.
So my question is, does Rails' (or Rack's) routing system provide a way to safely consume a web service which may or may not be a part of the same app under the same server instance, or will I have to hack a special case together with render_to_string for those times when the hostname in the URI matches my own?
It doesn't work in development because it's only serving one request at a time, and the controller's request gets stuck. If you need this you can run multiple server locally behind a load balancer. I recommend using Passenger even for development (and the prefpane if you are on OS X).
My recommendation for you is to separate the internal web services and the applications that use them. This way you do not duplicate the code and you can easily scale and control them individually.
This is in fact possible. However, you need to ensure that the services you call are not calling each other recursively.
A really simple "reentrant" Rack middleware could work like this:
class Reentry < Struct.new(:app)
def call(env)
#current_env = env
app.call(env.merge('reentry' => self)
end
def call_rack(request_uri)
env_for_recursive_call = #current_env.dup
env_for_recursive_call['PATH_INFO'] = request_uri # ...and more
status, headers, response = call(env_for_recursive_call)
# for example, return response as a String
response.inject(''){|part, buf| part + buf }
end
end
Then in the calling code:
env['reentry'].call_rack('/my/api/get-json')
A very valid use case for this is sideloading API responses in JSON
format within your main page.
Obviously the success of this technique will depend on the sophistication
of your Rack stack (as some parts of the Rack env will not like being reused).