diff --git a/app/furniture/content_block.rb b/app/furniture/content_block.rb new file mode 100644 index 000000000..91e6c7bfb --- /dev/null +++ b/app/furniture/content_block.rb @@ -0,0 +1,5 @@ +class ContentBlock < ApplicationRecord + belongs_to :slot + has_one :section, through: :slot + has_one :space, through: :section +end diff --git a/app/furniture/content_block/content_block_policy.rb b/app/furniture/content_block/content_block_policy.rb new file mode 100644 index 000000000..0f6adfe77 --- /dev/null +++ b/app/furniture/content_block/content_block_policy.rb @@ -0,0 +1,11 @@ +class ContentBlock + class ContentBlockPolicy < ApplicationPolicy + alias_method :content_block, :object + def create? + current_person.member_of?(content_block.space) + end + + class Scope < ApplicationScope + end + end +end diff --git a/app/furniture/content_block/content_blocks_controller.rb b/app/furniture/content_block/content_blocks_controller.rb new file mode 100644 index 000000000..b6ff520a7 --- /dev/null +++ b/app/furniture/content_block/content_blocks_controller.rb @@ -0,0 +1,9 @@ +class ContentBlock + class ContentBlocksController < ApplicationController + expose :content_block, scope: -> { policy_scope(ContentBlock, policy_scope_class: ContentBlockPolicy::Scope) }, model: ContentBlock + + def new + authorize(content_block, policy_class: ContentBlockPolicy) + end + end +end diff --git a/app/furniture/text_block/text_blocks/new.html.erb b/app/furniture/text_block/text_blocks/new.html.erb new file mode 100644 index 000000000..e69de29bb diff --git a/app/lib/appends_routes.rb b/app/lib/appends_routes.rb new file mode 100644 index 000000000..e69de29bb diff --git a/app/lib/space_routes.rb b/app/lib/space_routes.rb index 6f3891ee4..3b68fe6ff 100644 --- a/app/lib/space_routes.rb +++ b/app/lib/space_routes.rb @@ -9,6 +9,7 @@ def self.append_routes(router) Furniture.append_routes(router) router.resources :furnitures, only: %i[create edit update destroy] router.resource :hero_image, controller: "room/hero_images" + router.resources :content_block, controller: "content_block/content_blocks" end router.resources :utilities diff --git a/app/models/furniture.rb b/app/models/furniture.rb index 6b0f6560b..f264fafc0 100644 --- a/app/models/furniture.rb +++ b/app/models/furniture.rb @@ -5,9 +5,9 @@ # as JSON, so that {Furniture} can be tweaked and configured as appropriate for # it's particular use case. class Furniture < ApplicationRecord - include RankedModel location(parent: :room) + include RankedModel ranks :slot, with_same: [:room_id] belongs_to :room, inverse_of: :gizmos diff --git a/app/models/room.rb b/app/models/room.rb index 70e4bc042..453764637 100644 --- a/app/models/room.rb +++ b/app/models/room.rb @@ -30,6 +30,8 @@ class Room < ApplicationRecord has_many :gizmos, dependent: :destroy, inverse_of: :room, class_name: :Furniture accepts_nested_attributes_for :gizmos + has_many :slots, dependent: :destroy, inverse_of: :section + DESCRIPTION_MAX_LENGTH = 300 validates :description, length: {maximum: DESCRIPTION_MAX_LENGTH, allow_blank: true} diff --git a/app/models/section.rb b/app/models/section.rb new file mode 100644 index 000000000..b5439b760 --- /dev/null +++ b/app/models/section.rb @@ -0,0 +1,2 @@ +class Section < Room +end diff --git a/app/models/slot.rb b/app/models/slot.rb new file mode 100644 index 000000000..8e1dfced7 --- /dev/null +++ b/app/models/slot.rb @@ -0,0 +1,10 @@ +class Slot < ApplicationRecord + belongs_to :section, class_name: "Room", inverse_of: :slots + self.location_parent = :section + has_one :space, through: :section + + belongs_to :slottable, polymorphic: true, inverse_of: :slot + + include RankedModel + ranks :slot_order, with_same: [:section_id] +end diff --git a/app/policies/slot_policy.rb b/app/policies/slot_policy.rb new file mode 100644 index 000000000..80da6fd56 --- /dev/null +++ b/app/policies/slot_policy.rb @@ -0,0 +1,10 @@ +class SlotPolicy < ApplicationPolicy + alias_method :slot, :object + + def create? + current_person.operator? || current_person.member_of?(slot.space) + end + + class Scope < ApplicationScope + end +end diff --git a/app/views/rooms/edit.html.erb b/app/views/rooms/edit.html.erb index fab30b491..63f9f6ef8 100644 --- a/app/views/rooms/edit.html.erb +++ b/app/views/rooms/edit.html.erb @@ -11,6 +11,17 @@ <%= render "rooms/hero_image/form", room: room %> <% end %> <%- end %> + +<%- if current_person.operator? || Rails.env.test? %> + <%= render CardComponent.new do |card| %> + <%- card.with_header do %> +

