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

Fix and refactor acts as favorable and acts as watchable #15985

Merged
merged 20 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
722eb28
fix copy pasting in OpenProject::Acts::Favorable::Registry
toy Jun 26, 2024
880c232
check for inclusion of right module for acts_as_favorable/acts_as_wat…
toy Jun 26, 2024
bde7c1b
find instance method module and acts_as_ method name dynamically
toy Jun 26, 2024
965b69f
remove unneeded namespaces and add to registry at the right point
toy Jun 26, 2024
b406e5e
just mention acts_as_favorable/acts_as_watchable models instead of ex…
toy Jun 26, 2024
2c2e907
use to_prepare instead of after_initialize in acts_as_favorable/acts_…
toy Jun 26, 2024
e4c15e2
no need to explicitly require parts of watchable, as lib_static is in…
toy Jun 26, 2024
48c8987
remove unused exists? methods of registries
toy Jun 26, 2024
e7810b7
remove unused models accessor from Acts::Watchable::Routes
toy Jun 26, 2024
eaf2b1b
extract RegistryMethods
toy Jun 26, 2024
8be0804
make models registry method private
toy Jun 26, 2024
a867b3a
rename Routes to RouteConstraint
toy Jun 26, 2024
83a2306
refactor spec for Acts::Watchable::RouteConstraint and copy for Acts:…
toy Jun 26, 2024
f9cf8b6
cleaner heredoc in acts_as_watchable shared spec
toy Jun 27, 2024
47ca977
simplify check for project association for acts_as_watchable
toy Jun 27, 2024
4b37ca8
store class names instead of classes in acts_as_ registries
toy Jun 27, 2024
87e9803
spec for RegistryMethods
toy Jun 27, 2024
f752884
bring back using registries explicitly for clarity
toy Jul 1, 2024
ddf0dc2
use hash mapping name to class instead of storing just names
toy Jul 1, 2024
73424a7
explicitly reset registries on reload
toy Jul 1, 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
10 changes: 6 additions & 4 deletions config/initializers/acts_as_favorable.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Be sure to restart your server when you modify this file.

# For development and non-eager load mode, we need to register the acts_as_favorable models manually
# For development and non-eager load mode, we need to load models using acts_as_favorable manually
# as no eager loading takes place
Rails.application.config.after_initialize do
OpenProject::Acts::Favorable::Registry
.add(Project)
Rails.application.config.to_prepare do
OpenProject::Acts::Favorable::Registry.add(
Project,
reset: true
)
end
16 changes: 12 additions & 4 deletions config/initializers/acts_as_watchable.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
# Be sure to restart your server when you modify this file.

# In development and non-eager loaded mode, we need to register the acts_as_watchable models manually
# For development and non-eager load mode, we need to load models using acts_as_watchable manually
# as no eager loading takes place
Rails.application.config.after_initialize do
OpenProject::Acts::Watchable::Registry
.add(WorkPackage, Message, Forum, News, Meeting, Wiki, WikiPage)
Rails.application.config.to_prepare do
OpenProject::Acts::Watchable::Registry.add(
Forum,
Meeting,
Message,
News,
Wiki,
WikiPage,
WorkPackage,
reset: true
)
end
4 changes: 2 additions & 2 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,13 @@
end

# generic route for adding/removing watchers
scope ":object_type/:object_id", constraints: OpenProject::Acts::Watchable::Routes do
scope ":object_type/:object_id", constraints: OpenProject::Acts::Watchable::RouteConstraint do
post "/watch" => "watchers#watch"
delete "/unwatch" => "watchers#unwatch"
end

# generic route for adding/removing favorites
scope ":object_type/:object_id", constraints: OpenProject::Acts::Favorable::Routes do
scope ":object_type/:object_id", constraints: OpenProject::Acts::Favorable::RouteConstraint do
post "/favorite" => "favorites#favorite"
delete "/favorite" => "favorites#unfavorite"
end
Expand Down
8 changes: 4 additions & 4 deletions lib_static/open_project/acts/favorable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ module ClassMethods
# acts_as_favorable expects that the including module defines a +visible?(user)+ method,
# as it's used to identify whether a user can actually favorite the object.
def acts_as_favorable
return if included_modules.include?(::OpenProject::Acts::Favorable::InstanceMethods)

OpenProject::Acts::Favorable::Registry.add(self)
return if included_modules.include?(InstanceMethods)

class_eval do
prepend InstanceMethods

has_many :favorites, as: :favored, dependent: :delete_all, validate: false
has_many :favoring_users, through: :favorites, source: :user, validate: false

Expand All @@ -60,7 +60,7 @@ def acts_as_favorable
}
end

send :prepend, ::OpenProject::Acts::Favorable::InstanceMethods
Registry.add(self)
end
end

Expand Down
22 changes: 1 addition & 21 deletions lib_static/open_project/acts/favorable/registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,7 @@ module OpenProject
module Acts
module Favorable
module Registry
def self.models
@models ||= Set.new
end

def self.exists?(model)
models.include?(model)
end

