How can I force an ActionMailer error for testing? - ruby-on-rails

I have this code that sends an email based on conditions. If there is an error its supposed to catch the account that wasn't able to be sent in an array like so:
def process_email(delivery_method_name)
begin
Account::Access.new(account).spam! if delivery_method_name == 'mark_as_spam'
AccountMailer.send("#{delivery_method_name}", account).deliver_now
rescue
#first_notification_error << account.id
#second_notification_failure << account.id
#third_notification_failure << account.id
#fourth_notification_error << account.id
#fourth_notification_failure << account.id
return
end
update_reverification_fields
end
So in my test.rb file I want to be able to test that the account.id was caught inside of the #first_notification_error and other containers. It's not exactly clear to me though how to do this though. I read in another post to place this code in development.rb and/or test.rb config.action_mailer.raise_delivery_errors = true but I don't think this is what I'm looking for. Is there a way I can raise the error in my test, perhaps with a stub or something similar?

You need to mock AccountMailer. Put this line before calling the code you're testing:
delivery_method = 'some method on mailer that you want to mock'
allow(AccountMailer).to receive(delivery_method).and_raise("boom")

Related

How can we log everything done by Net::Sftp in a file

how to log every action of Net::Sftp into a file in ruby on rails for debugging and how can I log verbose::debug to a logfile.
sftp = Net::SFTP.start(config[:host], config[:user], config[:options], verbose::debug)
files_arr = []
sftp.connect!
sftp.dir.foreach(src_dir) do |entry|
file_name = entry.name
begin
if entry.file? && (file_name.end_with?(".xml") || file_name.end_with?(".zip"))
sftp.download!(File.join(src_dir, file_name), File.join(dest_dir, file_name))
files_arr << file_name
end
rescue Exception => e
next
end
end
sftp.channel.eof!
sftp.close_channel unless sftp.closed?
my objective is to log all that happens in this code to a file, how can I do it.
Following on from the comments, it seems that what you want is to define a custom logger that you can then use to log your custom info to its own file. Something like this in the relevant environment.rb should do what you need.
my_logger = Logger.new('log/my_logger.log')
my_logger.level = Logger::DEBUG
Then, you can add my_logger 'MESSAGE' at the relevant points in your code to add entries to your custom logfile.

Rspec mocks and stubs confuse with expect

I have confuse when use mocks and stubs in rspec on rails. I have test like below
require 'rails_helper'
class Payment
attr_accessor :total_cents
def initialize(payment_gateway, logger)
#payment_gateway = payment_gateway
#logger = logger
end
def save
response = #payment_gateway.charge(total_cents)
#logger.record_payment(response[:payment_id])
end
end
class PaymentGateway
def charge(total_cents)
puts "THIS HITS THE PRODUCTION API AND ALTERS PRODUCTION DATA. THAT'S BAD!"
{ payment_id: rand(1000) }
end
end
class LoggerA
def record_payment(payment_id)
puts "Payment id: #{payment_id}"
end
end
describe Payment do
it 'records the payment' do
payment_gateway = double()
allow(payment_gateway).to receive(:charge).and_return(payment_id: 1234)
logger = double('LoggerA')
expect(logger).to receive(:record_payment).with(1234)
payment = Payment.new(payment_gateway, logger)
payment.total_cents = 1800
payment.save
end
end
Ok when I run rspec it works, no problem, but when I try to move expect to last line like below:
payment = Payment.new(payment_gateway, logger)
payment.total_cents = 1800
payment.save
expect(logger).to receive(:record_payment).with(1234)
and I try to run rpsec, it fail, I dont know why expect is last line will fail, I thought that expect always puts in last line before we run something to get result to test. Anyone can explain for me ?
expect(sth).to receive sets a message expectation which is to be satisfied between the call and end of the test, and that expectation is verified after the test finishes. When you move the expect to the last line, expectation is set just at the end of the test and no code is executed to satisfy it so it fails. Unfortunately it means breaking the prepare-execute-test order.
Which is why you should really rarely use expect.to receive and replace it with allow.to receive with expect.to have_received
# prepare
allow(logger).to receive(:record_payment)
# execute
..
# test
expect(logger).to have_received(:record_payment).with(1234)
allow.to receive sets up a mock proxy which starts tracing received messages which then can be explicitly verified by expect.to have_received. Some objects automatically sets their mock proxies, for example you don't need allow.to receive for doubles with predefined responses or spies. In your case, you could write the test like:
payment_gateway = double
allow(payment_gateway).to receive(:charge).and_return(payment_id: 1234)
logger = double('LoggerA', record_payment: nil)
payment = Payment.new(payment_gateway, logger)
payment.total_cents = 1800
payment.save
expect(logger).to have_received(:record_payment).with(1234)
Other notes
I strongly recommend using verifiable_doubles, which will protect you from false positives:
payment_gateway = instance_double(PaymentGateway)
allow(payment_gateway).to receive(:charge).and_return(payment_id: 1234)
This test will now raise an exception if there is no charge method defined on PaymentGateway class - protecting you from your tests passing even in case you rename that method but forgot to rename it in the test and implementation.

