From ed41ff0ad8801c61c2d64b4706d89ac359f3c8d8 Mon Sep 17 00:00:00 2001 From: James Stubblefield Date: Thu, 17 Oct 2024 17:26:03 -0500 Subject: [PATCH] Add organization relationship to posts --- Gemfile | 1 + Gemfile.lock | 5 ++++ app/controllers/application_controller.rb | 1 + app/controllers/concerns/organizationable.rb | 21 ++++++++++++++ app/controllers/posts_controller.rb | 9 ++++-- app/controllers/registrations_controller.rb | 9 +----- app/models/current.rb | 2 +- app/models/organization.rb | 1 + app/models/post.rb | 1 + app/models/user.rb | 14 +++++++++ app/views/posts/show.html.erb | 29 ++++++------------- ...20241017211831_add_organization_to_post.rb | 5 ++++ db/schema.rb | 5 +++- test/controllers/posts_controller_test.rb | 12 +++++++- test/factories/memberships.rb | 4 +++ test/factories/posts.rb | 1 + test/models/organization_test.rb | 1 + test/models/user_test.rb | 19 ++++++++++++ 18 files changed, 107 insertions(+), 33 deletions(-) create mode 100644 app/controllers/concerns/organizationable.rb create mode 100644 db/migrate/20241017211831_add_organization_to_post.rb diff --git a/Gemfile b/Gemfile index fcd1f10..2adaed8 100644 --- a/Gemfile +++ b/Gemfile @@ -40,6 +40,7 @@ group :test do gem "minitest-rails" gem "minitest-spec-rails" gem "mocha" + gem "rails-controller-testing" gem "selenium-webdriver" gem "shoulda-context" gem "shoulda-matchers" diff --git a/Gemfile.lock b/Gemfile.lock index e19305b..75b53e9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -275,6 +275,10 @@ GEM rackup (2.1.0) rack (>= 3) webrick (~> 1.8) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -419,6 +423,7 @@ DEPENDENCIES pry-rails puma (>= 5.0) rails! + rails-controller-testing rbui! rubocop-rails-omakase selenium-webdriver diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3b5efbc..41c3366 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,6 @@ class ApplicationController < ActionController::Base include Authentication + include Organizationable # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. # TODO: Turn on after styling is complete. # allow_browser versions: :modern diff --git a/app/controllers/concerns/organizationable.rb b/app/controllers/concerns/organizationable.rb new file mode 100644 index 0000000..593ea27 --- /dev/null +++ b/app/controllers/concerns/organizationable.rb @@ -0,0 +1,21 @@ +module Organizationable + extend ActiveSupport::Concern + + included do + before_action :assign_organization + end + + def current_organization + Current.organization || Current.user.base_organization + end + + def assign_organization + return unless Current.user + + Current.organization = find_organization_by_cookie + end + + def find_organization_by_cookie + Organization.find_by(id: cookies.signed[:organization_id]) || Current.user.base_organization + end +end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index ddc7416..7282aa3 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -4,7 +4,7 @@ class PostsController < ApplicationController # GET /posts def index - @posts = Post.all + @posts = current_organization.posts.order(created_at: :desc) end def show @@ -47,7 +47,12 @@ def set_post end def post_params - params.expect(post: [ :title, :message, :user_id ]).merge(user_id: current_user.id) + params.expect( + post: [ :title, :message, :user_id ] + ).merge( + user_id: current_user.id, + organization_id: current_organization.id + ) end def authorize_user! diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 8af29e1..b24fbb5 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -10,7 +10,7 @@ def create User.transaction do @user = User.new(user_params) if @user.save - create_organization_for(@user) + @user.create_base_organization! start_new_session_for(@user) redirect_to root_path, notice: "Welcome to PostIt! Start adding projects and tasks." else @@ -32,11 +32,4 @@ def user_params ] ) end - - def create_organization_for(user) - Organization.transaction do - organization = Organization.create!(name: user.name) - user.memberships.create!(organization: organization, role: :owner) - end - end end diff --git a/app/models/current.rb b/app/models/current.rb index 2bef56d..18ecc59 100644 --- a/app/models/current.rb +++ b/app/models/current.rb @@ -1,4 +1,4 @@ class Current < ActiveSupport::CurrentAttributes - attribute :session + attribute :session, :organization delegate :user, to: :session, allow_nil: true end diff --git a/app/models/organization.rb b/app/models/organization.rb index 0cf65d1..5ec8fbc 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -11,4 +11,5 @@ class Organization < ApplicationRecord has_many :memberships, dependent: :destroy has_many :users, through: :memberships + has_many :posts, dependent: :destroy end diff --git a/app/models/post.rb b/app/models/post.rb index 4b3789b..d0788c9 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -3,6 +3,7 @@ class Post < ApplicationRecord # Associations #----------------------------------------------------------------------------- belongs_to :user + belongs_to :organization has_rich_text :message #----------------------------------------------------------------------------- diff --git a/app/models/user.rb b/app/models/user.rb index c95f7ec..0ba025e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -27,4 +27,18 @@ class User < ApplicationRecord #----------------------------------------------------------------------------- normalizes :email_address, with: ->(e) { e.strip.downcase } + + #----------------------------------------------------------------------------- + # Instance Methods + #----------------------------------------------------------------------------- + + def base_organization + organizations.joins(:memberships).merge(Membership.owner).first || create_base_organization! + end + + def create_base_organization! + organization = Organization.create!(name: name) + memberships.create!(organization: organization, role: :owner) + organization + end end diff --git a/app/views/posts/show.html.erb b/app/views/posts/show.html.erb index 9aa99cb..aa4923b 100644 --- a/app/views/posts/show.html.erb +++ b/app/views/posts/show.html.erb @@ -1,28 +1,17 @@
- <% if notice.present? %> -

