I'm trying to write an Ember application in Rails 4, and have decided to go with rails-api for the api controllers, while keeping the application controller intact for a few pages that aren't part of the single-page app. To put it in more concrete terms, here are my controllers:
app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
end
app/controllers/sample_controller.rb:
class SampleController < ApplicationController
# my methods
end
app/controllers/api/v1/api_controller.rb:
class Api::V1::ApiController < ActionController::Api
include ActionController::MimeResponds
end
app/controllers/api/v1/sample_controller.rb:
module Api::V1
class SampleController < ApiController
respond_to :json
# my methods
end
end
My application.html.slim contains the following line:
== render partial: "flash_msgs" unless flash.blank?
The inclusion of which results in the following error:
undefined method 'flash' for #< ActionDispatch::Request:0x007f99f41d8720 >
Per discussion on this thread, it seems that the culprit could be rails-api, but I'm not entirely convinced given the inheritance I've set up. Any suggestions?
Not sure but maybe you need to include the ActionDispatch::Flash middleware to support the flash. Using:
config.middleware.use ActionDispatch::Flash
The docs says:
ActionDispatch::Flash: Supports the flash mechanism in
ActionController.
I hope it helps
See: https://github.com/plataformatec/devise/issues/2775
Inside devise.rb change
config.navigational_formats = ['*/*', :html]
to:
config.navigational_formats = [:json]
or just [ ]
If you're like me and creating an API on top of an existing application, you can add this to your config/application.rb file:
config.api_only = false
well, in my case my API mode rails app I had this code in one of my controller:
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }
due to which handle_unverified_request was getting called that has this small piece of code request.flash = nil which was raising Undefined method 'flash' for ActionDispatch::Request for me.
Dealt with it by replacing protect_from_forgery with skip_before_action :verify_authenticity_token
Related
I generated an API-only rails app with Rails 5 via rails new <application-name> --api. I've decided I want to include a view for testing some things and am having issues getting a view to load.
I created a users/index.html.erb file with some text and my controller is now simply def index; end but there is nothing appearing when I hit the /users URL. I also tried commenting out the # config.api_only = true in config/application.rb but that didn't affect anything. Any suggestions on how to proceed?
You don't need to uncomment config.api_only = true for this purpose, just inherit your controller from ActionController::Base, or do it in your ApplicationController (default for common rails generation).
Code:
For this controller only YourController < ActionController::Base
For all apllication ApplicationController < ActionController::Base
this is from the ActionController::Metal docs https://apidock.com/rails/ActionController/Metal
it says:
ActionController::Metal by default provides no utilities for rendering >views, partials, or other responses aside from explicitly calling of >response_body=, content_type=, and status=. To add the render helpers >you’re used to having in a normal controller, you can do the following:
class HelloController < ActionController::Metal
include AbstractController::Rendering
include ActionView::Layouts
append_view_path "#{Rails.root}/app/views"
def index
render "hello/index"
end
end
So I've tried it myself and adding just by adding the two modules actually work just fine when using it for ActionController::API
Im implementing a Rest API on Ruby on Rails. So i want to respond to all requests in json format. I made this:
include ActionController::MimeResponds
before_filter :force_json
def force_json
response.format = "json"
#also tried
# response.content_type = Mime[:json]
end
Those two ways didn't worked. It gives me an html page with errors.
Also is there a way to implement this for the whole api and not for each class?
Thanks!
If you want it to happen application wide, you can do something like this in the application controller.
class ApplicationController < ActionController::Base
before_action :force_json
def force_json
request.format = :json
end
end
If you use the responders gem, you can define this at the top of your class:
class PostsController < ApplicationController
respond_to :json
...
Then this controller will respond using JSON by default.
I'm trying to access the request object in app/controllers/application_controller.rb. My code is:
class ApplicationController < ActionController::API
include ActionView::Layouts
include ActionController::RequestForgeryProtection
protect_from_forgery
before_action :require_login
private
def require_login
unless logged_in?
logger.log "#{request}"
end
end
def logged_in?
false
end
end
This results in the error:
comparison of String with 0 failed, highlighting the line logger.log "#{request}"
I thought it was a problem with the middleware not being loaded, so I tried to load it in config/application.rb:
config.middleware.use ActionDispatch::Request
But this results in another error:
undefined method 'call' for ActionDispatch::Request
I seem to keep having to add things back in since I used the --api option and it strips a lot of things out. But I don't know how to add back in access to the request option. Any help?
It looks like you are mis-using logger.log. In the simple example below, I have outlined three ways to approach this. If you want to use logger.log, you need to specify at minimum a severity level. That is the source of the comparison of String with 0 failed message you are receiving.
class ApplicationController < ActionController::API
before_action :log_request
def log_request
logger.log request # This doesn't work
logger.info("#{request}") # This works
logger.log(Logger::WARN,"#{request}") # This works
end
end
Valid levels are: FATAL, ERROR, WARN, INFO, DEBUG.
Reference: http://ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html#method-i-add (logger.log is an alias for logger.add)
Logger#add(severity, message = nil, progname = nil) { ... }
I want to add a filter to the ApplicationController but I want to do it within my gem.
What I want to avoid is the following:
class ApplicationController < ActionController::Base
include MyGem
end
I do not want that. I don't want to have to include my module in the source code.
I am having issues though.
Here is the relevant code:
lib/correlation_id/controller_extension
module CorrelationId
module ControllerExtension
def self.included(klass)
klass.class_eval do
after_filter :pass_correlation_id
end
end
def pass_correlation_id
correlation_id = request.headers['Correlation-ID'] || SecureRandom.uuid
headers['Correlation-ID'] = correlation_id
end
end
end
ApplicationController.send :include, CorrelationId::ControllerExtension
lib/correlation_id.rb
require 'correlation_id/controller_extension'
module CorrelationId
end
Now, when I'm in the test/dummy directory, which is a test rails app for my gem, I try to boot up the server using rails s and I get the following error:
/correlation_id/lib/correlation_id/controller_extension.rb:17:in `<top (required)>': uninitialized constant ApplicationController (NameError)
I'm clearly having problems with referencing ApplicationController to monkey-patch it.
How would I manage this? I want my gem to be self-contained.
The following code works. What I did was prematurely create ApplicationController with the appropriate inheritance. Note, many people use the rails-api gem, so I factored in them to ensure the fact that it would work.
Also, note: You must inherit from a class because otherwise ApplicationController will be a usual class that doesn't understand what after_filter is.
module CorrelationId
module ControllerExtension
def self.included(klass)
klass.class_eval do
after_filter :pass_correlation_id
end
end
def pass_correlation_id
correlation_id = request.headers['Correlation-ID'] || SecureRandom.uuid
headers['Correlation-ID'] = correlation_id
end
def self.base_controller_inheritance
if Gem::Specification.find_all_by_name('rails-api').any?
ActionController::API
else
ActionController::Base
end
end
end
end
class ApplicationController < CorrelationId::ControllerExtension.base_controller_inheritance
include CorrelationId::ControllerExtension
end
I imagine there might be a better way to check if they are using ActionController::API and if so, please do share, but as of now, this seems like the most solid way to do it.
There's a gem, which appends a before_filter to a Rails app:
class Railtie < Rails::Railtie
initializer "..." do
ActiveSupport.on_load(:action_controller) do
ActionController::Base.send(:include, Filter)
...
module Filter
extend ActiveSupport::Concern
included do
append_before_filter :set_locale
end
def set_locale
....
And here's some controller in the app:
class DesktopsController < ApplicationController
before_filter :set_language_in_session
Now the problem with this is that the before_filter from the gem is being put in the filter chain before the before_filter from the DesktopsController:
DesktopsController._process_action_callbacks.select { |c| c.kind == :before }.collect { |filter| filter.filter }
=> [
[0] :set_locale,
[1] :set_language_in_session
]
How can I make the before_filter from the gem (set_locale) be put after all other filters? The secret probably lies in this line:
ActiveSupport.on_load(:action_controller) do
But I've tried different libraries without any luck...
Btw. Here's the full code of the gem. Ruby 1.9.2, Rails 3.0.5.
Adapting the gem will never work. The gem is initialised as one of the first things in the rails process, so before any actual controller is ever launched. So this means, that whatever you do, most likely the filter from the gem will still remain the first gem.
So the only solutions I see are:
using prepend_before_filter in your own controllers for those actions that need to come before the set_locale.
if you need explicit control, use the skip_before_filter and explicit before_filter.
In a rails 2 application, we devised a hack to make sure that some filter was run as last filter, by overriding the perform_action_without_filters (see Is there a way to force a before_filter to always execute last?) but we removed that when upgrading to rails 3.
My answer is based on nickgrim's answer, which I also found quite clever. The block passed to included gets class evaled (class_eval) when the module is included. You can make set_locale_filter a class method by defining it in the included block (among other ways).
module Filter
extend ActiveSupport::Concern
included do
append_before_filter :set_locale_filter
def self.set_locale_filter
append_before_filter :set_locale
end
end
def set_locale
#
end
end
Somebody, please correct me if I'm mistaken. It's late here :)
Try this:
class DesktopsController < ApplicationController
skip_before_filter :set_locale
before_filter :set_language_in_session
before_filter :set_locale
end
Reference
skip_before_filter doc
I haven't tried it, but you might check out prepend_before_filter, which claims to bump the provided filter to the top of the list (and ultimately before :set_locale).
class DesktopsController < ApplicationController
prepend_before_filter :set_language_in_session
end
Docs: http://apidock.com/rails/ActionController/Filters/ClassMethods/prepend_before_filter
Another thing to try is using an around_filter for :set_language_in_session (even though you're not doing anything after the action). Around filters happen before before_filters, so it could be a good way to force it to happen first.
The gem itself is on Github so you're able to fork it and make the code changes, then have your code pull your version of the gem instead. Is this an option? If so, you could just comment out the before_filer in the gem and call it yourself in your ApplicationController:
module Filter
extend ActiveSupport::Concern
included do
# append_before_filter :set_locale
end
def set_locale
....
Now just call it yourself after all of your other filters:
class ApplicationController < ActionController::Base
before_filter :do_this, :do_that, :set_locale
...
end
Whatta ya think?
Not tried this at all, but: could you have the gem's before_filter run append_before_filter? Something like:
module Filter
...
included do
append_before_filter :append_set_locale_filter
end
def append_set_locale_filter
append_before_filter :set_locale
end
def set_locale
...
end
...
end
Personally, I'd probably write a piece of middleware that does your operation, because then you don't have to worry about the order the filters happen.
http://guides.rubyonrails.org/rails_on_rack.html