Official email validation in Ruby

I only want official email addresses such as xyz#company.com to sign up on my service rather than other generic email addresses such as gmail.com or Yahoo mail.com
Is there a ruby gem to achieve this kind of email validation? If not, how to make this happen?
You could write a custom validation in the appropriate model as shown here: http://www.rails-dev.com/custom-validators-in-ruby-on-rails-4
The basic idea in the article is as follows:
Make your validation method, and put it in a new directory called 'validators'
# app/validators/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^#\s]+)+#yourdomain.com\z/i
record.errors[attribute] << (options[:message] || "wrong email address")
end
end
end
(I have not tested this regex! Please use something like http://rubular.com/ and plug in your own email domain pattern to make sure it's working correctly.)
Then make sure Rails knows to load the new validators directory:
# config/application.rb
config.autoload_paths += %W["#{config.root}/app/validators/"]
Then add the new validation (email) to the appropriate model:
#MyModel.rb
validates :my_email_field, email: true
There is a free MailboxValidator web service that you can perform real-time email address validation in Ruby.
https://github.com/MailboxValidator/mailboxvalidator-ruby
require "mailboxvalidator_ruby"
apikey = "MY_API_KEY"
email = "example#example.com"
mbv = MailboxValidator::MBV.new()
mbv.apikey = apikey
mbv.query_single(email)
if mbv.error != nil
puts "Error: #{mbv.error}"
elsif mbv.result != nil
puts "email_address: #{mbv.result.email_address}"
puts "domain: #{mbv.result.domain}"
puts "is_free: #{mbv.result.is_free}"
puts "is_syntax: #{mbv.result.is_syntax}"
puts "is_domain: #{mbv.result.is_domain}"
puts "is_smtp: #{mbv.result.is_smtp}"
puts "is_verified: #{mbv.result.is_verified}"
puts "is_server_down: #{mbv.result.is_server_down}"
puts "is_greylisted: #{mbv.result.is_greylisted}"
puts "is_disposable: #{mbv.result.is_disposable}"
puts "is_suppressed: #{mbv.result.is_suppressed}"
puts "is_role: #{mbv.result.is_role}"
puts "is_high_risk: #{mbv.result.is_high_risk}"
puts "is_catchall: #{mbv.result.is_catchall}"
puts "mailboxvalidator_score: #{mbv.result.mailboxvalidator_score}"
puts "time_taken: #{mbv.result.time_taken}"
puts "status: #{mbv.result.status}"
puts "credits_available: #{mbv.result.credits_available}"
puts "error_code: #{mbv.result.error_code}"
puts "error_message: #{mbv.result.error_message}"
end

How to test the number of database calls in Rails

I am creating a REST API in rails. I'm using RSpec. I'd like to minimize the number of database calls, so I would like to add an automatic test that verifies the number of database calls being executed as part of a certain action.
Is there a simple way to add that to my test?
What I'm looking for is some way to monitor/record the calls that are being made to the database as a result of a single API call.
If this can't be done with RSpec but can be done with some other testing tool, that's also great.
The easiest thing in Rails 3 is probably to hook into the notifications api.
This subscriber
class SqlCounter< ActiveSupport::LogSubscriber
def self.count= value
Thread.current['query_count'] = value
end
def self.count
Thread.current['query_count'] || 0
end
def self.reset_count
result, self.count = self.count, 0
result
end
def sql(event)
self.class.count += 1
puts "logged #{event.payload[:sql]}"
end
end
SqlCounter.attach_to :active_record
will print every executed sql statement to the console and count them. You could then write specs such as
expect do
# do stuff
end.to change(SqlCounter, :count).by(2)
You'll probably want to filter out some statements, such as ones starting/committing transactions or the ones active record emits to determine the structures of tables.
You may be interested in using explain. But that won't be automatic. You will need to analyse each action manually. But maybe that is a good thing, since the important thing is not the number of db calls, but their nature. For example: Are they using indexes?
Check this:
http://weblog.rubyonrails.org/2011/12/6/what-s-new-in-edge-rails-explain/
Use the db-query-matchers gem.
expect { subject.make_one_query }.to make_database_queries(count: 1)
Fredrick's answer worked great for me, but in my case, I also wanted to know the number of calls for each ActiveRecord class individually. I made some modifications and ended up with this in case it's useful for others.
class SqlCounter< ActiveSupport::LogSubscriber
# Returns the number of database "Loads" for a given ActiveRecord class.
def self.count(clazz)
name = clazz.name + ' Load'
Thread.current['log'] ||= {}
Thread.current['log'][name] || 0
end
# Returns a list of ActiveRecord classes that were counted.
def self.counted_classes
log = Thread.current['log']
loads = log.keys.select {|key| key =~ /Load$/ }
loads.map { |key| Object.const_get(key.split.first) }
end
def self.reset_count
Thread.current['log'] = {}
end
def sql(event)
name = event.payload[:name]
Thread.current['log'] ||= {}
Thread.current['log'][name] ||= 0
Thread.current['log'][name] += 1
end
end
SqlCounter.attach_to :active_record
expect do
# do stuff
end.to change(SqlCounter, :count).by(2)

save an active records array

I have an array like this
a = []
a << B.new(:name => "c")
a << B.new(:name => "s")
a << B.new(:name => "e")
a << B.new(:name => "t")
How i can save it at once?
B.transaction do
a.each(&:save!)
end
This will create a transaction that loops through each element of the array and calls element.save on it.
You can read about ActiveRecord Transactions and the each method in the Rails and Ruby APIs.
a.each(&:save)
This will call B#save on each item in the array.
So I think we need a middle ground to Alexey's raising exceptions and aborting the transaction and Jordan's one-liner solution. May I propose:
B.transaction do
success = a.map(&:save)
unless success.all?
errored = a.select{|b| !b.errors.blank?}
# do something with the errored values
raise ActiveRecord::Rollback
end
end
This will give you a bit of both worlds: a transaction with rollback, knowledge of which records failed and even gives you access to the validation errors therein.
Wrapping save in transaction will not be enough: if a validation is not passed, there will be no exception raised and no rollback triggered.
I can suggest this:
B.transaction do
a.each do |o|
raise ActiveRecord::Rollback unless o.save
end
end
Just doing B.transaction do a.each(&:save!) end is not an option either, because the transaction block will not rescue any exception other than ActiveRecord::Rollback, and the application would crash on failed validation.
I do not know how to check afterwards if the records have been saved.
Update. As someone has downrated my answer, i assume that the person was looking for a cut-and-paste solution :), so here is some (ugly :)) way to process fail/success value:
save_failed = nil
B.transaction do
a.each do |o|
unless o.save
save_failed = true
raise ActiveRecord::Rollback
end
end
end
if save_failed
# ...
else
# ...
end
I know this is an old question but I'm suprised no one thought of this:
B.transaction do
broken = a.reject { |o| o.save }
raise ActiveRecord::Rollback if broken.present?
end
if broken.present?
# error message
end
In case you're looking for more efficient solution than save each row in the loop please look my answer here Ruby on Rails - Import Data from a CSV file
I'm suggesting to use gem activerecord-import there.

Resources