NoMethodError (undefined method `deliver_invitation') devise invitable rails - ruby-on-rails

Hello I have an app where a user is invited as an attendee
In the attendee controller, when the attendee is created the user is created but not sent an invite to the system
attendees_controller.rb
def create
#attendee = Attendee.new(attendee_params)
#user = User.invite!({email: "#{#attendee.email}"}, current_user) do |u|
u.skip_invitation = true
end
#attendee.user_id = #user.id
respond_to do |format|
if #attendee.save
format.html { redirect_to meeting_url(#attendee.meeting), notice: "Attendee was successfully created." }
format.json { render :show, status: :created, location: #attendee }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #attendee.errors, status: :unprocessable_entity }
end
end
end
in the same controller i then have a send_invite
def send_invite
#attendee = Attendee.find(params[:attendee_id])
User.where(id: #attendee.user_id).deliver_invitation
redirect_to meeting_url(#attendee.meeting)
end
when i hit it via a button I get
NoMethodError in AttendeesController#send_invite
undefined method `deliver_invitation' for #<ActiveRecord::Relation
[devise_invitable][1] clearly states
If you want to create the invitation but not send it, you can set skip_invitation to true.
user = User.invite!(email: 'new_user#example.com', name: 'John Doe') do |u|
u.skip_invitation = true
end
# => the record will be created, but the invitation email will not be sent
When generating the accept_user_invitation_url yourself, you must use the raw_invitation_token. This value is temporarily available when you invite a user and will be decrypted when received.
accept_user_invitation_url(invitation_token: user.raw_invitation_token)
When skip_invitation is used, you must also then set the invitation_sent_at field when the user is sent their token. Failure to do so will yield “Invalid invitation token” error when the user attempts to accept the invite. You can set the column, or call deliver_invitation to send the invitation and set the column:
user.deliver_invitation
What am I missing?
[1]: https://github.com/scambra/devise_invitable#send-an-invitation-

I guess .deliver_invitation is an instance method on the User Model. (through devise_invitable).
In that case you would probably want something like this:
User.where(id: #attendee.user_id).each do |user|
user.deliver_invitation
end

Assuming deliver_invitation is an instance method of the User model, you can modify your send_invite method like below:
def send_invite
#attendee = Attendee.find(params[:attendee_id])
user = User.find(#attendee.user_id)
user.deliver_invitation
redirect_to meeting_url(#attendee.meeting)
end

Related

Unable to destroy session/logout in Rails

I am trying to learn Rails API with React front end and have created a login, signup and logout feature. Currently, I am using localstorage to check if a user is signed in or registered. If the value is true, the logout button appears, and clicking logout empties the localstorage and sets the value to false. But I think this isn't the correct way of doing it since rails devise has its own session and logout endpoint. I tried writing some code for the destroy session and when testing it using the endpoint in POSTMAN it doesn't seem to work the way I want.
Here's the code:
class API::V1::SessionsController < Devise::SessionsController
def create
#user = User.find_by_email(user_params[:email])
if #user && #user.valid_password?(user_params[:password])
session[:user_id]=#user.id
sign_in :user, #user
render json: #user
elsif #user && not(#user.valid_password?(user_params[:password]))
invalid_attempt
else
no_user
end
end
def destroy
puts "logout clicked"
#user = User.find_by_email(user_params[:email])
if #user.valid_password(user_params[:password]) && #user.destroy
render :json => { success: "user was successfully deleted" }, :status => 201
else
render :json => { error: "user could not be deleted" }, :status => 422
end
end
private
def no_user
render json: {error: "An account with this email doesn't exist. Please create a new one"}, status: :unprocessable_entity
end
def invalid_attempt
render json: { error: "Your password isn't correct" }, status: :unprocessable_entity
end
def user_params
params.require(:user).permit(:email, :password)
end
end
http://localhost:3000/users/sign_out
{
"user":{
"email":"test1#test.com",
"password":"test1#test.com"
}
}
Please note that this user does exist. Sending a delete request to http://localhost:3000/users/sign_out isn't even logging the "logout clicked" and the status gives 204.

Can I validates_uniqueness_of email in two models at once in rails?

I have a model for a client, when I create a new client it creates a User with the client email. The same thing happens when I create an Affiliate. Can I use validates_uniqueness_of the email in both Client and user at the same time?
Or should I do something like, before save check if there is a User with the same email, and print an error?
I tried this, but it doesn't work
validate :uniqueness_of_user
private
def uniqueness_of_user
#user = User.find_by_email(:email)
if #user.present?
errors.add(:email, "Bang!")
end
end
Edit:
This is the controller:
def create
#affiliate = Affiliate.new(affiliate_params)
respond_to do |format|
if verify_recaptcha(model: #affiliate) && #affiliate.save
#user = User.create!(user_parameter)
pplicationMailer.confirmation(#affiliate).deliver_now
format.html {redirect_to :back, notice: 'Thanks for your submission, we will be in touch shortly. Check your email for your affiliate number and password.' }
format.json { render :show, status: :created, location: #affiliate }
else
format.html { render :signup, layout: "sign-ups" }
format.json { render json: #affiliate.errors, status: :unprocessable_entity }
end
end
end
I would have preferred to save the email_address in a different table. for example: address
class Address < ApplicationRecord
belongs_to :affilat
belongs_to :client
validates :email,
presence: true
uniqueness: true
end
and use nested form to save date here.
Check below code for a good article about this case, I copied the code but for full explanation check the article link :
# app/models/concerns/validate_identifier_uniqueness_across_models.rb
module ValidateIdentifierUniquenessAcrossModels
extend ActiveSupport::Concern
##included_classes = []
included do
##included_classes << self
validate :identifier_unique_across_all_models
end
private
def identifier_unique_across_all_models
return if self.identifier.blank?
##included_classes.each do |klass|
scope = klass.where(identifier: self.identifier)
if self.persisted? && klass == self.class
scope = scope.where('id != ?', self.id)
end
if scope.any?
self.errors.add :identifier, 'is already taken'
break
end
end
end
end
# app/models/product.rb
class Product < ActiveRecord::Base
include ValidateIdentifierUniquenessAcrossModels
end
# app/models/category.rb
class Category < ActiveRecord::Base
include ValidateIdentifierUniquenessAcrossModels
end
Reference:
https://www.krautcomputing.com/blog/2015/03/29/rails-validate-uniqueness-across-models/
UPDATE:
Solution 1 If data are saved at the same time, then the best thing is to include the data saved together in a Transaction as below:
def create
email_validity
#affiliate = Affiliate.new(affiliate_params)
respond_to do |format|
ActiveRecord::Base.transaction do
if verify_recaptcha(model: #affiliate) && #affiliate.save
#user = User.create!(user_parameter)
if #user.valid?
ApplicationMailer.confirmation(#affiliate).deliver_now
format.html {redirect_to :back, notice: 'Thanks for your submission, we will be in touch shortly. Check your email for your affiliate number and password.'}
format.json {render :show, status: :created, location: #affiliate}
else
email_validity = false
raise ActiveRecord::Rollback
end
else
format.html {render :signup, layout: "sign-ups"}
format.json {render json: #affiliate.errors, status: :unprocessable_entity}
end
end
if email_validity == false
respond_to do |format|
format.html {redirect_to :back, notice: 'Sorry, email is in use!'}
# you can add your json return as you like
end
end
end
end
Reference:
http://api.rubyonrails.org/classes/ActiveRecord/Rollback.html
Solution 2 before saving to affiliate or creating a user, check in both tables for email existence in your controller.

Calling a Model method from controller

I have a user view where I allow a user to upload his own image or allow him to import image from his LinkedIn profile. My LinkedIn OAuth works fine and returns the url of the image located on linkedin servers. I have a picture_from_url method in my User model which is called by UsersController in it's update method. I set the url to a sessions variable and then send that to the model method in the controller. This ,however, is not updating the image for the user. I have the papertrail gem setup so he can use the image from his computer and change his profile picture just fine.
My Model method
def picture_from_url(url)
encoded_url = URI.encode(url.to_s)
avatar= URI.parse(encoded_url)
end
My Controller Call
def update
#user = User.find(params[:id])
#user.picture_from_url(session[:linkedin_picture])
respond_to do |format|
if can?(current_user, #user)
if #user.update_attributes(user_params)
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { render_success #user }
else
format.html { render action: 'edit' }
format.json { render_error #user }
end
else
format.html { render action: 'edit', status: :unprocessable_entity }
format.json { render_error #user }
end
end
end
You're not actually saving it (as you said).
You supply your picture_from_url method with a URL, encode and parse it. The last thing you do with it is set it to avatar. I'm guessing that's an attribute on your User model. You're not saving it though.
The following line in your controller doesn't save it either:
#user.update_attributes(user_params)
The above line is updating your #user record with the specified user_params and the URL isn't specified in it.
So, you either have to save your #user record when you call the picture_from_url method like this:
def picture_from_url(url)
encoded_url = URI.encode(url.to_s)
update_attributes avatar: URI.parse(encoded_url)
end
Or you can add the formatted URL (which you can retrieve from the picture_from_url method) to the user_params hash which is than saved when update_attributes is called in your controller, like this:
def picture_from_url(url)
encoded_url = URI.encode(url.to_s)
URI.parse encoded_url
end
Change the picture_from_url method to the above in your User model (technically you don't have to do this, but it is probably more readable) and your update action to the following:
def update
#user = User.find(params[:id])
user_params.merge! avatar: #user.picture_from_url(session[:linkedin_picture])
...
end

How do I add customized devise error messages?

def create
build_resource(sign_up_params.select{|k,v| v.present?})
if Identity.identity_exists?(resource.email)
# Prompt user to sign in with Facebook instead
puts 'prompt user to sign in with facebook'
else
if resource.save
# redirect_to_signup_finish / return ?
else
clean_up_passwords resource
end
end
respond_to do |format|
format.html { render :new }
format.json { render :json => { errors: resource.errors }, :status => :unprocessable_entity }
end
end
I have this code where if a user has signed up with Facebook with the same email as before, we prevent user from signing up again. Instead, we prompt them to login with facebook. We want to create a custom resource.errors that states this scenario. What is the best way to achieve it?

sending a form to two recipients in rails

i have a rails model registrations that has the following fields
attr_accessible :address, :company, :name, :phone, :email
i have successfully been able to send a mail to the user via the email fielded in by the user and that works succesfully using action mailer
def create
#registration = Registration.new(params[:registration])
respond_to do |format|
if #registration.save
UserMailer.registration_confirmation(#registration).deliver
format.html { redirect_to root_path, notice:" Thanks! #{#registration.name}, Your registration have been
confirmed & your seat reserved" }
format.json { render :show, status: :created, location: #registration }
else
format.html { render action: "new" }
format.json { render json: #registration.errors, status: :unprocessable_entity }
end
end
end
and the registration mailer is as thus
def registration_confirmation(registration)
#registration = registration
#greeting = "Hi"
mail(to: #registration.email, subject: 'Welcome')
end
which works very well...
All i want to achieve is to be able to send a mail to another email address e.g (admin#gmail.com) stating that a user as registered and also showing the registration details ... thanks
I would generate a new mailer specifically for notifications that should be sent to the administrator.
rails g mailer AdminMailer registration_notice
You could then edit the AdminMailer registration_notice to be similar to your UserMailer, but with a different recipient:
def registration_notice(registration)
#registration = registration
mail(to: 'admin#gmail.com', subject: 'A new user has registered')
end
Put whatever registration details you would like to include into the views for registration_notice.html.erb (or text).
Then just add the call to the mailer in the create action of the controller, right after the call to the UserMailer:
def create
#registration = Registration.new(params[:registration])
respond_to do |format|
if #registration.save
UserMailer.registration_confirmation(#registration).deliver
AdminMailer.registration_notice(#registration).deliver
# etc
end
end
end
You'd probably also want to consider sending the mails in the background instead of making the user wait for the create request to finish, but that's beyond the scope of this question.

Resources