I'm developing an API using Rails 5.1.3 and I'm using the gem devise_token_auth for authenticate. Everything was working fine until I needed to customize the JSON renderized after an error occurred, like the client sending an request with an invalid email.
So, to do this, I redefined my routes from
mount_devise_token_auth_for 'User', at: 'auth'
to
mount_devise_token_auth_for 'User', at: 'auth', controllers: {
registrations: 'devise/registrations'
}
and created a file app/controllers/devise/registrations_controller.rb as below:
class RegistrationController < DeviseAuthToken::RegistrationController
def render_create_error
render 'devise/registrations/create_error.json'
end
def render_create_success
super
end
end
Now all requests that depends of RegistrationController are getting this error:
ActionView::Template::Error:
undefined method `protect_against_forgery?' for #<#<Class:0x007f84cfab70d8>:0x007f84cec53e10>
What I should do to fix this error?
Thanks in advance!
This is what I did on my rails server (rails 6). I have created folders called overrides where I place all my custom controllers / views
routes.rb
mount_devise_token_auth_for 'User', at: 'auth', controllers: {
registrations: 'overrides/registrations'
}
app/controllers/overrides/registrations_controller.rb
module Overrides
class RegistrationsController < DeviseTokenAuth::RegistrationsController
def render_create_success
render partial: 'overrides/registrations/render_create_success.json.jbuilder'
end
end
end
app/views/overrides/registrations/_render_create_success.json.jbuilder
json.status 'success'
json.data do
json.extract! #resource, :field1, :field2, etc.
end
Why does the Rack middleware fail to redirect when coupled with default GET and POST login routes and 401 handling in the Sinatra app?
Relevant Shield middleware extract :
module Shield
class Middleware
attr :url
def initialize(app, url = "/login")
#app = app
#url = url
end
def call(env)
tuple = #app.call(env)
if tuple[0] == 401
[302, headers(env["SCRIPT_NAME"] + env["PATH_INFO"]), []]
else
tuple
end
end
private
def headers(path)
{ "Location" => "%s?return=%s" % [url, encode(path)],
"Content-Type" => "text/html",
"Content-Length" => "0"
}
end
def encode(str)
URI.encode_www_form_component(str)
end
end
end
View full source code (104 lines/2.8kb).
Here a relevant extract of the Sinatra app:
# application_controller.rb
class ApplicationController < Sinatra::Base
helpers Shield::Helpers
use Shield::Middleware, "/login"
...
get '/noway' do
error(401) unless authenticated(User)
erb :app_noway
end
get '/login' do
erb :login
end
post "/login" do
if login(User, params[:login], params[:password])
remember(authenticated(User)) if params[:remember_me]
redirect(params[:return] || "/")
else
redirect "/login"
end
end
end
Full source code (basic app displaying the problem behavior), for easy and immediate perusal: https://github.com/shieldtest/shieldtest
The repository is ready for a "clone and rackup" with database, env and all. Login credentials; email: shield#example.org, password: shield.
Problem When accessing a protected route (/noway), the middleware injects a authentication process, as intended. But after the successful autentication, the subsequent redirect always defaults to root, instead of the return URL for the protected page (/noway).
Solution needed The protected page (/noway) should be redirected to automatically after authenticating successfully via Shield.
Visual walk-through
Step 1 (below): At the Sinatra main page. Click link to protected page (/noway)
Step 2 (below): Redirected to /login correctly, as no user is authenticated. Enter correct login credentials correctly.
PROBLEM BEHAVIOR - redirected to main instead of the protected pageStep 3A (below): After entering correct login credentials: sent back to main page (again)
TESTING LOGIN - protected page is accessible now (manually, by clicking page again)Step 4 (below): At the main page. Click the protected page (/noway) again => Access granted
The params[:return] was never forwarded to the POST request, it seems.
So, a 'dirty fix' would be to grab the return params and pass it via the login form to the POST request. This yields the desired behavior:
#login.rb
...
<% if params[:return] %>
<input type='hidden' name='redirect' value="<%= params[:return] %>">
<% end %>
...
And then redirecting to the redirect params from the login form:
#application_controller.rb
post "/login" do
if login(User, params[:login], params[:password])
...
redirect to params[:redirect] || "/"
...
end
end
Still, I would have preferred to understand why the middleware didn't perform as expected and how to fix/store this return params via the Rack middleware.
i am having trouble running my rspec examples in a loop.
describe "GET all providers" do
let(:current_user) { Factory(:user) }
[:twitter, :facebook, :google_oauth2].each do |provider|
before :each do
current_user.confirm!
sign_in current_user
request.env['devise.mapping'] = Devise.mappings[:user]
request.env["omniauth.auth"] = OmniAuth.config.add_mock provider, {
:uid => '123456789',
:info => {
:email => current_user.email
}
}
end
it 'should add the authorization' do
get provider # method to create the authorization
authorization = Authorization.where(:provider => request.env["omniauth.auth"][:provider], :uid => request.env["omniauth.auth"][:uid]).first
current_user.authorizations.should include authorization
end
end
end
currently these examples all pass. the problem though is that current_user is a new user instance through each iteration of the loop, despite memoizing the current_user method. so if i add a test for current_user.authorizations.count.should == 3 it fails.
this became less of needing to actually test it, and more understanding why it isnt behaving how i expect. shouldn't let(:current_user) { Factory(:user) } persist the same user instance across all examples?
Here is a gist I came up with that might help you understand let and let!:
https://gist.github.com/3489451
let memoizes the return value within each it example, but executes the block every time it is called for the first time in the example.
For example, if you call current_user the first time in an it example block, the { Factory(:user) } block is executed. Calling current_user a second or any subsequent time within the same it example block does not execute the { Factory(:user) } block; the return value is memoized.
I have written a Rails 3.1 engine with the namespace Posts. Hence, my controllers are found in app/controllers/posts/, my models in app/models/posts, etc. I can test the models just fine. The spec for one model looks like...
module Posts
describe Post do
describe 'Associations' do
it ...
end
... and everything works fine.
However, the specs for the controllers do not work. The Rails engine is mounted at /posts, yet the controller is Posts::PostController. Thus, the tests look for the controller route to be posts/posts.
describe "GET index" do
it "assigns all posts as #posts" do
Posts::Post.stub(:all) { [mock_post] }
get :index
assigns(:posts).should eq([mock_post])
end
end
which yields...
1) Posts::PostsController GET index assigns all posts as #posts
Failure/Error: get :index
ActionController::RoutingError:
No route matches {:controller=>"posts/posts"}
# ./spec/controllers/posts/posts_controller_spec.rb:16
I've tried all sorts of tricks in the test app's routes file... :namespace, etc, to no avail.
How do I make this work? It seems like it won't, since the engine puts the controller at /posts, yet the namespacing puts the controller at /posts/posts for the purpose of testing.
I'm assuming you're testing your engine with a dummy rails app, like the one that would be generated by enginex.
Your engine should be mounted in the dummy app:
In spec/dummy/config/routes.rb:
Dummy::Application.routes.draw do
mount Posts::Engine => '/posts-prefix'
end
My second assumption is that your engine is isolated:
In lib/posts.rb:
module Posts
class Engine < Rails::Engine
isolate_namespace Posts
end
end
I don't know if these two assumptions are really required, but that is how my own engine is structured.
The workaround is quite simple, instead of this
get :show, :id => 1
use this
get :show, {:id => 1, :use_route => :posts}
The :posts symbol should be the name of your engine and NOT the path where it is mounted.
This works because the get method parameters are passed straight to ActionDispatch::Routing::RouteSet::Generator#initialize (defined here), which in turn uses #named_route to get the correct route from Rack::Mount::RouteSet#generate (see here and here).
Plunging into the rails internals is fun, but quite time consuming, I would not do this every day ;-) .
HTH
I worked around this issue by overriding the get, post, put, and delete methods that are provided, making it so they always pass use_route as a parameter.
I used Benoit's answer as a basis for this. Thanks buddy!
module ControllerHacks
def get(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "GET")
end
# Executes a request simulating POST HTTP method and set/volley the response
def post(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "POST")
end
# Executes a request simulating PUT HTTP method and set/volley the response
def put(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "PUT")
end
# Executes a request simulating DELETE HTTP method and set/volley the response
def delete(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "DELETE")
end
private
def process_action(action, parameters = nil, session = nil, flash = nil, method = "GET")
parameters ||= {}
process(action, parameters.merge!(:use_route => :my_engine), session, flash, method)
end
end
RSpec.configure do |c|
c.include ControllerHacks, :type => :controller
end
Use the rspec-rails routes directive:
describe MyEngine::WidgetsController do
routes { MyEngine::Engine.routes }
# Specs can use the engine's routes & named URL helpers
# without any other special code.
end
– RSpec Rails 2.14 official docs.
Based on this answer I chose the following solution:
#spec/spec_helper.rb
RSpec.configure do |config|
# other code
config.before(:each) { #routes = UserManager::Engine.routes }
end
The additional benefit is, that you don't need to have the before(:each) block in every controller-spec.
Solution for a problem when you don't have or cannot use isolate_namespace:
module Posts
class Engine < Rails::Engine
end
end
In controller specs, to fix routes:
get :show, {:id => 1, :use_route => :posts_engine}
Rails adds _engine to your app routes if you don't use isolate_namespace.
I'm developing a gem for my company that provides an API for the applications we're running. We're using Rails 3.0.9 still, with latest Rspec-Rails (2.10.1). I was having a similar issue where I had defined routes like so in my Rails engine gem.
match '/companyname/api_name' => 'CompanyName/ApiName/ControllerName#apimethod'
I was getting an error like
ActionController::RoutingError:
No route matches {:controller=>"company_name/api_name/controller_name", :action=>"apimethod"}
It turns out I just needed to redefine my route in underscore case so that RSpec could match it.
match '/companyname/api_name' => 'company_name/api_name/controller_name#apimethod'
I guess Rspec controller tests use a reverse lookup based on underscore case, whereas Rails will setup and interpret the route if you define it in camelcase or underscore case.
It was already mentioned about adding routes { MyEngine::Engine.routes }, although it's possible to specify this for all controller tests:
# spec/support/test_helpers/controller_routes.rb
module TestHelpers
module ControllerRoutes
extend ActiveSupport::Concern
included do
routes { MyEngine::Engine.routes }
end
end
end
and use in rails_helper.rb:
RSpec.configure do |config|
config.include TestHelpers::ControllerRoutes, type: :controller
end
The problem is;
class ApplicationController < ActionController::Base
# Pick a unique cookie name to distinguish our session data from others'
session :session_key => '_simple_blog'
#session :disabled => true
private #------------
def authorize_access
if !session[:user_id]
flash[:notice] = "Please log in."
redirect_to(:controller => 'staff', :action => 'login')
return false
end
end
end
the error message is
DEPRECATION WARNING: Disabling sessions for a single controller has been deprecated. Sessions are now lazy loaded. So if you don't access them, consider them off. You can still modify the session cookie options with request.session_options.
Can somebody point em in the right direction.
Thanks
You are receiving this warning because you are explicitly loading the session context via the session method. You should instead use request.session_options[:session_key] = 'new_session_key' from within an action, as the framework now lazily loads the context if necessary (as you saw). If you want to do this for all actions, create a method and use before_filter:
class ApplicationController < ActionController::Base
before_filter :setup_session_key
protected
def setup_session_key
# Pick a unique cookie name to distinguish our session data from others'
request.session_options[:session_key] = '_simple_blog'
end
end