rails rspec testing cache method - ruby-on-rails

This is my user model and I don't know how can I write test for active_and_approved_creative_count cache test.
User.rb
class User < ActiveRecord::Base
class << self
def active_and_approved_creative_count
Rails.cache.fetch('active_and_approved_creative_count', :expires_in => 30.minutes) do
User.active_and_approved_creative.count
end
end
...
scope :active_and_approved_creative ,where("user_type = ? AND (membership_cancelled IS NULL OR membership_cancelled = false)", :approved_creative)
end

You could probably:
access active_and_approved_creative_count to prime the cache and verify the initial value
perform an action that will increment the count
Use TimeCop to move up 29 minutes
Verify that the count is still the cached value
Travel up another minute or two
Verify that the count has now incremented
One might argue that this is testing the Rails internals unnecessarily, consider whether simply performing step #1 may be enough.

Related

How can I write RSpec tests that make sure records are being reindexed by Searchkick?

The problem
I'm writing a spec to test whether my Product model gets reindexed when I create an associated Image record.
The docs recommend calling Product.search_index.refresh in tests to make sure that the index is up to date, but that defeats the purpose because I want to make sure that my after_create hooks on Image are causing Product to get reindexed.
Solution 1: Use sleep in my tests
I can call sleep to wait until Searchkick has updated the index, but that slows down my tests and makes them brittle.
product = create(:product)
Product.search_index.refresh
image_name = 'a_lovely_book.png'
search_results = Product.search image_name, fields: [:image_names]
# This passes.
expect(search_results.count).to eq(0)
image = create(:product_image, name: image_name)
# This causes the test to pass because it gives Searchkick time to reindex Product.
sleep 5
# This succeeds if I have the sleep call above.
search_results = Product.search image_name, fields: [:image_names]
expect(search_results.count).to eq(1)
Solution 2: Update the index immediately if Rails.env.test?
I've also considered doing something like this in my Image class so that reindexing happens immediately in tests. But I expect to write a good amount of these kinds of tests, and I don't want to repeat this code over and over again.
class Image
belongs_to :product
after_create :reindex_product
def reindex_product
if Rails.env.test?
product.search_index.refresh
else
product.reindex
end
end
end
Solution 3: Use spies or mocks
Not sure how I could do this exactly, but maybe there's a way to use spies or mocks to make sure that the reindex method gets called on Product?
I want to make sure that my after_create hooks on Image are causing Product to get reindexed.
You're not testing reindexing, just that reindexing is initiated at the appropriate time. So mocks are the way to go. Test the actual reindexing elsewhere, if you feel that's necessary.
Assuming Image looks something like this:
class Image < ApplicationRecord
belongs_to :product
# Note: the docs suggest after_commit so all saves will be reindexed.
after_commit :reindex_product
def reindex_product
product.reindex
end
end
The test, in RSpec, would look something like...
describe '.create' do
it 'reindexes the product' do
expect(product).to receive(:reindex)
Image.create( product: product, ... )
end
end
# This test illustrates why after_create might be insufficient.
describe '#save' do
it 'reindexes the product' do
expect(product).to receive(:reindex)
image = Image.new( product: product, ... )
image.save!
end
end
Or, if you're using asynchronous reindexing, you would check that a reindexing job was queued.

Can I call delayed_job with max attempts of 1?

