In phoenix framework with pipeline we can enable specify middlewares for some route, for example:
defmodule HelloPhoenix.Router do
use HelloPhoenix.Web, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", HelloPhoenix do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
end
scope "/api", HelloPhoenix do
pipe_through :api
end
end
if request from /api, will only trigger plug :accepts, ["json"] middleware
if request from /, will trigger session, flash, ...etc middlewares
how to achieve this on rails, If I am using grape for build api and rails for build web page and enable difference middleware for each other?
Unlike Phoenix applications, you cannot (easily) change what middleware is used for a request within Rails. The best way to get this kind of behaviour in a Rails application would be to make the controllers for a particular route inherit from a common base controller, and then define the behaviour for these particular controllers in that base controller.
Using the above example of the /api routes going through different "middleware", you can have a controller like this:
module API
class BaseController < ActionController::API
# things unique to API routes go here
end
end
Inside this controller, you could write some before_action callbacks that ensure things like:
Inbound requests are JSON only
Requests are authenticated with a particular token
Then you can inherit from this controller for all API controllers:
module API
class PostsController < API::BaseController
end
end
With your regular controllers you can do all the regular things:
class ApplicationController < ActionController::Base
# things unique to non-api routes go here
end
And then:
class PostsController < ApplicationController
end
You could of course segment it more; maybe you might have another route like /accounts/1/posts where you have to be authenticated as the user of that account to see the posts. You can take the same approach:
module Accounts
class BaseController < ActionController::Base
# find account, check if the user is authenticated, etc.
end
end
And:
module Accounts
class PostsController < Accounts::BaseController
end
end
So in summary: Rails doesn't let you do this on a routing level (easily), but you can accomplish the same thing at the controller level.
The solution I was looking for was something more in this direction:
application.rb:
# after Bundler.require(...)
require_relative '../lib/engines/website/lib/website'
lib/engines/website/lib/website.rb:
require_relative "website/engine"
module Website; end
lib/engines/website/lib/website/engine.rb:
module Website
class Engine < ::Rails::Engine
middleware.use ActionDispatch::Cookies
middleware.use ActionDispatch::Session::CookieStore
middleware.use ActionDispatch::Flash
end
end
config/routes.rb:
mount Website::Engine => "/website"
And everything for the website goes in the typical directory structure under the engine directory:
lib
engines
website
app
assets
...
controllers
...
views
...
config
routes.rb
lib
website
website.rb
Reference: Build 2 middleware stacks in Rails app
I'm pretty sure that you can achieve this result by configuring Rack middleware stack, but it won't be configurable in a rails application.
Sometimes it's affordable to have unnecessary middleware for some routes.
Almost all apps that I saw are using same middleware for every request.
And yes, Phoenix behavior is better here, Rails behavior done in earliest versions, so it's hard to change something now.
Related
Right now I have two API namespaces, Api::V1 and Api::V2
I plan on implementing V2 slowly over time rather than all in one shot. However, since part of it exists, I'd like it if the client could send all HTTP requests to the V2 URL and let the server deal with it if that particular endpoint is not yet implemented.
Is there a way to route V2 requests to the V1 controller if the V2 controller does not have an action?
Simple Example:
routes:
namespace :api do
namespace :v1 do
resources :items, only: [:index]
end
namespace :v2 do
resources :items, only: :create
end
end
will produce the following endpoints:
GET /api/v1/items
POST /api/v2/items
The goal is to send a GET request to /api/v2/items and have it invoke Api::V1:ItemsController#index since the V2 controller does not have this method yet.
namespace :api do
namespace :v1, path: "v2" do
# All endpoints that are on v1 of API
end
namespace :v2 do
# All endpoints that are on v2 of API
end
end
If you run rake routes here you'll see that they all match the route "api/v2/____#_____" but the ones in the top block invoke Api::V1 actions and the bottom block invoke Api::V2 actions, so youll have to move them from the top to the bottom as you implement those endpoints
I also have a versioned API. I haven't bumped to the next version yet. But I thought I'd share what I plan to do. This might be a little hairballed. And I have a feeling that this will be more useful to me (to think through my plan) than it is to you. So, sorry about that. But, here goes...
Before I start, I should say that I take a different sort of approach to my controller actions. In my apps, I like to delegate my controller actions to plain old ruby objects that I call 'managers'. Every controller has a 'manager_base'. So, in my controllers, I have something like this:
class ApplicationController < ActionController::Base
private
def do_action(action=nil)
action ||= caller[0][/`.*'/][1..-2]
manager.send("manage_#{action}", self, cookies, request)
end
def manager
base = self.class.name.split('::')
base.pop
base << "#{controller_name.camelize}Managers::ManagerBase"
base.join('::').constantize
end
end
class Api::V1::FooController < ApplicationController
def index
do_action
render_result
end
end
And then I also have:
class ManagerBase
class << self
def manage_index(controller, cookies, request)
sub_out("Index", controller, cookies, request)
end
def manage(controller, cookies, request)
new(controller, cookies, request).manage
end
private
def sub_out(method, controller, cookies, request)
sub_manager(method).manage(controller, cookies, request)
end
end # Class Methods
def initialize(controller, cookies, request)
#controller = controller
#cookies = cookies
#request = request
end
end
class Api::V1::FooManagers::ManagerBase < ManagerBase
class << self
private
def sub_manager(method)
"Api::V1::FooManagers::#{method}Manager".constantize
end
end # Class Methods
end
class Api::V1::FooManagers::IndexManager < Api::V1::FooManagers::ManagerBase
def manage
... do stuff
end
end
If you follow the bouncing ball, here's how my application flow goes:
index gets called on Api::V1::FooController
index calls do_action (inherited from ApplicationController) which, in turn calls manager (also inherited from ApplicationController)
manager returns the Api::V1::FooManagers::ManagerBase class
do_action then calls manage_index on Api::V1::FooManagers::ManagerBase
manage_index calls sub_out which in turn calls sub_manager
sub_manager returns the Api::V1::FooManagers::IndexManager
sub_out then calls manage on Api::V1::FooManagers::IndexManager
manage (the class method - inherited from ManagerBase) creates a new instance of Api::V1::FooManagers::IndexManager and then calls manage (the instance method) on the new instance.
As may or may not be apparent, when I move to Api::V2, I have two opportunities to 'hook' back into the Api::V1 versions of my managers (which is equivalent to using my V1 controller methods - your original question).
First, if I haven't implemented Api::V2::FooManagers::ManagerBase yet, I can cause ApplicationController.manager to fall back to the last implemented version of ManagerBase (i.e., Api::V1::FooManagers::ManagerBase). In which case I'll be using all of the Api::V1::FooManager sub managers (like IndexManager).
Second, if I've implemented Api::V2::FooManagers::ManagerBase but have not yet implemented Api::V2::FooManagers::IndexManager, then I can cause Api::V2::FooManagers#sub_manager to fall back to Api::V1::FooManagers::IndexManager.
Okay, I'm going to stop now. Thanks for the opportunity to think this through out loud. Apologies if it's a totally useless, hot mess.
I have found a few articles addressing the issue of helpers within an engine not being accessible to the consuming (parent) application. To make sure we are all on the same page, let's say we have this:
module MyEngine
module ImportantHelper
def some_important_helper
...do something important...
end
end
end
If you look at the rails engine documentation in the "Isolated engine's helpers" (L293), it says:
# Sometimes you may want to isolate engine, but use helpers that are defined for it.
# If you want to share just a few specific helpers you can add them to application's
# helpers in ApplicationController:
#
# class ApplicationController < ActionController::Base
# helper MyEngine::SharedEngineHelper
# end
#
# If you want to include all of the engine's helpers, you can use #helpers method on an engine's
# instance:
#
# class ApplicationController < ActionController::Base
# helper MyEngine::Engine.helpers
# end
So if I ask anybody consuming my engine to add this to their application_controller.rb, then they will get access to all my important helper methods:
class ApplicationController < ActionController::Base
helper MyEngine::ImportantHelper
end
This is what I want and it works, but that's kind of a pain, especially if, as is my use case, everything the engine exposes can/should be used anywhere in the consuming app. So I dug around a bit more and found a solution that suggested I do the following:
module MyEngine
class Engine < Rails::Engine
isolate_namespace MyEngine
config.to_prepare do
ApplicationController.helper(ImportantHelper)
end
end
end
Now this is exactly what I want: to add all my ImportantHelper method to the parent app's application helper. However, it doesn't work. Can anybody help me figure out why this more-better solution does not work?
I am running ruby 1.8.7 with rails 3.1.3. Let me know if I missed any important info germane to the issue, and thanks in advance.
You can create an initializer to accomplish this like so:
module MyEngine
class Engine < Rails::Engine
initializer 'my_engine.action_controller' do |app|
ActiveSupport.on_load :action_controller do
helper MyEngine::ImportantHelper
end
end
end
end
I have written two blog posts about creating engines from scratch, and following them everything should work as expected (without additional configurations needed). Maybe you are still interested in:
Rails 3.1 Engines: Part I – The engine
Rails 3.1 Engines: Part II – The gem
Rails 3.1 Engines: Part III – The environment
Update: It's three articles in the meantime, and there's still some info to come. Please give me feedback.
If you want to keep the code in the engine, instead of every implementing application, use this:
module MyEngine
class Engine < Rails::Engine
isolate_namespace MyEngine
config.to_prepare do
MyEngine::ApplicationController.helper Rails.application.helpers
end
end
end
module YourEngine
module Helpers
def a_helper
end
...
end
end
ActionController::Base.send(:helper, YourEngine::Helpers)
Include this code in engine.rb is also be very helpful
config.before_initialize do
ActiveSupport.on_load :action_controller do
helper MyEngine::Engine.helpers
end
end
Basically your engine would look like
module MyEngine
class Engine < Rails::Engine
isolate_namespace MyEngine
# Here comes the code quoted above
end
end
My project name is clog, so I named my models and controllers like this: Clog::User Clog::Userscontroller.
Is this naming convention mandatory?
No, in a conventional Rails project, that's not necessary. Just name your models and controllers the usual way, like eg User or UsersController.
The other thing is that, when your project grows in size, you may need to organize your models into submodules. One approach to do so is extending your models with app concerns, as show eg here or here.
As for organizing controllers, one approach is to create a module in the lib directory, which you then include in your ApplicationController, like so:
In lib/authentication.rb:
module Authentication
def self.included(base)
base.send :before_filter, :login_required
base.send :helper_method, :current_user, :logged_in?
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token]) if cookies[:remember_token].present?
end
#...
end
In app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include Authentication
#...
end
For this to work, you need to add
config.autoload_paths << "#{config.root}/lib"
to your config/application.rb file
However, if you plan to build your Rails project as a Rails Engine, you may want to follow some naming convention. A good example of a Rails Engine is forem.
Yes, following the naming convention helps a great deal because not only does rails use it to generate other names, but other gems as well.
Specific to your question, you may be asking if you need to name the controller as UserController given that your model is called User. That is not necessary at all, and you may call it anything else if it better fits your purpose.
In this case, you will probably want to create a few controllers like so:
My::AccountController # for e.g.. /my/account
Admin::UsersController # for e.g. /admin/users/1
For a user, you refer to your own user record, as 'your account' so this makes more sense. However, the administrator's perspective would be to manage user records. You may also name a controller one thing and serve it under a different route. In your routes file, you may do this:
namespace :admin do
resources :users, :path => "user-accounts"
end
To reiterate, your model name need not match up to the controller name. They are only named similarly by association: UserController is understood to handle User records.
I have a Rails 3 application with several engines containing additional functionality. Each engine is a separate service that customers can purchase access to.
I am, however, having a problem with routes from the engines that aren't readily available to the controllers and views.
controller:
class ClassroomsController < ApplicationController
..
respond_to :html
def index
respond_with(#classrooms = #company.classrooms.all)
end
def new
respond_with(#classroom = #company.classrooms.build)
end
..
end
app/views/classrooms/new.html.haml:
= form_for #classroom do |f|
..
f.submit
config/routes.rb in engine:
MyEngineName::Engine.routes.draw do
resources :classrooms
end
config/routes.rb in app:
Seabed::Application.routes.draw do
mount MyEngineName::Engine => '/engine'
...
end
lib/my_engine_name.rb in engine:
module MyEngineName
class Engine < ::Rails::Engine
end
end
attempting to go to /classrooms/new results in
NoMethodError in Classrooms#new
Showing app/views/classrooms/_form.html.haml where line #1 raised:
undefined method `hash_for_classrooms_path' for #<Module:0x00000104cff0f8>
and attempting to call classrooms_path from any other view results in the same error.
I can, however, call MyEngineName::Engine.routes.url_helpers.classrooms_path and get it working. I'm thinking I might have defined the routes wrong, but can't find another way that works.
Tried running the app with both Passenger (standalone and Apache module) and WEBrick (rails server). Using latest Rails from Git (7c920631ec3b314cfaa3a60d265de40cba3e8135).
I had the same problem, and found this in the documentation:
Since you can now mount an engine inside application’s routes, you do not have direct access to Engine‘s url_helpers inside Application. When you mount an engine in an application’s routes, a special helper is created to allow you to do that. Consider such a scenario:
# config/routes.rb
MyApplication::Application.routes.draw do
mount MyEngine::Engine => "/my_engine", :as => "my_engine"
get "/foo" => "foo#index"
end
Now, you can use the my_engine helper inside your application:
class FooController < ApplicationController
def index
my_engine.root_url #=> /my_engine/
end
end
Change config.routes in your engine to:
Rails.application.routes.draw do # NOT MyEngineName::Engine.routes.draw
resources :classrooms
end
The way you have it, the routes are only available in the MyEngineName::Engine namespace and not in the rest of the host rails application.
There used to be a blog post with more info, but unfortunately it is no longer available:
http://blog.loopedstrange.com/modest-rubyist-archive/rails-3-plugins-part2-writing-an-engine
For me also help to add
require 'engine' if defined?(Rails)
to my main gem file (lib/.rb).
Good example - https://github.com/mankind/Rails-3-engine-example/blob/master/lib/dummy.rb
Our application is developed using Rails 2.3.5 along with ActiveScaffold. ActiveScaffold adds quite a bit of magic at run time just by declaring as following in a controller:
class SomeController < ApplicationController
active_scaffold :model
end
Just by adding that one line in the controller, all the restful actions and their corresponding views are made available due to ActiveScaffold's meta programming. As most of the code is added at runtime, in development mode requests seems to be little slower as there is no class_caching.
We needed to add a authorization layer and my team has chosen Lockdown plugin which parses an init.rb file where you declare all the authorization rules. The way that Lockdown stores the authorization rules is by parsing the init.rb file and evaluating the controllers declared in the init.rb file. So for every request Lockdown evaluates all the controllers thereby forcing ActiveScaffold to add lot of meta programming which in turn makes db queries to find out the column definitions of every model. This is considerably slowing down the request in development as there is no class_caching. Some times are requesting are taking almost 30-45 seconds.
Is there any way to force ActiveScaffold to do its magic in a before_filter? Something like the following:
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.class_eval do
active_scaffold :model
end
end
end
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.instance_eval do
active_scaffold :model
end
end
end
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.class.class_eval do
active_scaffold :model
end
end
end
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.class.instance_eval do
active_scaffold :model
end
end
end
I tried all the above four options, when I make a request, browser seem to show the loading indicator but nothing is happening.
Any help is appreciated.
Thanks in advance.
Lockdown only reparses init.rb in development mode so you can make changes without restarting the application. It will be slower - a convenience trade off. Good news is that Lockdown will only do this parsing once in production mode.
I don't use ActiveScaffold, so I can't offer any help there, but thought this would be of interest to you.