Skip to content

Commit

Permalink
🧹 Journal: Entries store their Keywords (#1663)
Browse files Browse the repository at this point in the history
* `Journal`: `Entries` may be queried by `Keywords`

- https://github.com/zinc-collective/convene/issues/1662
- https://github.com/zinc-collective/convene/issues/1566

This gets us a little bit closer to being able to browse `Entry` by
`Keyword`s, since each `Entry` will know it's keywords

* `Journal`: `Keyword.search` provides case-insensitive tag search
  • Loading branch information
zspencer authored Jul 15, 2023
1 parent c0635b1 commit d413ff8
Show file tree
Hide file tree
Showing 7 changed files with 38 additions and 7 deletions.
4 changes: 2 additions & 2 deletions app/furniture/journal/entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Entry < ApplicationRecord
belongs_to :journal, inverse_of: :entries
has_one :room, through: :journal
has_one :space, through: :journal
after_save :extract_keywords, if: :saved_change_to_body?
before_save :extract_keywords, if: :will_save_change_to_body?

def published?
published_at.present?
Expand All @@ -52,7 +52,7 @@ def self.renderer
end

def extract_keywords
journal.keywords.extract_and_create_from!(body)
self.keywords = journal.keywords.extract_and_create_from!(body).pluck(:canonical_keyword)
end

def to_param
Expand Down
12 changes: 9 additions & 3 deletions app/furniture/journal/keyword.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ class Keyword < ApplicationRecord
validates :canonical_keyword, presence: true, uniqueness: {case_sensitive: false, scope: :journal_id}
belongs_to :journal, inverse_of: :keywords

scope(:search, lambda do |*keywords|
where("lower(aliases::text)::text[] && ARRAY[?]::text[]", keywords.map(&:downcase))
.or(where("lower(canonical_keyword) IN (?)", keywords.map(&:downcase)))
end)

def self.extract_and_create_from!(body)
body.scan(/#(\w+)/)&.flatten&.each do |keyword|
next if where(":aliases = ANY (aliases)", aliases: keyword)
.or(where(canonical_keyword: keyword)).exists?
body.scan(/#(\w+)/)&.flatten&.map do |keyword|
existing_keyword = search(keyword).first

next existing_keyword if existing_keyword.present?

find_or_create_by!(canonical_keyword: keyword)
end
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20230713230334_journal_add_keywords_to_entries.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class JournalAddKeywordsToEntries < ActiveRecord::Migration[7.0]
def change
add_column :journal_entries, :keywords, :string, array: true
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.0].define(version: 2023_07_06_003709) do
ActiveRecord::Schema[7.0].define(version: 2023_07_13_230334) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
Expand Down Expand Up @@ -114,6 +114,7 @@
t.datetime "published_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "keywords", array: true
t.index ["journal_id"], name: "index_journal_entries_on_journal_id"
end

Expand Down
4 changes: 4 additions & 0 deletions spec/factories/furniture/journal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@
body { 5.times.map { headline }.join("\n") }
journal
end

factory :journal_keyword, class: "Journal::Keyword" do
journal
end
end
3 changes: 2 additions & 1 deletion spec/furniture/journal/entry_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
let(:journal) { entry.journal }

context "when the body is changing" do
it "idempotently creates `Keywords` in the `Journal`" do
it "idempotently creates `Keywords` in the `Journal` and `Entry`" do
bad_apple = entry.journal.keywords.create!(canonical_keyword: "BadApple", aliases: ["BadApples"])
good_times = entry.journal.keywords.find_by!(canonical_keyword: "GoodTimes")
expect do
Expand All @@ -35,6 +35,7 @@
expect(journal.keywords.where(canonical_keyword: "GoodTimes")).to exist
expect(journal.keywords.where(canonical_keyword: "HardCider")).to exist
expect(journal.keywords.where(canonical_keyword: "BadApples")).not_to exist
expect(entry.reload.keywords).to eq(["GoodTimes", "HardCider", "BadApple"])
end
end
end
Expand Down
14 changes: 14 additions & 0 deletions spec/furniture/journal/keyword_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,18 @@
it { is_expected.to validate_presence_of(:canonical_keyword) }
it { is_expected.to validate_uniqueness_of(:canonical_keyword).case_insensitive.scoped_to(:journal_id) }
it { is_expected.to belong_to(:journal).inverse_of(:keywords) }

describe ".search" do
it "returns the `Keywords` that match either canonicaly or via aliases" do
dog = create(:journal_keyword, canonical_keyword: "Dog", aliases: ["doggo"])
cat = create(:journal_keyword, canonical_keyword: "Cat", aliases: ["meower"])

expect(described_class.search("Doggo")).to include(dog)
expect(described_class.search("Dog")).to include(dog)
expect(described_class.search("Cat")).to include(cat)
expect(described_class.search("Meower")).to include(cat)
expect(described_class.search("Meower", "Dog")).to include(cat)
expect(described_class.search("Meower", "Dog")).to include(dog)
end
end
end

0 comments on commit d413ff8

Please sign in to comment.