Rails: invite to sign up as friend with devise gem - ruby-on-rails

I'm using devise in my Rails App. I want user can give a link to their friends, and through the link, they can sign up and then become friends with their inviter.
Here is what I come up with.
First, I give every user a unique code, like dbgadf34t42a, and people could visit url like localhost/signup?invite_code=dbgadf34t42a. Then customize Devise's sign up controller, use this code to find the inviter and then create the friendship relationship.
Code would be something like
def create
#Create user account, and then create Friendship
invite_code = paramsp[:invite_code]
if user.save
inviter = User.find_by_invite_code(invite_code)
inviter.friendships.create(friend: user)
end
end
I'm wondering
If there is any better way of doing this, gems or codes.
I didn't find good resources on customize Devise's Signup Controller, can I extract the logic to another place, store the invite code, and then continue creating friendship model?
Perhaps something like:
def after_sign_up
if params[:invite_code]
redirect_to new_friendship_path
else
# default_behavior
end
end

If there is any better way of doing this, gems or codes.
You may use devise-inviteable gem. For friendship create you can customize this gem.
I didn't find good resources on customize Devise's Signup Controller, can I extract the logic to another place, store the invite code, and then continue creating friendship model?
To customise Devise Signup you need to inherit Devise::RegistrationsController
class RegistrationsController < Devise::RegistrationsController
def create
super
# Now write your customize code for friendship
end
end
And then tell devise to use that controller instead of the default with:
# app/config/routes.rb
devise_for :users, :controllers => {:registrations => "registrations"}

Related

How to add this specific authorization feature to my rails app?

