From c8a9c2580c257da831bc6b93f239c3ef311f733e Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sun, 10 Mar 2024 16:53:02 +1100 Subject: [PATCH 1/7] add messages list (incomplete --- lib/outboxer/messages.rb | 40 ++++++++++++++++++- .../messages/list/no_arguments_spec.rb | 18 +++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 spec/lib/outboxer/messages/list/no_arguments_spec.rb diff --git a/lib/outboxer/messages.rb b/lib/outboxer/messages.rb index 6f66052e..8692f525 100644 --- a/lib/outboxer/messages.rb +++ b/lib/outboxer/messages.rb @@ -6,7 +6,7 @@ module Messages extend self class Error < Outboxer::Error; end; - class InvalidTransition < Error; end + class InvalidParameter < Error; end def unpublished!(limit: 1, order: :asc) ActiveRecord::Base.connection_pool.with_connection do @@ -31,5 +31,43 @@ def unpublished!(limit: 1, order: :asc) .to_a end end + + def list(status: nil, sort: 'created_at', order: 'asc', page: 1, per_page: 100) + if !status.nil? && !Models::Message::STATUSES.include?(status) + raise InvalidParameter, "status must be #{Models::Message::STATUSES.join(' ')}" + end + + sort_options = ['id', 'status', 'messageable', 'created_at', 'updated_at'] + if !sort_options.include?(sort) + raise InvalidParameter, "sort must be #{sort_options.join(' ')}" + end + + order_options = ['asc', 'desc'] + if !order_options.include?(order) + raise InvalidParameter, "order must be #{order_options.join(' ')}" + end + + if !page.is_a?(Integer) || page <= 0 + raise InvalidParameter, "page must be >= 1" + end + + per_page_options = [100, 200, 500, 1000] + if !per_page_options.include?(per_page) + raise InvalidParameter, "per_page must be #{per_page_options.join(' ')}" + end + + messages = status.nil? ? Models::Message.all : Models::Message.where(status: status) + + messages = + if sort == 'messageable' + messages.order(messageable_type: order, messageable_id: order) + else + messages.order(sort => order) + end + + ActiveRecord::Base.connection_pool.with_connection do + messages.paginate(page: page, per_page: per_page) + end + end end end diff --git a/spec/lib/outboxer/messages/list/no_arguments_spec.rb b/spec/lib/outboxer/messages/list/no_arguments_spec.rb new file mode 100644 index 00000000..3cc4fde9 --- /dev/null +++ b/spec/lib/outboxer/messages/list/no_arguments_spec.rb @@ -0,0 +1,18 @@ +require 'rails_helper' + +module Outboxer + RSpec.describe Messages do + describe '.list' do + let!(:messages) do + create_list(:outboxer_message, 2, :publishing, created_at: 2.days.ago) + create(:outboxer_message, :unpublished, created_at: 1.day.ago) + create(:outboxer_message, :failed) + end + + it 'returns all publishing messages' do + result = Messages.list + expect(result.size).to eq(4) + end + end + end +end From 604270aeb1fdc0383ae24c8f4cdd58352d87d956 Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sun, 10 Mar 2024 18:04:49 +1100 Subject: [PATCH 2/7] use new factories --- spec/lib/outboxer/messages/list/no_arguments_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/lib/outboxer/messages/list/no_arguments_spec.rb b/spec/lib/outboxer/messages/list/no_arguments_spec.rb index 3cc4fde9..cff4ea46 100644 --- a/spec/lib/outboxer/messages/list/no_arguments_spec.rb +++ b/spec/lib/outboxer/messages/list/no_arguments_spec.rb @@ -4,7 +4,8 @@ module Outboxer RSpec.describe Messages do describe '.list' do let!(:messages) do - create_list(:outboxer_message, 2, :publishing, created_at: 2.days.ago) + create(:outboxer_message, :publishing, created_at: 2.days.ago) + create(:outboxer_message, :publishing, created_at: 2.days.ago) create(:outboxer_message, :unpublished, created_at: 1.day.ago) create(:outboxer_message, :failed) end From 2c145f3de861de3a694824a1b799591a40eb6e9f Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Fri, 15 Mar 2024 08:34:08 +1100 Subject: [PATCH 3/7] add no arguments spec --- Gemfile.lock | 38 +++++++++++++++++++ lib/outboxer.rb | 1 + lib/outboxer/messages.rb | 2 +- outboxer.gemspec | 1 + .../messages/list/no_arguments_spec.rb | 21 ++++++---- spec/spec_helper.rb | 8 +++- 6 files changed, 61 insertions(+), 10 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 7a324f53..f9a03fc4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,10 +3,17 @@ PATH specs: outboxer (0.1.11) activerecord (~> 7.0) + kaminari (~> 1.2) GEM remote: https://rubygems.org/ specs: + actionview (7.1.2) + activesupport (= 7.1.2) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) activemodel (7.1.2) activesupport (= 7.1.2) activerecord (7.1.2) @@ -26,10 +33,12 @@ GEM ast (2.4.2) base64 (0.2.0) bigdecimal (3.1.5) + builder (3.2.4) byebug (11.1.3) coderay (1.1.3) concurrent-ruby (1.2.2) connection_pool (2.4.1) + crass (1.0.6) database_cleaner (2.0.2) database_cleaner-active_record (>= 2, < 3) database_cleaner-active_record (2.1.0) @@ -40,16 +49,38 @@ GEM docile (1.4.0) drb (2.2.0) ruby2_keywords + erubi (1.12.0) factory_bot (6.4.6) activesupport (>= 5.0.0) foreman (0.87.2) i18n (1.14.1) concurrent-ruby (~> 1.0) json (2.6.3) + kaminari (1.2.2) + activesupport (>= 4.1.0) + kaminari-actionview (= 1.2.2) + kaminari-activerecord (= 1.2.2) + kaminari-core (= 1.2.2) + kaminari-actionview (1.2.2) + actionview + kaminari-core (= 1.2.2) + kaminari-activerecord (1.2.2) + activerecord + kaminari-core (= 1.2.2) + kaminari-core (1.2.2) language_server-protocol (3.17.0.3) + loofah (2.22.0) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) method_source (1.0.0) minitest (5.20.0) mutex_m (0.2.0) + nokogiri (1.16.2-arm64-darwin) + racc (~> 1.4) + nokogiri (1.16.2-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.16.2-x86_64-linux) + racc (~> 1.4) parallel (1.23.0) parser (3.2.2.3) ast (~> 2.4.1) @@ -63,6 +94,13 @@ GEM pry (>= 0.13, < 0.15) racc (1.7.1) rack (3.0.8) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) rainbow (3.1.1) rake (13.0.6) redis-client (0.19.1) diff --git a/lib/outboxer.rb b/lib/outboxer.rb index a81535d2..5928ba16 100644 --- a/lib/outboxer.rb +++ b/lib/outboxer.rb @@ -1,4 +1,5 @@ require "active_support" +require "kaminari" require_relative "outboxer/version" require_relative "outboxer/railtie" if defined?(Rails) diff --git a/lib/outboxer/messages.rb b/lib/outboxer/messages.rb index 8692f525..9cb0a13f 100644 --- a/lib/outboxer/messages.rb +++ b/lib/outboxer/messages.rb @@ -66,7 +66,7 @@ def list(status: nil, sort: 'created_at', order: 'asc', page: 1, per_page: 100) end ActiveRecord::Base.connection_pool.with_connection do - messages.paginate(page: page, per_page: per_page) + messages.page(page).per(per_page) end end end diff --git a/outboxer.gemspec b/outboxer.gemspec index 70ba904d..b09208f0 100644 --- a/outboxer.gemspec +++ b/outboxer.gemspec @@ -29,6 +29,7 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency "activerecord", "~> 7.0" + spec.add_dependency 'kaminari', '~> 1.2' spec.add_development_dependency 'foreman', '~> 0.87.2' spec.add_development_dependency 'pry-byebug', '3.10' diff --git a/spec/lib/outboxer/messages/list/no_arguments_spec.rb b/spec/lib/outboxer/messages/list/no_arguments_spec.rb index cff4ea46..9eed159e 100644 --- a/spec/lib/outboxer/messages/list/no_arguments_spec.rb +++ b/spec/lib/outboxer/messages/list/no_arguments_spec.rb @@ -1,18 +1,23 @@ -require 'rails_helper' +require 'spec_helper' module Outboxer RSpec.describe Messages do describe '.list' do let!(:messages) do - create(:outboxer_message, :publishing, created_at: 2.days.ago) - create(:outboxer_message, :publishing, created_at: 2.days.ago) - create(:outboxer_message, :unpublished, created_at: 1.day.ago) - create(:outboxer_message, :failed) + [ + create(:outboxer_message, :unpublished, + id: 1, created_at: 4.minutes.ago, updated_at: 2.minutes.ago), + create(:outboxer_message, :failed, + id: 2, created_at: 3.minutes.ago, updated_at: 3.minutes.ago), + create(:outboxer_message, :publishing, + id: 3, created_at: 2.minute.ago, updated_at: 2.minutes.ago), + create(:outboxer_message, :unpublished, + id: 4, created_at: 1.minute.ago, updated_at: 1.minutes.ago) + ] end - it 'returns all publishing messages' do - result = Messages.list - expect(result.size).to eq(4) + it 'returns all messages ordered by last updated at desc' do + expect(Messages.list.map(&:id)).to eq([1, 2, 3, 4]) end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 332a614b..a63a01c1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -29,7 +29,13 @@ config.after(:each) do begin DatabaseCleaner.clean - rescue ActiveRecord::DatabaseConnectionError, ActiveRecord::ConnectionNotEstablished + + ActiveRecord::Base.connection.tables.each do |table| + ActiveRecord::Base.connection.reset_pk_sequence!(table) + end + rescue ActiveRecord::DatabaseConnectionError, + ActiveRecord::ConnectionNotEstablished, + ActiveRecord::StatementInvalid # ignore end end From 84ff54b5fa6ccdd89eafe4ac6e8c2f19a63d6ac7 Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Fri, 15 Mar 2024 08:55:03 +1100 Subject: [PATCH 4/7] add sort spec --- lib/outboxer/messages.rb | 8 +-- spec/lib/outboxer/messages/list/sort_spec.rb | 64 ++++++++++++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 spec/lib/outboxer/messages/list/sort_spec.rb diff --git a/lib/outboxer/messages.rb b/lib/outboxer/messages.rb index 9cb0a13f..3c65c9ca 100644 --- a/lib/outboxer/messages.rb +++ b/lib/outboxer/messages.rb @@ -32,17 +32,17 @@ def unpublished!(limit: 1, order: :asc) end end - def list(status: nil, sort: 'created_at', order: 'asc', page: 1, per_page: 100) + def list(status: nil, sort: :updated_at, order: :asc, page: 1, per_page: 100) if !status.nil? && !Models::Message::STATUSES.include?(status) raise InvalidParameter, "status must be #{Models::Message::STATUSES.join(' ')}" end - sort_options = ['id', 'status', 'messageable', 'created_at', 'updated_at'] + sort_options = [:id, :status, :messageable, :created_at, :updated_at] if !sort_options.include?(sort) raise InvalidParameter, "sort must be #{sort_options.join(' ')}" end - order_options = ['asc', 'desc'] + order_options = [:asc, :desc] if !order_options.include?(order) raise InvalidParameter, "order must be #{order_options.join(' ')}" end @@ -59,7 +59,7 @@ def list(status: nil, sort: 'created_at', order: 'asc', page: 1, per_page: 100) messages = status.nil? ? Models::Message.all : Models::Message.where(status: status) messages = - if sort == 'messageable' + if sort == :messageable messages.order(messageable_type: order, messageable_id: order) else messages.order(sort => order) diff --git a/spec/lib/outboxer/messages/list/sort_spec.rb b/spec/lib/outboxer/messages/list/sort_spec.rb new file mode 100644 index 00000000..b5ef1603 --- /dev/null +++ b/spec/lib/outboxer/messages/list/sort_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +module Outboxer + RSpec.describe Messages do + before do + create(:outboxer_message, id: 4, status: :unpublished, messageable_type: 'TypeA', + messageable_id: 1, created_at: 5.minutes.ago, updated_at: 4.minutes.ago) + create(:outboxer_message, id: 3, status: :failed, messageable_type: 'TypeB', + messageable_id: 2, created_at: 4.minutes.ago, updated_at: 3.minutes.ago) + create(:outboxer_message, id: 2, status: :publishing, messageable_type: 'TypeA', + messageable_id: 3, created_at: 3.minutes.ago, updated_at: 2.minutes.ago) + create(:outboxer_message, id: 1, status: :unpublished, messageable_type: 'TypeC', + messageable_id: 4, created_at: 2.minutes.ago, updated_at: 1.minute.ago) + end + + describe '.list' do + context 'with sort by status' do + it 'sorts messages by status in ascending order' do + expect(Messages.list(sort: :status, order: :asc).map(&:status)).to eq( + ['failed', 'publishing', 'unpublished', 'unpublished']) + end + + it 'sorts messages by status in descending order' do + expect(Messages.list(sort: :status, order: :desc).map(&:status)).to eq( + ['unpublished', 'unpublished', 'publishing', 'failed']) + end + end + + context 'with sort by messageable' do + it 'sorts messages by messageable in ascending order' do + sorted_messages = Messages.list(sort: :messageable, order: :asc) + expect(sorted_messages.map { |m| [m.messageable_type, m.messageable_id] }).to eq( + [['TypeA', '1'], ['TypeA', '3'], ['TypeB', '2'], ['TypeC', '4']]) + end + + it 'sorts messages by messageable in descending order' do + sorted_messages = Messages.list(sort: :messageable, order: :desc) + expect(sorted_messages.map { |m| [m.messageable_type, m.messageable_id] }).to eq( + [['TypeC', '4'], ['TypeB', '2'], ['TypeA', '3'], ['TypeA', '1']]) + end + end + + context 'with sort by created_at' do + it 'sorts messages by created_at in ascending order' do + expect(Messages.list(sort: :created_at, order: :asc).map(&:id)).to eq([4, 3, 2, 1]) + end + + it 'sorts messages by created_at in descending order' do + expect(Messages.list(sort: :created_at, order: :desc).map(&:id)).to eq([1, 2, 3, 4]) + end + end + + context 'with sort by updated_at' do + it 'sorts messages by updated_at in ascending order' do + expect(Messages.list(sort: :updated_at, order: :asc).map(&:id)).to eq([4, 3, 2, 1]) + end + + it 'sorts messages by updated_at in descending order' do + expect(Messages.list(sort: :updated_at, order: :desc).map(&:id)).to eq([1, 2, 3, 4]) + end + end + end + end +end From 900cf9559744e5074a10fd714f59becb0e568e1c Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sat, 16 Mar 2024 12:24:45 +1100 Subject: [PATCH 5/7] update type in spec --- spec/lib/outboxer/messages/list/sort_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/lib/outboxer/messages/list/sort_spec.rb b/spec/lib/outboxer/messages/list/sort_spec.rb index b5ef1603..0eb1d4b7 100644 --- a/spec/lib/outboxer/messages/list/sort_spec.rb +++ b/spec/lib/outboxer/messages/list/sort_spec.rb @@ -3,13 +3,13 @@ module Outboxer RSpec.describe Messages do before do - create(:outboxer_message, id: 4, status: :unpublished, messageable_type: 'TypeA', + create(:outboxer_message, id: 4, status: :unpublished, messageable_type: 'Event', messageable_id: 1, created_at: 5.minutes.ago, updated_at: 4.minutes.ago) - create(:outboxer_message, id: 3, status: :failed, messageable_type: 'TypeB', + create(:outboxer_message, id: 3, status: :failed, messageable_type: 'Event', messageable_id: 2, created_at: 4.minutes.ago, updated_at: 3.minutes.ago) - create(:outboxer_message, id: 2, status: :publishing, messageable_type: 'TypeA', + create(:outboxer_message, id: 2, status: :publishing, messageable_type: 'Event', messageable_id: 3, created_at: 3.minutes.ago, updated_at: 2.minutes.ago) - create(:outboxer_message, id: 1, status: :unpublished, messageable_type: 'TypeC', + create(:outboxer_message, id: 1, status: :unpublished, messageable_type: 'Event', messageable_id: 4, created_at: 2.minutes.ago, updated_at: 1.minute.ago) end From 23d4b737635fbd7577df54d4f1eabd71ba6f38da Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sat, 23 Mar 2024 01:51:10 +1100 Subject: [PATCH 6/7] pass sorting specs --- lib/outboxer/messages.rb | 12 +++++----- spec/lib/outboxer/messages/list/sort_spec.rb | 24 ++++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/outboxer/messages.rb b/lib/outboxer/messages.rb index 3c65c9ca..c977328d 100644 --- a/lib/outboxer/messages.rb +++ b/lib/outboxer/messages.rb @@ -33,17 +33,17 @@ def unpublished!(limit: 1, order: :asc) end def list(status: nil, sort: :updated_at, order: :asc, page: 1, per_page: 100) - if !status.nil? && !Models::Message::STATUSES.include?(status) + if !status.nil? && !Models::Message::STATUSES.include?(status.to_sym) raise InvalidParameter, "status must be #{Models::Message::STATUSES.join(' ')}" end sort_options = [:id, :status, :messageable, :created_at, :updated_at] - if !sort_options.include?(sort) + if !sort_options.include?(sort.to_sym) raise InvalidParameter, "sort must be #{sort_options.join(' ')}" end order_options = [:asc, :desc] - if !order_options.include?(order) + if !order_options.include?(order.to_sym) raise InvalidParameter, "order must be #{order_options.join(' ')}" end @@ -59,10 +59,10 @@ def list(status: nil, sort: :updated_at, order: :asc, page: 1, per_page: 100) messages = status.nil? ? Models::Message.all : Models::Message.where(status: status) messages = - if sort == :messageable - messages.order(messageable_type: order, messageable_id: order) + if sort.to_sym == :messageable + messages.order(messageable_type: order.to_sym, messageable_id: order.to_sym) else - messages.order(sort => order) + messages.order(sort.to_sym => order.to_sym) end ActiveRecord::Base.connection_pool.with_connection do diff --git a/spec/lib/outboxer/messages/list/sort_spec.rb b/spec/lib/outboxer/messages/list/sort_spec.rb index 0eb1d4b7..57663dd0 100644 --- a/spec/lib/outboxer/messages/list/sort_spec.rb +++ b/spec/lib/outboxer/messages/list/sort_spec.rb @@ -3,14 +3,18 @@ module Outboxer RSpec.describe Messages do before do - create(:outboxer_message, id: 4, status: :unpublished, messageable_type: 'Event', - messageable_id: 1, created_at: 5.minutes.ago, updated_at: 4.minutes.ago) - create(:outboxer_message, id: 3, status: :failed, messageable_type: 'Event', - messageable_id: 2, created_at: 4.minutes.ago, updated_at: 3.minutes.ago) - create(:outboxer_message, id: 2, status: :publishing, messageable_type: 'Event', - messageable_id: 3, created_at: 3.minutes.ago, updated_at: 2.minutes.ago) - create(:outboxer_message, id: 1, status: :unpublished, messageable_type: 'Event', - messageable_id: 4, created_at: 2.minutes.ago, updated_at: 1.minute.ago) + create(:outboxer_message, id: 4, status: :unpublished, + messageable_type: 'Event', messageable_id: 1, + created_at: 5.minutes.ago, updated_at: 4.minutes.ago) + create(:outboxer_message, id: 3, status: :failed, + messageable_type: 'Event', messageable_id: 2, + created_at: 4.minutes.ago, updated_at: 3.minutes.ago) + create(:outboxer_message, id: 2, status: :publishing, + messageable_type: 'Event', messageable_id: 3, + created_at: 3.minutes.ago, updated_at: 2.minutes.ago) + create(:outboxer_message, id: 1, status: :unpublished, + messageable_type: 'Event', messageable_id: 4, + created_at: 2.minutes.ago, updated_at: 1.minute.ago) end describe '.list' do @@ -30,13 +34,13 @@ module Outboxer it 'sorts messages by messageable in ascending order' do sorted_messages = Messages.list(sort: :messageable, order: :asc) expect(sorted_messages.map { |m| [m.messageable_type, m.messageable_id] }).to eq( - [['TypeA', '1'], ['TypeA', '3'], ['TypeB', '2'], ['TypeC', '4']]) + [['Event', '1'], ['Event', '2'], ['Event', '3'], ['Event', '4']]) end it 'sorts messages by messageable in descending order' do sorted_messages = Messages.list(sort: :messageable, order: :desc) expect(sorted_messages.map { |m| [m.messageable_type, m.messageable_id] }).to eq( - [['TypeC', '4'], ['TypeB', '2'], ['TypeA', '3'], ['TypeA', '1']]) + [['Event', '4'], ['Event', '3'], ['Event', '2'], ['Event', '1']]) end end From ad7313427e83b7dacedd312b47f267688d40dab0 Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sat, 23 Mar 2024 03:04:24 +1100 Subject: [PATCH 7/7] add specs --- lib/outboxer.rb | 2 + lib/outboxer/argument_error.rb | 4 + lib/outboxer/messages.rb | 38 +++--- lib/outboxer/models/message.rb | 3 +- spec/factories/outboxer_messages.rb | 2 +- .../lib/outboxer/messages/list/filter_spec.rb | 123 ++++++++++++++++++ .../messages/list/no_arguments_spec.rb | 19 ++- .../outboxer/messages/list/pagination_spec.rb | 47 +++++++ spec/lib/outboxer/messages/list/sort_spec.rb | 71 ++++++++-- .../outboxer/publisher/pop_message_spec.rb | 2 +- spec/lib/outboxer/publisher/publish_spec.rb | 2 +- 11 files changed, 273 insertions(+), 40 deletions(-) create mode 100644 lib/outboxer/argument_error.rb create mode 100644 spec/lib/outboxer/messages/list/filter_spec.rb create mode 100644 spec/lib/outboxer/messages/list/pagination_spec.rb diff --git a/lib/outboxer.rb b/lib/outboxer.rb index 5928ba16..d3c1562f 100644 --- a/lib/outboxer.rb +++ b/lib/outboxer.rb @@ -5,6 +5,8 @@ require_relative "outboxer/railtie" if defined?(Rails) require_relative "outboxer/error" +require_relative "outboxer/argument_error" + require_relative "outboxer/logger" require_relative "outboxer/models" diff --git a/lib/outboxer/argument_error.rb b/lib/outboxer/argument_error.rb new file mode 100644 index 00000000..100ba5fc --- /dev/null +++ b/lib/outboxer/argument_error.rb @@ -0,0 +1,4 @@ +module Outboxer + class ArgumentError < Error + end +end diff --git a/lib/outboxer/messages.rb b/lib/outboxer/messages.rb index c977328d..f8108314 100644 --- a/lib/outboxer/messages.rb +++ b/lib/outboxer/messages.rb @@ -5,9 +5,6 @@ module Outboxer module Messages extend self - class Error < Outboxer::Error; end; - class InvalidParameter < Error; end - def unpublished!(limit: 1, order: :asc) ActiveRecord::Base.connection_pool.with_connection do message_ids = ActiveRecord::Base.transaction do @@ -33,40 +30,51 @@ def unpublished!(limit: 1, order: :asc) end def list(status: nil, sort: :updated_at, order: :asc, page: 1, per_page: 100) - if !status.nil? && !Models::Message::STATUSES.include?(status.to_sym) - raise InvalidParameter, "status must be #{Models::Message::STATUSES.join(' ')}" + if !status.nil? && !Models::Message::STATUSES.include?(status.to_s) + raise ArgumentError, "status must be #{Models::Message::STATUSES.join(' ')}" end sort_options = [:id, :status, :messageable, :created_at, :updated_at] if !sort_options.include?(sort.to_sym) - raise InvalidParameter, "sort must be #{sort_options.join(' ')}" + raise ArgumentError, "sort must be #{sort_options.join(' ')}" end order_options = [:asc, :desc] if !order_options.include?(order.to_sym) - raise InvalidParameter, "order must be #{order_options.join(' ')}" + raise ArgumentError, "order must be #{order_options.join(' ')}" end if !page.is_a?(Integer) || page <= 0 - raise InvalidParameter, "page must be >= 1" + raise ArgumentError, "page must be >= 1" end per_page_options = [100, 200, 500, 1000] if !per_page_options.include?(per_page) - raise InvalidParameter, "per_page must be #{per_page_options.join(' ')}" + raise ArgumentError, "per_page must be #{per_page_options.join(' ')}" end - messages = status.nil? ? Models::Message.all : Models::Message.where(status: status) + message_scope = Models::Message + message_scope = status.nil? ? message_scope.all : message_scope.where(status: status) - messages = + message_scope = if sort.to_sym == :messageable - messages.order(messageable_type: order.to_sym, messageable_id: order.to_sym) + message_scope.order(messageable_type: order.to_sym, messageable_id: order.to_sym) else - messages.order(sort.to_sym => order.to_sym) + message_scope.order(sort.to_sym => order.to_sym) end - ActiveRecord::Base.connection_pool.with_connection do - messages.page(page).per(per_page) + messages = ActiveRecord::Base.connection_pool.with_connection do + message_scope.page(page).per(per_page) + end + + messages.map do |message| + { + 'id' => message.id, + 'status' => message.status, + 'messageable' => "#{message.messageable_type}::#{message.messageable_id}", + 'created_at' => message.created_at.utc.to_s, + 'updated_at' => message.updated_at.utc.to_s + } end end end diff --git a/lib/outboxer/models/message.rb b/lib/outboxer/models/message.rb index 3451b405..b70df92f 100644 --- a/lib/outboxer/models/message.rb +++ b/lib/outboxer/models/message.rb @@ -22,11 +22,10 @@ class Message < ::ActiveRecord::Base module Status UNPUBLISHED = 'unpublished' PUBLISHING = 'publishing' - PUBLISHED = 'published' FAILED = 'failed' end - STATUSES = [Status::UNPUBLISHED, Status::PUBLISHING, Status::PUBLISHED, Status::FAILED] + STATUSES = [Status::UNPUBLISHED, Status::PUBLISHING, Status::FAILED] scope :unpublished, -> { where(status: Status::UNPUBLISHED) } scope :publishing, -> { where(status: Status::PUBLISHING) } diff --git a/spec/factories/outboxer_messages.rb b/spec/factories/outboxer_messages.rb index 3736bbb2..5b664bd0 100644 --- a/spec/factories/outboxer_messages.rb +++ b/spec/factories/outboxer_messages.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :outboxer_message, class: 'Outboxer::Models::Message' do - messageable_type { 'DummyType' } + messageable_type { 'Event' } messageable_id { 1 } status { Outboxer::Models::Message::Status::PUBLISHING } created_at { 1.day.ago } diff --git a/spec/lib/outboxer/messages/list/filter_spec.rb b/spec/lib/outboxer/messages/list/filter_spec.rb new file mode 100644 index 00000000..5e28142e --- /dev/null +++ b/spec/lib/outboxer/messages/list/filter_spec.rb @@ -0,0 +1,123 @@ +require 'spec_helper' + +module Outboxer + RSpec.describe Messages do + before do + create(:outboxer_message, :unpublished, id: 4, + messageable_type: 'Event', messageable_id: 1, + created_at: 5.minutes.ago, updated_at: 4.minutes.ago) + create(:outboxer_message, :failed, id: 3, + messageable_type: 'Event', messageable_id: 2, + created_at: 4.minutes.ago, updated_at: 3.minutes.ago) + create(:outboxer_message, :publishing, id: 2, + messageable_type: 'Event', messageable_id: 3, + created_at: 3.minutes.ago, updated_at: 2.minutes.ago) + create(:outboxer_message, :unpublished, id: 1, + messageable_type: 'Event', messageable_id: 4, + created_at: 2.minutes.ago, updated_at: 1.minute.ago) + end + + describe '.list' do + context 'when an invalid status is specified' do + it 'raises an ArgumentError' do + expect do + Messages.list(status: :invalid) + end.to raise_error( + ArgumentError, "status must be #{Models::Message::STATUSES.join(' ')}") + end + end + + context 'with no status specified' do + it 'returns all messages' do + expect( + Messages.list(sort: :status, order: :asc) + ).to match_array([ + { + 'id' => 3, + 'status' => 'failed', + 'messageable' => 'Event::2', + 'created_at' => 4.minutes.ago.utc.to_s, + 'updated_at' => 3.minutes.ago.utc.to_s + }, + { + 'id' => 2, + 'status' => 'publishing', + 'messageable' => 'Event::3', + 'created_at' => 3.minutes.ago.utc.to_s, + 'updated_at' => 2.minutes.ago.utc.to_s + }, + { + 'id' => 4, + 'status' => 'unpublished', + 'messageable' => 'Event::1', + 'created_at' => 5.minutes.ago.utc.to_s, + 'updated_at' => 4.minutes.ago.utc.to_s + }, + { + 'id' => 1, + 'status' => 'unpublished', + 'messageable' => 'Event::4', + 'created_at' => 2.minutes.ago.utc.to_s, + 'updated_at' => 1.minute.ago.utc.to_s + } + ]) + end + end + + context 'with unpublished status' do + it 'returns unpublished messages' do + expect( + Messages.list(status: :unpublished, sort: :status, order: :asc) + ).to match_array([ + { + 'id' => 4, + 'status' => 'unpublished', + 'messageable' => 'Event::1', + 'created_at' => 5.minutes.ago.utc.to_s, + 'updated_at' => 4.minutes.ago.utc.to_s + }, + { + 'id' => 1, + 'status' => 'unpublished', + 'messageable' => 'Event::4', + 'created_at' => 2.minutes.ago.utc.to_s, + 'updated_at' => 1.minute.ago.utc.to_s + } + ]) + end + end + + context 'with publishing status' do + it 'returns publishing messages' do + expect( + Messages.list(status: :publishing, sort: :status, order: :asc) + ).to match_array([ + { + 'id' => 2, + 'status' => 'publishing', + 'messageable' => 'Event::3', + 'created_at' => 3.minutes.ago.utc.to_s, + 'updated_at' => 2.minutes.ago.utc.to_s + } + ]) + end + end + + context 'with failed status' do + it 'returns failed messages' do + expect( + Messages.list(status: :failed, sort: :status, order: :asc) + ).to match_array([ + { + 'id' => 3, + 'status' => 'failed', + 'messageable' => 'Event::2', + 'created_at' => 4.minutes.ago.utc.to_s, + 'updated_at' => 3.minutes.ago.utc.to_s + } + ]) + end + end + end + end +end diff --git a/spec/lib/outboxer/messages/list/no_arguments_spec.rb b/spec/lib/outboxer/messages/list/no_arguments_spec.rb index 9eed159e..692b0722 100644 --- a/spec/lib/outboxer/messages/list/no_arguments_spec.rb +++ b/spec/lib/outboxer/messages/list/no_arguments_spec.rb @@ -6,18 +6,25 @@ module Outboxer let!(:messages) do [ create(:outboxer_message, :unpublished, - id: 1, created_at: 4.minutes.ago, updated_at: 2.minutes.ago), + id: 1, messageable_type: 'Event', messageable_id: 1), create(:outboxer_message, :failed, - id: 2, created_at: 3.minutes.ago, updated_at: 3.minutes.ago), + id: 2, messageable_type: 'Event', messageable_id: 2), create(:outboxer_message, :publishing, - id: 3, created_at: 2.minute.ago, updated_at: 2.minutes.ago), + id: 3, messageable_type: 'Event', messageable_id: 3), create(:outboxer_message, :unpublished, - id: 4, created_at: 1.minute.ago, updated_at: 1.minutes.ago) + id: 4, messageable_type: 'Event', messageable_id: 4) ] end - it 'returns all messages ordered by last updated at desc' do - expect(Messages.list.map(&:id)).to eq([1, 2, 3, 4]) + it 'returns all messages ordered by last updated at asc' do + expect( + Messages.list.map { |message| message.slice('id', 'status', 'messageable') } + ).to match_array([ + { 'id' => 1, 'status' => 'unpublished', 'messageable' => 'Event::1' }, + { 'id' => 2, 'status' => 'failed', 'messageable' => 'Event::2' }, + { 'id' => 3, 'status' => 'publishing', 'messageable' => 'Event::3' }, + { 'id' => 4, 'status' => 'unpublished', 'messageable' => 'Event::4' }, + ]) end end end diff --git a/spec/lib/outboxer/messages/list/pagination_spec.rb b/spec/lib/outboxer/messages/list/pagination_spec.rb new file mode 100644 index 00000000..335ba3fe --- /dev/null +++ b/spec/lib/outboxer/messages/list/pagination_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +module Outboxer + RSpec.describe Messages do + before do + create_list(:outboxer_message, 101, status: :unpublished, + messageable_type: 'Event', messageable_id: 1) + end + + describe '.list' do + let(:first_page) { Messages.list(page: 1, per_page: 100) } + let(:second_page) { Messages.list(page: 2, per_page: 100) } + + context 'when invalid per page is specified' do + it 'raises an ArgumentError' do + expect do + Messages.list(per_page: 1) + end.to raise_error(ArgumentError, "per_page must be 100 200 500 1000") + end + end + + context 'when invalid page is specified' do + it 'raises an ArgumentError' do + expect do + Messages.list(page: 'hello') + end.to raise_error(ArgumentError, "page must be >= 1") + end + end + + context 'when 100 messages per page' do + it 'first page returns 100 messages' do + expect(first_page.size).to eq(100) + end + + it 'second page returns 1 message' do + expect(second_page.size).to eq(1) + end + + it 'ensures no overlap between first and second page' do + first_page_ids = first_page.map { |message| message['id'] } + second_page_ids = second_page.map { |message| message['id'] } + expect(first_page_ids & second_page_ids).to be_empty + end + end + end + end +end diff --git a/spec/lib/outboxer/messages/list/sort_spec.rb b/spec/lib/outboxer/messages/list/sort_spec.rb index 57663dd0..0f6c4ee7 100644 --- a/spec/lib/outboxer/messages/list/sort_spec.rb +++ b/spec/lib/outboxer/messages/list/sort_spec.rb @@ -18,49 +18,92 @@ module Outboxer end describe '.list' do + context 'when an invalid sort is specified' do + it 'raises an ArgumentError' do + expect do + Messages.list(sort: :invalid) + end.to raise_error( + ArgumentError, "sort must be id status messageable created_at updated_at") + end + end + + context 'when an invalid order is specified' do + it 'raises an ArgumentError' do + expect do + Messages.list(order: :invalid) + end.to raise_error(ArgumentError, "order must be asc desc") + end + end + context 'with sort by status' do it 'sorts messages by status in ascending order' do - expect(Messages.list(sort: :status, order: :asc).map(&:status)).to eq( - ['failed', 'publishing', 'unpublished', 'unpublished']) + expect( + Messages + .list(sort: :status, order: :asc) + .map { |message| message['status'] } + ).to eq(['failed', 'publishing', 'unpublished', 'unpublished']) end it 'sorts messages by status in descending order' do - expect(Messages.list(sort: :status, order: :desc).map(&:status)).to eq( - ['unpublished', 'unpublished', 'publishing', 'failed']) + expect( + Messages + .list(sort: :status, order: :desc) + .map { |message| message['status'] } + ).to eq(['unpublished', 'unpublished', 'publishing', 'failed']) end end context 'with sort by messageable' do it 'sorts messages by messageable in ascending order' do - sorted_messages = Messages.list(sort: :messageable, order: :asc) - expect(sorted_messages.map { |m| [m.messageable_type, m.messageable_id] }).to eq( - [['Event', '1'], ['Event', '2'], ['Event', '3'], ['Event', '4']]) + expect( + Messages + .list(sort: :messageable, order: :asc) + .map { |message| message['messageable'] } + ).to eq(['Event::1', 'Event::2', 'Event::3', 'Event::4']) end it 'sorts messages by messageable in descending order' do - sorted_messages = Messages.list(sort: :messageable, order: :desc) - expect(sorted_messages.map { |m| [m.messageable_type, m.messageable_id] }).to eq( - [['Event', '4'], ['Event', '3'], ['Event', '2'], ['Event', '1']]) + expect( + Messages + .list(sort: :messageable, order: :desc) + .map { |message| message['messageable'] } + ).to eq(['Event::4', 'Event::3', 'Event::2', 'Event::1']) end end context 'with sort by created_at' do it 'sorts messages by created_at in ascending order' do - expect(Messages.list(sort: :created_at, order: :asc).map(&:id)).to eq([4, 3, 2, 1]) + expect( + Messages + .list(sort: :created_at, order: :asc) + .map { |message| message['id'] } + ).to eq([4, 3, 2, 1]) end it 'sorts messages by created_at in descending order' do - expect(Messages.list(sort: :created_at, order: :desc).map(&:id)).to eq([1, 2, 3, 4]) + expect( + Messages + .list(sort: :created_at, order: :desc) + .map { |message| message['id'] } + ).to eq([1, 2, 3, 4]) end end context 'with sort by updated_at' do it 'sorts messages by updated_at in ascending order' do - expect(Messages.list(sort: :updated_at, order: :asc).map(&:id)).to eq([4, 3, 2, 1]) + expect( + Messages + .list(sort: :updated_at, order: :asc) + .map { |message| message['id'] } + ).to eq([4, 3, 2, 1]) end it 'sorts messages by updated_at in descending order' do - expect(Messages.list(sort: :updated_at, order: :desc).map(&:id)).to eq([1, 2, 3, 4]) + expect( + Messages + .list(sort: :updated_at, order: :desc) + .map { |message| message['id'] } + ).to eq([1, 2, 3, 4]) end end end diff --git a/spec/lib/outboxer/publisher/pop_message_spec.rb b/spec/lib/outboxer/publisher/pop_message_spec.rb index 0520e342..9f9cfe4f 100644 --- a/spec/lib/outboxer/publisher/pop_message_spec.rb +++ b/spec/lib/outboxer/publisher/pop_message_spec.rb @@ -15,7 +15,7 @@ module Outboxer it 'processes the message' do Publisher.pop_message!(queue: queue, logger: logger) do |msg| - expect(msg.messageable_type).to eq('DummyType') + expect(msg.messageable_type).to eq('Event') expect(msg.id).to eq(message.id) end end diff --git a/spec/lib/outboxer/publisher/publish_spec.rb b/spec/lib/outboxer/publisher/publish_spec.rb index 584af790..d350fdfc 100644 --- a/spec/lib/outboxer/publisher/publish_spec.rb +++ b/spec/lib/outboxer/publisher/publish_spec.rb @@ -24,7 +24,7 @@ module Outboxer logger: logger, kernel: kernel ) do |publishing_message| - expect(publishing_message.messageable_type).to eq('DummyType') + expect(publishing_message.messageable_type).to eq('Event') expect(publishing_message.messageable_id).to eq('1') expect(publishing_message.status).to eq(Models::Message::Status::PUBLISHING)