How to invite an existing user through devise_invitable - ruby-on-rails

I implemented
gem 'devise_invitable'
for model User and I am facing an issue while inviting an existing user. The error says "USER IS ALREADY REGISTERED". I would like to add the same user in another User invited list. How can this be done?

For those looking for a different implementation of the same problem, you can add the new behavior to the InvitationsController protected method, invite_resource.
A more verbose explanation of the example below can be found on the DeviseInvitable wiki page, titled Invite a Resource (or User) that Has Already Signed Up without Invitation.
class Users::InvitationsController < Devise::InvitationsController
protected
# invite_resource is called when creating invitation
# should return an instance of resource class
# this is devise_invitable's implementation
# def invite_resource(&block)
# resource_class.invite!(invite_params, current_inviter, &block)
# end
def invite_resource(&block)
#user = User.find_by(email: invite_params[:email])
# #user is an instance or nil
if #user && #user.email != current_user.email
# invite! instance method returns a Mail::Message instance
#user.invite!(current_user)
# return the user instance to match expected return type
#user
else
# invite! class method returns invitable var, which is a User instance
resource_class.invite!(invite_params, current_inviter, &block)
end
end
end

To accomplish this, you'll need to create a new Invitations Controller that inherits from the original Devise::Invitations controller, but has modified logic in the create method.
The gem's README has a section on "Configuring Controllers" which describes this process. I also suggest having a look at the source code for the parent controller as it will help provide some context.
I did something similar to what you desire, and used the Rails built-in method of find_by_email. Here's some of the code I used...
def create
# new user
if User.find_by_email(invite_params[:email]).nil?
super
# existing user
else
#u = User.find_by_email!(invite_params[:email])
....more code that does what you want....
end
end
NOTE: Rails is smart and will use logic from the parent controller if no conflicting instructions are given the child controller you create. The point is that you don't need to re-write the whole controller. Ideally, you'll just make your modifications in the child controller then call super to revert back to the same method in the parent controller to finish the action.

Related

Add an empty nested object to newly created user in Devise

I have a User model in my Rails 5 app using Devise.
I also have a relationship: user belongs_to account.
Now, when I create a new user I want to automatically create a new empty account for it.
I already have my registrations controller and a method:
def create
super
end
I would like to do something like this:
def create
super
user.account.create()
end
Are you looking for something like that ?
In your model User.rb
after_create do
self.account = Account.create() if self.account.nil?
end
In devise controllers, user object is called as resource by default. So something like this will work:
def create
super
if resource.persisted? # user has been created with no errors
resource.account.create # assuming it will pass validations without arguments
end
end
Another way, as #julien-js mentioned, is to create account in after-create hook, but instead of after_create you can use after_commit(read more about the difference):
after_commit :create_account, on: :create
def create_account
self.account.create unless self.account.present?
end

