I have a web application and I want to extend it to a Web API. I have split my controller into 2 folders Web, and Api. I have my ApplicationController in the Web folder and want to have a ApiController in the api folder.
I know that the ActionController is only supposed to be inherited from the ApplicationController. Is there a way that I can use 2 separate controllers so I can have different behaviour in each?
An example why I want 2 is so I can handle CanCan exceptions in different ways and protect_from_forgery with differently.
Update
When I try to run my tests I get the following error
api_sessions_controller.rb:1:in `': uninitialized constant ApiController (NameError)
My Api Sessions Controller is
class ApiSessionsController < ApiController
def create
user_password = params[:session][:password]
user_email = params[:session][:email]
user = user_email.present? && User.find_by(email: user_email)
if user.valid_password? user_password
sign_in user, store: false
user.generate_authentication_token!
user.save
render json: user, status: 200, location: [:api, user]
else
render json: { errors: "Invalid email or password" }, status: 422
end
end
end
And the Api Controller is
class ApiController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :null_session
# include SessionsHelper
rescue_from CanCan::AccessDenied do |exception|
if user_signed_in?
flash[:error] = "Access denied!"
redirect_to root_url
else
flash[:error] = "Please Sign in"
redirect_to new_user_session_path
end
end
end
I'm at a loss as to what is causing this, I can't see what is wrong with either controller and this is why I thought there might have been some conflict with what I was doing and the rails framework
I agree with ABMagil, nobody says you HAVE to inherit ApplicationController. In your case, you especially mention you want different access control + CSRF protections, so it's totaly justified to have another application controller that inherits from ActionControlle::Base
Anyway, everything will happen in your routes :
namespace :api do
# Stuff here will automatically look inside controllers/api/ folder,
# URL prefixed with /api/
# View url/path helpers with prefix api_
end
# your_domain.com/api/my_resource/
scope module: 'web' do
# Stuff here will look for controllers in controllers/web/
# NO URL prefix
# NO url/path prefix for View helpers
end
# your_domain.com/my_resource
EDIT : concerning your error
When namespacing, it means you are wrapping your classes in a module, so you have to specify this for every sub-controller. i.e
You can either have ActionController and ApiController in the /controller/ folder (I suggest you to do this + 3rd bullet point, feels more clear)
controllers/api_controller.rb,
controllers/application_controller.rb
class ApiController, class ApplicationController
If you want to move them to api/web subfolders, you have to reflect that in the files
controllers/api/api_controller.rb,
controllers/web/application_controller.rb
class Api::ApiController, class Web::ApplicationController
And obviously, also respect this for your other controllers
controllers/api/session_controller.rb
class Api::SessionController
Also note, if you want to keep API (only capital letters), I think it works when you use folder name /API/
Who says only ApplicationController can/should inherit from ActionController? ApplicationController sets the path to view files (not relevant for APIs) and defines require_local! and local_request?. If you don't use those methods (require_local! is called by InfoController and MailersController, within the Rails codebase), then you're not losing anything.
If you have logic that's only for your API, you certainly could subclass ActionController directly, but is it necessary? If you subclass ApplicationController but override relevant methods, won't that cover your use case?
Related
I'm new to Ruby on rails. I need to maintain a project which is a complete web app. Now I need to introduce APIs in it. I've searched and got many tutorials on API and web app separately. But didn't get any of them showing how these things will work together. I'm confused how that authentication will work for both.
Here is the application_controller.rb:
class ApplicationController < ActionController::Base
helper_method :sort_column, :sort_direction
protect_from_forgery
before_filter :authenticate_user!
# before_filter :authenticate # HTTP AUTH DISABLED
rescue_from CanCan::AccessDenied do |exception|
render :file => "#{Rails.root}/public/403.html", :status => 403, :layout => false
## to avoid deprecation warnings with Rails 3.2.x (and incidentally using Ruby 1.9.3 hash syntax)
## this render call should be:
# render file: "#{Rails.root}/public/403", formats: [:html], status: 403, layout: false
end
def user_for_paper_trail
if user_signed_in?
current_user.full_name
end
end
def info_for_paper_trail
if user_signed_in?
{ :user_id => current_user.id }
end
end
protected
def authenticate
authenticate_or_request_with_http_basic do |username, password|
username == "admin" && password == "123"
end
end
end
I need to know how to authenticate APIs? If I use JWT then how to override sign_in methods and do all that stuff separately for APIs and that also look overhead to me because authentication is already there.
Moreover it would be helpful if I get to know how to involve API functions in controller? Like I've user controller and all the methods for that for web app. Now I need the same methods for API. So I need to make new controller for API or that controller can be used?
There are many questions here so I'll try to give a big picture answer:
In general, other controllers inherit from ApplicationController which (in your case) runs a before_filter. The filter can redirect or render and therefore prevent the execution of the specific route. Since all controllers inherit from ApplicationController, the filter is run before every action of your app (assuming the most common default case).
Presumably, API authentication is supposed to work in a different way than for the app's html frontend (perhaps an api key in a header). It looks like your app is using https://github.com/plataformatec/devise. I'd have a look at it to see if you can just "switch on" a suitable authentication method for your API with it.
I hope this helps.
The solution worked for me is to use friendly token with devise_token_auth for api. And here is my before filter now:
before_filter do
if check_api_request
authenticate_api_request
else
authenticate_user!
end
end
I have a single-page application written in React with Ruby on Rails back-end (API mode). Rails is also serving static files. I'm pointing Rails router to public/index.html, so my SPA could manage his own routing with react-router. This is common practice in order to make direct links and refresh to work.
routes.rb
match '*all', to: 'application#index', via: [:get]
application_controller.rb
class ApplicationController < ActionController::API
def index
render file: 'public/index.html'
end
end
The problem is this doesn't work in API mode. It's just an empty response. If I change the parent class to ActionController::Base everything works as expected. But I don't want to inherit the bloat of full class, I need slim API version.
I've tried adding modules like ActionController::Renderers::All and AbstractController::Rendering without success.
If I change the parent class to ActionController::Base everything works as expected. But I don't want to inherit the bloat of full class, I need slim API version.
Yes, if you serve index from ApplicationController, changing its base class would affect all other controllers. This is not good. But what if you had a specialized controller to serve this page?
class StaticPagesController < ActionController::Base
def index
render file: 'public/index.html'
end
end
This way, you have only one "bloated" controller and the others remain slim and fast.
You could do
render text: File.read(Rails.root.join('public', 'index.html')), layout: false
I usually just redirect_to 'file path'.
def export
# When the route coming to get 'some_report/export', to: 'greate_controller#export'
# The file where you write or preparing, then you can redirect some path like : http://localhost:3000/public/tmpfile/report20210507.xlsx
# And it will just redirect the file for you
file_path = "public/tmpfile/report20210507.xlsx"
redirect_to "#{root_url}#{file_path}"
end
For this example
root_url = "http://localhost:3000/"
This should work, and allow you to keep inheriting from ActionController::API--
class ApplicationController < ActionController::API
def index
respond_to do |format|
format.html { render body: Rails.root.join('public/index.html').read }
end
end
end
The render logic changed for ActionController::API with Rails 5.
The code below shows two controllers that have before_filters added in different orders. As a result, the controllers have different behavior.
When current_user is nil (logged out):
FooController -> redirects to login_path
BarController -> redirects to root_path
You could, of course, combine the two methods and make them smarter to make this specific example go away -- but that is missing the point of this question.
The issue at hand is that, in ActionController, the order that filters get added is relevant. (You could also argue that that implementation isn't the greatest, but that's how it is -- so let's move on)
Since that's how ActionController works, I would like to test that behavior. However, it isn't necessary to test the results of the before filters on each individual action (not DRY). As far as I can figure, what's actually important is the resulting order of the filter chain on each action.
The only thing that before_filter :require_user actually does is inject an item into the callback chain - and that's all I should have to test for in my subclasses - that an additional item has been added to the chain. I don't need to test the effect that require_user has on my actions - they can, and should, be stubbed.
That all said, here's the money question: Is there a public API that returns what methods are in a given controller and action's filter chain? This should include before, after and around filters in their appropriate order.
class ApplicationController < ActionController::Base
def require_user
unless current_user
redirect_to login_path and return
end
end
def require_admin
unless current_user.try(:admin?)
redirect_to root_path and return
end
end
end
class FooController < ApplicationController
before_filter :require_user
before_filter :require_admin
end
class BarController < ApplicationController
# reverse the order of the filters
before_filter :require_admin
before_filter :require_user
end
You can access the list of filters via the _process_action_callbacks class method. For a list of filters, you can:
FooController._process_action_callbacks.collect &:filter
I have a Rails 4 app (that was upgraded from Rails 3) in which I decided to delete one of the controllers. I then moved the methods from that deleted controller to the ApplicationController, which included before_filter :authenticate_user!
Here's what my ApplicationController looks like now:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :authenticate_user!
respond_to :json
def index
gon.rabl
#user = current_user
gon.rabl "app/views/users/show.json.rabl", as: "current_user"
end
def markdown
require 'redcarpet'
renderer = Redcarpet::Render::HTML.new
extensions = {}
Redcarpet::Markdown.new(renderer, extensions)
end
helper_method :markdown
end
Now, I'm getting this error:
ActionController::UnknownFormat in Devise::SessionsController#new
I think this might be due to the fact that you have set your application controller to respond only to json. If your Devise Controller inherits from ApplicationController (I think this is the default), then it will expect to see a content-type: json header, or your urls must all end in .json
You shouldn't have the index method defined in application_controller. You should move it to the appropriate controller. If this is something you want to do before every action you might want to try something like this:
before_action :gon_user, only: :index
private
def gon_user
gon.rabl
#user = current_user
gon.rabl "app/views/users/show.json.rabl", as: "current_user"
end
Though i've to be honest that i'm not sure about the gon stuff, can't remember if it was for moving data from ruby to javascript or for responding to ajax/json request.
Thanks Slicedpan. Got me thinking about a
respond_to :json
Used in my Rails Application as an API with Angular. As in my Rails controllers I use for requests from my Angular Services.
respond_with
In my case I ended up adding html to the respond_to:
respond_to :json, :html
The Default Mime Types can be seen here:
http://apidock.com/rails/Mime
I am just getting into rails and begining to understand it slowly. Can someone explain or give me ideas on the benefits or when and whys for coding inside the application_controller? What are some usecases. How are you using the application controller for your rails app? I dont want to put too much code in there because from what I understand, this controller gets called for every request. Is this true?
ApplicationController is practically the class which every other controller in you application is going to inherit from (although this is not mandatory in any mean).
I agree with the attitude of not messing it with too much code and keeping it clean and tidy, although there are some cases in which ApplicationController would be a good place to put your code at.
For example: If you are working with multiple locale files and want to set the locale based on the URL requested you'd do this in your ApplicationController:
before_filter :set_locale
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end
This will spare you the headache of setting locale in each controller separately. You do it once, and you have the locale set all over the system.
Same goes for the famous protect_from_forgery which can be found on the default ApplicationController of a new rails app.
Another use case could be rescuing all exception of a certain type in your application:
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
private
def record_not_found
render :text => "404 Not Found", :status => 404
end
In general, if you have a feature that all other controllers would definitely use, ApplicationController might be a good place for it.
I use it for helpers and methods that need to be used in multiple controllers. A good example is Ryan Bates "Super Simple Authentication from Scratch"
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :admin?
protected
def authorize
unless admin?
flash[:error] = 'Sorry, that page is only for admins'
redirect_to :root
false
end
end
def admin?
session[:password] == "password"
end
end
Then you can all a before_filter :authorize or if admin? from anywhere in your app.