Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add rich text reaction model #451

Merged
merged 38 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8b589c4
feat: add RichTextReaction model
wwrk22 Dec 7, 2023
12c40b3
feat: rollback and remigrate RichTextReaction creation with emoji and…
wwrk22 Dec 8, 2023
dd54eef
chore: add missing association
wwrk22 Dec 8, 2023
869b1e6
feat: allow RichTextReaction to interface with a limited set of emoji…
wwrk22 Dec 8, 2023
ca3a2f9
feat: add routes and actions for rich text reaction create and destroy
wwrk22 Dec 9, 2023
e34fa78
refactor: Emoji class isn't needed
wwrk22 Dec 10, 2023
feed2fa
feat: add rich text reaction view component
wwrk22 Dec 11, 2023
8b8e4a0
feat: have standup update rendered with emoji selector
wwrk22 Dec 11, 2023
dbd7f68
chore: comment for future improvement
wwrk22 Dec 11, 2023
285c7d6
refacto: improve test
wwrk22 Dec 11, 2023
e90e62b
refactor: organize in order to more easily add new rich text reaction…
wwrk22 Dec 12, 2023
a8b5330
rubocop corrections
wwrk22 Dec 12, 2023
d03a799
refactor: using the emoji text as the key is easier to use with bette…
wwrk22 Dec 12, 2023
1f1523b
chore: remove temporary components that were used for testing
wwrk22 Dec 19, 2023
135b632
feat: gather reaction info for all rich text of a class
wwrk22 Dec 19, 2023
c1b5c04
chore: EmojiSelectorComponent has been deleted
wwrk22 Dec 19, 2023
f97b927
refactor: use a more fitting column name
wwrk22 Dec 19, 2023
c21e34e
clean up
wwrk22 Dec 20, 2023
99ae7bc
rubocop corrections
wwrk22 Dec 20, 2023
b43cbb9
refactor: store emojis and their captions for global usage
wwrk22 Dec 22, 2023
62dbb9b
refactor: `Reactionable` is not a fitting way of DRYing up as of now
wwrk22 Dec 26, 2023
c5f9186
chore: use `require` for the one lib class
wwrk22 Dec 26, 2023
5063d0d
refactor: emojis and their captions defnitions were moved
wwrk22 Dec 26, 2023
7348de7
refactor: change Emoji to a reusable class that provides API for fron…
wwrk22 Dec 26, 2023
fb1e1d3
fix: rescuing the error doesn't stop ctlr action flow
wwrk22 Dec 26, 2023
b958d53
chore: clean up tests
wwrk22 Dec 26, 2023
290d63b
chore: rubocop corrections
wwrk22 Dec 26, 2023
e413d32
chore: add helpful comment
wwrk22 Dec 28, 2023
a094daa
feat: update Emoji for easier API for frontend to use
wwrk22 Dec 31, 2023
9e38c27
feat: scope for finding all reactions belonging to given rich text(s)
wwrk22 Dec 31, 2023
f692cc3
rubocop
wwrk22 Dec 31, 2023
dbaf208
refactor: eliminate view component query and do some clean up
wwrk22 Jan 2, 2024
f11a8d0
clean up
wwrk22 Jan 4, 2024
6c34708
chore: revert changes that should go in another branch
wwrk22 Jan 4, 2024
1388b6d
chore: remove scopes that are too trivial and aren't even needed yet
wwrk22 Jan 4, 2024
042a8e8
feat: add factory for ActionText::RichText, skip lint
wwrk22 Jan 8, 2024
56156d4
Merge branch 'main' into feature/add-rich-text-reaction-model
wwrk22 Jan 9, 2024
008127f
chore: add test for code coverage
wwrk22 Jan 9, 2024
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
34 changes: 34 additions & 0 deletions app/controllers/rich_text_reactions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
class RichTextReactionsController < ApplicationController
before_action :build_rich_text_reaction, only: %i[create]

# POST /rich_text_reactions.turbo_stream
def create
if @rich_text_reaction.save
render :create, status: :created
else
flash.now[:alert] = 'Reaction could not be created.'
end
end

# DELETE /rich_text_reactions/:id.turbo_stream
def destroy
@rich_text_reaction = RichTextReaction.find(params[:id])

msg = 'Reaction could not be deleted.'
flash.now[:alert] = msg unless @rich_text_reaction.destroy
rescue ActiveRecord::RecordNotFound
flash.now[:alert] = "RichTextReaction with id #{params[:id]} could not be found."
end

