DEV: Drop WithServiceHelper

This patch removes the `with_service` helper from the code base.
Instead, we can pass a block with actions directly to the `.call` method
of a service.

This simplifies how to use services:
- use `.call` without a block to run the service and get its result
  object.
- use `.call` with a block of actions to run the service and execute
  arbitrary code depending on the service outcome.

It also means a service is now “self-contained” and can be used anywhere
without having to include a helper or whatever.
This commit is contained in:
Loïc Guitaut 2024-09-03 18:30:22 +02:00 committed by Loïc Guitaut
parent c76ff5c994
commit e94707acdf
39 changed files with 99 additions and 167 deletions

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::AdminController < ApplicationController class Admin::AdminController < ApplicationController
include WithServiceHelper
requires_login requires_login
before_action :ensure_admin before_action :ensure_admin

View File

@ -36,8 +36,8 @@ class Admin::Config::AboutController < Admin::AdminController
end end
settings_map.each do |name, value| settings_map.each do |name, value|
with_service( UpdateSiteSetting.call(
UpdateSiteSetting, guardian: guardian,
setting_name: name, setting_name: name,
new_value: value, new_value: value,
allow_changing_hidden: %i[ allow_changing_hidden: %i[

View File

@ -2,7 +2,7 @@
class Admin::Config::FlagsController < Admin::AdminController class Admin::Config::FlagsController < Admin::AdminController
def toggle def toggle
with_service(Flags::ToggleFlag) do Flags::ToggleFlag.call do
on_success do on_success do
Discourse.request_refresh! Discourse.request_refresh!
render(json: success_json) render(json: success_json)
@ -26,7 +26,7 @@ class Admin::Config::FlagsController < Admin::AdminController
end end
def create def create
with_service(Flags::CreateFlag) do Flags::CreateFlag.call do
on_success do on_success do
Discourse.request_refresh! Discourse.request_refresh!
render json: result.flag, serializer: FlagSerializer, used_flag_ids: Flag.used_flag_ids render json: result.flag, serializer: FlagSerializer, used_flag_ids: Flag.used_flag_ids
@ -40,7 +40,7 @@ class Admin::Config::FlagsController < Admin::AdminController
end end
def update def update
with_service(Flags::UpdateFlag) do Flags::UpdateFlag.call do
on_success do on_success do
Discourse.request_refresh! Discourse.request_refresh!
render json: result.flag, serializer: FlagSerializer, used_flag_ids: Flag.used_flag_ids render json: result.flag, serializer: FlagSerializer, used_flag_ids: Flag.used_flag_ids
@ -57,7 +57,7 @@ class Admin::Config::FlagsController < Admin::AdminController
end end
def reorder def reorder
with_service(Flags::ReorderFlag) do Flags::ReorderFlag.call do
on_success do on_success do
Discourse.request_refresh! Discourse.request_refresh!
render(json: success_json) render(json: success_json)
@ -73,7 +73,7 @@ class Admin::Config::FlagsController < Admin::AdminController
end end
def destroy def destroy
with_service(Flags::DestroyFlag) do Flags::DestroyFlag.call do
on_success do on_success do
Discourse.request_refresh! Discourse.request_refresh!
render(json: success_json) render(json: success_json)

View File

@ -39,18 +39,16 @@ class Admin::SiteSettingsController < Admin::AdminController
previous_value = value_or_default(SiteSetting.get(id)) if update_existing_users previous_value = value_or_default(SiteSetting.get(id)) if update_existing_users
with_service(UpdateSiteSetting, setting_name: id, new_value: value) do UpdateSiteSetting.call(setting_name: id, new_value: value) do
on_success do on_success do
value = result.new_value if update_existing_users
SiteSettingUpdateExistingUsers.call(id, value, previous_value) if update_existing_users SiteSettingUpdateExistingUsers.call(id, result.new_value, previous_value)
end
render body: nil render body: nil
end end
on_failed_policy(:setting_is_visible) do on_failed_policy(:setting_is_visible) do
raise Discourse::InvalidParameters, I18n.t("errors.site_settings.site_setting_is_hidden") raise Discourse::InvalidParameters, I18n.t("errors.site_settings.site_setting_is_hidden")
end end
on_failed_policy(:setting_is_configurable) do on_failed_policy(:setting_is_configurable) do
raise Discourse::InvalidParameters, raise Discourse::InvalidParameters,
I18n.t("errors.site_settings.site_setting_is_unconfigurable") I18n.t("errors.site_settings.site_setting_is_unconfigurable")

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::StaffController < ApplicationController class Admin::StaffController < ApplicationController
include WithServiceHelper
requires_login requires_login
before_action :ensure_staff before_action :ensure_staff
end end

View File

@ -119,7 +119,7 @@ class Admin::UsersController < Admin::StaffController
end end
def suspend def suspend
with_service(SuspendUser, user: @user) do SuspendUser.call(user: @user) do
on_success do on_success do
render_json_dump( render_json_dump(
suspension: { suspension: {
@ -131,9 +131,7 @@ class Admin::UsersController < Admin::StaffController
}, },
) )
end end
on_failed_policy(:can_suspend) { raise Discourse::InvalidAccess.new } on_failed_policy(:can_suspend) { raise Discourse::InvalidAccess.new }
on_failed_policy(:not_suspended_already) do on_failed_policy(:not_suspended_already) do
suspend_record = @user.suspend_record suspend_record = @user.suspend_record
message = message =
@ -149,7 +147,6 @@ class Admin::UsersController < Admin::StaffController
) )
render json: failed_json.merge(message: message), status: 409 render json: failed_json.merge(message: message), status: 409
end end
on_failed_contract do |contract| on_failed_contract do |contract|
render json: failed_json.merge(errors: contract.errors.full_messages), status: 400 render json: failed_json.merge(errors: contract.errors.full_messages), status: 400
end end
@ -328,7 +325,7 @@ class Admin::UsersController < Admin::StaffController
end end
def silence def silence
with_service(SilenceUser, user: @user) do SilenceUser.call(user: @user) do
on_success do on_success do
render_json_dump( render_json_dump(
silence: { silence: {
@ -340,9 +337,7 @@ class Admin::UsersController < Admin::StaffController
}, },
) )
end end
on_failed_policy(:can_silence) { raise Discourse::InvalidAccess.new } on_failed_policy(:can_silence) { raise Discourse::InvalidAccess.new }
on_failed_policy(:not_silenced_already) do on_failed_policy(:not_silenced_already) do
silenced_record = @user.silenced_record silenced_record = @user.silenced_record
message = message =
@ -358,7 +353,6 @@ class Admin::UsersController < Admin::StaffController
) )
render json: failed_json.merge(message: message), status: 409 render json: failed_json.merge(message: message), status: 409
end end
on_failed_contract do |contract| on_failed_contract do |contract|
render json: failed_json.merge(errors: contract.errors.full_messages), status: 400 render json: failed_json.merge(errors: contract.errors.full_messages), status: 400
end end

