Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ gem 'activerecord-import'
# Get env variables from .env file
gem 'dotenv-rails'

# Cron job scheduling
gem 'whenever'

group :development, :test do
# Run specs in parallel
gem 'parallel_tests'
Expand Down
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ GEM
cheat (1.3.3)
pager (~> 1.0)
choice (0.2.0)
chronic (0.10.2)
codecov (0.2.12)
json
simplecov
Expand Down Expand Up @@ -524,6 +525,8 @@ GEM
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
whenever (1.0.0)
chronic (>= 0.6.3)
zeitwerk (2.5.1)

PLATFORMS
Expand Down Expand Up @@ -607,6 +610,7 @@ DEPENDENCIES
vcr
web-console
webmock
whenever

BUNDLED WITH
2.3.7
14 changes: 10 additions & 4 deletions app/models/exercise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class Exercise < ApplicationRecord
scope :can_release_to_a15k, -> { where(release_to_a15k: true) }
scope :not_released_to_a15k, -> { where(a15k_identifier: nil) }

before_validation :set_context, :set_slug_tags
before_validation :set_context, :set_slug_tags!

def content_equals?(other_exercise)
return false unless other_exercise.is_a? ActiveRecord::Base
Expand Down Expand Up @@ -247,7 +247,7 @@ def set_context(archive_version: nil)
return
end

def set_slug_tags
def set_slug_tags!
existing_book_slug_tags, other_tags = tags.partition do |tag|
tag.name.starts_with? 'book-slug:'
end
Expand All @@ -268,10 +268,10 @@ def set_slug_tags
"module-slug:#{slug[:book]}:#{slug[:page]}"
end

kept_book_slug_tags = existing_book_slug_tags.filter do |tag|
kept_book_slug_tags, removed_book_slug_tags = existing_book_slug_tags.partition do |tag|
desired_book_slugs.include? tag.name
end
kept_page_slug_tags = existing_page_slug_tags.filter do |tag|
kept_page_slug_tags, removed_page_slug_tags = existing_page_slug_tags.partition do |tag|
desired_page_slugs.include? tag.name
end

Expand All @@ -284,5 +284,11 @@ def set_slug_tags
self.tags = non_slug_tags +
kept_book_slug_tags + kept_page_slug_tags +
new_book_slugs + new_page_slugs

# Return whether or not any tags changed
!removed_book_slug_tags.empty? ||
!removed_page_slug_tags.empty? ||
!new_book_slugs.empty? ||
!new_page_slugs.empty?
end
end
16 changes: 16 additions & 0 deletions app/routines/update_slugs.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class UpdateSlugs
lev_routine transaction: :no_transaction

protected

def exec
Exercise.preload(:publication).in_batches(of: 100, load: true) do |exercises|
Exercise.transaction do
updated_exercise_ids = exercises.select(:id).filter(&:set_slug_tags!).map(&:id)
next if updated_exercise_ids.empty?

Exercise.where(id: updated_exercise_ids).update_all updated_at: Time.current
end
end
end
end
5 changes: 5 additions & 0 deletions config/schedule.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Server time is UTC
# Times below are interpreted that way

every(1.minute) { rake 'cron:minute' }
every(1.day, at: '8 AM') { rake 'cron:day' } # Midnight-1AM Pacific/2-3AM Central/3-4AM Eastern
2 changes: 1 addition & 1 deletion db/migrate/20220302162344_create_slug_tags.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class CreateSlugTags < ActiveRecord::Migration[6.1]
def up
Exercise.preload(:publication).in_batches(of: 100, load: true) do |exercises|
Exercise.transaction do
exercises.each(&:set_slug_tags)
exercises.each(&:set_slug_tags!)
exercises.update_all updated_at: Time.current
end
end
Expand Down
10 changes: 10 additions & 0 deletions lib/tasks/cron/day.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace :cron do
task day: :log_to_stdout do
Rails.logger.debug 'Starting daily cron'

Rails.logger.info 'UpdateSlugs.call'
OpenStax::RescueFrom.this { UpdateSlugs.call }

Rails.logger.debug 'Finished daily cron'
end
end
10 changes: 10 additions & 0 deletions lib/tasks/cron/minute.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace :cron do
task minute: :log_to_stdout do
Rails.logger.debug 'Starting minute cron'

Rails.logger.info 'rake openstax:accounts:sync:accounts'
OpenStax::RescueFrom.this { Rake::Task['openstax:accounts:sync:accounts'].invoke }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first I thought this might be too often but it seems to only check for updates and seems quick.