<%= notice %>

- <% end %> - <%= render @post %> <%= render RBUI::Link.new(href: edit_post_path(@post), variant: :outline, class: "me-3") { "Edit" } %> - <%#= render RBUI::Link.new( - href: post_path(@post), - variant: :destructive, - data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this post?" }, - class: "ms-3") { - "Delete" - } %> - <%= render Components::ConfirmDialog.new( - variant: :destructive, - href: post_path(@post), - title: "Are you sure?", - description: "This action cannot be undone. This will permanently delete your post.", - action_text: "Delete post", - cancel_text: "Cancel", - data: { turbo_method: :delete }, - ) { "Delete" } %> + <%= render Components::ConfirmDialog.new( + variant: :destructive, + href: post_path(@post), + title: "Are you sure?", + description: "This action cannot be undone. This will permanently delete your post.", + action_text: "Delete post", + cancel_text: "Cancel", + data: { turbo_method: :delete }, + ) { "Delete" } %>
diff --git a/db/migrate/20241017211831_add_organization_to_post.rb b/db/migrate/20241017211831_add_organization_to_post.rb new file mode 100644 index 0000000..77ac39c --- /dev/null +++ b/db/migrate/20241017211831_add_organization_to_post.rb @@ -0,0 +1,5 @@ +class AddOrganizationToPost < ActiveRecord::Migration[8.0] + def change + add_reference :posts, :organization, null: false, foreign_key: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 986301a..7c6ff99 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2024_10_16_231107) do +ActiveRecord::Schema[8.0].define(version: 2024_10_17_211831) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -73,6 +73,8 @@ t.bigint "user_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "organization_id", null: false + t.index ["organization_id"], name: "index_posts_on_organization_id" t.index ["user_id"], name: "index_posts_on_user_id" end @@ -98,6 +100,7 @@ add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "memberships", "organizations" add_foreign_key "memberships", "users" + add_foreign_key "posts", "organizations" add_foreign_key "posts", "users" add_foreign_key "sessions", "users" end diff --git a/test/controllers/posts_controller_test.rb b/test/controllers/posts_controller_test.rb index f09bd1d..3d5de9e 100644 --- a/test/controllers/posts_controller_test.rb +++ b/test/controllers/posts_controller_test.rb @@ -13,11 +13,21 @@ class PostsControllerTest < ActionDispatch::IntegrationTest assert_response :success end + + test "should only get posts for the current organization" do + organization = create(:organization) + user.organizations << organization + Current.stubs(:organization).returns(organization) + + post = create(:post, organization: organization) + get posts_url + + assert_includes assigns(:posts), post + end end # GET /posts describe "GET /posts/new" do test "should be successful" do - create(:user) # TODO: replace once authentication built get new_post_url assert_response :success diff --git a/test/factories/memberships.rb b/test/factories/memberships.rb index e19e39e..a306e6d 100644 --- a/test/factories/memberships.rb +++ b/test/factories/memberships.rb @@ -7,5 +7,9 @@ trait :admin do role { :admin } end + + trait :owner do + role { :owner } + end end end diff --git a/test/factories/posts.rb b/test/factories/posts.rb index dc8a384..07c5365 100644 --- a/test/factories/posts.rb +++ b/test/factories/posts.rb @@ -1,6 +1,7 @@ FactoryBot.define do factory :post do user + organization title { "MyString" } end end diff --git a/test/models/organization_test.rb b/test/models/organization_test.rb index c1b2ef5..1e8e271 100644 --- a/test/models/organization_test.rb +++ b/test/models/organization_test.rb @@ -6,6 +6,7 @@ class OrganizationTest < ActiveSupport::TestCase should have_many(:memberships).dependent(:destroy) should have_many(:users).through(:memberships) + should have_many(:posts).dependent(:destroy) end describe "Validations" do diff --git a/test/models/user_test.rb b/test/models/user_test.rb index 755332f..787dc4a 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -33,4 +33,23 @@ class UserTest < ActiveSupport::TestCase assert_equal email.downcase, user.email_address end end + + describe "Instance Methods" do + describe "#base_organization" do + it "should return the user's base organization" do + membership = create(:membership, :owner) + user = membership.user + assert_equal membership.organization, user.base_organization + end + + it "should create a base organization if one does not exist" do + user = create(:user) + assert_difference("Organization.count", 1) do + assert_difference("Membership.count", 1) do + user.base_organization + end + end + end + end + end end