devise after create hook - ruby-on-rails-3

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

Related

Devise sign_in_params method missing

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.

rails before_destroy partial delete

I'm trying to figure out a way to partially delete/destroy dependent models in rails.
Code looks something like this:
class User < ActiveRecord::Base
has_many :subscriptions
has_many :photos, :dependent => :destroy
has_many :badges, :dependent => :destroy
before_destroy :partial_destroy
def partial_destroy
self.photos.destroy_all
self.badges.destroy_all
return false if self.subscriptions.any?
end
...
Essentially, I want to destroy the photos and badges, but if the user has any subscriptions, I want to keep those, and also keep the user from being destroyed.
I tried with .each { |obj| obj.destroy } and using delete and delete_all, but it seems to not matter.
It looks like rails is performing some kind of a rollback whenever the before_destroy returns false. Is there a way to destroy part of the dependents but not others?
This is old so I expect you've forgotten it, but I stumbled across it.
I'm not surprised delete and delete_all didn't work, since those bypass callbacks.
You're exactly right that Rails performs a rollback if any before_ callback returns false. Because Rails wraps the entire callback chain in a transaction, you're not going to be able to perform database calls (like destroys) inside the chain. What I would recommend is putting a conditional in the callback:
If the user has subscriptions, kick off a background job which will do this partial delete later (outside the callback transaction), and return false from the callback.
If they don't have subscriptions, you don't start the background job, return true from the callback, and destroy your model as usual.
I ended up doing the following:
override destroy on the User model (see below)
not actually deleting the User, but rather destroying the dependants that are not needed, and blanking any fields on the User model itself, e.g. email.
I created a UserDeleter class that takes the user and performs all clearing operations, just to keep things cleaner / having some kind of single-responsibility
overriding destroy
def destroy
run_callbacks(:destroy) do
UserDeleter.new(self).delete
end
end
deleting dependants and clearing data on User
class UserDeleter
def initialize(user)
#user = user
end
def delete
delete_photos
delete_badges
clear_personal_data
# ...
end
private
def delete_photos
#user.photos.destroy_all
end
def clear_personal_data
#user.update_attributes!(
:email => deleted_email,
:nickname => '<deleted>')
end
def deleted_email
"deleted##{random_string}.com"
end
def random_string(length = 20)
SecureRandom.hex(length)[0..length]
end
#...
end

Devise + Declarative_authorization + role_model + different users model name : undefined method `current_user'

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.

Whenever a User object is created, create UserInfo object too

Whenever a User object is created, I want a UserInfo object to be created too, and linked to it.
Unfortunately this does not create any UserInfo:
class User < ActiveRecord::Base
has_one :user_info
...
def init
self.user_info = user_info
self.save!
end
Why is the init method not called? How to reach my goal?
sombe's technique is right, but his details aren't ideal. In fact, since create_user_info is already a method on User instances, all you want is something like:
class User < ActiveRecord::Base
has_one :user_info
before_create :create_user_info
end
Edit: init doesn't do anything particularly magical under Rails (I... don't think it does under basic Ruby either - are you thinking of initialize? I'll assume you are). initialize is fired off when an instance of the Ruby class is created in memory. That's divorced by quite some margin from an instance of the model being created in the database; a new class instance could be due to you calling build (and not saving yet), or even due to reading an instance out of the database.
If you want to step in on database operations, you need to make use of the ActiveRecord callbacks. You might find my answer to this question useful.
before_save callback triggers on create and update.
I'd suggest to use after_create because before_create can return errors
class User < ActiveRecord::Base
has_one :user_info
...
after_create do
create_user_info
end
In your User model, use a before_save filter instead of init like this:
before_save :create_user_info
...
private
def create_user_info
user_info = UserInfo.new
if user_info.save
self.user_info_id = user_info.id
end
end

Rails 3 route scoping and/or nesting

I am having trouble scoping routes that I don't want to nest.
I have the following routes:
resources :foos
resources :bars
resources :bazs do
resources :hellos
resources :worlds
end
The foo, bar, and baz models all belong_to a user model. I don't want to nest another layer, but I do want to have a prefix in my url that corresponds to a user's permalink attribute (similar to each github repo prefixed by a username). So I have a before filter on all of my controllers
def get_scope
#user = User.find_by_permalink(params[:permalink])
end
Modified to_param thanks to #cowboycoded
class User < ActiveRecord::Base
def to_param
permalink
end
end
I wrapped those routes with
scope ":permalink", :as => :user do
#nested routes here
end
Now everything works fine as long as I pass #user to every non-index route. It doesn't seem very dry to have to go back to all of my views and replace (#foo) with (#user, #foo) when it is already scoped.
Unless I am mistaken, the to_param method simply replaces :id so that urls such as /users/:id appear as users/permalink instead of users/1. I attempted to use this :id in my scope, but it conflicts with foo's :id param and breaks everything. Maybe there is a connection to paths that I am missing?
Thanks for any suggestions that you may have!
Have you tried using the to_param method in your model? This will allow you to override the default and use something other than id, and will work with the URL helpers
http://api.rubyonrails.org/classes/ActiveRecord/Base.html#method-i-to_param
Example from documentation:
class User < ActiveRecord::Base
def to_param # overridden
name
end
end
user = User.find_by_name('Phusion')
user_path(user) # => "/users/Phusion"
I'm not sure how well this plays with scope, since I haven't tried it, but I guess its worth a shot.