diff --git a/app/controllers/bubbles/watches_controller.rb b/app/controllers/bubbles/watches_controller.rb index e5a1873b5..cecd292b5 100644 --- a/app/controllers/bubbles/watches_controller.rb +++ b/app/controllers/bubbles/watches_controller.rb @@ -1,10 +1,6 @@ class Bubbles::WatchesController < ApplicationController include BubbleScoped, BucketScoped - def show - @watchers = @bubble.watchers_and_subscribers.sorted_with_user_first(Current.user) - end - def create set_watching_and_redirect(true) end diff --git a/app/controllers/buckets/involvements_controller.rb b/app/controllers/buckets/involvements_controller.rb new file mode 100644 index 000000000..dbfcbb6d7 --- /dev/null +++ b/app/controllers/buckets/involvements_controller.rb @@ -0,0 +1,7 @@ +class Buckets::InvolvementsController < ApplicationController + include BucketScoped + + def update + @bucket.access_for(Current.user).update!(involvement: params[:involvement]) + end +end diff --git a/app/controllers/buckets/subscriptions_controller.rb b/app/controllers/buckets/subscriptions_controller.rb deleted file mode 100644 index a5a3b92f7..000000000 --- a/app/controllers/buckets/subscriptions_controller.rb +++ /dev/null @@ -1,11 +0,0 @@ -class Buckets::SubscriptionsController < ApplicationController - include BucketScoped - - def update - if params[:subscribed] - @bucket.subscribe(Current.user) - else - @bucket.unsubscribe(Current.user) - end - end -end diff --git a/app/helpers/accesses_helper.rb b/app/helpers/accesses_helper.rb index e1fb3ad0f..4528191f0 100644 --- a/app/helpers/accesses_helper.rb +++ b/app/helpers/accesses_helper.rb @@ -12,4 +12,16 @@ module AccessesHelper locals: { selected: selected }, cached: ->(user) { [ user, selected ] } end + + def access_involvement_advance_button(bucket, user) + access = bucket.access_for(user) + + button_to access.involvement, bucket_involvement_path(bucket), method: :put, params: { involvement: next_involvement(access.involvement) } + end + + private + def next_involvement(involvement) + order = %w[ access_only watching everything ] + order[(order.index(involvement.to_s) + 1) % order.size] + end end diff --git a/app/helpers/buckets/involvements_helper.rb b/app/helpers/buckets/involvements_helper.rb new file mode 100644 index 000000000..c4adecb12 --- /dev/null +++ b/app/helpers/buckets/involvements_helper.rb @@ -0,0 +1,2 @@ +module Buckets::InvolvementsHelper +end diff --git a/app/models/access.rb b/app/models/access.rb index dbbb11590..1ca40d9e2 100644 --- a/app/models/access.rb +++ b/app/models/access.rb @@ -1,4 +1,6 @@ class Access < ApplicationRecord belongs_to :bucket belongs_to :user + + enum :involvement, %i[ access_only watching everything ].index_by(&:itself) end diff --git a/app/models/bubble/watchable.rb b/app/models/bubble/watchable.rb index efd2c5cb5..8ad47554d 100644 --- a/app/models/bubble/watchable.rb +++ b/app/models/bubble/watchable.rb @@ -9,15 +9,18 @@ module Bubble::Watchable end def watched_by?(user) - watchers_and_subscribers.include?(user) + watchers_and_subscribers(include_only_watching: true).include?(user) end def set_watching(user, watching) watches.where(user: user).first_or_create.update!(watching: watching) end - def watchers_and_subscribers - User.where(id: bucket.subscribers.pluck(:id) + + def watchers_and_subscribers(include_only_watching: false) + involvements = include_only_watching ? [ :watching, :everything ] : :everything + subscribers = bucket.users.where(accesses: { involvement: involvements }) + + User.where(id: subscribers.pluck(:id) + watches.watching.pluck(:user_id) - watches.not_watching.pluck(:user_id)) end diff --git a/app/models/bucket.rb b/app/models/bucket.rb index 2fcf38e13..20a7f0336 100644 --- a/app/models/bucket.rb +++ b/app/models/bucket.rb @@ -1,5 +1,5 @@ class Bucket < ApplicationRecord - include Accessible, Broadcastable, Filterable, Subscribable + include Accessible, Broadcastable, Filterable belongs_to :account belongs_to :creator, class_name: "User", default: -> { Current.user } diff --git a/app/models/bucket/accessible.rb b/app/models/bucket/accessible.rb index 4b21c117b..3c72e9983 100644 --- a/app/models/bucket/accessible.rb +++ b/app/models/bucket/accessible.rb @@ -20,6 +20,7 @@ module Bucket::Accessible end has_many :users, through: :accesses + has_many :access_only_users, -> { merge(Access.access_only) }, through: :accesses, source: :user scope :all_access, -> { where(all_access: true) } @@ -27,6 +28,10 @@ module Bucket::Accessible after_save_commit :grant_access_to_everyone end + def access_for(user) + accesses.find_by(user: user) + end + private def grant_access_to_everyone accesses.grant_to(account.users) if all_access_previously_changed?(to: true) diff --git a/app/models/concerns/subscribable.rb b/app/models/concerns/subscribable.rb deleted file mode 100644 index 92cf92d1c..000000000 --- a/app/models/concerns/subscribable.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Subscribable - extend ActiveSupport::Concern - - TYPES = %w[ Bucket ] - - included do - has_many :subscriptions, as: :subscribable, dependent: :destroy - has_many :subscribers, through: :subscriptions, source: :user - end - - def subscribe(user) - subscriptions.create_or_find_by!(user: user) - end - - def unsubscribe(user) - subscriptions.find_by(user: user)&.destroy! - end - - def subscribed_by?(user) - subscriptions.exists?(user: user) - end -end diff --git a/app/models/notifier.rb b/app/models/notifier.rb index 10b1ba3c2..9c89d6bdf 100644 --- a/app/models/notifier.rb +++ b/app/models/notifier.rb @@ -27,7 +27,7 @@ class Notifier end def recipients - bubble.watchers.without(creator) + bubble.watchers_and_subscribers.without(creator) end def resource diff --git a/app/models/notifier/assigned.rb b/app/models/notifier/assigned.rb index 283011b2a..3027ab56c 100644 --- a/app/models/notifier/assigned.rb +++ b/app/models/notifier/assigned.rb @@ -1,6 +1,6 @@ class Notifier::Assigned < Notifier private def recipients - event.assignees + event.assignees.excluding(bubble.bucket.access_only_users) end end diff --git a/app/models/notifier/published.rb b/app/models/notifier/published.rb index d43b8c8e3..3aadc3c93 100644 --- a/app/models/notifier/published.rb +++ b/app/models/notifier/published.rb @@ -1,6 +1,6 @@ class Notifier::Published < Notifier private def recipients - bubble.watchers_and_subscribers.without(creator) + bubble.watchers_and_subscribers(include_only_watching: true).without(creator) end end diff --git a/app/models/subscription.rb b/app/models/subscription.rb deleted file mode 100644 index 1a6ed8ad7..000000000 --- a/app/models/subscription.rb +++ /dev/null @@ -1,5 +0,0 @@ -class Subscription < ApplicationRecord - belongs_to :user - - delegated_type :subscribable, types: Subscribable::TYPES, inverse_of: :subscription -end diff --git a/app/models/user.rb b/app/models/user.rb index 57a8e636c..54dca6b3b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,7 +9,6 @@ class User < ApplicationRecord has_many :accesses, dependent: :destroy has_many :buckets, through: :accesses has_many :accessible_bubbles, through: :buckets, source: :bubbles - has_many :subscriptions, dependent: :destroy has_many :filters, foreign_key: :creator_id, inverse_of: :creator, dependent: :destroy diff --git a/app/views/buckets/involvements/update.html.erb b/app/views/buckets/involvements/update.html.erb new file mode 100644 index 000000000..0acb52d76 --- /dev/null +++ b/app/views/buckets/involvements/update.html.erb @@ -0,0 +1,3 @@ +<%= turbo_frame_tag dom_id(@bucket, :involvement) do %> + <%= render "notifications/settings/bucket", bucket: @bucket, user: Current.user %> +<% end %> diff --git a/app/views/notifications/settings/_bucket.html.erb b/app/views/notifications/settings/_bucket.html.erb index a03384467..16839b0c3 100644 --- a/app/views/notifications/settings/_bucket.html.erb +++ b/app/views/notifications/settings/_bucket.html.erb @@ -1,18 +1,13 @@ -<%= turbo_frame_tag dom_id(bucket, :subscription) do %> - <%= form_with url: bucket_subscriptions_path(bucket), method: :put, data: { controller: "form"} do |form| %> -
-

- <%= bucket.name %> -

+<%= turbo_frame_tag dom_id(bucket, :involvement) do %> +
+

+ <%= bucket.name %> +

- + - -
- <% end %> + +
<% end %> diff --git a/config/routes.rb b/config/routes.rb index 4468079a5..0aacdba10 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -36,6 +36,7 @@ Rails.application.routes.draw do scope module: :buckets do resource :subscriptions resource :workflow, only: :update + resource :involvement end resources :bubbles do diff --git a/db/migrate/20250319092704_add_involvement_to_accesses.rb b/db/migrate/20250319092704_add_involvement_to_accesses.rb new file mode 100644 index 000000000..73c70a3a9 --- /dev/null +++ b/db/migrate/20250319092704_add_involvement_to_accesses.rb @@ -0,0 +1,7 @@ +class AddInvolvementToAccesses < ActiveRecord::Migration[8.1] + def change + change_table :accesses do |t| + t.string :involvement, null: false, default: "watching" + end + end +end diff --git a/db/migrate/20250319161627_drop_subscriptions.rb b/db/migrate/20250319161627_drop_subscriptions.rb new file mode 100644 index 000000000..9734075c8 --- /dev/null +++ b/db/migrate/20250319161627_drop_subscriptions.rb @@ -0,0 +1,14 @@ +class DropSubscriptions < ActiveRecord::Migration[8.1] + def change + execute " + update accesses set involvement = 'access_only' + " + execute " + update accesses set involvement = 'watching' + from (select user_id, subscribable_id as bucket_id from subscriptions) as subscriptions + where subscriptions.user_id = accesses.user_id and subscriptions.bucket_id = accesses.bucket_id + " + + drop_table :subscriptions + end +end diff --git a/db/schema.rb b/db/schema.rb index ef0105d28..68ace61c5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,10 +10,11 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2025_03_27_191456) do +ActiveRecord::Schema[8.1].define(version: 2025_04_03_094604) do create_table "accesses", force: :cascade do |t| t.integer "bucket_id", null: false t.datetime "created_at", null: false + t.string "involvement", default: "watching", null: false t.datetime "updated_at", null: false t.integer "user_id", null: false t.index ["bucket_id", "user_id"], name: "index_accesses_on_bucket_id_and_user_id", unique: true @@ -94,6 +95,13 @@ ActiveRecord::Schema[8.1].define(version: 2025_03_27_191456) do t.index ["bubble_id"], name: "index_assignments_on_bubble_id" end + create_table "bubble_engagements", force: :cascade do |t| + t.integer "bubble_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["bubble_id"], name: "index_bubble_engagements_on_bubble_id" + end + create_table "bubbles", force: :cascade do |t| t.float "activity_score", default: 0.0, null: false t.datetime "activity_score_at" @@ -222,6 +230,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_03_27_191456) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "user_id", null: false + t.index ["bubble_id", "user_id"], name: "index_pins_on_bubble_id_and_user_id", unique: true t.index ["bubble_id"], name: "index_pins_on_bubble_id" t.index ["user_id"], name: "index_pins_on_user_id" end @@ -254,17 +263,6 @@ ActiveRecord::Schema[8.1].define(version: 2025_03_27_191456) do t.index ["user_id"], name: "index_sessions_on_user_id" end - create_table "subscriptions", force: :cascade do |t| - t.datetime "created_at", null: false - t.integer "subscribable_id", null: false - t.string "subscribable_type", null: false - t.datetime "updated_at", null: false - t.integer "user_id", null: false - t.index ["subscribable_type", "subscribable_id", "user_id"], name: "idx_on_subscribable_type_subscribable_id_user_id_81936d569b", unique: true - t.index ["subscribable_type", "subscribable_id"], name: "index_subscriptions_on_subscribable" - t.index ["user_id"], name: "index_subscriptions_on_user_id" - end - create_table "taggings", force: :cascade do |t| t.integer "bubble_id", null: false t.datetime "created_at", null: false @@ -337,7 +335,6 @@ ActiveRecord::Schema[8.1].define(version: 2025_03_27_191456) do add_foreign_key "pops", "bubbles" add_foreign_key "pops", "users" add_foreign_key "sessions", "users" - add_foreign_key "subscriptions", "users" add_foreign_key "taggings", "bubbles" add_foreign_key "taggings", "tags" add_foreign_key "users", "accounts" diff --git a/db/schema_cache.yml b/db/schema_cache.yml index 40d083029..7e5d9cea3 100644 --- a/db/schema_cache.yml +++ b/db/schema_cache.yml @@ -21,7 +21,7 @@ columns: default_function: collation: comment: - - &21 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + - &23 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: bucket_id cast_type: *1 @@ -31,16 +31,6 @@ columns: default_function: collation: comment: - - &30 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: user_id - cast_type: *1 - sql_type_metadata: *2 - 'null': false - default: - default_function: - collation: - comment: - &6 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: created_at @@ -60,7 +50,7 @@ columns: default_function: collation: comment: - - &7 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + - &9 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: updated_at cast_type: *3 @@ -70,55 +60,9 @@ columns: default_function: collation: comment: - accounts: - - *5 - - &10 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + - &30 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: - name: name - cast_type: &8 !ruby/object:ActiveModel::Type::String - true: t - false: f - precision: - scale: - limit: - sql_type_metadata: &9 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata - sql_type: varchar - type: :string - limit: - precision: - scale: - 'null': false - default: - default_function: - collation: - comment: - - *6 - - *7 - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: join_code - cast_type: *8 - sql_type_metadata: *9 - 'null': true - default: - default_function: - collation: - comment: - action_text_markdowns: - - *5 - - &11 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: record_type - cast_type: *8 - sql_type_metadata: *9 - 'null': false - default: - default_function: - collation: - comment: - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: record_id + name: user_id cast_type: *1 sql_type_metadata: *2 'null': false @@ -126,7 +70,52 @@ columns: default_function: collation: comment: - - *10 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: involvement + cast_type: &7 !ruby/object:ActiveModel::Type::String + true: t + false: f + precision: + scale: + limit: + sql_type_metadata: &8 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: varchar + type: :string + limit: + precision: + scale: + 'null': false + default: watching + default_function: + collation: + comment: + accounts: + - *5 + - *6 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: join_code + cast_type: *7 + sql_type_metadata: *8 + 'null': true + default: + default_function: + collation: + comment: + - &10 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: name + cast_type: *7 + sql_type_metadata: *8 + 'null': false + default: + default_function: + collation: + comment: + - *9 + action_text_markdowns: + - *5 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: content @@ -148,21 +137,40 @@ columns: collation: comment: - *6 - - *7 - active_storage_attachments: - - *5 - *10 - - *11 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: record_id - cast_type: &12 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3Adapter::SQLite3Integer + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - &13 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: record_type + cast_type: *7 + sql_type_metadata: *8 + 'null': false + default: + default_function: + collation: + comment: + - *9 + active_storage_attachments: + - *5 + - &16 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: blob_id + cast_type: &11 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3Adapter::SQLite3Integer precision: scale: limit: max: 9223372036854775808 min: -9223372036854775808 - sql_type_metadata: &13 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type_metadata: &12 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata sql_type: bigint type: :integer limit: @@ -173,22 +181,24 @@ columns: default_function: collation: comment: - - &16 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + - *6 + - *10 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: - name: blob_id - cast_type: *12 - sql_type_metadata: *13 + name: record_id + cast_type: *11 + sql_type_metadata: *12 'null': false default: default_function: collation: comment: - - *6 + - *13 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: slug - cast_type: *8 - sql_type_metadata: *9 + cast_type: *7 + sql_type_metadata: *8 'null': true default: default_function: @@ -196,11 +206,11 @@ columns: comment: active_storage_blobs: - *5 - - &17 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: - name: key - cast_type: *8 - sql_type_metadata: *9 + name: byte_size + cast_type: *11 + sql_type_metadata: *12 'null': false default: default_function: @@ -208,10 +218,10 @@ columns: comment: - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: - name: filename - cast_type: *8 - sql_type_metadata: *9 - 'null': false + name: checksum + cast_type: *7 + sql_type_metadata: *8 + 'null': true default: default_function: collation: @@ -219,13 +229,34 @@ columns: - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: content_type - cast_type: *8 - sql_type_metadata: *9 + cast_type: *7 + sql_type_metadata: *8 'null': true default: default_function: collation: comment: + - *6 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: filename + cast_type: *7 + sql_type_metadata: *8 + 'null': false + default: + default_function: + collation: + comment: + - &17 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: key + cast_type: *7 + sql_type_metadata: *8 + 'null': false + default: + default_function: + collation: + comment: - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: metadata @@ -239,42 +270,21 @@ columns: - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: service_name - cast_type: *8 - sql_type_metadata: *9 + cast_type: *7 + sql_type_metadata: *8 'null': false default: default_function: collation: comment: - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: byte_size - cast_type: *12 - sql_type_metadata: *13 - 'null': false - default: - default_function: - collation: - comment: - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: checksum - cast_type: *8 - sql_type_metadata: *9 - 'null': true - default: - default_function: - collation: - comment: - - *6 active_storage_variant_records: - *5 - *16 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: variation_digest - cast_type: *8 - sql_type_metadata: *9 + cast_type: *7 + sql_type_metadata: *8 'null': false default: default_function: @@ -285,26 +295,16 @@ columns: - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: value - cast_type: *8 - sql_type_metadata: *9 + cast_type: *7 + sql_type_metadata: *8 'null': true default: default_function: collation: comment: - *6 - - *7 + - *9 assignees_filters: - - &18 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: filter_id - cast_type: *1 - sql_type_metadata: *2 - 'null': false - default: - default_function: - collation: - comment: - &19 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: assignee_id @@ -315,8 +315,17 @@ columns: default_function: collation: comment: + - &18 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: filter_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: assigners_filters: - - *18 - &20 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: assigner_id @@ -327,9 +336,11 @@ columns: default_function: collation: comment: + - *18 assignments: - *5 - *19 + - *20 - &25 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: bubble_id @@ -341,32 +352,103 @@ columns: collation: comment: - *6 - - *7 - - *20 + - *9 + bubble_engagements: + - *5 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: bubble_id + cast_type: *1 + sql_type_metadata: *2 + 'null': true + default: + default_function: + collation: + comment: + - *6 + - *9 bubbles: - *5 - - &32 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: - name: title - cast_type: *8 - sql_type_metadata: *9 + name: activity_score + cast_type: &21 !ruby/object:ActiveModel::Type::Float + precision: + scale: + limit: + sql_type_metadata: &22 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: float + type: :float + limit: + precision: + scale: + 'null': false + default: 0.0 + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: activity_score_at + cast_type: *3 + sql_type_metadata: *4 'null': true default: default_function: collation: comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: activity_score_order + cast_type: *21 + sql_type_metadata: *22 + 'null': false + default: 0.0 + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: auto_pop_at + cast_type: *3 + sql_type_metadata: *4 + 'null': false + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: boosts_count + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: 0 + default_function: + collation: + comment: + - *23 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: color - cast_type: *8 - sql_type_metadata: *9 + cast_type: *7 + sql_type_metadata: *8 'null': true default: default_function: collation: comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: comments_count + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: 0 + default_function: + collation: + comment: - *6 - - *7 - &24 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: creator_id @@ -396,17 +478,6 @@ columns: default_function: collation: comment: - - *21 - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: boosts_count - cast_type: *1 - sql_type_metadata: *2 - 'null': false - default: 0 - default_function: - collation: - comment: - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: stage_id @@ -417,34 +488,6 @@ columns: default_function: collation: comment: - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: comments_count - cast_type: *1 - sql_type_metadata: *2 - 'null': false - default: 0 - default_function: - collation: - comment: - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: activity_score - cast_type: &22 !ruby/object:ActiveModel::Type::Float - precision: - scale: - limit: - sql_type_metadata: &23 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata - sql_type: float - type: :float - limit: - precision: - scale: - 'null': false - default: 0.0 - default_function: - collation: - comment: - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: status @@ -455,39 +498,20 @@ columns: default_function: collation: comment: - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + - &33 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: - name: auto_pop_at - cast_type: *3 - sql_type_metadata: *4 - 'null': false - default: - default_function: - collation: - comment: - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: activity_score_at - cast_type: *3 - sql_type_metadata: *4 + name: title + cast_type: *7 + sql_type_metadata: *8 'null': true default: default_function: collation: comment: - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: activity_score_order - cast_type: *22 - sql_type_metadata: *23 - 'null': false - default: 0.0 - default_function: - collation: - comment: + - *9 buckets: - *5 - - &33 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + - &32 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: account_id cast_type: *1 @@ -497,10 +521,6 @@ columns: default_function: collation: comment: - - *24 - - *10 - - *6 - - *7 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: all_access @@ -519,6 +539,10 @@ columns: default_function: collation: comment: + - *6 + - *24 + - *10 + - *9 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: workflow_id @@ -530,23 +554,45 @@ columns: collation: comment: buckets_filters: + - *23 - *18 - - *21 comments: - *5 - - *24 - *6 - - *7 - creators_filters: - - *18 - *24 + - *9 + creators_filters: + - *24 + - *18 event_summaries: - *5 - *6 - - *7 + - *9 events: - *5 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: action + cast_type: *7 + sql_type_metadata: *8 + 'null': false + default: + default_function: + collation: + comment: + - *25 + - *6 - *24 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: due_date + cast_type: *26 + sql_type_metadata: *27 + 'null': true + default: + default_function: + collation: + comment: - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: particulars @@ -565,18 +611,6 @@ columns: default_function: collation: comment: - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: action - cast_type: *8 - sql_type_metadata: *9 - 'null': false - default: - default_function: - collation: - comment: - - *6 - - *7 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: summary_id @@ -587,32 +621,11 @@ columns: default_function: collation: comment: - - *25 - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: due_date - cast_type: *26 - sql_type_metadata: *27 - 'null': true - default: - default_function: - collation: - comment: + - *9 filters: - *5 - - *24 - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: params_digest - cast_type: *8 - sql_type_metadata: *9 - 'null': false - default: - default_function: - collation: - comment: - *6 - - *7 + - *24 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: fields @@ -623,6 +636,17 @@ columns: default_function: collation: comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: params_digest + cast_type: *7 + sql_type_metadata: *8 + 'null': false + default: + default_function: + collation: + comment: + - *9 filters_stages: - *18 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column @@ -650,16 +674,7 @@ columns: messages: - *5 - *25 - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: messageable_type - cast_type: *8 - sql_type_metadata: *9 - 'null': false - default: - default_function: - collation: - comment: + - *6 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: messageable_id @@ -670,11 +685,21 @@ columns: default_function: collation: comment: - - *6 - - *7 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: messageable_type + cast_type: *7 + sql_type_metadata: *8 + 'null': false + default: + default_function: + collation: + comment: + - *9 notifications: - *5 - - *30 + - *25 + - *6 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: event_id @@ -685,13 +710,12 @@ columns: default_function: collation: comment: - - *25 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: - name: resource_type - cast_type: *8 - sql_type_metadata: *9 - 'null': false + name: read_at + cast_type: *3 + sql_type_metadata: *4 + 'null': true default: default_function: collation: @@ -706,27 +730,29 @@ columns: default_function: collation: comment: - - *6 - - *7 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: - name: read_at - cast_type: *3 - sql_type_metadata: *4 - 'null': true + name: resource_type + cast_type: *7 + sql_type_metadata: *8 + 'null': false default: default_function: collation: comment: + - *9 + - *30 pins: - *5 - *25 - - *30 - *6 - - *7 + - *9 + - *30 pops: - *5 - *25 + - *6 + - *9 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: user_id @@ -737,8 +763,6 @@ columns: default_function: collation: comment: - - *6 - - *7 reactions: - *5 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column @@ -751,16 +775,6 @@ columns: default_function: collation: comment: - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: reacter_id - cast_type: *1 - sql_type_metadata: *2 - 'null': false - default: - default_function: - collation: - comment: - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: content @@ -782,58 +796,9 @@ columns: collation: comment: - *6 - - *7 - schema_migrations: - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: - name: version - cast_type: *8 - sql_type_metadata: *9 - 'null': false - default: - default_function: - collation: - comment: - sessions: - - *5 - - *30 - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: ip_address - cast_type: *8 - sql_type_metadata: *9 - 'null': true - default: - default_function: - collation: - comment: - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: user_agent - cast_type: *8 - sql_type_metadata: *9 - 'null': true - default: - default_function: - collation: - comment: - - *6 - - *7 - subscriptions: - - *5 - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: subscribable_type - cast_type: *8 - sql_type_metadata: *9 - 'null': false - default: - default_function: - collation: - comment: - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: subscribable_id + name: reacter_id cast_type: *1 sql_type_metadata: *2 'null': false @@ -841,45 +806,58 @@ columns: default_function: collation: comment: - - *30 + - *9 + schema_migrations: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: version + cast_type: *7 + sql_type_metadata: *8 + 'null': false + default: + default_function: + collation: + comment: + sessions: + - *5 - *6 - - *7 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: ip_address + cast_type: *7 + sql_type_metadata: *8 + 'null': true + default: + default_function: + collation: + comment: + - *9 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: user_agent + cast_type: *7 + sql_type_metadata: *8 + 'null': true + default: + default_function: + collation: + comment: + - *30 taggings: - *5 - *25 - - *31 - *6 - - *7 + - *31 + - *9 tags: - *5 - *32 - *6 - - *7 - *33 + - *9 users: - *5 - - *33 - - *10 - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: email_address - cast_type: *8 - sql_type_metadata: *9 - 'null': true - default: - default_function: - collation: - comment: - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: password_digest - cast_type: *8 - sql_type_metadata: *9 - 'null': true - default: - default_function: - collation: - comment: + - *32 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: active @@ -891,21 +869,44 @@ columns: collation: comment: - *6 - - *7 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: email_address + cast_type: *7 + sql_type_metadata: *8 + 'null': true + default: + default_function: + collation: + comment: + - *10 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: password_digest + cast_type: *7 + sql_type_metadata: *8 + 'null': true + default: + default_function: + collation: + comment: - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: role - cast_type: *8 - sql_type_metadata: *9 + cast_type: *7 + sql_type_metadata: *8 'null': false default: member default_function: collation: comment: + - *9 watches: - *5 - - *30 - *25 + - *6 + - *9 + - *30 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: watching @@ -916,10 +917,11 @@ columns: default_function: collation: comment: - - *6 - - *7 workflow_stages: - *5 + - *6 + - *10 + - *9 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: workflow_id @@ -930,15 +932,12 @@ columns: default_function: collation: comment: - - *10 - - *6 - - *7 workflows: - *5 - - *33 - - *10 + - *32 - *6 - - *7 + - *10 + - *9 primary_keys: accesses: id accounts: id @@ -950,6 +949,7 @@ primary_keys: assignees_filters: assigners_filters: assignments: id + bubble_engagements: id bubbles: id buckets: id buckets_filters: @@ -967,7 +967,6 @@ primary_keys: reactions: id schema_migrations: version sessions: id - subscriptions: id taggings: id tags: id users: id @@ -985,6 +984,7 @@ data_sources: assignees_filters: true assigners_filters: true assignments: true + bubble_engagements: true bubbles: true buckets: true buckets_filters: true @@ -1002,7 +1002,6 @@ data_sources: reactions: true schema_migrations: true sessions: true - subscriptions: true taggings: true tags: true users: true @@ -1299,6 +1298,23 @@ indexes: nulls_not_distinct: comment: valid: true + bubble_engagements: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: bubble_engagements + name: index_bubble_engagements_on_bubble_id + unique: false + columns: + - bubble_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true bubbles: - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition table: bubbles @@ -1768,6 +1784,23 @@ indexes: nulls_not_distinct: comment: valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: pins + name: index_pins_on_bubble_id_and_user_id + unique: true + columns: + - bubble_id + - user_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true pops: - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition table: pops @@ -1852,58 +1885,6 @@ indexes: nulls_not_distinct: comment: valid: true - subscriptions: - - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition - table: subscriptions - name: index_subscriptions_on_user_id - unique: false - columns: - - user_id - lengths: {} - orders: {} - opclasses: {} - where: - type: - using: - include: - nulls_not_distinct: - comment: - valid: true - - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition - table: subscriptions - name: index_subscriptions_on_subscribable - unique: false - columns: - - subscribable_type - - subscribable_id - lengths: {} - orders: {} - opclasses: {} - where: - type: - using: - include: - nulls_not_distinct: - comment: - valid: true - - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition - table: subscriptions - name: idx_on_subscribable_type_subscribable_id_user_id_81936d569b - unique: true - columns: - - subscribable_type - - subscribable_id - - user_id - lengths: {} - orders: {} - opclasses: {} - where: - type: - using: - include: - nulls_not_distinct: - comment: - valid: true taggings: - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition table: taggings @@ -2071,4 +2052,4 @@ indexes: nulls_not_distinct: comment: valid: true -version: 20250327191456 +version: 20250403094604 diff --git a/test/controllers/buckets/involvements_controller_test.rb b/test/controllers/buckets/involvements_controller_test.rb new file mode 100644 index 000000000..014b98f1e --- /dev/null +++ b/test/controllers/buckets/involvements_controller_test.rb @@ -0,0 +1,18 @@ +require "test_helper" + +class Buckets::InvolvementsControllerTest < ActionDispatch::IntegrationTest + setup do + sign_in_as :kevin + end + + test "update" do + bucket = buckets(:writebook) + bucket.access_for(users(:kevin)).access_only! + + assert_changes -> { bucket.access_for(users(:kevin)).involvement }, from: "access_only", to: "watching" do + put bucket_involvement_url(bucket, involvement: "watching") + end + + assert_response :success + end +end diff --git a/test/controllers/buckets/subscriptions_controller_test.rb b/test/controllers/buckets/subscriptions_controller_test.rb deleted file mode 100644 index ccd019145..000000000 --- a/test/controllers/buckets/subscriptions_controller_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -require "test_helper" - -class Buckets::SubscriptionsControllerTest < ActionDispatch::IntegrationTest - setup do - sign_in_as :david - end - - test "subscribing to a bucket" do - assert_changes -> { buckets(:writebook).subscribed_by?(users(:david)) }, from: false, to: true do - put bucket_subscriptions_url(buckets(:writebook)), params: { subscribed: "1" } - end - - assert_response :success - end - - test "unsubscribing from a bucket" do - buckets(:writebook).subscribe(users(:david)) - - assert_changes -> { buckets(:writebook).subscribed_by?(users(:david)) }, from: true, to: false do - put bucket_subscriptions_url(buckets(:writebook)) - end - - assert_response :success - end -end diff --git a/test/fixtures/subscriptions.yml b/test/fixtures/subscriptions.yml deleted file mode 100644 index 6ca23ecf3..000000000 --- a/test/fixtures/subscriptions.yml +++ /dev/null @@ -1,7 +0,0 @@ -writebook_kevin: - subscribable: writebook (Bucket) - user: kevin - -writebook_jz: - subscribable: writebook (Bucket) - user: jz diff --git a/test/models/bubble/commentable_test.rb b/test/models/bubble/commentable_test.rb index 43100006e..dc5f6903b 100644 --- a/test/models/bubble/commentable_test.rb +++ b/test/models/bubble/commentable_test.rb @@ -2,7 +2,7 @@ require "test_helper" class Bubble::CommentableTest < ActiveSupport::TestCase test "creating a comment on a bubble makes the creator watch the bubble" do - buckets(:writebook).subscriptions.destroy_all + buckets(:writebook).access_for(users(:kevin)).access_only! assert_not bubbles(:text).watched_by?(users(:kevin)) with_current_user(:kevin) do diff --git a/test/models/bubble/watchable_test.rb b/test/models/bubble/watchable_test.rb index 2f9c4a0f0..267bbf2d8 100644 --- a/test/models/bubble/watchable_test.rb +++ b/test/models/bubble/watchable_test.rb @@ -3,7 +3,7 @@ require "test_helper" class Bubble::WatchableTest < ActiveSupport::TestCase setup do Watch.destroy_all - Subscription.destroy_all + Access.all.update!(involvement: :access_only) end test "watched_by?" do @@ -16,9 +16,8 @@ class Bubble::WatchableTest < ActiveSupport::TestCase assert_not bubbles(:logo).watched_by?(users(:kevin)) end - test "watched_by? when subscribed to the bucket" do - buckets(:writebook).subscribe(users(:kevin)) - + test "watched_by? when notifications are set on the bucket" do + buckets(:writebook).access_for(users(:kevin)).watching! assert bubbles(:text).watched_by?(users(:kevin)) bubbles(:logo).set_watching(users(:kevin), false) @@ -26,16 +25,14 @@ class Bubble::WatchableTest < ActiveSupport::TestCase end test "bubbles are initially watched by their creator" do - buckets(:writebook).unsubscribe(users(:kevin)) - bubble = buckets(:writebook).bubbles.create!(creator: users(:kevin)) assert bubble.watched_by?(users(:kevin)) end test "watchers_and_subscribers" do - buckets(:writebook).subscribe(users(:kevin)) - buckets(:writebook).subscribe(users(:jz)) + buckets(:writebook).access_for(users(:kevin)).watching! + buckets(:writebook).access_for(users(:jz)).everything! bubbles(:logo).set_watching(users(:kevin), true) bubbles(:logo).set_watching(users(:jz), false) diff --git a/test/models/notifier_test.rb b/test/models/notifier_test.rb index df6faa127..49050b20a 100644 --- a/test/models/notifier_test.rb +++ b/test/models/notifier_test.rb @@ -28,6 +28,14 @@ class NotifierTest < ActiveSupport::TestCase assert_equal [ users(:kevin) ], notifications.map(&:user) end + test "does not create a notification for access-only users" do + buckets(:writebook).access_for(users(:kevin)).access_only! + + notifications = Notifier.for(events(:layout_commented)).generate + + assert_equal [ users(:kevin) ], notifications.map(&:user) + end + test "the published event creates notifications for subscribers as well as watchers" do notifications = Notifier.for(events(:logo_published)).generate @@ -40,9 +48,20 @@ class NotifierTest < ActiveSupport::TestCase assert_equal bubbles(:logo), Notification.last.resource end - test "for assignment events only create a notification for the assignee" do + test "assignment events only create a notification for the assignee" do + buckets(:writebook).access_for(users(:jz)).watching! + buckets(:writebook).access_for(users(:kevin)).everything! + notifications = Notifier.for(events(:logo_assignment_jz)).generate assert_equal [ users(:jz) ], notifications.map(&:user) end + + test "assignment events do not notify users who are access-only for the collection" do + buckets(:writebook).access_for(users(:jz)).access_only! + + notifications = Notifier.for(events(:logo_assignment_jz)).generate + + assert_empty notifications + end end