lib/hexx/service.rb in hexx-2.2.0 vs lib/hexx/service.rb in hexx-3.0.0
- old
+ new
@@ -1,23 +1,271 @@
-# Service modules the +Service+ class depends on.
-def service_modules
- Dir[File.expand_path("../service/*.rb", __FILE__)]
-end
+# encoding: utf-8
-service_modules.each { |file| require file }
-
module Hexx
- # Base class for service objects.
+ # @abstract
+ # The base class for service objects.
+ #
+ # @example
+ # require "hexx"
+ # class GetItem < Hexx::Service
+ # allow_params :name
+ # def run
+ # publish :found, item = Item.where(name: name).first
+ # end
+ # end
+ #
+ # service = GetItem.new name: name
+ # service.subscribe listener, prefix: :on
+ # service.run
+ # # => This will call the listener's method #on_found(item).
class Service
- include Messages, Parameters, Transactions, Validations, Callbacks
+ include Wisper::Publisher
+ include ActiveModel::Validations
+ include Parameters
- # Runs the service object.
+ # @!scope class
+ # @!method new(options = {})
+ # Constructs the service object.
#
- # The method must be defined in a specific service class,
- # inherited from <tt>Hexx::Service</tt>.
+ # @example
+ # service = Hexx::Service.new name: name
#
+ # @param [Hash] options The options to be assigned to the {#params}.
+ # @return [Hexx::Service] The service object.
+
+ # @!scope class
+ # @!visibility private
+ # @!method allow_params(*params)
+ # Whitelists {#params} and declares a parameter for corresponding keys.
+ #
+ # @example (see Hexx::Service#params)
+ #
+ # @example Defines corresponding readonly attributes.
+ # class GetItem < Hexx::Service
+ # allow_params :name
+ # end
+ #
+ # service = GetItem.new name: "Олег", family: "Рюрикович"
+ # service.name # => "Олег"
+ #
+ # @param [Array<Symbol, String>] params The list of allowed keys.
+
+ # @!scope class
+ # @!method validates(attribute, options)
+ # Adds a standard validation for the attribute.
+ # @note The method is defined in the {ActiveModel::Validations} module.
+
+ # @!scope class
+ # @!method validate(method, options)
+ # Adds a custom validation (calls given method).
+ # @note The method is defined in the {ActiveModel::Validations} module.
+
+ # @!attribute params [r] The list of service object parameters.
+ # The attribute is assigned via the {.new} method options.
+ # The keys should be explicitly declared by the {.allow_params} helper.
+ #
+ # @example Only whitelisted params are being assigned.
+ # class GetItem < Hexx::Service
+ # allow_params :name
+ # end
+ #
+ # service = GetItem.new name: "Олег", family: "Рюрикович"
+ # service.params # => { "name" => "Олег" }
+
+ # @!method subscribe(listener, options = {})
+ # Subscribes the listener to service object's notifications.
+ # The <tt>:prefix</tt> sets the prefix to be added to a notification name
+ # to provide a corresponding listener method, that should be called by
+ # the publisher.
+ #
+ # @example (see Hexx::Service)
+ # @param [Object] listener The object that should receive notifications from
+ # the service object.
+ # @param [Hash] options The list of the subscription options.
+ # @option options [Symbol] :prefix The prefix for the listener's callbacks.
+
+ # @abstract
+ # Runs the service object.
+ # @note The method must be reloaded by a specific service class,
+ # inherited from <tt>Hexx::Service</tt>.
+ # @raise [NotImplementedError] if a child class hasn't redefined the method.
def run
fail NotImplementedError.new "#{ self.class.name }#run not implemented."
+ end
+
+ # Makes private methods with given prefix public.
+ #
+ # @example Opens private methods.
+ # def GetItem < Hexx::Service
+ # private
+ # def on_success
+ # publish :success
+ # end
+ # end
+ #
+ # service = GetItem.new
+ # service.respond_to? :on_success
+ # # => false
+ #
+ # service_with_callbacks = service.with_callbacks
+ # service_with_callbacks.respond_to? :on_success
+ # # => true
+ #
+ # @return [Hexx::Service::WithCallbacks<Hexx::Service>]
+ # The decorator that allows access to the service's private methods.
+ def with_callbacks(prefix: nil)
+ WithCallbacks.new(self, prefix: prefix)
+ end
+
+ private
+
+ # The helper runs another service object and subscribes +self+ for the
+ # service object's notifications.
+ #
+ # @example
+ # class AddItem < Hexx::Service
+ # allow_params :id
+ #
+ # # Runs a service for finding an item.
+ # # Service notifications to be received with a prefix :on_item
+ # def find_item
+ # run_service GetItem, :on_item, id: params["id"]
+ # end
+ #
+ # private
+ #
+ # attr_reader :item
+ #
+ # # Receives GetItem's :found notification
+ # def on_item_found(item)
+ # @item = item
+ # publish :found, item
+ # end
+ #
+ # # Receives GetItem's :not_found notification
+ # def on_item_not_found(*)
+ # # ... do some stuff here
+ # end
+ # end
+ #
+ # @param [Class] service_class The service class to instantiate and run
+ # a service object.
+ # @param [Symbol] prefix The prefix for callbacks to receive the service
+ # object's notifications.
+ # @param [Hash] options ({}) The options for the service object initializer.
+ # @raise [TypeError] when the service_class is not a <tt>Hexx::Service</tt>.
+ def run_service(service_class, prefix, options = {})
+ fail TypeError unless service_class.ancestors.include? Hexx::Service
+ service = service_class.new(options)
+ service.subscribe with_callbacks, prefix: prefix
+ service.run
+ end
+
+ # @!method escape
+ #
+ # The method rescues block runtime errors and publishes the :error
+ # notification.
+ #
+ # @example
+ # class GetItem < Hexx::Service
+ # def run
+ # escape do
+ # errors.add :base, :error
+ # fail Invalid.new self
+ # end
+ # end
+ # end
+ #
+ # service = GetItem.new
+ # service.subscribe listener
+ # service.run
+ # # => the listener will be sent the error(messages).
+ #
+ # @yield the block.
+ # @return the value returned by the block.
+ def escape
+ yield
+ rescue Service::Invalid => err
+ publish :error, Message.from(err.service)
+ rescue => err
+ publish :error, [Message.new(type: "error", text: err.message)]
+ end
+
+ # Translates given key in current service's scope.
+ #
+ # @note The method uses I18n.t library method.
+ #
+ # @example Returns a translation if the first argument is a symbol.
+ # class Test < Hexx::Service
+ # end
+ # service = Test.new
+ # service.t :name
+ # # => "translation not found: en.activemodel.messages.models.test.name"
+ #
+ # @example Returns the string argument.
+ # service = Hexx::Service.new
+ # service.t "name"
+ # # => "name"
+ #
+ # @param [Symbol, String] text The text to be translated.
+ # @param [Hash] options The translation options.
+ # @return [String] The translation.
+ def t(text, options = {})
+ return text unless text.is_a? Symbol
+ scope = %w(activemodel messages models) << self.class.name.underscore
+ I18n.t text, options.merge(scope: scope)
+ end
+
+ # The array of service messages, added by the {#add_message} helper.
+ #
+ # @example
+ # class Test < Hexx::Service
+ # def run
+ # add_message "info", :ok
+ # messages
+ # end
+ # end
+ # result = Test.new.run.first
+ # result.type
+ # # => "info"
+ # result.text
+ # # => "translation not found: en.activemodel.messages.models.test.ok"
+ #
+ # @return [Array<Hexx::Service::Message>] The array of message objects.
+ def messages
+ @messages ||= []
+ end
+
+ # Adds the translated message to the {#messages} array.
+ # @example (see Service#messages)
+ # @param [String] type The type of the message ("error", "info", "success")
+ # @param [String, Symbol] text The text of the message. The symbol will
+ # be translated using the {#t} method.
+ # @param [Hash] options The translation options.
+ # @return The updated {#messages} array.
+ def add_message(type, text, options = {})
+ messages << Message.new(type: type, text: t(text, options))
+ end
+
+ # Runs validations and fails if the service is invalid.
+ #
+ # @example (see Hexx::Service::Invalid)
+ #
+ # @example Safe usage (recommended) with the {#escape} wrapper.
+ # service GetItem < Hexx::Service
+ # allow_params :uuid
+ # validates :uuid, presence: true
+ # def run
+ # escape { validate! }
+ # end
+ # end
+ #
+ # service = GetItem.new
+ # service.run # => publishes :error notification
+ #
+ # @raise [Hexx::Service::Invalid] when the service object isn't valid.
+ def validate!
+ fail Invalid.new(self) unless valid?
end
end
end