private

def build_rich_text_reaction
@rich_text_reaction = current_user.rich_text_reactions.build(
rich_text_reaction_params
)
end

def rich_text_reaction_params
params.require(:rich_text_reaction).permit(:emoji_caption, :rich_text_id)
end
end
33 changes: 33 additions & 0 deletions app/models/rich_text_reaction.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# == Schema Information
#
# Table name: rich_text_reactions
#
# id :bigint not null, primary key
# emoji_caption :string not null
# created_at :datetime not null
# updated_at :datetime not null
# rich_text_id :bigint not null
# user_id :bigint not null
#
# Indexes
#
# index_rich_text_reactions_on_rich_text_id (rich_text_id)
# index_rich_text_reactions_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (rich_text_id => action_text_rich_texts.id)
# fk_rails_... (user_id => users.id)
#
require 'emoji'

class RichTextReaction < ApplicationRecord
belongs_to :user
belongs_to :rich_text, class_name: 'ActionText::RichText'

validates :emoji_caption,
inclusion: {
in: Emoji.captions,
message: 'must be present in permissible set'
}
end
2 changes: 2 additions & 0 deletions app/models/standup_meeting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class StandupMeeting < ApplicationRecord
belongs_to :standup_meeting_group, inverse_of: :standup_meetings
belongs_to :user

# Under the hood, Rails calls a `has_one :rich_text_yesterday_work_description`,
# so we can do `StandupMeeting.includes(:rich_text_yesterday_work_description).
has_rich_text :yesterday_work_description
has_rich_text :today_work_description
has_rich_text :blockers_description
Expand Down
2 changes: 2 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ class User < ApplicationRecord

has_many :mentee_applications, class_name: 'UserMenteeApplication', dependent: :destroy

has_many :rich_text_reactions, dependent: :destroy

# rubocop:disable Rails/InverseOf
has_one :current_resume, -> { where(current: true) }, class_name: 'Resume', dependent: nil
# rubocop:enable Rails/InverseOf
Expand Down
1 change: 1 addition & 0 deletions app/views/rich_text_reactions/create.turbo_stream.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= "PLACEHOLDER" %>
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,6 @@
scope controller: :static do
get :faq
end

resources :rich_text_reactions, only: %i[create destroy]
end
11 changes: 11 additions & 0 deletions db/migrate/20231207143236_create_rich_text_reactions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class CreateRichTextReactions < ActiveRecord::Migration[7.0]
def change
create_table :rich_text_reactions do |t|
t.references :user, null: false, foreign_key: true
t.references :rich_text, null: false, foreign_key: { to_table: :action_text_rich_texts }
t.string :emoji_caption, null: false

t.timestamps
end
end
end
14 changes: 13 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified erd.pdf
Binary file not shown.
32 changes: 32 additions & 0 deletions lib/emoji.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

class Emoji
DICTIONARY = {
'THUMBS_UP' => '👍',
'THUMBS_DOWN' => '👎',
'THINKING' => '🤔',
'HOORAY' => '🎉',
'SHRUG' => '🤷',
'EYES' => '👀'
}.freeze

attr_reader :caption

def initialize(caption = DICTIONARY.keys.sample)
@caption = caption
end

def emoji
DICTIONARY[@caption]
end

class << self
def captions
DICTIONARY.keys
end

def emojis
DICTIONARY.values
end
end
end
19 changes: 19 additions & 0 deletions spec/factories/action_text_rich_texts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FactoryBot.define do
factory :action_text_rich_text, class: 'ActionText::RichText' do
# +rich_text_owner+ must be given by the user of this factory, and it may
# be any persisted ActiveRecord model record.
transient do
rich_text_owner { nil }
end

name { Faker::Lorem.word }
body { ActionText::Content.new "<div>#{Faker::Lorem.sentence}</div>" }

