Skip to content
Artem Kuzko edited this page Mar 24, 2021 · 7 revisions

Table of Contents

Usage

Instantiating command with arguments

Zen services are initialized with attributes. To specify list of available attributes, use attributes class method. All attributes are optional during service initialization. It is possible to omit keys during initialization, and pass attributes as parameters - in this case attributes will be filled in correspondance to the order they were defined. However, you cannot pass more attributes than declared attributes list, as well as cannot pass single attribute multiple times (as parameter and as named attribute) or attributes that were not declared with attributes class method.

class MyService < Zen::Service
  attributes :foo, :bar

  def execute!
    # do something
  end

  def foo
    super || 5
  end
end

s1 = MyService.new
s1.foo # => 5
s1.bar # => nil

s2 = MyService.new(6, bar: 2)
s2.foo # => 6
s2.bar # => 2

Arguments/Options overriding

To override attributes for an instantiated service, use #with_attributes method. This methods return an unexecuted copy of original service:

class MyService < Zen::Service
  attributes :foo, :bar
end

s1 = MyService.new(6, bar: 2)
s1.foo # => 6
s1.bar # => 2

s2 = s2.with_attributes(foo: 1, bar: 3)
s2.foo # => 1
s2.bar # => 3

Service Execution

The main purpose of every service object is to be executed. When service is executed, it calls execute! method which you should define, and returns self. Upon execution service has result and responds to success? method, which tells how did execution go. Usually, successful execution means presence of truthy result. Some usage examples are as following

  • Explicitly set execution result:
class Users::Create < Zen::Service
  attributes :params

  def execute!
    result { User.create(params }
    send_invitation_email if success?
  end
end

service = Users::Create.new(params).execute
command.result # => instance of User
command.success? # => true
  • When result method is not used, return value of execute! method is used as result. If the result has falsy value, execution will be considered as failure:
class Users::Update < Zen::Service
  attributes :user, :params

  delegate :errors, to: :user

  def execute!
    user.update(params)
  end
end

service = Users::Update.new(user, params: params).execute
service.result # => instance of User
service.success? # => true

other_service = Users::Update.new(user, params: invalid_params).execute
other_service.result # => false
other_service.success? # => false
  • set only execution state by success! and failure! methods with no result:
class MyService < Zen::Service
  def execute!
    success!
    do_something_that_returns_nil
  end
end

service = MyService.new.execute
command.success? # => true

Execution Delegation

You can use ~ method to delegate execution to another service, adopting it's execution state:

class Posts::Publish < Zen::Service
  attributes :post, :publisher

  def execute!
    post.update(published_by: publisher, published_at: Time.current)
  end
end

module Admin
  class Posts::Publish < Zen::Service
    use :context

    attributes :post

    delegate :current_admin, to: :context

    def execute!
      ~::Posts::Publish.(post, publisher: current_admin)
    end
  end
end

Core API

Instance Methods

  • execute(*) - executes a service and returns service itself, thus making possible expressions like if service.execute.success?. Internally, calls #execute! method. May be overloaded by plugins to provide additional options or modified behavior, but should always return command instance itself.

  • executed? - returns true if service has already been executed.

  • result - used as reader and writer. When called with no block returns current service result. When block is given, yields and assigns return value to result.

  • success? - returns true if service has been successfully executed.

  • failure? - returns !success?

  • success!(**opts) - sets execution status to "success", even if no result is present. Optional opts` can be used by plugins to provide additional behavior.

  • failure!(**opts) - same as success!, but sets execution state to "failure".

  • with_attributes(attributes) - returns an unexecuted copy of the command with attributes replaced by passed attributes

ClassMethods

  • .attributes(*attributes_list) - used to specify list of attributes that can be passed for service instantiation. Passing extra attributes will result in ArgumentError. Also defines corresponding reader methods.

  • .call(*attributes) - instantiates a service with attributes and executes it immediately:

service = MyService.(:foo, bar: :baz) # => is the same as:
# service = MyService.new(:foo, bar: :baz).execute
  • [](*args) - instantiates a service with args, executes it and returns it's execution result:
result = MyService[:foo, bar: :baz] # => is the same as
# result = MyService.new(:foo, bar: :baz).execute.result