DEV: Have contract take a block in services

Currently in services, the `contract` step is only used to define where
the contract will be called in the execution flow. Then, a `Contract`
class has to be defined with validations in it.

This patch allows the `contract` step to take a block containing
validations, attributes, etc. directly. No need to then open a
`Contract` class later in the service.

It also has a nice side effect, as it’s now easy to define multiples
contracts inside the same service. Before, we had the `class_name:`
option, but it wasn’t really useful as you had to redefine a complete
new contract class.
Now, when using a name for the contract other than `default`, a new
contract will be created automatically using the provided name.

Example:
```ruby
contract(:user) do
  attribute :user_id, :integer

  validates :user_id, presence: true
end
```
This will create a `UserContract` class and use it, also putting the
resulting contract in `context[:user_contract]`.
This commit is contained in:
Loïc Guitaut
2024-10-01 17:17:14 +02:00
committed by Loïc Guitaut
parent 76ad581f67
commit fc1c5f6a8d
45 changed files with 369 additions and 498 deletions

View File

@@ -3,28 +3,26 @@
class Flags::CreateFlag
include Service::Base
contract
policy :invalid_access
policy :unique_name
model :flag, :instantiate_flag
transaction do
step :create
step :log
end
class Contract
contract do
attribute :name, :string
attribute :description, :string
attribute :require_message, :boolean
attribute :enabled, :boolean
attribute :applies_to
validates :name, presence: true
validates :description, presence: true
validates :name, length: { maximum: Flag::MAX_NAME_LENGTH }
validates :description, length: { maximum: Flag::MAX_DESCRIPTION_LENGTH }
validates :applies_to, inclusion: { in: -> { Flag.valid_applies_to_types } }, allow_nil: false
end
policy :invalid_access
policy :unique_name
model :flag, :instantiate_flag
transaction do
step :create
step :log
end
private

View File

@@ -5,23 +5,21 @@ VALID_DIRECTIONS = %w[up down]
class Flags::ReorderFlag
include Service::Base
contract
contract do
attribute :flag_id, :integer
attribute :direction, :string
validates :flag_id, presence: true
validates :direction, inclusion: { in: VALID_DIRECTIONS }
end
model :flag
policy :invalid_access
policy :invalid_move
transaction do
step :move
step :log
end
class Contract
attribute :flag_id, :integer
attribute :direction, :string
validates :flag_id, presence: true
validates :direction, inclusion: { in: VALID_DIRECTIONS }
end
private
def fetch_flag(flag_id:)

View File

@@ -3,20 +3,18 @@
class Flags::ToggleFlag
include Service::Base
contract
contract do
attribute :flag_id, :integer
validates :flag_id, presence: true
end
model :flag
policy :invalid_access
transaction do
step :toggle
step :log
end
class Contract
attribute :flag_id, :integer
validates :flag_id, presence: true
end
private
def fetch_flag(flag_id:)

View File

@@ -3,30 +3,28 @@
class Flags::UpdateFlag
include Service::Base
contract
model :flag
policy :not_system
policy :not_used
policy :invalid_access
policy :unique_name
transaction do
step :update
step :log
end
class Contract
contract do
attribute :name, :string
attribute :description, :string
attribute :require_message, :boolean
attribute :enabled, :boolean
attribute :applies_to
validates :name, presence: true
validates :description, presence: true
validates :name, length: { maximum: Flag::MAX_NAME_LENGTH }
validates :description, length: { maximum: Flag::MAX_DESCRIPTION_LENGTH }
validates :applies_to, inclusion: { in: -> { Flag.valid_applies_to_types } }, allow_nil: false
end
model :flag
policy :not_system
policy :not_used
policy :invalid_access
policy :unique_name
transaction do
step :update
step :log
end
private

View File

@@ -4,20 +4,18 @@ class UpdateSiteSetting
include Service::Base
policy :current_user_is_admin
contract
step :convert_name_to_sym
policy :setting_is_visible
policy :setting_is_configurable
step :cleanup_value
step :save
class Contract
contract do
attribute :setting_name
attribute :new_value
attribute :allow_changing_hidden, :boolean, default: false
validates :setting_name, presence: true
end
step :convert_name_to_sym
policy :setting_is_visible
policy :setting_is_configurable
step :cleanup_value
step :save
private

View File

@@ -3,16 +3,7 @@
class User::Silence
include Service::Base
contract
model :user
policy :not_silenced_already, class_name: User::Policy::NotAlreadySilenced
model :users
policy :can_silence_all_users
step :silence
model :post, optional: true
step :perform_post_action
class Contract
contract do
attribute :user_id, :integer
attribute :reason, :string
attribute :message, :string
@@ -28,6 +19,13 @@ class User::Silence
validates :other_user_ids, length: { maximum: User::MAX_SIMILAR_USERS }
validates :post_action, inclusion: { in: %w[delete delete_replies edit] }, allow_blank: true
end
model :user
policy :not_silenced_already, class_name: User::Policy::NotAlreadySilenced
model :users
policy :can_silence_all_users
step :silence
model :post, optional: true
step :perform_post_action
private

View File

@@ -3,16 +3,7 @@
class User::Suspend
include Service::Base
contract
model :user
policy :not_suspended_already, class_name: User::Policy::NotAlreadySuspended
model :users
policy :can_suspend_all_users
step :suspend
model :post, optional: true
step :perform_post_action
class Contract
contract do
attribute :user_id, :integer
attribute :reason, :string
attribute :message, :string
@@ -28,6 +19,13 @@ class User::Suspend
validates :other_user_ids, length: { maximum: User::MAX_SIMILAR_USERS }
validates :post_action, inclusion: { in: %w[delete delete_replies edit] }, allow_blank: true
end
model :user
policy :not_suspended_already, class_name: User::Policy::NotAlreadySuspended
model :users
policy :can_suspend_all_users
step :suspend
model :post, optional: true
step :perform_post_action
private