email sending with delay - ruby-on-rails

I wrote my website with Ruby on Rails. I want to add mail services to my app so that at a specific state a user gets an email that contain some details for them. My problem is when I test in development mode I get emails at least with one hours delay and I don't know why? Does it depend on server configuration?
I use Amazon as my email gateway
This is my code for sending emails
module Api
module V1
module User
module DailyCheckin
class Create
attribute :state, ::String
attribute :user, ::User
validates :state, presence: true
validates :user, presence: true
def call
add_error!(I18n.t('messages.daily_checkin.errors.state_invalid')) unless state.present?
#checkin = ::DailyCheckin.create!(
mood: ::Mood.find_by(state: state),
user: user
)
if state == "overwhelmed" || state == "not_coping"
logs = {
user_id: user.id,
email: user.profile.email,
organisation: user.organisation.name,
phone_number: user.profile.phone,
mood: state
}
::Api::V1::ApiGateway::Mail::DailyCheckinRiskNotice.call(logs: logs) if ENV.fetch("SEND_GATEWAY_MAIL", "true").eql? "true"
DailyCheckinRiskWorker.perform_async(user.profile.email, user.profile&.current_first_name, "daily_checkin_risk")
end
add_error!(Helpers::ErrorsHandler.parse(#checkin.errors)) unless #checkin.valid?
context[:mood] = #checkin.mood
end
end
end
end
end
end
and this is my gatways
module Api
module V1
module ApiGateway
module Mail
class DailyCheckinRiskNotice
include HTTParty
attribute :logs
validates :logs, presence: true
def call
options = {
headers: {
'Content-Type' => 'application/json',
'x-api-key' => ENV.fetch('MAIL_API_GATEWAY_API_KEY', '')
},
body: {
email_id: ENV.fetch('USER_ACTIVITY_NOTICE_MAIL_ID', ''),
content: {
logs: logs
}
}.to_json
}
response = HTTParty.post('', options)
add_error!(response.body) unless [201, 200].include?(response.code.to_i)
context[:body] = response.parsed_response
end
end
end
end
end
end
this is my mood model
class Mood < ApplicationRecord
enum state: %i[not_coping overwhelmed ok good great]
has_many :daily_checkins
end
and this my create method that create my logs is state was not coping or overwelmed
def create
result = ::Api::V1::User::DailyCheckin::Create.call(create_checkin_params.merge(user: #current_user))
return render json: Helpers::ErrorsHandler.view_parse(result.errors), status: :bad_request if result.errors
#mood = result.mood
render 'api/v1/moods/show'
end

Related

How to send email notifications with action mailer once payment has been successful

I need help on this rails challenge, I have a model that accepts payment, and payment is working fine with Mollie's payment API, and in the dashboard I see the amounts, but I want to be able to send email notifications once payment has been successful, currently, email notifications are not getting sent even after implementing sending emails with action mailer.
Here is my code:
class DonationsController < ApplicationController
before_action :set_donation, only: %i[ show edit update destroy ]
def new
#donation = Donation.new
end
def create
#donation = Donation.create donation_params
flash[:success] = "Thanks for your awesome contribution!"
payment = Mollie::Payment.create(
amount: #donation.amount,
description: 'Thank you for contributing',
redirect_url: "https://ejoka.com",
webhook_url: "https://ejoka.com/donations/callback"
)
#donation.payments.create(
amount: #donation.amount,
name: #donation.name,
email: #donation.email,
donation_id: #donation.id,
identifier: payment.id
)
redirect_to payment.checkout_url, { allow_other_host: true }
end
def callback
unless params[:testByMollie]
id = params[:id]
payment = Mollie::Payment.get(id)
if payment.paid?
pay = Payment.find_by_identifier id
#donation = pay.donation
#donation.has_been_paid(pay)
end
end
end
private
def set_donation
#donation = Donation.find(params[:id])
end
def donation_params
params.require(:donation).permit(:name, :email, :amount, :paid)
end
end
Here is my model
class Donation < ApplicationRecord
validates :amount, presence: true
validates :email, presence: true
scope :paid, -> {where(paid: :true)}
has_many :payments
def has_been_paid(pay)
pay.update paid: true
self.update paid: true
DonationMailer.donation_created(self).deliver_now
DonationMailer.new_donation(self).deliver_now
end
end
Payment model
class Payment < ApplicationRecord
belongs_to :donation, optional: true
end
My Mailer
class DonationMailer < ApplicationMailer
def donation_created(donation)
#donation = donation
mail to: #donation.email, subject: "Thank You for Your Contribution"
end
def new_donation(donation)
#donation = donation
mail to: #donation.email, subject: "Thank You for Your Donation"
end
end
Mailer views
donation_created.html.erb
Hi,
Thank you for your donation of #donation.amount
new_donation.html.erb
Thank you for your donation of #donation.amount
there is 2 condition in your callback
unless params[:testByMollie]
has to be a false value
and the second is
if payment.paid?
has to be true for has_been_paid to be called
you can easily print the output of both

Rails: after_create callback triggered only on production

Anyone know why an after_create callback is triggered only on production environment ?
class Schedule < ActiveRecord::Base
belongs_to :account
validates :schedule_type, presence: true # default = 'appointment'
after_create :send_notification, if: :devices_and_appointment?
def devices_and_appointment?
account_has_devices? && schedule_type.appointment?
end
def account_has_devices?
!account.devices.blank?
end
def appointment?
schedule_type.eql?('appointment')
end
end
On production, the callback is triggered, even if schedule_type is not appointment. I check the logs and the controller receive the params as expected (schedule_type != appointment)
For a strange reason, when after_create is invoked, the schedule_type is the default.
We check this on development and even in staging env and we cannot reproduce the bug.
The differences between staging and production is the DB sharding (Octopus) but we only have this activated for reads, in some other controllers, not even in the ScheduleController
We end up moving the callback method to a service, calling the function after the .save and that fix the problem, but I still want to know why this happen.
Controller:
def create
#schedule = Schedule.new(schedule_params)
if #schedule.save
render json: #schedule, status: 201
else
render json: { errors: #schedule.errors }, status: 422
end
end
def schedule_params
params
.require(schedule)
.permit(:account_id, :cause, :ends_at, :schedule_type, :starts_at)
end
Request:
Parameters: {"schedule"=>{"cause"=>"cause random", "starts_at"=>"2018-09-17T21:00:00.000-05:00", "ends_at"=>"2018-09-17T21:30:00.000-05:00", "schedule_type"=>"blocked", "account_id"=>"123"}}

Rails Saving a Status Value, (compared using ==)

I'm trying to create a mailer and allow a status to be changed to pending upon creation. So I have the following in my controller:
class Api::UserTrainingResourcesController < Api::BaseController
respond_to :html, :json
def create
#user_training_resource = UserTrainingResource::Create.call(user_training_resource_params)
respond_with(#user_training_resource)
end
def destroy
#user_training_resource = UserTrainingResource.find_by(params[:id])
#user_training_resource.destroy
end
private
def user_training_resource_params
params.require(:user_training_resources).permit(:training_resources_id, :status).merge(spud_user_id: current_user_id)
end
end
Then in my Operations I have the following create:
class UserTrainingResource
class Create < Operation
def call(params)
params[:status] = :pending
training_resource = UserTrainingResource.new(params)
UserTrainingResourceMailer.requested(training_resource).deliver_later if training_resource.save
training_resource
end
end
end
My model has:
class UserTrainingResource < ApplicationRecord
belongs_to :user
belongs_to :training_resource
enum status: [:pending, :confirmed, :rejected], _prefix: :status
scope :employee, -> { where.not(status: ['rejected']) }
scope :hr, -> { where(status: SameScope::HR) }
module SameScope
HR = ['pending', 'confirmed', 'rejected'].freeze
end
def view_user_training_resource_request?(user)
return true if user.human_resources && SameScope::HR.include?(status)
false
end
def change_status?(user, status)
return true if user.human_resources && SameScope::HR.include?(status)
false
end
end
Then in my test I have:
require 'rails_helper'
RSpec.describe UserTrainingResource::Create do
let(:params) { attributes_for(:user_training_resource).merge(user_id: create(:user).id) }
describe '#call' do
it 'queues a mailer' do
ut = UserTrainingResource::Create.call(params)
expect(UserTrainingResourceMailer).to send_mail(:requested)
expect(ut.status).to eq('pending')
end
end
end
So this ends up with a Failure/Error: expect(ut.status).to eq('pending') Expect: "pending", got" nil. (compared using ==)
I thought the issue was that pending was not saving to the db but that's because I had status present as a string not an integer when using enum in the model. But it's still getting nil.
Edit:
After noticing that I had status using string I went ahead and added in a migration file to change it to integer. Test failed with the same issue. Tried to rollback my test environment, failed. Did a rake db:reset. Re-added in data in both development and test. Still getting the issue of essentially:
Failure/Error: expect(ut.status).to eq('pending')
expected: "pending"
got: nil
(compared using ==)

RSpec pass validation test, which should be failed

I have a Company model with attr_accessor :administrator, so when user creates company, he also need to fill some fields for administrator of this company. I'm trying to test, that he fill all fields correctly.
class Company < ActiveRecord::Base
attr_accessor :administrator
validates :name, presence: true
validates :administrator, presence: true, if: :administrator_is_valid?
private
def administrator_is_valid?
administrator[:name].present? and
administrator[:phone].present? and
administrator[:email].present? and
administrator[:password].present? and
administrator[:password_confirmation].present? and
administrator[:password] == administrator[:password_confirmation]
end
end
company_spec.rb is:
require 'rails_helper'
describe Company do
it 'is valid with name and administrator' do
company = Company.new(name: 'Company',
administrator: {
name: nil,
email: nil,
phone: nil,
password: 'password',
password_confirmation: ''
})
expect(company).to be_valid
end
end
So, as you see, I have a lot of mistakes in validation test, but RSpec pass it.
Thanks!
That's because you didn't construct your validation properly. See, if: administrator_is_valid? will return false for your test, telling Rails to skip this validation rule.
I suggest you drop using the presence validator in favor of using administrator_is_valid? method as a validation method, because after all, if the administrator is valid then it is present. The code should look like this
validate :administrator_is_valid?
private
def administrator_is_valid?
(administrator[:name].present? and
administrator[:phone].present? and
administrator[:email].present? and
administrator[:password].present? and
administrator[:password_confirmation].present? and
administrator[:password] == administrator[:password_confirmation]) or
errors.add(:administrator, 'is not valid')
end
You could clean up your code like this:
validate :administrator_is_valid?
private
def administrator_is_valid?
if administrator_cols_present? && administrator_passwords_match?
true
else
errors.add(:administrator, 'is not valid')
end
end
def administrator_cols_present?
%w(name phone email password password_confirmation).all? do |col|
administrator[col.to_sym].present? # or use %i() instead of to_sym
end
end
def administrator_passwords_match?
administrator[:password] == administrator[:password_confirmation]
end
Another improvement might be to move your administrator to a struct, then call valid? on the object.
admin = Struct.new(cols) do
def valid?
cols_present? && passwords_match?
end
def cols_present?
cols.values.all? { |col| col.present? }
end
def passwords_match?
cols[:password] == cols[:password_confirmation]
end
end
Then:
validate :administrator_is_valid?
def admin_struct
#admin_struct ||= admin.new(administrator)
end
def administrator_is_valid?
errors.add(:administrator, 'is not valid') unless admin_struct.valid?
end

Catch Error from external API in Rails

I am building a simple web app that sends SMS messages to cell phones using Twilio. I want to ensure that the user has entered a full 10 digit phone number before it will allow a message to attempt to be sent.
When I test it with a less-than or greater-than 10 digit number, in heroku logs, I see Twilio::REST::RequestError (The 'To' number 1234567890 is not a valid phone number.).
I have tried to use a begin/rescue wrapper and am telling it to render text: "Try again with a valid number." and tried a variety of if statements to try to avoid the error.
I am pretty new to Ruby and Rails and Twilio, but I promise i have been through every guide I have found. Any help is greatly appreciated. Full code of my UserController below:
require 'twilio-ruby'
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(params[:user])
account_sid = '...'
auth_token = '...'
if #user.save
render text: "Wasn't that fun? Hit the back button in your browser to give it another go!"
begin
client = Twilio::REST::Client.new account_sid, auth_token
client.account.sms.messages.create(
from: '+16035093259',
to: #user.phone,
body: #user.message
)
rescue Twilio::REST::RequestError
render text: "Try again with a valid number."
end
else
render :new
end
end
end
I'd extract the SMS sending logic into a separate model/controller and use a background job to process the submitting. The UserController should only handle, well, user creation/modification.
Scaffolding:
$ rails g model sms_job user:references message:text phone submitted_at:datetime
$ rake db:migrate
Model:
class SmsJob < AR::Base
attr_accessible :user_id, :message, :phone
belongs_to :user
validates_presence_of :message, :phone, :user_id
validates :phone,
length: { min: 10 },
format: { with: /\+?\d+/ }
scope :unsubmitted, where(submitted_at: nil)
TWILIO = {
from_no: '...',
account_sid: '...',
auth_token: '...'
}
# find a way to call this method separately from user request
def self.process!
unsubmitted.find_each do |job|
begin
client = Twilio::REST::Client.new TWILIO[:account_sid], TWILIO[:auth_token]
client.account.sms.messages.create(
from: TWILIO[:from_no],
to: job.phone,
body: job.message
)
job.submitted_at = Time.zone.now
job.save
rescue Twilio::REST::RequestError
# maybe set update a tries counter
# or delete job record
# or just ignore this error
end
end
end
end
The controller then should just provide the information that the SMS is going to be send:
# don't forget the 'resources :sms_jobs' in your routes.rb
class SmsJobsController < ApplicationController
# index, update, destroy only for only admin?
def new
#sms_job = SmsJobs.new
end
def create
#sms_job = current_user.sms_jobs.build params[:sms_job]
if #sms_job.save
redirect_to root_url, notice: "Your message is being send!"
else
render :new
end
end
end
For the background processing, have a look at these excellent Railscasts :-) You probably need to workaround some concurrency problems if you have to process many messages and/or Twilio has a long response time (didn't use that service yet).

Resources