Can't get users to create a custom message with devise invitable - ruby-on-rails

I have 2 user-like models in my app: 'Participant' and 'Member'.
I'm trying to allow them to include a custom message when they invite other members/participants through Devise Invitable. However, I can't make it work.
I'm following this official tutorial so I've made the following changes to override Devise Invitable Controller but when using pry it seems that this custom controller goes untouched when sending an invite. What am I doing wrong:
controllers/participants/invitations_controller.rb
class Participants::InvitationsController < Devise::InvitationsController
before_action :update_sanitized_params, only: :update
def create
binding.pry
#from = params[:from]
#subject = params[:invite_subject]
#content = params[:invite_content]
#participant = Participant.invite!(params[:user], current_member) do |u| #XXX Check if :user should be changed
u.skip_invitation = true
end
ParticipantInvitationNotificationMailer.invite_message(#participant, #from, #subject, #content).deliver if #participant.errors.empty?
#participant.invitation_sent_at = Time.now.utc # mark invitation as delivered
if #participant.errors.empty?
flash[:notice] = "successfully sent invite to #{#participant.email}"
respond_with #participant, :location => root_path
else
render :new
end
end
def update
respond_to do |format|
format.js do
invitation_token = Devise.token_generator.digest(resource_class, :invitation_token, update_resource_params[:invitation_token])
self.resource = resource_class.where(invitation_token: invitation_token).first
resource.skip_password = true
resource.update_attributes update_resource_params.except(:invitation_token)
end
format.html do
super
end
end
end
protected
def update_sanitized_params
devise_parameter_sanitizer.permit(:accept_invitation, keys: [:password, :password_confirmation, :invitation_token, :username])
end
end
config/routes.rb
Rails.application.routes.draw do
devise_for :members, controllers: { invitations: "members/invitations" }
devise_for :participants, controllers: { invitations: "participants/invitations" }
end
models/participant.rb
class Participant < ApplicationRecord
attr_reader :raw_invitation_token
end
mailers/notification_mailer.rb
class NotificationMailer < ApplicationMailer
def invite_message(user, from, subject, content)
#user = user
#token = user.raw_invitation_token
invitation_link = accept_user_invitation_url(:invitation_token => #token)
mail(:from => from, :bcc => from, :to => #user.email, :subject => subject) do |format|
content = content.gsub '{{first_name}}', user.first_name
content = content.gsub '{{last_name}}', user.last_name
content = content.gsub '{{full_name}}', user.full_name
content = content.gsub('{{invitation_link}}', invitation_link)
format.text do
render :text => content
end
end
end
end
If I send an invitation:with Participant.invite!({:email => 'example#email.com'}, Member.first) the invitation is sent through the default mailer as shown in the console but not through my new mailer. why?
Rendering /Users/andres/.rvm/gems/ruby-2.4.0#pixiebob/gems/devise_invitable-1.7.1/app/views/devise/mailer/invitation_instructions.html.erb
Rendered /Users/andres/.rvm/gems/ruby-2.4.0#pixiebob/gems/devise_invitable-1.7.1/app/views/devise/mailer/invitation_instructions.html.erb (0.6ms)
Rendering /Users/andres/.rvm/gems/ruby-2.4.0#pixiebob/gems/devise_invitable-1.7.1/app/views/devise/mailer/invitation_instructions.text.erb
Rendered /Users/andres/.rvm/gems/ruby-2.4.0#pixiebob/gems/devise_invitable-1.7.1/app/views/devise/mailer/invitation_instructions.text.erb (0.8ms)

Finally, I could solve this issue.
It ended up being a rookie mistake I was thinking that calling the invite! method would have anything to do with the custom create method in the custom invitations controller.
I had of course to reach the create method through the specified route and within that method prevent the invite! method to send the email through the default mailer using code below (as established clearly in the Devise Invitable Documentation):
#participant = Participant.invite!({:email => #invitation_draft.email}, current_member) do |u|
u.skip_invitation = true
end
After this we can call any custom mailer in the create method.

Related

Devise confirm account and set password

In my app I am using Devise and Active Admin. Users are created in admin area without password, then they receive a mail with a link to a confirmation page where they can enter the password for the new account.
This is confirmation controller:
class ConfirmationsController < Devise::ConfirmationsController
def show
#original_token = params[:confirmation_token]
digested_token = Devise.token_generator.digest(self, :confirmation_token,params[:confirmation_token])
self.resource = resource_class.find_by_confirmation_token(digested_token) if params[:confirmation_token].present?
super if resource.nil? or resource.confirmed?
render :layout => "internal"
end
def confirm
digested_token = Devise.token_generator.digest(self, :confirmation_token, params[resource_name][:confirmation_token])
self.resource = resource_class.find_by_confirmation_token(digested_token) if params[resource_name][:confirmation_token].present?
if resource.update_attributes(params[resource_name].except(:confirmation_token).permit(:email, :password, :password_confirmation)) && resource.password_match?
self.resource = resource_class.confirm_by_token(params[resource_name][:confirmation_token])
set_flash_message :notice, :confirmed
sign_in_and_redirect(resource_name, resource)
else
#original_token = params[resource_name][:confirmation_token]
render :action => "show", :layout => "internal"
end
end
end
Relevant routes:
devise_for :users, :path_prefix => 'd', :controllers => {:confirmations => 'confirmations'}
devise_scope :user do
patch "/confirm" => "confirmations#confirm"
end
When the users clicks on the activation link in the background it sets the account as confirmed in the database, but instead of being redirected to confirmations/show to set the password, I get on this line render :layout => "internal"
Render and/or redirect were called multiple times in this action.
Please note that you may only call render OR redirect, and at most
once per action. Also note that neither redirect nor render terminate
execution of the action, so if you want to exit an action after
redirecting, you need to do something like "redirect_to(...) and
return".
Why is this happening?
In your ConfirmationsController:
class ConfirmationsController < Devise::ConfirmationsController
def show
#original_token = params[:confirmation_token]
digested_token = Devise.token_generator.digest(self, :confirmation_token,params[:confirmation_token])
self.resource = resource_class.find_by_confirmation_token(digested_token) if params[:confirmation_token].present?
# here you call super if resource is nil or confirmed
# super calls the show action of Devise::ConfirmationController
# it results with the first render (but doesn't call any return)
super if resource.nil? or resource.confirmed?
# after super is finished you continue and render layout internal
# (again, since it has happened in the parent controller already)
render :layout => "internal"
end
...
you can check what exactly is being done in Devise::ConfirmationsController on their github page: https://github.com/heartcombo/devise/blob/master/app/controllers/devise/confirmations_controller.rb
if you want to render render the confirmations/show page then just remove the
render layout: 'internal'
line.

ActionController::ParameterMissing (param is missing or the value is empty: name):

While implementing what I thought was a simple signup/login system for a Ruby on Rails app, results haven't matched what tutorials have shown.
I'm trying to use bcrypt for authentication and PostgreSQL for the database.
I continually get 'ActionController::ParameterMissing (param is missing or the value is empty: name): ', even though it will show name as being input. '"users"=>{"name"=>"asdf", "password"=>"Qq!1asdfasdf", "password_confirmation"=>"Qq!1asdfasdf"}, "commit"=>"Submit"} (0.1ms)
output from the console when attempting to sign in
users controller
class UsersController < ApplicationController
def new
end
def create
user = User.new(
name: params[:name],
password: params[:password],
password_confirmation: params[:password_confirmation])
if user.save
session[:user_id] = user.id
redirect_to '/'
else
redirect_to '/signup'
end
end
private
end
The table
class UsersController < ApplicationController
def new
end
def create
user = User.new(
name: params[:name],
password: params[:password],
password_confirmation: params[:password_confirmation])
if user.save
session[:user_id] = user.id
redirect_to '/'
else
redirect_to '/signup'
end
end
private
end
and the signup form
class UsersController < ApplicationController
def new
end
def create
user = User.new(
name: params[:name],
password: params[:password],
password_confirmation: params[:password_confirmation])
if user.save
session[:user_id] = user.id
redirect_to '/'
else
redirect_to '/signup'
end
end
private
end
the user model
class User < ActiveRecord::Base
PASSWORD_FORMAT = /\A
(?=.{10,}) # Must contain 10 or more characters
(?=.*\d) # Must contain a digit
(?=.*[a-z]) # Must contain a lower case character
(?=.*[A-Z]) # Must contain an upper case character
(?=.*[[:^alnum:]]) # Must contain a symbol
/x
#formatting for password
USERNAME_FORMAT = /\A[a-z0-9A-Z\-_]{2,15}\z/ #Can contain lowercase and upercase letters, numbers, - and _, must be between 2 and 15 length
#username formatting
validates :name,
:presence => true,
:uniqueness => true,
:format => USERNAME_FORMAT
validates :password,
:presence => true,
:format => PASSWORD_FORMAT,
:confirmation => true,
:on => create
has_secure_password
end
I've tried troubleshooting, all similar questions haven't yielded an answer or fix.
EDIT: More clarity on issue
You need to use rails Strong Parameter like the following
class UsersController < ApplicationController
def new
end
def create
user = User.new(user_params)
if user.save
session[:user_id] = user.id
redirect_to root_path
else
redirect_to new_user_path
end
end
private
def user_params
params.require(:user).permit(:name, :password, :password_confirmation)
end
end
I think it's a problem of passing data between your form and your controller.
In your logs your parameters for user looks like: "users"=>{"name"=> ...} but it should be "user"
To pass data between your controller and your view, you need to use instance variable such as #user to make the new instance of User available in the view. (source)
In that way your controller should be:
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id] = #user.id
redirect_to '/'
else
redirect_to '/signup'
end
end
private
def user_params
params.require(:user).permit(:name, :password, :password_confirmation)
end
(with strong parameters like #fool-dev explained)
Then in your view, use this #user to pass the parameters to the controller:
<%= form_for #user do |f| %>
//...the form
<% end %>

User sessions and logins

I am trying to implement signup & login features on ROR. on Index page i have created 2 links saying 'new user' and 'login' and in userscontroller i have signup & login methods(updated routes accordingly).
prob: upon clicking new user or login i am getting an error saying
"The action 'show' could not be found for UsersController"
routes.rb:
Rails.application.routes.draw do
resources :users
get 'users/register', :to=>'users#register'
get 'users/signup', :to=>'users#signup'
post 'users/signup', :to=>'users#signup'
post 'users/login', :to=>'users#login'
get 'users/login', :to=>'users#login'
post "users/change_password" => "users#change_password"
get "users/change_password" => "users#change_password"
index.html.erb
<%= link_to "New User", users_register_path %><br>
<%= link_to "Login", users_login_path %><br>
<%= link_to "Change Password", users_change_password_path %><br>
userscontroller:
def index
#user_details = User.all
end
def register
puts "**********************"
puts params
#new_user = User.new
end
def signup
#new_user = User.new(user_register)
if #new_user.save
session[:user] = User.authenticate(#user.name,#user.password)
redirect_to :action=>"welcome"
else
redirect_to :action=>"login"
end
end
def login
puts params
if request.post?
session[:user] = User.authenticate(params[:user][:name], params[:user][:password])
redirect_to :action=>"welcome"
else
# redirect_to :action=>"signup"
end
end
def change_password
puts "**********************"
puts params
puts "**********************"
if request.post?
#pass_change = User.new_password(params[:user][:name], params[:user][:password], params[:user][:new_password])
end
end
def welcome
end
def user_register
params.require(:user).permit(:name,:email,:password,:password_confirmation)
end
end
usermodel.rb:
require 'digest/sha1'
class User < ActiveRecord::Base
attr_accessor :password, :password_confirmation
def password=(pass)
#password = pass
self.salt = User.random_met(10)
self.hashedpassword = User.encrypt(#password, self.salt)
end
def self.encrypt(pass,salt)
Digest::SHA1.hexdigest(pass+salt)
end
def self.authenticate(login, pass)
user_auth = User.find(:first, :conditions=>["login = ?", login])
return nil if user_auth.nil?
return user_auth if User.encrypt(pass,user_auth.salt)==user_auth.hashedpassword
end
def self.new_password(name,old,new)
user = where(:name=>name)
#puts user.email
# how to call setter(password) method from here. because My idea is
#if User.encrypt(old, user.salt)==user.hashedpassword
# run def password=(pass), thereby storing new password.
end
def self.random_met(len)
char = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
salted = ""
1.upto(len) { |i| salted << char[rand(char.size-1)] }
return salted
end
end
As written in the Guides,
Rails routes are matched in the order they are specified, so if you
have a resources :photos above a get 'photos/poll' the show action's
route for the resources line will be matched before the get line. To
fix this, move the get line above the resources line so that it is
matched first.
You have defined resources :users above the get routes, so the rails will look for a show route, so is the error.

PayPal IPN Send Email

I have a controller that handles PayPal's IPN callback. I want to mark an attendee as 'paid' and send them a confirmation email if they've successfully paid.
The mark paid action is working but the email is not sending.
Here's my controller:
class PaymentNotificationsController < ApplicationController
protect_from_forgery :except => [:create]
def create
PaymentNotification.create!(:params => params, :attendee_id => params[:invoice], :status => params[:payment_status], :transaction_id => params[:txn_id])
if params[:payment_status] == 'Complete'
#attendee = Attendee.find(params[:invoice])
## Working
#attendee.update_attribute(:paid, Time.now)
## Not Working
UserMailer.welcome_email(#attendee).deliver
end
render nothing: true
end
end
Here's my user_mailer file:
class UserMailer < ActionMailer::Base
default from: 'example#email.com'
def welcome_email(user)
#user = user
email_with_name = "#{#user.first_name} #{#user.last_name} <#{#user.email}>"
#url = 'http://example.com'
mail(
to: email_with_name,
subject: 'Welcome to Yadda Yadda'
)
end
end
Here's the weird thing, in another controller that doesn't have PayPal the mailer works:
class VendorsController < ApplicationController
def create
#vendor = Vendor.new(vendor_params)
if #vendor.save
UserMailer.welcome_email(#vendor).deliver
redirect_to vendor_success_path
else
render 'new'
end
end
end
I am pulling your answer out of your question and posting it here for future reference.
This takes two actions (mark paid and send mail). It has been moved to the model as an after_create method.
Here's the model:
class PaymentNotification < ActiveRecord::Base
...
after_create :mark_attendee_paid
private
def mark_attendee_paid
if status == 'Completed'
attendee.update_attribute(:paid, Time.now)
UserMailer.welcome_email(attendee).deliver
end
end
end

REST API Help in Rails

I am trying to get some information posted using our accountancy package (FreeAgentCentral) using their API via a GEM.
http://github.com/aaronrussell/freeagent_api/
I have the following code to get it working (supposedly):
Kase Controller
def create
#kase = Kase.new(params[:kase])
#company = Company.find(params[:kase][:company_id])
#kase = #company.kases.create!(params[:kase])
respond_to do |format|
if #kase.save
UserMailer.deliver_makeakase("dropbox#12808311.macandco.highrisehq.com", "Highrise", #kase)
#kase.create_freeagent_project(current_user)
#flash[:notice] = 'Case was successfully created.'
flash[:notice] = fading_flash_message("Case was successfully created & sent to Highrise.", 5)
format.html { redirect_to(#kase) }
format.xml { render :xml => #kase, :status => :created, :location => #kase }
else
format.html { render :action => "new" }
format.xml { render :xml => #kase.errors, :status => :unprocessable_entity }
end
end
end
To save you looking through, the important part is:
#kase.create_freeagent_project(current_user)
Kase Model
# FreeAgent API Project Create
# Required attribues
# :contact_id
# :name
# :payment_term_in_days
# :billing_basis # must be 1, 7, 7.5, or 8
# :budget_units # must be Hours, Days, or Monetary
# :status # must be Active or Completed
def create_freeagent_project(current_user)
p = Freeagent::Project.create(
:contact_id => 0,
:name => "#{jobno} - #{highrisesubject}",
:payment_terms_in_days => 5,
:billing_basis => 1,
:budget_units => 'Hours',
:status => 'Active'
)
user = Freeagent::User.find_by_email(current_user.email)
Freeagent::Timeslip.create(
:project_id => p.id,
:user_id => user.id,
:hours => 1,
:new_task => 'Setup',
:dated_on => Time.now
)
end
lib/freeagent_api.rb
require 'rubygems'
gem 'activeresource', '< 3.0.0.beta1'
require 'active_resource'
module Freeagent
class << self
def authenticate(options)
Base.authenticate(options)
end
end
class Error < StandardError; end
class Base < ActiveResource::Base
def self.authenticate(options)
self.site = "https://#{options[:domain]}"
self.user = options[:username]
self.password = options[:password]
end
end
# Company
class Company
def self.invoice_timeline
InvoiceTimeline.find :all, :from => '/company/invoice_timeline.xml'
end
def self.tax_timeline
TaxTimeline.find :all, :from => '/company/tax_timeline.xml'
end
end
class InvoiceTimeline < Base
self.prefix = '/company/'
end
class TaxTimeline < Base
self.prefix = '/company/'
end
# Contacts
class Contact < Base
end
# Projects
class Project < Base
def invoices
Invoice.find :all, :from => "/projects/#{id}/invoices.xml"
end
def timeslips
Timeslip.find :all, :from => "/projects/#{id}/timeslips.xml"
end
end
# Tasks - Complete
class Task < Base
self.prefix = '/projects/:project_id/'
end
# Invoices - Complete
class Invoice < Base
def mark_as_draft
connection.put("/invoices/#{id}/mark_as_draft.xml", encode, self.class.headers).tap do |response|
load_attributes_from_response(response)
end
end
def mark_as_sent
connection.put("/invoices/#{id}/mark_as_sent.xml", encode, self.class.headers).tap do |response|
load_attributes_from_response(response)
end
end
def mark_as_cancelled
connection.put("/invoices/#{id}/mark_as_cancelled.xml", encode, self.class.headers).tap do |response|
load_attributes_from_response(response)
end
end
end
# Invoice items - Complete
class InvoiceItem < Base
self.prefix = '/invoices/:invoice_id/'
end
# Timeslips
class Timeslip < Base
def self.find(*arguments)
scope = arguments.slice!(0)
options = arguments.slice!(0) || {}
if options[:params] && options[:params][:from] && options[:params][:to]
options[:params][:view] = options[:params][:from]+'_'+options[:params][:to]
options[:params].delete(:from)
options[:params].delete(:to)
end
case scope
when :all then find_every(options)
when :first then find_every(options).first
when :last then find_every(options).last
when :one then find_one(options)
else find_single(scope, options)
end
end
end
# Users
class User < Base
self.prefix = '/company/'
def self.find_by_email(email)
users = User.find :all
users.each do |u|
u.email == email ? (return u) : next
end
raise Error, "No user matches that email!"
end
end
end
config/initializers/freeagent.rb
Freeagent.authenticate({
:domain => 'XXXXX.freeagentcentral.com',
:username => 'XXXX#XXXXXXX.co.uk',
:password => 'XXXXXX'
})
The above render the following error when trying to create a new Case and send the details to FreeAgent:
ActiveResource::ResourceNotFound in KasesController#create
Failed with 404 Not Found
and
ActiveResource::ResourceNotFound (Failed with 404 Not Found):
app/models/kase.rb:56:in `create_freeagent_project'
app/controllers/kases_controller.rb:96:in `create'
app/controllers/kases_controller.rb:93:in `create'
Rendered rescues/_trace (176.5ms)
Rendered rescues/_request_and_response (1.1ms)
Rendering rescues/layout (internal_server_error)
If anyone can shed any light on this problem it would be greatly appreciated!
Thanks,
Danny
How are you calling create? With a normal restful create action it would be with a POST from a form or something, but 404s are generally rendered from a failed GET action, where an ActiveRecord find fails to locate a record with a specific id. My best guess is that you're calling create with a GET, and that the line
user = Freeagent::User.find_by_email(current_user.email)
simply cannot locate a user with that email, and so is throwing the ResourceNotFound exception.
Additionally, this bit of code is confusing to me:
#kase = Kase.new(params[:kase])
#company = Company.find(params[:kase][:company_id])
#kase = #company.kases.create!(params[:kase])
respond_to do |format|
if #kase.save
Why are you creating #kase twice here, once with Kase.new and once with kases.create? Also, note that the line:
if #kase.save
will always evaluate true, because the line:
#company.kases.create!(params[:kase])
would have thrown an exception if it were false, which is another way of saying that #kase.save is redundant because create! would have already persisted the new Kase record.
EDIT: What I think you meant to do was:
# this line can go #kase = Kase.new(params[:kase])
#company = Company.find(params[:kase][:company_id])
#kase = #company.kases.build(params[:kase])
EDIT: You probably want a new action like this:
def new
#kase = Kase.new # no params here
end
The 'new' erb template will have a form_for something like:
<% form_for #kase do |k| %>
etc. That form will by default post the params from the form to the create action, assuming you've set up something like resources :kase in your routes. That should get you started. Follow the standard tutorials like you're doing and things should get simpler as you go.

Resources