Rails 3: Manipulating devise "resource" in controller? - ruby-on-rails-3

I'm using devise & devise_invitable in a rails 3 project, and I'm trying to manipulate some of the 'User' object fields in the devise controller.
The action is question is this:
def update
self.resource = resource_class.accept_invitation!(params[resource_name])
resource.first_name = 'Lemmechangethis'
if resource.errors.empty?
set_flash_message :notice, :updated
sign_in(resource_name, resource)
respond_with resource, :location => after_accept_path_for(resource)
else
respond_with_navigational(resource){ render_with_scope :edit }
end
end
I'd have thought that the (commented out) resource.first_name call would influence resource in much the same way as a model - but it doesn't seem to. I'm still getting a 'blank' validation error on this form.
So, the question is, how do I specify values to the User model in devise (and/or devise_invitable) that will actually be subject to verification?
Any suggestions appreciated,
John

resource does return a User models instance. So the resource.first_name = 'Lemmechangethis' statement does change your User models instance but it doesnot trigger your User models validations, which is probably why resource.errors always returns an empty array. One way to trigger the User models validation is to call resource.valid? for example. You can then check the resource.errors array for specific error messages.

Related

Rails 3 and update_attributes

I am moving an application from Rails 2.2.2 to Rails 3. I have a form that is used to update a user's info that was fully functional in Rails 2 but breaks in Rails 3
When the form is submitted, a method is called that creates the user object and then tries to do the update like this:
if #user.update_attributes params[:user] ## line 126
Then the controller throws this exception:
undefined method `update_attributes' for #<ActiveRecord::Relation:0xacfc178>
in: app/controllers/admin_controller.rb:126:in `save_user'
So it looks like ActiveRecord in Rails 3 is returning a different type of object? One that doesn't inherit update_attributes? How do I fix this?
Here is the full controller method in question:
def save_user
#needs_password_gen = "YES"
#user = B2bUser.where("id = ?",params[:id])
#needsAPICredentials = false
##### Make sure thay gave us an email address
if !params[:user][:email] || !validEmailAddress(params[:user][:email].to_s)
flash[:warning] = "Valid email address is required."
redirect_to :controller => "admin/edit_user/#{#user.id}" and return
end
#user.first.update_attributes params[:user]
end
THANKS
Looks like your #user object may be an array. You are probably using :where to query and are forgetting to pop it off the array. Try this:
#user.first.update_attributes params[:user]

Changing devise flash messages from notice to error

I know that you can override the default devise controllers and I did so for the Registrations and Sessions Controller. I know that you can also change the text for the flash messages in devise under locale. However, I am not sure how to change the type of flash message showing for the sessions controller when there is an invalid combination of username and password.
The create method looks like
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
I suspect that the validation is done during the follow call
warden.authenticate!(auth_options)
But this is where I am not sure how to overwrite that in my app.
Also, I think it is a complex override for such a simple use case of changing the color of a flash notice.
Any insights would be much appreciated.
Thanks!
Nick
You can do it with custom failure app. As you can see this flash message is setting right here So you can change it in your custom failure app.
So at first you inherit your failure app from Devise's one:
class CustomFailure < Devise::FailureApp
def recall
env["PATH_INFO"] = attempted_path
flash.now[:error] = i18n_message(:invalid)
self.response = recall_app(warden_options[:recall]).call(env)
end
end
place this file somewhere in your app and say Devise to use it like this (config/initializers/devise.rb):
config.warden do |manager|
manager.failure_app = CustomFailure
end

Rspec controller spec

I am new to Rspec please tell me what would be the controller Spec for the following two methods In index method only login page is seen by entering the username control goes to login method and find the name of person. If person is find then control goes to people path otherwise it goes back to root path that is index page it self.
class HomeController < ApplicationController
def index
end
def login
#person = Person.find(:all, :conditions => ['people.name =?', params[:person][:name]] )
if #person.blank?
redirect_to root_path
else
redirect_to people_path
end
end
end
Please help me.
Thanks.
Your rspec controller tests could be like this:
describe HomeController do
render_views
it "Logs in Person with non-blank name" do
person = Factory(:Person, name: "non-blank name")
get :login
response.should redirect_to(people_path)
end
it "does not log in Person with blank name" do
person = Factory(:Person, name: "") # blank name
get :login
response.should redirect_to(root_path)
end
end
Refer to rails controller specs for details.
EDIT:
Factory: the code that creates objects (test objects in this case). This is a preferred method for creating test objects because you can customize your code to create objects with varying attributes with least duplication.
Fixtures: If you are not using factories, you can specify the attributes for each of the objects you are going to create. For more than 2-3 object, this data quickly becomes unmanageable to maintain (for example, when you add an attribute, you need to make changes for each of these objects).
Stubs: If you prefer not to create database records while creating model objects, you can stub the model code white testing controllers.
For more information, refer:
1. testing guide
2. asciicast (Note: this code refers to an older version of FactoryGirl gem. Refer below for up-to-date API of FactoryGirl)
3. FactoryGirl Readme

RSpec and CanCan Controller Testing

