I'm attempting to use Devise (2.2.4), which I'm new to, with the Rails 3.2.13/Ruby 2.0.0p195 app I'm building. I turned scoped_views on because I want to have my own separate users and admins views. And I created my own Users::RegistrationsController which seems to be doing what I want it to. I've just added my own Users::SessionsController, which is where I've hit problems.
I straight copied over a couple of action methods from the Devise::SessionsController source as a first step, planning to modify them once they were working (my controller code is at the bottom of this post). But my 'new' method is failing, when called, with a NameError because `sign_in_params' is apparently undefined.
Well, that seems pretty strange because I'm inheriting from Devise::SessionsController, and when I look at the source for that on GitHub, there's the sign_in_params defined in the protected section at the bottom. So I decided to investigate whether my controller is inheriting correctly from Devise::SessionsController - and it certainly seem to be. I can list out all the inherited methods, just not that one missing one. So I ended up running the following piece of code in the Rails Console:
(Devise::SessionsController.new.methods - DeviseController.new.methods).each {|m| puts m}
And it produces the following output:
_one_time_conditions_valid_68?
_one_time_conditions_valid_72?
_callback_before_75
_one_time_conditions_valid_76?
new
create
destroy
serialize_options
auth_options
If I ignore the underscored methods, the remainder are all those methods defined in the Devise::SessionsController source except sign_in_params. I can't see how anything I've written can be deleting that method, and I can't think what else to try. Google is silent on this problem, so I assume I'm doing something uniquely foolish, but I can't work out what. Any suggestions please? And might someone else try running that bit of Rails Console code to see what they get?
class Users::SessionsController < Devise::SessionsController
prepend_before_filter :require_no_authentication, :only => [ :new, :create ]
prepend_before_filter :allow_params_authentication!, :only => :create
prepend_before_filter { request.env["devise.skip_timeout"] = true }
# GET /resource/sign_in
def new
self.resource = resource_class.new(sign_in_params)
clean_up_passwords(resource)
respond_with(resource, serialize_options(resource))
end
# POST /resource/sign_in
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
end
end
I think you are using code from a devise version compatible with Rails 4 on a rails 3 application.
sign_in_params is a method to be used with strong parameters. A gem used in rails 4.
If you check the controller on devise version 2.2. https://github.com/plataformatec/devise/blob/v2.2/app/controllers/devise/sessions_controller.rb
You will see that there is no sign_in_params method.
Check which version of devise you are using and copy the code based on that devise version in your controller, rather than the latest code from github.
Related
I'm relatively new to testing and very new to Rails 4 and rSpec. I am trying to test a controller that uses Devise for authentication and I am stuck. All of the examples I can find are for Rails 3.
I'm using Rails 4.0.3, Devise 3.2.3, rSpec 2.14.1 and FactoryGirl 4.4.0.
class LessonPlansController < ApplicationController
before_action :authenticate_user!
# GET /lesson_plans
def index
#lesson_plans = current_user.lesson_plans.to_a
end
.
.
.
private
# Use callbacks to share common setup or constraints between actions.
def set_lesson_plan
#lesson_plan = LessonPlan.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def lesson_plan_params
params[:lesson_plan]
end
def lesson_plan_params
params.require(:lesson_plan).permit(:title, :synopsis)
end
end
Here are my factory definitions: (Maybe I don't need to define user_id in the lesson_plan factory?)
FactoryGirl.define do
factory :user do
sequence( :username ) { |n| "user#{n}" }
sequence( :email ) { |n| "foo#{n}#example.com" }
password 'foobarbaz'
password_confirmation 'foobarbaz'
created_at Time.now
updated_at Time.now
end
end
FactoryGirl.define do
factory :lesson_plan do
user_id 1
title "The French Revolution"
synopsis "Background and events leading up to the French Revolution"
end
end
And the test part is where I get stuck.
describe LessonPlansController do
let(:valid_attributes) { { } }
let(:valid_session) { {} }
# describe "GET index" do
it "assigns all lesson_plans as #lesson_plans" do
user=FactoryGirl.create(:user)
sign_in user
lesson_plan = LessonPlan.create! valid_attributes
get :index, {}, valid_session
assigns(:lesson_plans).should eq([lesson_plan])
end
end
I'm not sure what to put in valid_attributes and valid_session (or if I even need them). The test will get as far as signing in the user, but will fail on creation of the lesson_plan. Admittedly this is the default/generated test for rSpec, but I am not sure how to proceed.
Examples I have seen use a before block to set up the user. I haven't been able to find anything on the Devise wiki page covering how to write basic rSpec tests for a controller that requires the user to be logged in. Any pointers would be greatly appreciated!
"I'm not sure what to put in valid_attributes and valid_session (or if I even need them)."
Well that depends what you're testing for.. Say you're testing validations & want to ensure that a record not be created if x column is set to null... then you could try to specifically create a record with invalid attributes (e.g. column: nil) and expect the result to not return true; maybe you want to ensure that it IS created with valid attributes.
You can btw, use `attributes_for(:factory_name)`` since you're using FactoryGirl. And no you don't necessarily need to specify the user's id in your lesson plan factory; unless you always want it to reference user 1. You can simply reference user with no value. Check out http://everydayrails.com/2012/03/12/testing-series-intro.html and especially parts 3-5 for an introduction to testing with RSPec.. I found this a pretty easy to follow guide when I was getting started.
I do have this famous error : "undefined method `current_user'" with declarative authorization, though I set up this variable in the application_controller.rb :
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_current_user
protected
def set_current_user
Authorization.current_user = current_admin_utilisateur
end
end
I'm using a table called "admin_utilisateurs" instead of "users". Which was activated in Devise with : "rails generate devise admin_utilisateur"
Devise is working great.
For info, I customized my users table (admin_utilisateurs) with "roles_model" gem, So that I do have an attribut roles_mask that allows me to manage different roles while providing a role_symbols method for declarative authorization.
The problem is now that I got this strange error though the Authorization.current_user is set by the application_controller.rb.
This is the begning of one my resource controllers that procude the error :
class PubResponsablesController < ApplicationController
before_filter :authenticate_admin_utilisateur!
filter_resource_access
...
end
I search by google for this error, but none of the results provide a working solution.
Could anybody help me on this ?
Many Thanks
Ok this is the final answer.
I modified my app/controller/application_controller.rb because I don't use the #current_user instance variable in the views :
class ApplicationController < ActionController::Base
protect_from_forgery
# This is mandatory if you want to secure as well your app/models
before_filter :set_current_user
# This method is required by declarative_authorization on every controller
# that is using filter_resource_access (or any other declarative_auth.. mechanism)
def current_user
current_admin_utilisateur
end
protected
def set_current_user
Authorization.current_user = current_admin_utilisateur
end
end
As I said I'm using the following gem in collaboration :
gem devise for the authentication
The user-model-name is "admin_utilisateur" instead of "user", but it could have been : account, member, group or what you need.
gem role_model to provide a brillant role method "role_symbols" to my user model
*The method role_symbols was returning a "Set" subclass instead of an "Array" but after quick post on Github, the developer (martinrehfeld) fixed this compatibility issue in a lightning matter of minutes. Great !*
gem declarative_authorization to provide access management based on roles.
My will to use a different model name than "user" is confirmed to work by the following post.
The only thing that declarative_authorization needs is the current_user method on each controller. As I'm using a different model name with Devise (such as admin_utilisateur, account, member, ...) the helper created by devise have a different name. Instead of current_user, it is current_admin_utilisateur (or current_account, current_member). So I have to create my own current_user method.
The role_symbols method required by declarative_authorization is provided by role_model gem.
I hope this will help other developer cause I spent two days to sort out how all this fabric works together. Devise took me even more with routing issues.
My few cents to RoRrrr ;-)
Ok I managed to solve this error by modifying my app/controller/application_controller.rb :
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_current_user
def current_user
#current_user = current_admin_utilisateur
end
protected
def set_current_user
Authorization.current_user = current_admin_utilisateur
end
end
I just created my own current_user method that create an instance variable #current_user. This one is initialized with the value of current_admin_utilisateur which is based on Devise helpers and my customized user model called admin_utilisateur. (my model could as well be called acount, member, or whatever...)
I placed my current_user method in application_controller in order that it to be available in every controller of my application.
Now, I'm getting another error :
User.role_symbols doesn't return an Array of Symbols (#<RoleModel::Roles: {:developer, :admin, :coordinator, :manager, :assistant, :distributor, :exporter, :historian}>)
I don't understand because the roles_model gem provide an alias method 'role_symbols' to the admin_utilisateur model.
My controller is using the default RESTful routes for creating, adding, editing etc
I want to change the default :id to use :guuid. So what I did was:
# routes.rb
resources :posts
# Post Model
class Post < ActiveRecord::Base
def to_param # overridden
guuid
end
end
This works but my modifed REST controller code has something like this
def show
#post = Post.find_by_guuid(params[:id])
#title = "Review"
respond_to do |format|
format.html # show.html.erb
end
end
When I see this this code ..
Post.find_by_guuid(params[:id])
it would seem wrong but it works.
I don't understand why I can't write it out like this:
Post.find_by_guuid(params[:guuid])
Why do I still have to pass in the params[:id] when I'm not using it?
Looking for feedback on whether my approach is correct or anything else to consider.
Even though it works it doesn't always mean it's right.
Type rake routes in your console, and check the output of the routes. You'll see the fragment ':id' in some of them, that's where the params[:id] comes from. It's a rails convention : when you use resources in your routes, the parameter is named id. I don't know if you can change it (while keeping resources; otherwise you could just go with matching rules), but you shouldn't anyway : even if it seems not very logic, it actually has sense, once your understand how rails routing works.
I have a double namespace situation, where my controllers look like this:
CandidateController
Candidate::PerformanceController
Candidate::Performance::ReviewController
In Rails 2, I was able to use redirect_to from the Candidate::Performance::ReviewController controller in order to redirect to an action in the CandidateController, like so:
class Candidate::Performance::ReviewController < ApplicationController
before_filter :ensure_manager
# ...
def ensure_manager
if !current_user.manager?
flash[:warning] = t(:must_be_manager)
redirect_to :controller => '/candidate', :action => :index
end
end
end
The / in controller => '/candidate' would allow Rails to redirect from app.com/performance/reviews to app.com/candidate.
However, this seems to not work the same in Rails 3.1. Instead, my redirect_to goes to app.com/candidate//candidate. What is the correct way to specify a "absolute" controller within a redirect_to hash (ie. without using a path helper)?
Update: I know this would be infinitely easier if I just use named route helpers (ie. candidate_path). Unfortunately, there is a lot of legacy code in our codebase which doesn't use RESTful routing and instead uses the default "catch-all" route; ie. we have a lot of actions with no named route to fallback on.
I wonder if something else is wrong. In the doc:
In particular, a leading slash ensures no namespace is assumed. Thus,
while url_for :controller => ‘users‘ may resolve to
Admin::UsersController if the current controller lives under that
module, url_for :controller => ’/users‘ ensures you link to
::UsersController no matter what.
And I don't think it changed...
Also, shouldn't the catch-all routes be after the default routes in your config?
I think that redirect_to :controller => ... uses url_for to build the url, so in the end, if your custom routes catches /candidates, I don't really see the difference.
Some people have the same problem: https://github.com/rails/rails/issues/2575
Patching actionpack/lib/action_dispatch/routing/route_set.rb line 434
as follows fixes this: if !named_route && different_controller? &&
!controller.starts_with?('/')
If anyone else runs into this problem, it seems to be a known issue (unsure whether they consider it a bug or not, given the lack of response on the issue page from anyone working on Rails).
Until they patch it (assuming they do), I've added the following little monkey patch into an initializer, based on the code given in the original post of that issue:
module ActionDispatch
module Routing
class RouteSet
class Generator
def use_relative_controller_with_absolute_paths!
return if controller.starts_with?('/')
use_relative_controller_without_absolute_paths!
end
alias_method_chain :use_relative_controller!, :absolute_paths
end
end
end
end
Hope this can help someone else!
Update: It seems that this was fixed in Rails here.
Is there a hook or callback that I can implement so that right after the user is created, I would like to invoke some custom code ?
I tried after_confirmation hook in the user model but that didn't work.
Use the standard after_create callback provided by Rails.
class User < ActiveRecord::Base
after_create :do_something
def do_something
puts "Doing something"
end
end
Using a callback is perfectly legit if you're dealing with the internal state of the model you created.
After creating a User, I needed to create default a Team. It's preferable to avoid using callbacks to deal with other objects.
“after_*” callbacks are primarily used in relation to saving or persisting the object. Once the object is saved, the purpose (i.e. responsibility) of the object has been fulfilled, and so what we usually see are callbacks reaching outside of its area of responsibility, and that’s when we run into problems.
From this awesome blog post.
In this case it's better to act on the controller, where you can add your functionality directly, or delegate to a service for an even cleaner solution:
# shell
rails g devise:controllers users
# config/routes.rb
devise_for :users, controllers: { registrations: "users/registrations" }
# app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
after_action :create_default_team, only: :create
private
def create_default_team
Team.create_default(#user) if #user.persisted?
end
end
I'm using Rails 4 with Devise 3.5 with confirmable and had to do this due to various surprises.
class User < ActiveRecord::Base
# don't use after_create, see https://github.com/plataformatec/devise/issues/2615
after_commit :do_something, on: :create
private
def do_something
# don't do self.save, see http://stackoverflow.com/questions/22567358/
self.update_column(:my_column, "foo")
end
end