I have a method that I run asynchronously
User.delay(queue: 'users').grab_third_party_info(user.id)
In case this fails, I want it to not retry. My default retries are 3, which I cannot change. I just want to have this only try once. The following doesn't seem to work:
User.delay(queue: 'users', attempts: 3).grab_third_party_info(user.id)
Any ideas?
This isn't my favorite solution, but if you need to use the delay method that you can set the attempts: to one less your max attempts. So in your case the following should work
User.delay(queue: 'users', attempts: 2).grab_third_party_info(user.id)
Better yet you could make it safer by using Delayed::Worker.max_attempts
User.delay(queue: 'users', attempts: Delayed::Worker.max_attempts-1).grab_third_party_info(user.id)
This would enter it into your delayed_jobs table as if it already ran twice so when it runs again it will be at the max attempts.
From https://github.com/collectiveidea/delayed_job#custom-jobs
To set a per-job max attempts that overrides the Delayed::Worker.max_attempts you can define a max_attempts method on the job
NewsletterJob = Struct.new(:text, :emails) do
def perform
emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) }
end
def max_attempts
3
end
end
Does this help you?
You have to use a Custom Job.
Just like #lazzi showed, you have to create a custom job in order to override the max_attempts.
As you can see in the README here, the only params that the .delay method take are:
priority
run_at
queue
And if you think about it, a value for max_attempts is not stored in the delayed_jobs table, only the attempts are stored, so there's no way for it to be persisted.
The only way to do it is to create a custom job that gets re-instantiated when the delayed job worker processes the job. It then reads the value from the max_attempts method and uses that to determine if the current attempts in the table record equals or exceeds the max_attempts value.
In your case, the simplest way to do it would be something like this:
# Inside your user.rb
class User < ApplicationRecord
FetchThirdPartyInfoJob = Struct.new( :user ) do
def perform
User.grab_third_party_info(user.id) # REFACTOR: Make this an instance method so you don't need to pass the User's id to it.
end
def queue_name
"users"
end
def max_attempts
3
end
end
end
Then run it wherever you need to by using enqueue, like this:
Delayed::Job.enqueue( User::FetchThirdPartyInfoJob.new( user ) )
I also added a little REFACTOR comment on your code because User.grab_third_party_info(user.id) looks to be incorrectly setup as a class method that you then pass the instance id to instead of just calling it directly on the user instance. I can't think of a reason why you would want this, but if there is, please leave it in the comments so we can all learn.

Custom Model Method, setting scope for automatic sending of mail

There are several stages to this, and as I am relatively new to rails I am unsure if I am approaching this in the best way.
Users follow Firms, Firms applications open and close on certain days. If a user follows a firm I would like them to automatically get an email when a) the firms application opens, b) a week before the firms applications close, c) on the day that the firms applications close.
I have tried using named scope. I have the following model method (I presume this will need a little work) setting each firms scope, depending on the date.
model firms.rb
def application_status
if open_date == Today.date
self.opening = true
else
self.opening = false
end
if ((close_day - Today.date) == 7)
self.warning = true
else
self.warning = false
end
if close_day == Today.date
self.closing = true
else
self.closing = false
end
end
I would like this method to be called on each firm once a day, so that each firm has the appropriate scope - so I have tried using the whenever gem (cron) and the following code. Running the above model method on each firm.
Schedule.rb
every 1.day do
runner "Firm.all.each do |firm|
firm.application_status
end"
end
Then for each of the scopes opening, warning, closing i have a method in the whenever schedules file, For simplicity I shall show just the opening methods. The following queries for all firms that have had the opening scope applied to them, and runs the application_open_notification method on them.
Schedule.rb
every 1.day do
runner "Firm.opening.each do |firm|
firm.application_open_notification
end"
end
This calls the following method in the Firm.rb model
def application_open_notification
self.users.each do |user|
FirmMailer.application_open(user, self).deliver
end
end
Which in turn calls the final piece of the puzzle... which should send the user an email, including the name of the firm.
def application_open(user,firm)
#firm = firm
#user = user
mail to: #user.email, subject: #firm' is now accepting applications'
end
end
Is this a viable way to approach this problem? In particular I am not very familiar with coding in the model.
Many thanks for any help that you can offer.
I'll guess that opening, warning and closing are database fields, and you have scopes like:
class Firm < ActiveRecord::Base
scope :opening, :where => { :opening => true }
# etc
end
There is a general rule for database (and, well, all storage): don't store things you can caculate, if you don't have to.
Since an application's status can be dermined from the day's date and the open_date and close_day fields, you could calculate them as needed instead of creating extra fields for them. You can do this with SQL and Active Record:
scope :opening, :where { :open_date => (Date.today .. Date.today+1) }
scope :warning, :where { :close_day => (Date.today+7 .. Date.today+8) }
scope :closing, :where { :close_day => (Date.today .. Date.today+1) }
(Note that these select time ranges. They may have to be changed depending on if you are using date or time fields.)
But there is another issue: what happens if, for some reason (computer crash, code bug etc) your scheduled program doesn't run on a particular day? You need a way of making sure notices are sent eventually even if something breaks. There are two solutions:
Write your schedule program to optionally accept a date besides today (via ARGV)
keep flags for each firm for whether each kind of notice has been sent. These will have to be stored in the databse.
Note that scopes aren't necessary. You are able to do this:
Firm.where(:open_date => (Date.today .. Date.today+1)).each do |firm|
#...
end
but the scope at least encapsulates the details of identifying the various sets of records.