Rails.logger.debug 'Finished minute cron'
end
end
20 changes: 20 additions & 0 deletions lib/tasks/log_to_stdout.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# http://jerryclinesmith.me/blog/2014/01/16/logging-from-rake-tasks/
desc 'Include this task as another task\'s dependency to cause Rails.logger to also print to STDOUT'
task :log_to_stdout, [ :log_level ] => :environment do |tt, args|
# Do nothing in the test environment, since we don't want stdout there
next if Rails.env.test?

# Clone the main Rails logger to dissociate it from the other module loggers
# so we don't receive database, background job and mailer logs
Rails.logger = Rails.logger.clone

stdout_logger = ActiveSupport::Logger.new(STDOUT)

# By default, use a log level of at least 1 so we don't receive debug messages
stdout_logger.level = args.fetch(:log_level) do
ENV.fetch('LOG_LEVEL') { [ Rails.logger.level, 1 ].max }
end
stdout_logger.formatter = Rails.logger.formatter

Rails.logger.extend(ActiveSupport::Logger.broadcast(stdout_logger))
end
17 changes: 17 additions & 0 deletions spec/lib/tasks/cron/day.rake_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'rails_helper'
require 'rake'

RSpec.describe 'cron:day', type: :rake do
before :all do
Rake.application.rake_require 'tasks/cron/day'
Rake::Task.define_task :log_to_stdout
end

before { Rake::Task['cron:day'].reenable }

it 'calls the UpdateSlugs lev routine' do
expect(UpdateSlugs).to receive(:call)

Rake.application.invoke_task 'cron:day'
end
end
20 changes: 20 additions & 0 deletions spec/lib/tasks/cron/minute.rake_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require 'rails_helper'
require 'rake'

RSpec.describe 'cron:minute', type: :rake do
before :all do
Rake.application.rake_require 'tasks/cron/minute'
Rake::Task.define_task :log_to_stdout
end

before { Rake::Task['cron:minute'].reenable }

let!(:task) { instance_double(Rake::Task) }

it 'calls the openstax:accounts:sync rake task' do
expect(Rake::Task).to receive(:[]).with('openstax:accounts:sync:accounts').and_return(task)
expect(task).to receive(:invoke)

Rake.application.invoke_task 'cron:minute'
end
end
3 changes: 2 additions & 1 deletion spec/models/exercise_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
it { is_expected.to have_many(:exercise_tags).dependent(:destroy) }

it 'automatically sets the context based on tags and rewrites image links' do
expect_any_instance_of(Exercise).to receive(:set_slug_tags).twice
# Disable set_slug_tags!
expect_any_instance_of(Exercise).to receive(:set_slug_tags!).twice

exercise.tags = [
'context-cnxmod:4ee317f2-cc23-4075-b377-51ee4d11bb61',
Expand Down
4 changes: 2 additions & 2 deletions spec/routines/exercises/tag/xlsx_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@

let!(:exercises) { (1..6).map { |ii| FactoryBot.create(:publication, number: ii).publishable } }

# Disable set_slug_tags
before { allow_any_instance_of(Exercise).to receive(:set_slug_tags) }
# Disable set_slug_tags!
before { allow_any_instance_of(Exercise).to receive(:set_slug_tags!) }

it 'tags exercises with the sample spreadsheet' do
expect { described_class.call(filename: fixture_path) }.to change { ExerciseTag.count }.by(20)
Expand Down
4 changes: 2 additions & 2 deletions spec/routines/exercises/untag/xlsx_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

RSpec.describe Exercises::Untag::Xlsx, type: :routine do
before do
# Disable set_slug_tags
allow_any_instance_of(Exercise).to receive(:set_slug_tags)
# Disable set_slug_tags!
allow_any_instance_of(Exercise).to receive(:set_slug_tags!)

@exercises = (1..6).map { |ii| FactoryBot.create(:publication, number: ii).publishable }

Expand Down
17 changes: 17 additions & 0 deletions spec/routines/update_slugs_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'rails_helper'

RSpec.describe UpdateSlugs, type: :routine do
let!(:exercise) { FactoryBot.create :exercise }
let!(:updated_exercise) { FactoryBot.create :exercise }

before do
allow_any_instance_of(Exercise).to(
receive(:set_slug_tags!) { |exercise| exercise.id == updated_exercise.id }
)
end

it 'calls set_slug_tags! on all exercises and sets updated_at for updated exercises' do
expect { described_class.call }.to change { updated_exercise.reload.updated_at }
.and not_change { exercise.reload.updated_at }
end
end