after(:build) do |action_text_rich_text, evaluator|
if evaluator.rich_text_owner
action_text_rich_text.record_type = evaluator.rich_text_owner.class.name
action_text_rich_text.record_id = evaluator.rich_text_owner.id
end
end
end
end
35 changes: 35 additions & 0 deletions spec/factories/rich_text_reactions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# == Schema Information
#
# Table name: rich_text_reactions
#
# id :bigint not null, primary key
# emoji_caption :string not null
# created_at :datetime not null
# updated_at :datetime not null
# rich_text_id :bigint not null
# user_id :bigint not null
#
# Indexes
#
# index_rich_text_reactions_on_rich_text_id (rich_text_id)
# index_rich_text_reactions_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (rich_text_id => action_text_rich_texts.id)
# fk_rails_... (user_id => users.id)
#
FactoryBot.define do
factory :rich_text_reaction do
# +rich_text_owner+ can be given by the user of this factory, and it may
# be any persisted ActiveRecord model record. By default, a User record is
# used.
transient do
rich_text_owner { user }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@JoshDevHub
Some ActiveRecord model object has to be given for creating ActionText::RichText.
I decided the User model is probably the safest to use since it most likely won’t be going away or changed to a different name.

end

user
emoji_caption { Emoji.captions.sample }
rich_text { association(:action_text_rich_text, rich_text_owner:) }
end
end
2 changes: 2 additions & 0 deletions spec/factories_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

RSpec.describe 'FactoryBot' do
FactoryBot.factories.each do |factory|
next if factory.name == :action_text_rich_text
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@JoshDevHub
I think unless we can somehow have a fake ActiveRecord model with a fake database table, it’s best to skip linting the ActionText::RichText factory.


# rubocop:disable RSpec/NoExpectationExample
it "#{factory.name} should pass lint" do
FactoryBot::Linter.new([factory]).lint!
Expand Down
26 changes: 26 additions & 0 deletions spec/lib/emoji_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require 'rails_helper'
require 'emoji'

RSpec.describe Emoji do
let(:caption) { described_class.captions.sample }

describe '#emoji' do
subject { described_class.new(caption) }

it 'returns the emoji in string format' do
expect(subject.emoji).to eq(Emoji::DICTIONARY[caption])
end
end

describe '.captions' do
it 'returns the set of allowed emoji captions' do
expect(described_class.captions).to eq(described_class::DICTIONARY.keys)
end
end

describe '.emojis' do
it 'returns the set of allowed emojis' do
expect(described_class.emojis).to eq(described_class::DICTIONARY.values)
end
end
end
17 changes: 17 additions & 0 deletions spec/models/rich_text_reactions_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'rails_helper'

RSpec.describe RichTextReaction do
it 'is valid with an emoji in the permissible set' do
emoji_caption = Emoji.captions.sample
rich_text_reaction = described_class.new(emoji_caption:)

expect(rich_text_reaction.errors[:emoji_caption]).to be_empty
end

it 'is invalid with an emoji not in the permissible set' do
rich_text_reaction = described_class.new(emoji_caption: 'disguised_face')
rich_text_reaction.valid?

expect(rich_text_reaction.errors[:emoji_caption]).to be_present
end
end
67 changes: 67 additions & 0 deletions spec/requests/rich_text_reactions_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
require 'rails_helper'
require 'emoji'

RSpec.describe 'RichTextReaction requests' do
let!(:user) { create(:user) }

before do
sign_in user
end

describe 'POST /rich_text_reactions' do
it 'creates a rich text reaction' do
count = RichTextReaction.count
params = {
rich_text_reaction: {
emoji_caption: Emoji.captions.sample,
rich_text_id: create(:standup_meeting).yesterday_work_description.id
},
format: :turbo_stream
}

post(rich_text_reactions_url, params:)

expect(RichTextReaction.count).to eq(count + 1)
expect(response).to have_http_status :created
end

it 'sets a flash message to indicate failure to create' do
post rich_text_reactions_url,
params: {
rich_text_reaction: {
emoji_caption: Emoji.captions.sample,
rich_text_id: 2
},
format: :turbo_stream
}

expect(flash.now[:alert]).to be_present
end
end

describe 'DELETE /rich_text_reactions/:id' do
it 'deletes a rich text reaction' do
id = create(:rich_text_reaction).id

delete(rich_text_reaction_url(id), params: { format: :turbo_stream })

expect(RichTextReaction.count).to be_zero
expect(response).to have_http_status :no_content
end

it 'sets a flash message to indicate failure to delete' do
fake_id = '1'
rich_text_reaction = instance_double(RichTextReaction, destroy: false)
allow(RichTextReaction).to receive(:find).with(fake_id).and_return(rich_text_reaction)

delete rich_text_reaction_url(fake_id),
params: {
format: :turbo_stream
}

expect(flash.now[:alert]).to be_present

expect(response).to have_http_status :no_content
end
end
end