View File

@ -1,27 +0,0 @@
# frozen_string_literal: true
module WithServiceHelper
def result
@_result
end
# @param service [Class] A class including {Service::Base}
# @param dependencies [kwargs] Any additional params to load into the service context,
# in addition to controller @params.
def with_service(service, **dependencies, &block)
object = self
ServiceRunner.call(
service,
object,
**dependencies,
&proc { instance_exec(&(block || proc {})) }
)
end
def run_service(service, dependencies)
params = self.try(:params) || ActionController::Parameters.new
@_result =
service.call(params.to_unsafe_h.merge(guardian: self.try(:guardian) || nil, **dependencies))
end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class ServiceJob < ::Jobs::Base
include WithServiceHelper
def run_service(service, dependencies)
@_result = service.call(dependencies)
end
end

View File

@ -230,8 +230,9 @@ module Service
class_methods do class_methods do
include StepsHelpers include StepsHelpers
def call(context = {}) def call(context = {}, &actions)
new(context).tap(&:run).context return new(context).tap(&:run).context unless block_given?
ServiceRunner.call(self, context, &actions)
end end
def call!(context = {}) def call!(context = {})

View File

@ -2,11 +2,11 @@
# #
# = ServiceRunner # = ServiceRunner
# #
# This class is to be used via its helper +with_service+ in any class. Its # This class is automatically used when passing a block to the `.call` method
# main purpose is to ease how actions can be run upon a service completion. # of a service. Its main purpose is to ease how actions can be run upon a
# Since a service will likely return the same kind of things over and over, # service completion. Since a service will likely return the same kind of
# this allows us to not have to repeat the same boilerplate code in every # things over and over, this allows us to not have to repeat the same
# object. # boilerplate code in every object.
# #
# There are several available actions and we can add new ones very easily: # There are several available actions and we can add new ones very easily:
# #
@ -29,7 +29,7 @@
# #
# @example In a controller # @example In a controller
# def create # def create
# with_service MyService do # MyService.call do
# on_success do # on_success do
# flash[:notice] = "Success!" # flash[:notice] = "Success!"
# redirect_to a_path # redirect_to a_path
@ -39,19 +39,18 @@
# end # end
# end # end
# #
# @example In a job (inheriting from +ServiceJob+) # @example In a job
# def execute(args = {}) # def execute(*)
# with_service(MyService, **args) do # MyService.call(*) do
# on_success { Rails.logger.info "SUCCESS" } # on_success { Rails.logger.info "SUCCESS" }
# on_failure { Rails.logger.error "FAILURE" } # on_failure { Rails.logger.error "FAILURE" }
# end # end
# end # end
# #
# The actions will be evaluated in the order they appear. So even if the # The actions will be evaluated in the order they appear. So even if the
# service will ultimately fail with a failed policy, in this example only the # service ultimately fails with a failed policy, in this example only the
# +on_failed_policy+ action will be executed and not the +on_failure+ one. # +on_failed_policy+ action will be executed and not the +on_failure+ one. The
# The only exception to this being +on_failure+ as it will always be executed # only exception to this being +on_failure+ as it will always be executed last.
# last.
# #
class ServiceRunner class ServiceRunner
@ -101,7 +100,7 @@ class ServiceRunner
delegate :result, to: :object delegate :result, to: :object
# @!visibility private # @!visibility private
def initialize(service, object, **dependencies) def initialize(service, object, dependencies)
@service = service @service = service
@object = object @object = object
@dependencies = dependencies @dependencies = dependencies
@ -109,16 +108,17 @@ class ServiceRunner
end end
# @param service [Class] a class including {Service::Base} # @param service [Class] a class including {Service::Base}
# @param dependencies [Hash] dependencies to be provided to the service
# @param block [Proc] a block containing the steps to match on # @param block [Proc] a block containing the steps to match on
# @return [void] # @return [void]
def self.call(service, object, **dependencies, &block) def self.call(service, dependencies = {}, &block)
new(service, object, **dependencies).call(&block) new(service, block.binding.eval("self"), dependencies).call(&block)
end end
# @!visibility private # @!visibility private
def call(&block) def call(&block)
instance_eval(&block) instance_eval(&block)
object.run_service(service, dependencies) setup_and_run_service
# Always have `on_failure` as the last action # Always have `on_failure` as the last action
( (
actions actions
@ -132,8 +132,20 @@ class ServiceRunner
attr_reader :actions attr_reader :actions
def setup_and_run_service
runner = self
params = object.try(:params) || ActionController::Parameters.new
object.instance_eval do
def result = @_result
@_result =
runner.service.call(
params.to_unsafe_h.merge(guardian: try(:guardian), **runner.dependencies),
)
end
end
def failure_for?(key) def failure_for?(key)
object.result[key]&.failure? result[key]&.failure?
end end
def add_action(name, *args, &block) def add_action(name, *args, &block)

View File

@ -2,7 +2,7 @@
class Chat::Api::ChannelMessagesController < Chat::ApiController class Chat::Api::ChannelMessagesController < Chat::ApiController
def index def index
with_service(::Chat::ListChannelMessages) do ::Chat::ListChannelMessages.call do
on_success { render_serialized(result, ::Chat::MessagesSerializer, root: false) } on_success { render_serialized(result, ::Chat::MessagesSerializer, root: false) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_failed_policy(:can_view_channel) { raise Discourse::InvalidAccess } on_failed_policy(:can_view_channel) { raise Discourse::InvalidAccess }
@ -15,7 +15,7 @@ class Chat::Api::ChannelMessagesController < Chat::ApiController
end end
def destroy def destroy
with_service(Chat::TrashMessage) do Chat::TrashMessage.call do
on_success { render(json: success_json) } on_success { render(json: success_json) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_model_not_found(:message) { raise Discourse::NotFound } on_model_not_found(:message) { raise Discourse::NotFound }
@ -27,7 +27,7 @@ class Chat::Api::ChannelMessagesController < Chat::ApiController
end end
def bulk_destroy def bulk_destroy
with_service(Chat::TrashMessages) do Chat::TrashMessages.call do
on_success { render(json: success_json) } on_success { render(json: success_json) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_model_not_found(:messages) { raise Discourse::NotFound } on_model_not_found(:messages) { raise Discourse::NotFound }
@ -39,7 +39,7 @@ class Chat::Api::ChannelMessagesController < Chat::ApiController
end end
def restore def restore
with_service(Chat::RestoreMessage) do Chat::RestoreMessage.call do
on_success { render(json: success_json) } on_success { render(json: success_json) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_failed_policy(:invalid_access) { raise Discourse::InvalidAccess } on_failed_policy(:invalid_access) { raise Discourse::InvalidAccess }
@ -51,7 +51,7 @@ class Chat::Api::ChannelMessagesController < Chat::ApiController
end end
def update def update
with_service(Chat::UpdateMessage) do Chat::UpdateMessage.call do
on_success { render json: success_json.merge(message_id: result[:message].id) } on_success { render json: success_json.merge(message_id: result[:message].id) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_model_not_found(:message) { raise Discourse::NotFound } on_model_not_found(:message) { raise Discourse::NotFound }
@ -65,12 +65,10 @@ class Chat::Api::ChannelMessagesController < Chat::ApiController
end end
def create def create
# users can't force a thread through JSON API
params[:force_thread] = false
Chat::MessageRateLimiter.run!(current_user) Chat::MessageRateLimiter.run!(current_user)
with_service(Chat::CreateMessage) do # users can't force a thread through JSON API
Chat::CreateMessage.call(force_thread: false) do
on_success { render json: success_json.merge(message_id: result[:message_instance].id) } on_success { render json: success_json.merge(message_id: result[:message_instance].id) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_failed_policy(:no_silenced_user) { raise Discourse::InvalidAccess } on_failed_policy(:no_silenced_user) { raise Discourse::InvalidAccess }

View File

@ -2,7 +2,7 @@
class Chat::Api::ChannelThreadMessagesController < Chat::ApiController class Chat::Api::ChannelThreadMessagesController < Chat::ApiController
def index def index
with_service(::Chat::ListChannelThreadMessages) do ::Chat::ListChannelThreadMessages.call do
on_success do on_success do
render_serialized( render_serialized(
result, result,

View File

@ -2,7 +2,7 @@
class Chat::Api::ChannelThreadsController < Chat::ApiController class Chat::Api::ChannelThreadsController < Chat::ApiController
def index def index
with_service(::Chat::LookupChannelThreads) do ::Chat::LookupChannelThreads.call do
on_success do on_success do
render_serialized( render_serialized(
::Chat::ThreadsView.new( ::Chat::ThreadsView.new(
@ -30,7 +30,7 @@ class Chat::Api::ChannelThreadsController < Chat::ApiController
end end
def show def show
with_service(::Chat::LookupThread) do ::Chat::LookupThread.call do
on_success do on_success do
render_serialized( render_serialized(
result.thread, result.thread,
@ -53,7 +53,7 @@ class Chat::Api::ChannelThreadsController < Chat::ApiController
end end
def update def update
with_service(::Chat::UpdateThread) do ::Chat::UpdateThread.call do
on_failed_policy(:threading_enabled_for_channel) { raise Discourse::NotFound } on_failed_policy(:threading_enabled_for_channel) { raise Discourse::NotFound }
on_failed_policy(:can_view_channel) { raise Discourse::InvalidAccess } on_failed_policy(:can_view_channel) { raise Discourse::InvalidAccess }
on_failed_policy(:can_edit_thread) { raise Discourse::InvalidAccess } on_failed_policy(:can_edit_thread) { raise Discourse::InvalidAccess }
@ -70,7 +70,7 @@ class Chat::Api::ChannelThreadsController < Chat::ApiController
end end
def create def create
with_service(::Chat::CreateThread) do ::Chat::CreateThread.call do
on_success do on_success do
render_serialized( render_serialized(
result.thread, result.thread,

View File

@ -2,7 +2,7 @@
class Chat::Api::ChannelThreadsCurrentUserNotificationsSettingsController < Chat::ApiController class Chat::Api::ChannelThreadsCurrentUserNotificationsSettingsController < Chat::ApiController
def update def update
with_service(Chat::UpdateThreadNotificationSettings) do Chat::UpdateThreadNotificationSettings.call do
on_failed_policy(:threading_enabled_for_channel) { raise Discourse::NotFound } on_failed_policy(:threading_enabled_for_channel) { raise Discourse::NotFound }
on_failed_policy(:can_view_channel) { raise Discourse::InvalidAccess } on_failed_policy(:can_view_channel) { raise Discourse::InvalidAccess }
on_model_not_found(:thread) { raise Discourse::NotFound } on_model_not_found(:thread) { raise Discourse::NotFound }

View File

@ -2,7 +2,7 @@
class Chat::Api::ChannelThreadsCurrentUserTitlePromptSeenController < Chat::ApiController class Chat::Api::ChannelThreadsCurrentUserTitlePromptSeenController < Chat::ApiController
def update def update
with_service(Chat::MarkThreadTitlePromptSeen) do Chat::MarkThreadTitlePromptSeen.call do
on_failed_policy(:threading_enabled_for_channel) { raise Discourse::NotFound } on_failed_policy(:threading_enabled_for_channel) { raise Discourse::NotFound }
on_failed_policy(:can_view_channel) { raise Discourse::InvalidAccess } on_failed_policy(:can_view_channel) { raise Discourse::InvalidAccess }
on_model_not_found(:thread) { raise Discourse::NotFound } on_model_not_found(:thread) { raise Discourse::NotFound }

View File

@ -33,7 +33,7 @@ class Chat::Api::ChannelsController < Chat::ApiController
end end
def destroy def destroy
with_service Chat::TrashChannel do Chat::TrashChannel.call do
on_failed_policy(:invalid_access) { raise Discourse::InvalidAccess } on_failed_policy(:invalid_access) { raise Discourse::InvalidAccess }
on_model_not_found(:channel) { raise ActiveRecord::RecordNotFound } on_model_not_found(:channel) { raise ActiveRecord::RecordNotFound }
on_success { render(json: success_json) } on_success { render(json: success_json) }
@ -58,9 +58,8 @@ class Chat::Api::ChannelsController < Chat::ApiController
# NOTE: We don't allow creating channels for anything but category chatable types # NOTE: We don't allow creating channels for anything but category chatable types
# at the moment. This may change in future, at which point we will need to pass in # at the moment. This may change in future, at which point we will need to pass in
# a chatable_type param as well and switch to the correct service here. # a chatable_type param as well and switch to the correct service here.
with_service( Chat::CreateCategoryChannel.call(
Chat::CreateCategoryChannel, channel_params.merge(category_id: channel_params[:chatable_id]),
**channel_params.merge(category_id: channel_params[:chatable_id]),
) do ) do
on_success do on_success do
render_serialized( render_serialized(
@ -105,7 +104,7 @@ class Chat::Api::ChannelsController < Chat::ApiController
auto_join_limiter(channel_from_params).performed! auto_join_limiter(channel_from_params).performed!
end end
with_service(Chat::UpdateChannel, **params_to_edit) do Chat::UpdateChannel.call(params_to_edit) do
on_success do on_success do
render_serialized( render_serialized(
result.channel, result.channel,

View File

@ -12,7 +12,7 @@ class Chat::Api::ChannelsCurrentUserMembershipController < Chat::Api::ChannelsCo
end end
def destroy def destroy
with_service(Chat::LeaveChannel) do Chat::LeaveChannel.call do
on_success { render(json: success_json) } on_success { render(json: success_json) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_model_not_found(:channel) { raise Discourse::NotFound } on_model_not_found(:channel) { raise Discourse::NotFound }

View File

@ -2,7 +2,7 @@
class Chat::Api::ChannelsCurrentUserMembershipFollowsController < Chat::Api::ChannelsController class Chat::Api::ChannelsCurrentUserMembershipFollowsController < Chat::Api::ChannelsController
def destroy def destroy
with_service(Chat::UnfollowChannel) do Chat::UnfollowChannel.call do
on_success do on_success do
render_serialized( render_serialized(
result.membership, result.membership,

View File

@ -2,7 +2,7 @@
class Chat::Api::ChannelsDraftsController < Chat::ApiController class Chat::Api::ChannelsDraftsController < Chat::ApiController
def create def create
with_service(Chat::UpsertDraft) do Chat::UpsertDraft.call do
on_success { render(json: success_json) } on_success { render(json: success_json) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_model_not_found(:channel) { raise Discourse::NotFound } on_model_not_found(:channel) { raise Discourse::NotFound }

View File

@ -2,7 +2,7 @@
class Chat::Api::ChannelsInvitesController < Chat::ApiController class Chat::Api::ChannelsInvitesController < Chat::ApiController
def create def create
with_service(Chat::InviteUsersToChannel) do Chat::InviteUsersToChannel.call do
on_success { render(json: success_json) } on_success { render(json: success_json) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_failed_policy(:can_view_channel) { raise Discourse::InvalidAccess } on_failed_policy(:can_view_channel) { raise Discourse::InvalidAccess }

View File

@ -30,7 +30,7 @@ class Chat::Api::ChannelsMembershipsController < Chat::Api::ChannelsController
end end
def create def create
with_service(Chat::AddUsersToChannel) do Chat::AddUsersToChannel.call do
on_success { render(json: success_json) } on_success { render(json: success_json) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_failed_policy(:can_add_users_to_channel) do on_failed_policy(:can_add_users_to_channel) do

View File

@ -4,7 +4,7 @@ class Chat::Api::ChannelsMessagesFlagsController < Chat::ApiController
def create def create
RateLimiter.new(current_user, "flag_chat_message", 4, 1.minutes).performed! RateLimiter.new(current_user, "flag_chat_message", 4, 1.minutes).performed!
with_service(Chat::FlagMessage) do Chat::FlagMessage.call do
on_success { render(json: success_json) } on_success { render(json: success_json) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_model_not_found(:message) { raise Discourse::NotFound } on_model_not_found(:message) { raise Discourse::NotFound }

View File

@ -2,7 +2,7 @@
class Chat::Api::ChannelsMessagesStreamingController < Chat::Api::ChannelsController class Chat::Api::ChannelsMessagesStreamingController < Chat::Api::ChannelsController
def destroy def destroy
with_service(Chat::StopMessageStreaming) do Chat::StopMessageStreaming.call do
on_success { render(json: success_json) } on_success { render(json: success_json) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_model_not_found(:message) { raise Discourse::NotFound } on_model_not_found(:message) { raise Discourse::NotFound }

View File

@ -2,7 +2,7 @@
class Chat::Api::ChannelsReadController < Chat::ApiController class Chat::Api::ChannelsReadController < Chat::ApiController
def update def update
with_service(Chat::UpdateUserChannelLastRead) do Chat::UpdateUserChannelLastRead.call do
on_success { render(json: success_json) } on_success { render(json: success_json) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_failed_policy(:ensure_message_id_recency) do on_failed_policy(:ensure_message_id_recency) do
@ -19,7 +19,7 @@ class Chat::Api::ChannelsReadController < Chat::ApiController
end end
def update_all def update_all
with_service(Chat::MarkAllUserChannelsRead) do Chat::MarkAllUserChannelsRead.call do
on_success do on_success do
render(json: success_json.merge(updated_memberships: result.updated_memberships)) render(json: success_json.merge(updated_memberships: result.updated_memberships))
end end

View File

@ -2,7 +2,7 @@
class Chat::Api::ChannelsStatusController < Chat::Api::ChannelsController class Chat::Api::ChannelsStatusController < Chat::Api::ChannelsController
def update def update
with_service(Chat::UpdateChannelStatus) do Chat::UpdateChannelStatus.call do
on_success { render_serialized(result.channel, Chat::ChannelSerializer, root: "channel") } on_success { render_serialized(result.channel, Chat::ChannelSerializer, root: "channel") }
on_model_not_found(:channel) { raise ActiveRecord::RecordNotFound } on_model_not_found(:channel) { raise ActiveRecord::RecordNotFound }
on_failed_policy(:check_channel_permission) { raise Discourse::InvalidAccess } on_failed_policy(:check_channel_permission) { raise Discourse::InvalidAccess }

View File

@ -2,7 +2,7 @@
class Chat::Api::ChannelsThreadsDraftsController < Chat::ApiController class Chat::Api::ChannelsThreadsDraftsController < Chat::ApiController
def create def create
with_service(Chat::UpsertDraft) do Chat::UpsertDraft.call do
on_success { render(json: success_json) } on_success { render(json: success_json) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_model_not_found(:channel) { raise Discourse::NotFound } on_model_not_found(:channel) { raise Discourse::NotFound }

View File

@ -2,7 +2,7 @@
class Chat::Api::ChannelsThreadsReadController < Chat::ApiController class Chat::Api::ChannelsThreadsReadController < Chat::ApiController
def update def update
with_service(Chat::UpdateUserThreadLastRead) do Chat::UpdateUserThreadLastRead.call do
on_success { render(json: success_json) } on_success { render(json: success_json) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_model_not_found(:thread) { raise Discourse::NotFound } on_model_not_found(:thread) { raise Discourse::NotFound }

View File

@ -4,7 +4,7 @@ class Chat::Api::ChatablesController < Chat::ApiController
before_action :ensure_logged_in before_action :ensure_logged_in
def index def index
with_service(::Chat::SearchChatable) do ::Chat::SearchChatable.call do
on_success { render_serialized(result, ::Chat::ChatablesSerializer, root: false) } on_success { render_serialized(result, ::Chat::ChatablesSerializer, root: false) }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }
on_failed_contract do |contract| on_failed_contract do |contract|

View File

@ -2,7 +2,7 @@
class Chat::Api::CurrentUserChannelsController < Chat::ApiController class Chat::Api::CurrentUserChannelsController < Chat::ApiController
def index def index
with_service(Chat::ListUserChannels) do Chat::ListUserChannels.call do
on_success do on_success do
render_serialized( render_serialized(
result.structured, result.structured,

View File

@ -2,7 +2,7 @@
class Chat::Api::CurrentUserThreadsController < Chat::ApiController class Chat::Api::CurrentUserThreadsController < Chat::ApiController
def index def index
with_service(::Chat::LookupUserThreads) do ::Chat::LookupUserThreads.call do
on_success do on_success do
render_serialized( render_serialized(
::Chat::ThreadsView.new( ::Chat::ThreadsView.new(

View File

@ -2,7 +2,7 @@
class Chat::Api::DirectMessagesController < Chat::ApiController class Chat::Api::DirectMessagesController < Chat::ApiController
def create def create
with_service(Chat::CreateDirectMessageChannel) do Chat::CreateDirectMessageChannel.call do
on_success do on_success do
render_serialized( render_serialized(
result.channel, result.channel,

View File

@ -2,6 +2,5 @@
module Chat module Chat
class ApiController < ::Chat::BaseController class ApiController < ::Chat::BaseController
include WithServiceHelper
end end
end end

View File

@ -2,8 +2,6 @@
module Chat module Chat
class IncomingWebhooksController < ::ApplicationController class IncomingWebhooksController < ::ApplicationController
include WithServiceHelper
requires_plugin Chat::PLUGIN_NAME requires_plugin Chat::PLUGIN_NAME
WEBHOOK_MESSAGES_PER_MINUTE_LIMIT = 10 WEBHOOK_MESSAGES_PER_MINUTE_LIMIT = 10
@ -57,8 +55,7 @@ module Chat
webhook = find_and_rate_limit_webhook(key) webhook = find_and_rate_limit_webhook(key)
webhook.chat_channel.add(Discourse.system_user) webhook.chat_channel.add(Discourse.system_user)
with_service( Chat::CreateMessage.call(
Chat::CreateMessage,
chat_channel_id: webhook.chat_channel_id, chat_channel_id: webhook.chat_channel_id,
guardian: Discourse.system_user.guardian, guardian: Discourse.system_user.guardian,
message: text, message: text,

View File

@ -2,9 +2,9 @@
module Jobs module Jobs
module Chat module Chat
class AutoJoinChannelBatch < ServiceJob class AutoJoinChannelBatch < ::Jobs::Base
def execute(args) def execute(*)
with_service(::Chat::AutoJoinChannelBatch, **args) do ::Chat::AutoJoinChannelBatch.call(*) do
on_failure { Rails.logger.error("Failed with unexpected error") } on_failure { Rails.logger.error("Failed with unexpected error") }
on_failed_contract do |contract| on_failed_contract do |contract|
Rails.logger.error(contract.errors.full_messages.join(", ")) Rails.logger.error(contract.errors.full_messages.join(", "))

View File

@ -2,8 +2,6 @@
module ChatSDK module ChatSDK
class Channel class Channel
include WithServiceHelper
# Retrieves messages from a specified channel. # Retrieves messages from a specified channel.
# #
# @param channel_id [Integer] The ID of the chat channel from which to fetch messages. # @param channel_id [Integer] The ID of the chat channel from which to fetch messages.
@ -14,17 +12,11 @@ module ChatSDK
# ChatSDK::Channel.messages(channel_id: 1, guardian: Guardian.new) # ChatSDK::Channel.messages(channel_id: 1, guardian: Guardian.new)
# #
def self.messages(channel_id:, guardian:, **params) def self.messages(channel_id:, guardian:, **params)
new.messages(channel_id: channel_id, guardian: guardian, **params) new.messages(channel_id:, guardian:, **params)
end end
def messages(channel_id:, guardian:, **params) def messages(channel_id:, guardian:, **params)
with_service( Chat::ListChannelMessages.call(channel_id:, guardian:, **params, direction: "future") do
Chat::ListChannelMessages,
channel_id: channel_id,
guardian: guardian,
**params,
direction: "future",
) do
on_success { result.messages } on_success { result.messages }
on_failure { raise "Unexpected error" } on_failure { raise "Unexpected error" }
on_failed_policy(:can_view_channel) { raise "Guardian can't view channel" } on_failed_policy(:can_view_channel) { raise "Guardian can't view channel" }

View File

@ -2,8 +2,6 @@
module ChatSDK module ChatSDK
class Message class Message
include WithServiceHelper
# Creates a new message in a chat channel. # Creates a new message in a chat channel.
# #
# @param raw [String] The content of the message. # @param raw [String] The content of the message.
@ -91,7 +89,7 @@ module ChatSDK
end end
def stop_stream(message_id:, guardian:) def stop_stream(message_id:, guardian:)
with_service(Chat::StopMessageStreaming, message_id: message_id, guardian: guardian) do Chat::StopMessageStreaming.call(message_id:, guardian:) do
on_success { result.message } on_success { result.message }
on_model_not_found(:message) { raise "Couldn't find message with id: `#{message_id}`" } on_model_not_found(:message) { raise "Couldn't find message with id: `#{message_id}`" }
on_model_not_found(:membership) do on_model_not_found(:membership) do
@ -121,8 +119,7 @@ module ChatSDK
&block &block
) )
message = message =
with_service( Chat::CreateMessage.call(
Chat::CreateMessage,
message: raw, message: raw,
guardian: guardian, guardian: guardian,
chat_channel_id: channel_id, chat_channel_id: channel_id,
@ -165,8 +162,6 @@ module ChatSDK
end end
class StreamHelper class StreamHelper
include WithServiceHelper
attr_reader :message attr_reader :message
attr_reader :guardian attr_reader :guardian
@ -176,19 +171,17 @@ module ChatSDK
end end
def stream(raw: nil) def stream(raw: nil)
return false if !self.message.streaming return false if !message.streaming || !raw
return false if !raw
with_service( Chat::UpdateMessage.call(
Chat::UpdateMessage, message_id: message.id,
message_id: self.message.id, message: message.message + raw,
message: self.message.message + raw, guardian: guardian,
guardian: self.guardian,
streaming: true, streaming: true,
strip_whitespaces: false, strip_whitespaces: false,
) { on_failure { raise "Unexpected error" } } ) { on_failure { raise "Unexpected error" } }
self.message message
end end
end end
end end

View File

@ -2,8 +2,6 @@
module ChatSDK module ChatSDK
class Thread class Thread
include WithServiceHelper
# Updates the title of a specified chat thread. # Updates the title of a specified chat thread.
# #
# @param title [String] The new title for the chat thread. # @param title [String] The new title for the chat thread.
@ -76,13 +74,7 @@ module ChatSDK
end end
def messages(thread_id:, guardian:, direction: "future", **params) def messages(thread_id:, guardian:, direction: "future", **params)
with_service( Chat::ListChannelThreadMessages.call(thread_id:, guardian:, direction:, **params) do
Chat::ListChannelThreadMessages,
thread_id: thread_id,
guardian: guardian,
direction: direction,
**params,
) do
on_success { result.messages } on_success { result.messages }
on_failed_policy(:can_view_thread) { raise "Guardian can't view thread" } on_failed_policy(:can_view_thread) { raise "Guardian can't view thread" }
on_failed_policy(:target_message_exists) { raise "Target message doesn't exist" } on_failed_policy(:target_message_exists) { raise "Target message doesn't exist" }
@ -91,7 +83,7 @@ module ChatSDK
end end
def update(**params) def update(**params)
with_service(Chat::UpdateThread, **params) do Chat::UpdateThread.call(params) do
on_model_not_found(:channel) do on_model_not_found(:channel) do
raise "Couldn’t find channel with id: `#{params[:channel_id]}`" raise "Couldn’t find channel with id: `#{params[:channel_id]}`"
end end

View File

@ -148,8 +148,8 @@ RSpec.describe ServiceRunner do
end end
end end
describe ".call(service, &block)" do describe ".call" do
subject(:runner) { described_class.call(service, object, &actions_block) } subject(:runner) { described_class.call(service, &actions_block) }
let(:result) { object.result } let(:result) { object.result }
let(:actions_block) { object.instance_eval(actions) } let(:actions_block) { object.instance_eval(actions) }
@ -158,8 +158,6 @@ RSpec.describe ServiceRunner do
let(:object) do let(:object) do
Class Class
.new(ApplicationController) do .new(ApplicationController) do
include WithServiceHelper
def request def request
OpenStruct.new OpenStruct.new
end end
@ -450,7 +448,7 @@ RSpec.describe ServiceRunner do
end end
context "when running in the context of a job" do context "when running in the context of a job" do
let(:object) { Class.new(ServiceJob).new } let(:object) { Class.new(Jobs::Base).new }
let(:actions) { <<-BLOCK } let(:actions) { <<-BLOCK }
proc do proc do
on_success { :success } on_success { :success }

View File

@ -214,7 +214,6 @@ RSpec.configure do |config|
config.include BackupsHelpers config.include BackupsHelpers
config.include OneboxHelpers config.include OneboxHelpers
config.include FastImageHelpers config.include FastImageHelpers
config.include WithServiceHelper
config.include ServiceMatchers config.include ServiceMatchers
config.include I18nHelpers config.include I18nHelpers