def self.instance(model_name)
models.detect { |cls| cls.name == model_name.singularize.camelize }
end

def self.add(*models)
models.each do |model|
unless model.ancestors.include?(::OpenProject::Acts::Watchable)
raise ArgumentError.new("Model #{model} does not include acts_as_watchable")
end

self.models << model
end
end
extend RegistryMethods
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
module OpenProject
module Acts
module Favorable
module Routes
module RouteConstraint
def self.matches?(request)
params = request.path_parameters

Expand Down
58 changes: 58 additions & 0 deletions lib_static/open_project/acts/registry_methods.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2024 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

module OpenProject
module Acts
module RegistryMethods
def instance(model_name)
models[model_name.singularize.camelize]
end

def add(*models, reset: false)
instance_methods_module = module_parent.const_get(:InstanceMethods)
acts_as_method_name = "acts_as_#{module_parent_name.demodulize.underscore}"

self.models.clear if reset

models.each do |model|
unless model.ancestors.include?(instance_methods_module)
raise ArgumentError.new("Model #{model} does not include #{acts_as_method_name}")
end

self.models[model.name] = model
end
end

private

def models
@models ||= Hash.new
end
end
end
end
27 changes: 12 additions & 15 deletions lib_static/open_project/acts/watchable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@
#
# See COPYRIGHT and LICENSE files for more details.
#++
require_relative "watchable/registry"
require_relative "watchable/routes"

module OpenProject
module Acts
Expand Down Expand Up @@ -54,11 +52,13 @@ module ClassMethods
# is allowed to watch

def acts_as_watchable(options = {})
return if included_modules.include?(::OpenProject::Acts::Watchable::InstanceMethods)
return if included_modules.include?(InstanceMethods)

acts_as_watchable_enforce_project_association

class_eval do
prepend InstanceMethods

has_many :watchers, as: :watchable, dependent: :delete_all, validate: false
has_many :watcher_users, through: :watchers, source: :user, validate: false

Expand All @@ -70,26 +70,23 @@ def acts_as_watchable(options = {})
class_attribute :acts_as_watchable_options

self.acts_as_watchable_options = options
::OpenProject::Acts::Watchable::Registry.add(self)
end

send :prepend, ::OpenProject::Acts::Watchable::InstanceMethods
Registry.add(self)
end

def acts_as_watchable_enforce_project_association
unless reflect_on_association(:project)
message = <<-MESSAGE
return if reflect_on_association(:project)

The #{self} model does not have an association to the Project model.
raise <<-MESSAGE
The #{self} model does not have an association to the Project model.

acts_as_watchable requires the including model to have such an association.
acts_as_watchable requires the including model to have such an association.

If no direct association exists, consider adding a
has_one :project, through: ...
association.
MESSAGE
raise message
end
If no direct association exists, consider adding a
has_one :project, through: ...
association.
MESSAGE
end
end

Expand Down
22 changes: 1 addition & 21 deletions lib_static/open_project/acts/watchable/registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,7 @@ module OpenProject
module Acts
module Watchable
module Registry
def self.models
@models ||= Set.new
end

def self.exists?(model)
models.include?(model)
end

def self.instance(model_name)
models.detect { |cls| cls.name == model_name.singularize.camelize }
end

def self.add(*models)
models.each do |model|
unless model.ancestors.include?(::OpenProject::Acts::Watchable)
raise ArgumentError.new("Model #{model} does not include acts_as_watchable")
end

self.models << model
end
end
extend RegistryMethods
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,11 @@
#
# See COPYRIGHT and LICENSE files for more details.
#++

module OpenProject
module Acts
module Watchable
module Routes
mattr_accessor :models

module RouteConstraint
def self.matches?(request)
params = request.path_parameters

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,39 +28,32 @@

require "spec_helper"

RSpec.describe OpenProject::Acts::Watchable::Routes do
let(:request) do
Struct.new(:type, :id) do
def path_parameters
{ object_id: id, object_type: type }
end
end.new(type, id)
end
RSpec.describe OpenProject::Acts::Favorable::RouteConstraint do
let(:request) { instance_double(ActionDispatch::Request, path_parameters:) }
let(:path_parameters) { { object_id: id, object_type: type } }

describe "matches?" do
shared_examples_for "watched model" do
describe "for a valid id string" do
let(:id) { "1" }

it "is true" do
expect(described_class).to be_matches(request)
end
end
%w[
projects
].each do |type|
describe "routing #{type} watches" do
let(:type) { type }

describe "for an invalid id string" do
let(:id) { "schmu" }
describe "for a valid id string" do
let(:id) { "1" }

it "is false" do
expect(described_class).not_to be_matches(request)
it "is true" do
expect(described_class).to be_matches(request)
end
end
end
end

["work_packages", "news", "forums", "messages", "wikis", "wiki_pages"].each do |type|
describe "routing #{type} watches" do
let(:type) { type }
describe "for an invalid id string" do
let(:id) { "schmu" }

it_behaves_like "watched model"
it "is false" do
expect(described_class).not_to be_matches(request)
end
end
end
end

Expand Down
Loading
Loading