Calling a model method at a specific time (Ruby on Rails)

I have a Ruby on Rails model that has a column called expiration_date. Once the expiration date is reached, I want another column on the model to be modified (e.g. expired = true). What are some good ways of doing this?
Ideally I'd like a model function to be called at the exact moment the expiry date is reached.
Use delayed_job gem. After installing delayed_job gem do the following:
class Model < ActiveRecord::Base
after_create :set_expiry_timer
# register the timer
def set_expiry_timer
delay(:run_at => expiration_date).expire
end
def expire
update_attribute(:expired, true) unless expired?
end
end
For the scenario you describe, the best solution is to have an expired method instead of a column, that would return true iff the expiration_date is greater or equal than the current date.
For other scenarios, I would go with a DB scheduled event triggering a stored procedure. That procedure would check the expiration_date column for all the rows in the model table, and update the expired (or other(s)) column(s) accordingly.
Have you considered using a scheduler to automate this? Something like Resque, Delayed Job or Cron would work fine.
Then in your scheduled task you could have something like this:
if foo.expiration_date < Time.now
foo.is_expired = true
foo.save
end

Scope vs Class Method in Rails 3

Based on the Rails 3 API, the difference between a scope and a class method is almost non-existent.
class Shipment < ActiveRecord::Base
def self.unshipped
where(:shipped => false)
end
end
is the same as
scope :unshipped, where(:shipped => false)
However, I'm finding that I'm sometimes getting different results using them.
While they both generate the same, correct SQL query, the scope doesn't always seem to return the correct values when called. It looks like this problem only occurs when its called the same way twice, albeit on a different shipment, in the method. The second time it's called, when using scope it returns the same thing it did the first time. Whereas if I use the class method it works correctly.
Is there some sort of query caching that occurs when using scope?
Edit:
order.line_items.unshipped
The line above is how the scope is being called. Orders have many line_items.
The generate_multiple_shipments method is being called twice because the test creates an order and generates the shipments to see how many there are. It then makes a change to the order and regenerates the shipments. However, group_by_ship_date returns the same results it did from the first iteration of the order.
def generate_multiple_shipments(order)
line_items_by_date = group_by_ship_date(order.line_items.unshipped)
line_items_by_date.keys.sort.map do |date|
shipment = clone_from_order(order)
shipment.ship_date = date
line_items_by_date[date].each { |line_item| shipment.line_items << line_item }
shipment
end
end
def group_by_ship_date(line_items)
hash = {}
line_items.each do |line_item|
hash[line_item.ship_date] ||= []
hash[line_item.ship_date] << line_item
end
hash
end
I think your invocation is incorrect. You should add so-called query method to execute the scope, such as all, first, last, i.e.:
order.line_items.unshipped.all
I've observed some inconsistencies, especially in rspec, that are avoided by adding the query method.
You didn't post your test code, so it's hard to say precisely, but my exeprience has been that after you modify associated records, you have to force a reload, as the query cache isn't always smart enough to detect a change. By passing true to the association, you can force the association to reload and the query to re-run:
order.line_items(true).unshipped.all
Assuming that you are referencing Rails 3.1, a scope can be affected by the default scope that may be defined on your model whereas a class method will not be.

Resources