Gizmos (but lighter)

+ <%- end %> + + <%= link_to "Add Content Block", room.location(:new, child: :content_block), class: "button"%> + <%- end %> +<%- end %> +
<%= render CardComponent.new do %>

Gizmos

diff --git a/db/migrate/20240314003612_create_slots.rb b/db/migrate/20240314003612_create_slots.rb new file mode 100644 index 000000000..803dcd37a --- /dev/null +++ b/db/migrate/20240314003612_create_slots.rb @@ -0,0 +1,10 @@ +class CreateSlots < ActiveRecord::Migration[7.1] + def change + create_table :slots, id: :uuid do |t| + t.references :section, foreign_key: {to_table: :rooms}, type: :uuid + t.references :slottable, polymorphic: true, type: :uuid + t.integer :slot_order, null: true + t.timestamps + end + end +end diff --git a/db/migrate/20240328004901_create_content_blocks.rb b/db/migrate/20240328004901_create_content_blocks.rb new file mode 100644 index 000000000..695003982 --- /dev/null +++ b/db/migrate/20240328004901_create_content_blocks.rb @@ -0,0 +1,7 @@ +class CreateContentBlocks < ActiveRecord::Migration[7.1] + def change + create_table :content_blocks, id: :uuid do |t| + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index fa5c62e9c..955c90fa7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -82,6 +82,11 @@ t.index ["person_id"], name: "index_authentication_methods_on_person_id" end + create_table "content_blocks", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "friendly_id_slugs", force: :cascade do |t| t.string "slug", null: false t.integer "sluggable_id", null: false @@ -311,6 +316,17 @@ t.index ["space_id"], name: "index_rooms_on_space_id" end + create_table "slots", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "section_id" + t.string "slottable_type" + t.uuid "slottable_id" + t.integer "slot_order" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["section_id"], name: "index_slots_on_section_id" + t.index ["slottable_type", "slottable_id"], name: "index_slots_on_slottable" + end + create_table "space_agreements", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.uuid "space_id" t.string "name", null: false @@ -373,6 +389,7 @@ add_foreign_key "marketplace_vendor_representatives", "people" add_foreign_key "memberships", "invitations" add_foreign_key "rooms", "media", column: "hero_image_id" + add_foreign_key "slots", "rooms", column: "section_id" add_foreign_key "space_agreements", "spaces" add_foreign_key "spaces", "rooms", column: "entrance_id" end diff --git a/db/seeds.rb b/db/seeds.rb index 381b112b3..b4921354f 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -50,3 +50,8 @@ ) journal = FactoryBot.create(:journal, room: journal_section) FactoryBot.create_list(:journal_entry, 7, :with_keywords, :published, journal:) + +_content_block_section = FactoryBot.create(:room, space:, name: "Content Block-o-Clock", + description: "Content Blocks show static Words, Photos, or Videos!") + +# FactoryBot.create(:content_block, in_section: content_block_section) diff --git a/spec/models/room_spec.rb b/spec/models/room_spec.rb index 1db962ace..38a1746fe 100644 --- a/spec/models/room_spec.rb +++ b/spec/models/room_spec.rb @@ -4,6 +4,8 @@ let(:space) { Space.new } it { is_expected.to have_many(:gizmos).inverse_of(:room).dependent(:destroy) } + it { is_expected.to have_many(:slots).inverse_of(:section).dependent(:destroy) } + it { is_expected.to belong_to(:space).inverse_of(:rooms) } describe "#description" do diff --git a/spec/models/slot_spec.rb b/spec/models/slot_spec.rb new file mode 100644 index 000000000..cd0b5fd8c --- /dev/null +++ b/spec/models/slot_spec.rb @@ -0,0 +1,6 @@ +require "rails_helper" + +RSpec.describe Slot do + it { is_expected.to belong_to(:section).class_name(:Room).inverse_of(:slots) } + it { is_expected.to belong_to(:slottable).inverse_of(:slot) } +end diff --git a/spec/system/slots_system_spec.rb b/spec/system/slots_system_spec.rb new file mode 100644 index 000000000..6fad5a881 --- /dev/null +++ b/spec/system/slots_system_spec.rb @@ -0,0 +1,23 @@ +require "rails_helper" + +RSpec.describe "Slots" do + include ActionText::SystemTestHelper + + let(:space) { create(:space, :with_members) } + let(:section) { create(:room, space:) } + + scenario "Adding a Content Block to a Slot" do + sign_in(space.members.first, space) + visit(polymorphic_path(section.location(:edit))) + + click_link("Add Content Block") + expect(page).to have_current_path(polymorphic_path(section.location(:new, child: :text_block))) + + fill_in_rich_text_area("Body", with: "Prepare yourself for AMAZING") + click_button("Create") + + expect(section.slots.count).to eq(1) + expect(section.slots.first.slottable).to be_a(ContentBlock) + expect(sections.slots.first.slottable.body).to eq("Prepare yourself for AMAZING") + end +end