I'm using RSpec and CanCan in a project. I'm testing my permission logic in specs related to the Ability class. For the controllers I really just want to make sure I'm doing an authorization check. I set up a controller macro, but it doesn't seem to be working correctly.
So really I have two questions. One, is the strategy sufficient for testing the permission logic of my controllers (or should I be testing the controller authorization logic more)?
Two, does anyone see what I'm doing wrong to make this not work?
#plan_orders_controller.rb
def approve
plan_order = PlanOrder.find(params[:id])
authorize! :update, plan_order
current_user.approve_plan_order(plan_order)
redirect_to plan_order_workout_plan_url(plan_order)
end
#controller_macros.rb
def it_should_check_permissions(*actions)
actions.each do |action|
it "#{action} action should authorize user to do this action" do
#plan_order = Factory(:plan_order, :id=>1)
ability = Object.new
ability.extend(CanCan::Ability)
controller.stub!(:current_ability).and_return(ability)
get action, :id => 1
ability.should_receive(:can?)
end
end
end
The output I get from RSpec is the following
Failure/Error: ability.should_receive(:can?)
(#<Object:0x00000006d4fa20>).can?(any args)
expected: 1 time
received: 0 times
# ./spec/controllers/controller_macros/controller_macros.rb:27:in `block (2 levels) in it_should_check_permissions'
I'm not really sure which method I should be checking when I call !authorize in the controller (or it is automatically called via load_and_authorize_resource)
should_receive is an expectation of something that happens in the future. Reverse these two lines:
get action, :id => 1
ability.should_receive(:can?)
so you have this:
ability.should_receive(:can?)
get action, :id => 1

Where to put business logic that requires the current_user to be known? (Rails)

I have a model (say Car) in which a method needs access to the current_user to determine if the user is allowed to perform the things that the method does.
For example, a method might want to do these things:
Check that current_user owns this object
Check that the object status == 1 (Active)
Check that a related object exists and it's X field is not NULL
I need this business logic to be in the model, not in the controller, so that it's the one place where my business logic will be. The method might get called from places other than a controller.
I know that there are gems like cancan, declarative_authorization etc. but they seem to be overkill for what I need to do. And also, accessing current_user in a model is not considered the "right way".
Then, how do I make that check in the model but still feel "clean"?
I have experienced a situation where "current_user" needs be tightly connected to a model, but I handled it all in the Controller and it works pretty well. Here are some examples:
My model is "Photos". Photos are owned by users, and how people interact with photos is obviously tightly related to whether or not they own the photo.
In the show action I need to load either the existing rating a user has given to a photo (so they can edit it) or allow them to create a new one:
def show
#photo = Photo.find(params[:id])
if user_signed_in?
if #rating = current_user.ratings.find_by_photo_id(params[:id])
#rating
#current_user_rating = #rating.value
else
#rating = current_user.ratings.new
#current_user_rating = "n/a"
end
end
end
When people create photos I want them to be automatically assigned to the current user.
def new
#photo = Photo.new
end
def create
#photo = current_user.photos.create(params[:photo])
if #photo.save
redirect_to user_path(current_user), :notice => t('photo.notice.created')
else
render 'new'
end
end
Only the owners of a photo can change them:
def edit
#photo = Photo.find(params[:id])
if #photo.user == current_user
render 'edit'
else
redirect_to user_path(current_user), :alert => t('application.error.unauthorized')
end
end
def update
#photo = current_user.photos.find_by_id(params[:id])
#photo.update_attributes(params[:photo])
if #photo.save
redirect_to user_path(current_user), :notice => t('photo.notice.updated')
else
render 'edit'
end
end
This approach is based on the constraints that a "current_user" object is tied to the session, which only the controller knows about. So, in short, I have yet to find a good way to integrate "current_user" into a model, but I've been able to find (I think) pretty clean ways to tie the model and controller together so that this can be provided by the controller.
One fairly simple solution to most problems, if your controller is starting to get messy, would be to take a chunk of logic and define as a method in the model, but require one argument = a user object. Then you can just feed "current_user" to that method from your controller and the model handles the rest.
Good luck! Also, if anyone else has any better ideas for this, I'd love to hear them!
Handle auth'ing in a Controller.
Example: Putting auth logic in parent ApplicationController.
class ApplicationController < ActionController::Base
protect_from_forgery
protected
# Returns the currently logged in user or nil if there isn't one
def current_user
return unless session[:user_id]
#current_user ||= User.find_by_id(session[:user_id])
end
# Make current_user available in templates as a helper
helper_method :current_user
# Filter method to enforce a login requirement
# Apply as a before_filter on any controller you want to protect
def authenticate
logged_in? ? true : access_denied
end
# Predicate method to test for a logged in user
def logged_in?
current_user.is_a? User
end
# Make logged_in? available in templates as a helper
helper_method :logged_in?
def access_denied
redirect_to login_path, :notice => "Please log in to continue" and return false
end
end
Now that current_user is an accessor to the logged in user and you can access it in any controller, you can do your authorization logic in the appropriate controller before you do anything with the model.
Your right, though. Models don't care about authorization or who is accessing them.