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