diff --git a/README.md b/README.md index f08f9ae..b540bb4 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,16 @@ class Message < ApplicationRecord end ``` +If you want to use a different column, you can specify it with `acts_as_shiroyagi` + +```ruby +class Message < ApplicationRecord + include Shiroyagi::ActsAsShiroyagi + + acts_as_shiroyagi column: 'user_read_at' +end +``` + That's it! Then you can use the following methods. ```ruby diff --git a/lib/shiroyagi/acts_as_shiroyagi.rb b/lib/shiroyagi/acts_as_shiroyagi.rb index 9415507..51409f5 100644 --- a/lib/shiroyagi/acts_as_shiroyagi.rb +++ b/lib/shiroyagi/acts_as_shiroyagi.rb @@ -3,8 +3,12 @@ module ActsAsShiroyagi extend ActiveSupport::Concern class_methods do - # TODO: The read_at column name should be customizable via an argument. def acts_as_shiroyagi(options = {}) + @read_management_column_name = options[:column].to_sym if options[:column].present? + end + + def read_management_column_name + @read_management_column_name || :read_at end def reads_count @@ -16,41 +20,47 @@ def unreads_count end def mark_all_as_read - unreads.update(read_at: Time.current) + unreads.update(read_management_column_name => Time.current) end def mark_all_as_unread - reads.update(read_at: nil) + reads.update(read_management_column_name => nil) end end included do - scope :reads, -> { where.not(read_at: nil) } - scope :unreads, -> { where(read_at: nil) } + scope :reads, -> { where.not(read_management_column_name => nil) } + scope :unreads, -> { where(read_management_column_name => nil) } def mark_as_read - update(read_at: Time.current) if unread? + update(read_management_column_name => Time.current) if unread? end def mark_as_unread - update(read_at: nil) if read? + update(read_management_column_name => nil) if read? end def mark_as_read! - update!(read_at: Time.current) if unread? + update!(read_management_column_name => Time.current) if unread? end def mark_as_unread! - update!(read_at: nil) if read? + update!(read_management_column_name => nil) if read? end def read? - read_at.present? + send(read_management_column_name).present? end def unread? - read_at.blank? + send(read_management_column_name).blank? end end + + private + + def read_management_column_name + self.class.read_management_column_name + end end end diff --git a/spec/dummy/app/models/admin_message.rb b/spec/dummy/app/models/admin_message.rb new file mode 100644 index 0000000..fa93f3a --- /dev/null +++ b/spec/dummy/app/models/admin_message.rb @@ -0,0 +1,5 @@ +class AdminMessage < ApplicationRecord + include Shiroyagi::ActsAsShiroyagi + + acts_as_shiroyagi column: 'user_read_at' +end diff --git a/spec/dummy/db/migrate/20220726044640_create_admin_messages.rb b/spec/dummy/db/migrate/20220726044640_create_admin_messages.rb new file mode 100644 index 0000000..946d93c --- /dev/null +++ b/spec/dummy/db/migrate/20220726044640_create_admin_messages.rb @@ -0,0 +1,8 @@ +class CreateAdminMessages < ActiveRecord::Migration[6.1] + def change + create_table :admin_messages do |t| + t.datetime :user_read_at + t.timestamps + end + end +end diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index f0d5805..175ef59 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -2,15 +2,21 @@ # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171127060854) do +ActiveRecord::Schema.define(version: 2022_07_26_044640) do + + create_table "admin_messages", force: :cascade do |t| + t.datetime "user_read_at" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end create_table "messages", force: :cascade do |t| t.integer "from_user_id", null: false diff --git a/spec/factories/admin_messages.rb b/spec/factories/admin_messages.rb new file mode 100644 index 0000000..2b4334c --- /dev/null +++ b/spec/factories/admin_messages.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :admin_message, aliases: %i(unread_admin_message) do + trait :with_read_at do + user_read_at { Time.current } + end + + factory :read_admin_message, traits: %i(with_read_at) + end +end diff --git a/spec/models/admin_message_spec.rb b/spec/models/admin_message_spec.rb new file mode 100644 index 0000000..6a9c3c9 --- /dev/null +++ b/spec/models/admin_message_spec.rb @@ -0,0 +1,417 @@ +require 'rails_helper' + +RSpec.describe AdminMessage, type: :model do + describe '.reads' do + subject { AdminMessage.reads } + + context 'when there are no messages' do + it { is_expected.to match_array(AdminMessage.none) } + end + + context 'when there is a message' do + context 'when the message is read' do + let!(:admin_message) { create :read_admin_message } + + it { is_expected.to match_array(admin_message) } + end + + context 'when the message is unread' do + let!(:admin_message) { create :unread_admin_message } + + it { is_expected.to match_array(AdminMessage.none) } + end + end + + context 'when there are messages' do + context 'when these messages are all read' do + let!(:admin_message_1) { create :read_admin_message } + let!(:admin_message_2) { create :read_admin_message } + + it { is_expected.to match_array(AdminMessage.all) } + end + + context 'when a message is read' do + let!(:admin_message_1) { create :read_admin_message } + let!(:admin_message_2) { create :unread_admin_message } + + it { is_expected.to match_array(admin_message_1) } + end + + context 'when these messages are all unread' do + let!(:admin_message_1) { create :unread_admin_message } + let!(:admin_message_2) { create :unread_admin_message } + + it { is_expected.to match_array(AdminMessage.none) } + end + end +end + +describe '.unreads' do + subject { AdminMessage.unreads } + + context 'when there are no messages' do + it { is_expected.to match_array(AdminMessage.none) } + end + + context 'when there is a message' do + context 'when the message is unread' do + let!(:admin_message) { create :unread_admin_message } + + it { is_expected.to match_array(admin_message) } + end + + context 'when the message is read' do + let!(:admin_message) { create :read_admin_message } + + it { is_expected.to match_array(AdminMessage.none) } + end + end + + context 'when there are messages' do + context 'when these messages are all unread' do + let!(:admin_message_1) { create :unread_admin_message } + let!(:admin_message_2) { create :unread_admin_message } + + it { is_expected.to match_array(AdminMessage.all) } + end + + context 'when a message is unread' do + let!(:admin_message_1) { create :unread_admin_message } + let!(:admin_message_2) { create :read_admin_message } + + it { is_expected.to match_array(admin_message_1) } + end + + context 'when these messages are all read' do + let!(:admin_message_1) { create :read_admin_message } + let!(:admin_message_2) { create :read_admin_message } + + it { is_expected.to match_array(AdminMessage.none) } + end + end + end + + describe '.reads_count' do + subject { AdminMessage.reads_count } + + context 'when there are no messages' do + it { is_expected.to eq(0) } + end + + context 'when there is a message' do + context 'when the message is read' do + let!(:admin_message) { create :read_admin_message } + + it { is_expected.to eq(1) } + end + + context 'when the message is unread' do + let!(:admin_message) { create :unread_admin_message } + + it { is_expected.to eq(0) } + end + end + + context 'when there are messages' do + context 'when these messages are all read' do + let!(:admin_message_1) { create :read_admin_message } + let!(:admin_message_2) { create :read_admin_message } + + it { is_expected.to eq(2) } + end + + context 'when a message is read' do + let!(:admin_message_1) { create :read_admin_message } + let!(:admin_message_2) { create :unread_admin_message } + + it { is_expected.to eq(1) } + end + + context 'when these messages are all unread' do + let!(:admin_message_1) { create :unread_admin_message } + let!(:admin_message_2) { create :unread_admin_message } + + it { is_expected.to eq(0) } + end + end + end + + describe '.unreads_count' do + subject { AdminMessage.unreads_count } + + context 'when there are no messages' do + it { is_expected.to eq(0) } + end + + context 'when there is a message' do + context 'when the message is unread' do + let!(:admin_message) { create :unread_admin_message } + + it { is_expected.to eq(1) } + end + + context 'when the message is read' do + let!(:admin_message) { create :read_admin_message } + + it { is_expected.to eq(0) } + end + end + + context 'when there are messages' do + context 'when these messages are all unread' do + let!(:admin_message_1) { create :unread_admin_message } + let!(:admin_message_2) { create :unread_admin_message } + + it { is_expected.to eq(2) } + end + + context 'when a message is unread' do + let!(:admin_message_1) { create :unread_admin_message } + let!(:admin_message_2) { create :read_admin_message } + + it { is_expected.to eq(1) } + end + + context 'when these messages are all read' do + let!(:admin_message_1) { create :read_admin_message } + let!(:admin_message_2) { create :read_admin_message } + + it { is_expected.to eq(0) } + end + end + end + + describe '.mark_all_as_read' do + subject { AdminMessage.mark_all_as_read } + + context 'when there are no messages' do + it { is_expected.to eq([]) } + end + + context 'when there is a message' do + context 'when the message is read' do + let!(:admin_message) { create :read_admin_message } + + it { is_expected.to eq([]) } + + it 'should not change user_read_at' do + expect{ subject }.to_not change{ admin_message.reload.user_read_at } + end + end + + context 'when the message is unread' do + let!(:admin_message) { create :unread_admin_message } + + it { is_expected.to match_array(admin_message) } + + it 'should record user_read_at' do + expect{ subject }.to change{ admin_message.reload.user_read_at }.from(nil).to(be_an_instance_of(ActiveSupport::TimeWithZone)) + end + end + end + + context 'when there are messages' do + context 'when these messages are all unread' do + let!(:admin_message_1) { create :unread_admin_message } + let!(:admin_message_2) { create :unread_admin_message } + + it { is_expected.to match_array(AdminMessage.all) } + + it 'should record admin_message_1 user_read_at' do + expect{ subject }.to change{ admin_message_1.reload.user_read_at }.from(nil).to(be_an_instance_of(ActiveSupport::TimeWithZone)) + end + + it 'should record admin_message_2 user_read_at' do + expect{ subject }.to change{ admin_message_2.reload.user_read_at }.from(nil).to(be_an_instance_of(ActiveSupport::TimeWithZone)) + end + end + + context 'when a message is read' do + let!(:admin_message_1) { create :unread_admin_message } + let!(:admin_message_2) { create :read_admin_message } + + it { is_expected.to match_array(admin_message_1) } + + it 'should record admin_message_1 user_read_at' do + expect{ subject }.to change{ admin_message_1.reload.user_read_at }.from(nil).to(be_an_instance_of(ActiveSupport::TimeWithZone)) + end + + it 'should not change admin_message_2 user_read_at' do + expect{ subject }.to_not change{ admin_message_2.reload.user_read_at } + end + end + + context 'when these messages are all read' do + let!(:admin_message_1) { create :read_admin_message } + let!(:admin_message_2) { create :read_admin_message } + + it { is_expected.to match_array(AdminMessage.none) } + + it 'should not change admin_message_1 user_read_at' do + expect{ subject }.to_not change{ admin_message_1.reload.user_read_at } + end + + it 'should not change admin_message_2 user_read_at' do + expect{ subject }.to_not change{ admin_message_2.reload.user_read_at } + end + end + end + end + + describe '.mark_all_as_unread' do + subject { AdminMessage.mark_all_as_unread } + + context 'when there are no messages' do + it { is_expected.to eq([]) } + end + + context 'when there is a message' do + context 'when the message is unread' do + let!(:admin_message) { create :unread_admin_message } + + it { is_expected.to eq([]) } + + it 'should not change user_read_at' do + expect{ subject }.to_not change{ admin_message.reload.user_read_at } + end + end + + context 'when the message is read' do + let!(:admin_message) { create :read_admin_message } + + it { is_expected.to match_array(admin_message) } + + it 'should clear user_read_at' do + expect{ subject }.to change{ admin_message.reload.user_read_at }.from(ActiveSupport::TimeWithZone).to(nil) + end + end + end + + context 'when there are messages' do + context 'when these messages are all read' do + let!(:admin_message_1) { create :read_admin_message } + let!(:admin_message_2) { create :read_admin_message } + + it { is_expected.to match_array(AdminMessage.all) } + + it 'should clear admin_message_1 user_read_at' do + expect{ subject }.to change{ admin_message_1.reload.user_read_at }.from(be_an_instance_of(ActiveSupport::TimeWithZone)).to(nil) + end + + it 'should clear admin_message_2 user_read_at' do + expect{ subject }.to change{ admin_message_2.reload.user_read_at }.from(be_an_instance_of(ActiveSupport::TimeWithZone)).to(nil) + end + end + + context 'when a message is unread' do + let!(:admin_message_1) { create :read_admin_message } + let!(:admin_message_2) { create :unread_admin_message } + + it { is_expected.to match_array(admin_message_1) } + + it 'should clear admin_message_1 user_read_at' do + expect{ subject }.to change{ admin_message_1.reload.user_read_at }.from(be_an_instance_of(ActiveSupport::TimeWithZone)).to(nil) + end + + it 'should not change admin_message_2 user_read_at' do + expect{ subject }.to_not change{ admin_message_2.reload.user_read_at } + end + end + + context 'when these messages are all unread' do + let!(:admin_message_1) { create :unread_admin_message } + let!(:admin_message_2) { create :unread_admin_message } + + it { is_expected.to match_array(AdminMessage.none) } + + it 'should not change admin_message_1 user_read_at' do + expect{ subject }.to_not change{ admin_message_1.reload.user_read_at } + end + + it 'should not change admin_message_2 user_read_at' do + expect{ subject }.to_not change{ admin_message_2.reload.user_read_at } + end + end + end + end + + describe '#mark_as_read' do + subject { admin_message.mark_as_read } + + context 'when the message is unread' do + let(:admin_message) { create :unread_admin_message } + + it { is_expected.to be_truthy } + + it 'should record user_read_at' do + expect{ subject }.to change{ admin_message.user_read_at }.from(nil).to(be_an_instance_of(ActiveSupport::TimeWithZone)) + end + end + + context 'when the message is read' do + let(:admin_message) { create :read_admin_message } + + it { is_expected.to be_nil } + + it 'should not change user_read_at' do + expect{ subject }.to_not change{ admin_message.user_read_at } + end + end + end + + describe '#mark_as_unread' do + subject { admin_message.mark_as_unread } + + context 'when the message is read' do + let(:admin_message) { create :read_admin_message } + + it { is_expected.to be_truthy } + + it 'should clear user_read_at' do + expect{ subject }.to change{ admin_message.user_read_at }.from(be_an_instance_of(ActiveSupport::TimeWithZone)).to(nil) + end + end + + context 'when the message is unread' do + let(:admin_message) { create :unread_admin_message } + + it { is_expected.to be_nil } + + it 'should not change user_read_at' do + expect{ subject }.to_not change{ admin_message.user_read_at } + end + end + end + + describe '#read?' do + subject { admin_message.read? } + + context 'when the message is read' do + let(:admin_message) { create :read_admin_message } + + it { is_expected.to be_truthy } + end + + context 'when the message is unread' do + let(:admin_message) { create :unread_admin_message } + + it { is_expected.to be_falsy } + end + end + + describe '#unread?' do + subject { admin_message.unread? } + + context 'when the message is unread' do + let(:admin_message) { create :unread_admin_message } + + it { is_expected.to be_truthy } + end + + context 'when the message is read' do + let(:admin_message) { create :read_admin_message } + + it { is_expected.to be_falsy } + end + end +end