ActiveAdmin and Devise - skip_confirmation! on create action - ruby-on-rails

I want to call user.skip_confirmation while his account is created by admin in admin panel. I want user to confirm his account in further steps of registration process, but not on create. The only idea I have is to override create in controller:
controller do
def create
user = User.new
user.skip_confirmation!
user.confirmed_at = nil
user.save!
end
end
The problem is, I have different attr_accessibles for standard user and admin, and it works, because ActiveAdmin uses InheritedResources:
attr_accessible :name, :surname
attr_accessible :name, :surname, invitation_token, :as => :admin
It doesn't work after I changed create (it worked before). How can I do what I want and still be able to use this :as => :admin feature?

I look at the answer and none is solving the issue at hand. I solve it the simplest way as shown below.
before_create do |user|
user.skip_confirmation!
end

controller do
def create
#user = User.new(params[:user].merge({:confirmed_at => nil}))
#user.skip_confirmation!
create! #or super
end
def role_given?
true
end
def as_role
# adapt this code if you need to
{ :as => current_user.role.to_sym }
end
end
something like that could work
EDIT: if you define role_given? to return true and as_role, InheritResources will use as_role to get the role information
also
controller do
with_role :admin
end
works, but this way you can't change the role given the user.

At your /app/models/user.rb
before_create :skip_confirmation
def skip_confirmation
self.skip_confirmation! if Rails.env.development?
end

Related

NameError when bypassing mass assignment while updating a user?