My rails app has a few cab operators and they have a few cabs associated with them, and they are related as follows:
class Operator < ActiveRecord::Base
has_many :cabs
end
I have used Devise as my authentication gem. It authenticates users, admins and super admins in my app. I have created separate models for users, admins and super admins (and have not assigned roles to users per se).
I now wish to add the authorization feature to the app, so that an admin (who essentially would be the cab operator in my case) can CRUD only its own cabs. For e.g., an admins belonging to operator# 2 can access only the link: http://localhost:3000/operators/2/cabs and not the link: http://localhost:3000/operators/3/cabs.
My admin model already has an operator_id that associates it to an operator when an admin signs_up. I tried to add the authorization feature through CanCan, but I am unable to configure CanCan to provide restriction such as the one exemplified above.
I also tried to extend my authentication feature in the cabs_controller, as follows:
class CabsController < ApplicationController
before_action :authenticate_admin!
def index
if current_admin.operator_id != params[:operator_id]
redirect_to new_admin_session_path, notice: "Unauthorized access!"
else
#operator = Operator.find(params[:operator_id])
#cabs = Operator.find(params[:operator_id]).cabs
end
end
But this redirects me to the root_path even if the operator_id of the current_admin is equal to the params[:operator_id]. How should I proceed?
EDIT:
Following is my routes.rb file:
Rails.application.routes.draw do
devise_for :super_admins
devise_for :users
resources :operators do
resources :cabs
end
scope "operators/:operator_id" do
devise_for :admins
end
end
I have three tables: users, admins and super_admins. I created these coz I wanted my admins to hold operator_ids so that the admins corresponding to an operator can be identified. Also, I wanted the admin sign_in paths to be of the type /operators/:operator_id/admins/sign_in, hence the tweak in the routes file.
Unfortunately, initially I didn't understand that you actually have 3 different tables for users and (super)admins... Not sure that Pundit can help you in this case, but I'll keep the old answer for future visitors.
Coming back to your problem, let's try to fix just the unexpected redirect.
Routes seems fine, so the problem can be one of this:
You're getting redirected because you're currently not logged in as an admin, so you don't pass the :authenticate_admin! before_action.
You say "even if the operator_id of the current_admin is equal to the params[:operator_id]", but this condition is probably not true. Can you debug or print somewhere the value of both current_admin.operator_id and params[:operator_id] to see if they're actually equals?
Another interesting thing, is that you have a redirect for new_admin_session_path in your code, but then you say "this redirects me to the root_path". Can you please double check this?
OLD ANSWER
If you want to setup a good authorization-logic layer, I advice you to use pundit.
You've probably heard about cancan, but it's not supported anymore...
Leave Devise managing only the authentication part and give it a try ;)
PUNDIT EXAMPLE
First of all, follow pundit installation steps to create the app/policies folder and the base ApplicationPolicy class.
Then, in your case, you'll need to create a CabPolicy class in that folder:
class CabPolicy < ApplicationPolicy
def update?
user.is_super_admin? or user.cabs.include?(record)
end
end
This is an example for the update action. The update? function have to return true if the user has the authorisation to update the cab (You'll see later WHICH cab), false otherwise. So, what I'm saying here is "if the user is a super_admin (is_super_admin? is a placeholder function, use your own) is enough to return true, otherwise check if the record (which is the cab your checking) is included in the cabs association of your user".
You could also use record.operator_id == record.id, but I'm not sure the association for cab is belongs_to :operator. Keep in mind that in CabPolicy, record is a Cab object, and user is the devise current_user, so implement the check that you prefer.
Next, in your controller, you just need to add a line in your update function:
def update
#cab = Cab.find(params[:id]) # this will change based on your implementation
authorize #cab # this will call CabPolicy#update? passing current_user and #cab as user and record
#cab.update(cab_params)
end
If you want to make things even better, I recommend you to use a before_action
class CabsController < ApplicationController
before_action :set_cab, only: [:show, :update, :delete]
def update
#cab.update(cab_params)
end
#def delete and show...
private
def set_cab
#cab = Cab.find(params[:id])
authorize #cab
end
And of course, remember to define also show? and delete? methods in your CabPolicy.

How to create an action where would be a form for sign in/up with Devise?

I am building a mobile version of our website and I would need to create a different views for sign up/in for mobile users. These two actions should be in a different controller.
My problem is that I don't know how to prepare instances for sign up/in for Devise in a different controller(s)...
How to make that?
Thanks
You mean you want to implement sign up/in in your own controller?
If so, you can modify routes.rb first
devise_for :users, :controllers => { :sessions => "controllername" }
And modify the controllername_controller.rb
class ControllernameController < Devise::SessionsController
def create
# your implementation
end
def destroy
# your implementation
end
end

Ruby on Rails : Create user profile in rails

I'm using devise gem. So, I can easily edit current user account by generating devise views. I've already a model user and it's related to devise gem for authenticating purpose. Now, I want to show user profile or user information in a single page. I can do it by creating a new controller. But, is it efficient or recommended way to do that??
Ryan Bates has a RailsCast that explains how to do this. Essentially, you make use of nested attributes for the models.
There's no performance penalty for creating another controller, all it does is help you organize your code. Here's how I did it:
**controller**
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
end
**routes**
devise_for :users,
resources :users, :only => [:show]

Devise and a separate users table

I'm working on a rails 3.2 app that authenticates using devise.
For a goal that is not related to the application (mostly statistics) I need to create a separate users table (let's call it alt_users) that only holds some fields of the users table (name, email, and some other fields) but no password digests and other sensitive infos. Also this records don't have to be modified if a user modifies his account or deletes it.
My idea is that when a user signs up, before devise makes his job some selected fields are inserted in the alt_users table.
What is the correct way to override devise behavior in order to make this happen?
What you can do is to override Devise's RegistrationsController in the following way:
In your routes.rb:
devise_for :users, :controllers => {:registrations => "registrations"}
Then create the file app/controllers/registrations_controller.rb :
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def create
# add your data to alt_users here
super
end
end
On a side note, if you can avoid overriding Devise's controller it would be best.
Try to think of other options like a before_create callback on the User model.

Devise/Rails - How to allow only admin to create account for others?

I am using devise as my authentication solution and now i am thinking about authorization. In my project I (the admin) is the only person authorized to create account for others.
I wonder if there is a way to do it without to much hack. In fact, Devise doesn't allow user to access to the signup page if he is already logged in.
Thanks for your advice on it!
Setting :skip => :registrations also kills the ability for a user to edit their user info. If that's not what you are after you can instead create a (minimal) custom registrations controller and only remove the new_user_registration_path while preserving the edit_user_registration_path.
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def new
# If you're not using CanCan, raise some other exception, or redirect as you please
raise CanCan::AccessDenied
end
end
# routes.rb
devise_for :users, :controllers => { :registrations => "registrations" }
Once you do this you also need to move the directory views/devise/registrations to just views/registrations.
You can try the rails_admin gem in conjunction with Devise to handle any admin-specific tasks. You'll need to add more code to set it up, but at least you avoid hacking around the solution in terms of changing your interactions with Devise.
It actually looks like in the later versions of Devise you can just remove the "registerable" declaration from your model and it will take care of this for you.

Resources