Ruby on Rails NoMethodError: undefined method `' for #<User:0x50987b8>

This is a really simple problem I believe.
users_controller
def testcall
end
routes
resources :users
In console I type
a = User.new
a.testcall
I've tried making it
self.testcall
and then calling
User.testcall
and its the same issue
Rails uses the model-view-controller pattern, meaning the model, in this case User, and its internal behaviors are separated from the controller, in this case UsersController. The convention is to have "fat" models and "skinny" controllers, meaning most of the code defining behaviors is in the model.
So, if you want the behavior to be for an instance of User, that behavior should be inside the User class (which should be in app/models/user.rb).
For instance:
class User < ActiveRecord::Base
def testcall
puts "This is a test"
end
end
Then you could call this from the rails console like you were doing. This is because you are bypassing the controller and calling methods directly on the model by calling:
a = User.new
a.testcall
in the console. If you actually want this to be performed when someone visits a webpage, submits a form, or whatever, then you must assign the appropriate request action in the controller to call the method on a designated instance of User.
For example,
class UsersController < ApplicationController
def create
#user = User.new
#user.testcall
end
end
Obviously, the routes must be properly set up for that to work.

Rails - calling an instance variable from anywhere in the app

I am fairly new to Rails, so apologies if it's not an 'instance variable' I am talking about!
I am using Devise for authentication, so can use things like current_user throughout the app. The app I am building has a User model, but also a Keyholder model (who is a sort of moderator for that user), and a Guest (who has read-only access to some things for that user).
What I want to know is - can I set it up so that I can use e.g. access_user when logged in as the keyholder to access the same object as current_user - and if so, where do I put the code in my app? It's quickly becoming very verbose and un-Rails-like having to repeat myself otherwise.
What I am trying to achieve is being able to use 'access_user' instead of current_user, so that regardless of whether it is the user, keyholder or guest logged in, it will use the user object.
For example:
def access_user
if user_signed_in?
access_user = current_user
end
if keyholder_signed_in?
access_user = current_keyholder.user
end
if guest_signed_in?
access_user = current_guest.user
end
end
Thanks!
Class level instance variables could also help you.
def access_user
if user_signed_in?
#access_user = current_user
end
if keyholder_signed_in?
#access_user = current_keyholder.user
end
if guest_signed_in?
#access_user = current_guest.user
end
end
You can just set this method in ApplicationController, and expose it to a helper method.
class ApplicationController
helper_method :access_user
def access_user
#blah blah
end
end
When method in ApplicationController, it's available to all controllers.
When you use helper_method, it is exposed as helper method to be used in View. More about helper_method: http://apidock.com/rails/ActionController/Helpers/ClassMethods/helper_method

What is the best way to ban/block users with Devise for Rails?

I'm using Devise for authentication in my rails app and I'd like to be able to block certain accounts and prevent users from reregistering with a blocked email. I'm just not sure what the best way is to go about it.
My first thought was to override the sessions and registrations controllers to check the model for a user with a blocked bit, but I have a feeling there might be a more elegant way.
The best approach is to do it in Devise way:
Below assumes that you are using Devise database_authenticatable module and your application's users model names User.
1. Implement an account_active? method.
Add boolean account_active column in users table or define account_active? method in User model (you can chose your own method name). For example:
# app/models/user.rb
def account_active?
blocked_at.nil?
end
2. Overwrite the active_for_authentication? method in your model (User).
# app/models/user.rb
def active_for_authentication?
super && account_active?
end
3. Add method which returns translation for flash message.
Whenever active_for_authentication? returns false, Devise asks the reason why your model is inactive using the inactive_message method.
# app/models/user.rb
def inactive_message
account_active? ? super : :locked
end
And that's it. You don't need to care about sign_out or redirect_to user.
Moreover, user is locked immediately, with next request, not after next sign in.
More: devise/authenticatable.rb.
I would do it like this:
def after_sign_in_path_for(resource)
if resource.is_a?(User) && resource.banned?
sign_out resource
banned_user_path
else
super
end
end
A better solution is to override the active_for_authentication? method on the devise model (User). Like so:
def active_for_authentication?
super && !self.banned?
end

Accessing a variable declared in a controller from a model

I'm using the facebooker gem which creates a variable called facebook_session in the controller scope (meaning when I can call facebook_session.user.name from the userscontroller section its okay). However when I'm rewriting the full_name function (located in my model) i can't access the facebook_session variable.
You'll have to pass the value into your model at some point, then store it if you need to access it regularly.
Models aren't allowed to pull data from controllers -- it would break things in console view, unit testing and in a few other situations.
The simplest answer is something like this:
class User
attr_accessor :facebook_name
before_create :update_full_name
def calculated_full_name
facebook_name || "not sure"
end
def update_full_name
full_name ||= calculated_full_name
end
end
class UsersController
def create
#user = User.new params[:user]
#user.facebook_name = facebook_session.user.name
#user.save
end
end

Resources