I followed the Railscast tutorial for bypassing mass assignment to edit my role attribute of my User model as the "admin". This is how I defined my roles:
class User < ActiveRecord::Base
attr_accessible :email, :password, :remember_me
attr_accessor :accessible
devise :database_authenticatable, ....etc
before_create :setup_default_role_for_new_users
ROLES = %w[admin default banned]
private
def setup_default_role_for_new_users
if self.role.blank?
self.role = "default"
end
end
def mass_assignment_authorizer
super + (accessible || [])
end
end
And then I created a new UsersController only to have issues with my update method:
def update
#user = User.find(params[:id])
#user.accessible = [:role] if user.role == "admin"
if #user.update_attributes(params[:user])
redirect_to #user, :notice => "Successfully updated user."
else
render :action => 'edit'
end
end
I can't do this though becuase this line: if user.role == "admin", is causing issue, giving me the error:
NameError (undefined local variable or method `user' for UsersController
What am I missing here?
Thanks in advance.
With the user part in user.role == "admin" you're trying to use a local variable, which hasn't been defined in your update method. If user isn't declared as a helper method that's accessible in your controllers then ruby won't find it.
From your code I'm assuming that only an admin user can update the role of another user? Thus you're not using #user.role == "admin" but user.role == "admin"?
If so you have to provide a user object whether it's through a helper method (i.e. in your ApplicationHelper class) or fetch it before you try to use it in your update method, or with a before_* callback in your controller.
I hope it's clear what I meant.

Devise remove a user but keep his data, and set a flag _is_deleted in rails 3.1

I would like to delete a user with devise but be able to save its data just setting a flag like is_deleted to true and prevent login for those users.
What would be the best way to do this in devise ? I have seen some write-ups on this but they were for rails 2.x projects, Im on rails 3.1
If you want to prevent sign_in users whose deleted_at fields are not null, override active_for_authentication? on your devise resource model:
def active_for_authentication?
super && !deleted_at
end
You can set that deleted flag normally then override the find_for_authentication class level method in the user model.
The following should work
def self.find_for_authentication(conditions)
super(conditions.merge(:is_deleted => false))
end
Another approach is to use a default scope on your model.
Define a state on your User model, and add a default scope (Rails 3), this will scope all the queries on the User model with the condition from the scope:
app/models/user.rb
class User < ActiveRecord::Base
default_scope where("state != 'disabled'")
def disable!
self.update_attribute(:state, 'disabled')
end
end
Then, over-write the destroy method in your session controller, make sure you to grab the destroy code from the version of devise you're using:
*app/controllers/registrations_controller.rb*
class Users::RegistrationsController < Devise::RegistrationsController
# paranoid DELETE /resource
def destroy
resource.disable! # we don't remove the record with resource.destroy
Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
set_flash_message :notice, :destroyed if is_navigational_format?
respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) }
end
end
You can take it a step further by defining a state machine on your User model (be careful of how this will not cascade down the dependency tree, like a :dependent => :destroy would):
app/models/user.rb
class User < ActiveRecord::Base
include ActiveRecord::Transitions
state_machine do
state :passive
state :active
state :disabled, :enter => :bye_bye_user
event :activate do
transitions :from => :passive, :to => :active
end
event :disable do
transitions :from => [:passive,:active], :to => :disabled
end
end
default_scope where("state != 'disabled'")
end
#dgmdan: In regards to using :deleted_at => nil instead of false:
Devise's find_for_authentication method runs the conditions through a filter which stringifies values. What's happening is that the nil value being passed in for deleted_at is being converted to an empty string. This makes the query match no one, and thus to the end user it looks like the username and password were incorrect.
find_for_authentication calls find_first_by_auth_conditions like this:
def find_for_authentication(tainted_conditions)
find_first_by_auth_conditions(tainted_conditions)
end
Per the author, find_first_by_auth_conditions takes an optional second parameter, another conditions hash, but this one does not go through the filter. So what you can do is change the method like this:
def self.find_for_authentication(conditions)
find_first_by_auth_conditions(conditions, {:deleted_at => nil})
end
The second conditions hash with the :deleted_at => nil should be passed straight through to the ORM layer.

Using the save method together with update_attributes. Is this common?

A user can sign up as an artist. All the user needs to do now, is provide his email.
In Artist controller, def create. Is it normal to have something like:
def create
#artist = current_user
respond_to do |format|
if #artist.update_attributes(params[:user]) # params[:user] contains email
#artist.is_artist = true
#artist.save
....
In my User model, I have:
attr_accessible :email
Which means, I can't simply do #artist.update_attributes(:is_artist => true). I would have to use the save method instead. Is this type of approach common? Or is there a better way?
You can define before_create method in your model:
class User < ActiveRecord::Base
...
before_create :fill_fields
def fill_fields
is_artist = true
end
end
I would do the following:
1st: I wound not set up an ArtistController if you do not have an Artist Model. rather I would add a non-restful method in your UserController, and push the implemention logic into the model ...
# config/routes.rb
resources :users do
member {post 'signup_as_artist'}
end
# UserController
def signup_as_artist
#user = User.find(params[:id])
#user.signup_as_artist
end
# User
def signup_as_artist
self.update_attribute :is_artist, true
end
Good luck

Is there a plugin or gem that can help me do "invite a friend" capability in rails?

I want to add the ability for users to invite a friend.
The email should be generated so that, if someone clicks on the link and register, that person is automatically a friend.
Not sure what the options are, but wanted some ideas and strategies as an alternative to building it from scratch.
I'm not aware of any gems that handle the entire process (user >> email >> signup). If you're just looking to create the relationship when a user comes from a specific link, create a special invitation route (the separate controller isn't necessary but just to make it clear):
# routes.rb
match '/invite/:friend_id' => 'public#invite', :as => :invite
# PublicController
def invite
session[:referring_friend] = params[:friend_id]
redirect_to root_path
end
# UsersController
def create
#user = User.new(params[:user])
if #user.save
#user.create_friendship(session[:referring_friend]) if session[:referring_friend]
...
else
...
end
end
If you want to track conversion metrics, I'd recommend creating a link model and using that to track clicks and signups:
class Link < ActiveRecord::Base
belongs_to :user
attr_accessible :user, :user_id, :clicks, :conversions
def click!
self.class.increment_count(:clicks, self.id)
end
def convert!
self.class.increment_count(:conversions, self.id)
end
end
# routes.rb
match '/invite/:link_id' => 'links#hit', :as => :invite
# LinksController
def hit
link = Link.find(params[:link_id])
link.click!
session[:referring_link_id] = link.id
redirect_to root_path # or whatever path (maybe provided by link...)
end
# UsersController
def create
#user = User.new(params[:user])
if #user.save
if session[:referring_link_id]
link = Link.find(session[:referring_link_id])
link.convert!
#user.create_friendship(link.user_id)
end
...
else
...
end
end
Which method you choose depends on what you'll want to track down the road.
I don't know gem for rails. But there's an extension for Spree, rails based e-commerce project. Check it out & probably you can refer how it's implemented.
https://github.com/spree/spree_email_to_friend
I don't know about some gem to support this, but solution should be rather trivial. I guess you need Friendship model, you can place some status in it like 'waiting_for_approvment' and send in mail link with that Friendship model id. When user accepts either way you just change status to 'approved' or even 'rejected' if you want to track that too.
Start by defining the relationship:
class User < ActiveRecord::Base
has_and_belongs_to_many :friends, :class_name => "User", :join_table => "friends_users"
end
So really, User relates to itself with a different name. Then you can use something along the lines of:
#current_user.friends << #selected_user
in your controller.

Any lazy way to do authentication on RoR?

I want to make a simple login, logout, also, different user have different user role. The Restful authentication seems works great, and the cancan is also very sweet for controlling user ability. But the question is how can I let these two works together. I watched the railcast, I was whether how to detect the user ability? Do I need to add a "ability" column in the user table?? Thank u.
http://railscasts.com/episodes/67-restful-authentication
http://railscasts.com/episodes/192-authorization-with-cancan
Look at the CanCan GitHub page: http://github.com/ryanb/cancan
Based on looking at both that and the RailsCast, I notice two things:
You define Ability as a separate model. There doesn't appear to be any necessary database columns.
There is no way you are forced to do roles, you are free to do this however you will.
With restful_authentication, just do the normal thing with your User model.
The most natural way to add CanCan would be to add an extra column to your User model called role or ability or something, then define methods as you see fit. Personally I'd probably do some kind of number system stored in the database, such as "0" for admin, "1" for high-level user, "2" for low-level user, etc.
Here's a few possibilities:
# Returns true if User is an admin
def admin?
self.role == 0
end
And:
# Returns true if User is admin and role?(:admin) is called, etc.
def role?(to_match)
{
0 => :admin,
1 => :super_user,
2 => :user,
3 => :commenter,
}[self.role] == to_match
end
Then in your Ability initialize method, you can use some kind of conditionals to set abilities, such as these snippets from the Railscast/readme:
if user.role? :admin
can :manage, :all
elsif user.role? :super_user
...
end
Or:
if user.admin?
can :manage, :all
else
...
end
I wrote a simple solution that works with CanCan too, just add a role_id:integer column to the User model:
# puts this in /lib/
module RolePlay
module PluginMethods
def has_roleplay(roles = {})
##roles = roles
##roles_ids = roles.invert
def roles
##roles
end
def find_by_role(role_name, *args)
find(:all, :conditions => { :role_id => ##roles[role_name]}, *args)
end
define_method 'role?' do |r|
r == ##roles_ids[role_id]
end
define_method :role do
##roles_ids[role_id]
end
end
end
end
then include this line in config/initializers/roleplay.rb
ActiveRecord::Base.extend RolePlay::PluginMethods
finally use it in your User model:
class User < ActiveRecord::Base
# ...
has_roleplay(:admin => 0, :teacher => 1, :student => 2)
# ...
end
now your model will have 2 new methods:
#user.role?(:admin) # true if user has admin role
#user.role # returns role name for the user

Resources