lib/hexx/service.rb in hexx-6.0.3 vs lib/hexx/service.rb in hexx-7.0.0
- old
+ new
@@ -18,190 +18,310 @@
# service = GetItem.new name: name
# service.subscribe listener, prefix: :on
# service.run
# # => This will call the listener's method #on_found(item).
class Service
- extend Dependable
+
include Wisper::Publisher
- include ActiveModel::Validations
- # Class helper methods
- class << self
-
- private
-
- # @api hide
- # Returns the list of allowed parameters for service objects.
- #
- # The parameters are added to the list by the {.allow_params} private
- # helper method.
- #
- # @example
- # class Service < Hexx::Service
- # allow_params :name
- # end
- #
- # Service.params # => "name"
- #
- # @return [Array<String>] Whitelist of parameters.
- def params
- @params ||= []
- end
-
- # Sets a list of allowed parameters for the class constructor and
- # defines the corresponding instance attributes.
- #
- # @example (see Hexx::Service::Parameters.params)
- # @param [Array<Symbol, String>] keys The list of allowed parameters.
- def allow_params(*keys)
- @params = keys.flatten.map(&:to_s)
- fail ArgumentError if @params == []
- params.each { |name| Helpers::Parameter.add self, name }
- end
- end
-
- # @!scope class
- # @!method validates(attribute, options)
- # Adds a standard validation for the attribute.
- # @param [Symbol, String] attribute The name of the attribute to validate.
- # @param [Hash] options The list of validation options.
- # @see ActiveModel validations {APIdocs}[
- # http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates]
-
- # @!scope class
- # @!method validate(method, options)
- # Adds a custom validation (calls given method).
- # @param [Symbol, String] method The name of the validation method.
- # @param [Hash] options The list of validation options.
- # @see ActiveModel validations {APIdocs}[
- # http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validate]
-
- # @!scope class
- # @!method new(params = {})
- # Constructs the service object with given parameters.
+ # @!method subscribe(listener, options = {})
+ # @!visibility public
+ # Subscribes the listener to service object's notifications.
#
# @example (see Hexx::Service)
- # @param [Hash] params ({}) The parameters of the service object to be
- # assigned to the {#params} attribute.
- # @return [Hexx::Service] The service object.
+ # @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.
+ # It defines the prefix to be added to a notification name
+ # to provide a corresponding listener method, that should be called by
+ # the publisher.
+ include Helpers::Parameters
# @api hide
- # Initializes the service object.
- # @param (see Hexx::Service.new)
- # @return (see Hexx::Service.new)
- def initialize(params = {})
- @params = params.dup.stringify_keys.slice(*class_params)
- @messages = []
- end
+ private :params
+ private_class_method :allow_params
+ # @!scope class
+ # @!method allow_params(*names)
+ # @!visibility private
+ # Sets a list of allowed parameters for the class constructor and
+ # defines the corresponding instance attributes.
+ #
+ # @example
+ # class MyService < Hexx::Service
+ # allows_params :name
+ # end
+ #
+ # service = MyService.new name: "name", code: "code"
+ # service.send :name # => "name"
+ # service.send :params # => { "name" => "name" }
+ #
+ # @param [Symbol, String, Array<Symbol, String>] names
+ # The list of allowed parameters.
+
# @!attribute [r] params
+ # @!visibility private
# The list of service object parameters.
#
# The attribute is assigned via the {.new} method options.
# On initialization the parameters (keys) are stringified and whitelisted.
#
- # Allowed parameters should be explicitly declared with the {.allow_params}.
+ # Allowed parameters should be explicitly declared via the {.allow_params}
+ # private class helper.
#
- # @example Only whitelisted params are being assigned.
+ # @example
# class GetItem < Hexx::Service
# allow_params :name
# end
#
# service = GetItem.new name: "Олег", family: "Рюрикович"
# service.params # => { "name" => "Олег" }
+ #
# @return [Hash] the service object parameters.
+ include Helpers::Messages
+ # @api hide
+ public :messages
+ # @api hide
+ private :messages=, :add_message, :t
+
# @!attribute [r] messages
- # The array of service messages (instances of {Hexx::Service::Message})
+ # @!visibility public
+ # The array of service messages (instances of {Hexx::Message})
# with +text+ and +type+ attributes.
#
- # @example
- # class Test < Hexx::Service
+ # @note The attribute setter is private!
+ #
+ # @example The messages can be added by the {#add_message} private helper
+ # class EditItem < Hexx::Service
# def run
- # add_message "info", :ok
+ # # ...
+ # else
+ # add_message "success", "changed"
+ # publish :changed, messages
# end
# end
#
# service = Test.new
- # service.run # adds message
+ # service.run
# service.messages
- # # => [#<Hexx::Service::Message @text="ok" @type="info" >]
+ # # => [#<Hexx::Message @type="info" @text="some_text" >]
#
- # @return [Array<Hexx::Service::Message>] The array of messages.
+ # @return [Array<Hexx::Message>] The array of messages.
- attr_reader :messages
+ # @!scope instance
+ # @!method add_message(type, text)
+ # @!visibility private
+ # Adds the translated message to the {#messages} array.
+ #
+ # @example
+ # class Hello < Hexx::Service
+ # def run
+ # add_message "success", "Hello!"
+ # publish :hello, messages
+ # end
+ # end
+ #
+ # hello = Hello.new
+ # hello.subscribe listener
+ # hello.run
+ #
+ # # The listener.hello [#<Hexx::Message @type="success", @text="Hello!" >]
+ # # will be called.
+ #
+ # @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.
- # @!scope class
+ # @!scope instance
+ # @!method t(text, options = {})
# @!visibility private
- # @!method allow_params(*params)
- # Whitelists {#params} and declares a parameter for corresponding keys.
+ # Translates given key in current service's scope.
#
- # @example (see Hexx::Service#params)
+ # @note The method uses I18n.t library method.
#
- # @example Defines corresponding readonly attributes.
- # class GetItem < Hexx::Service
- # allow_params :name
+ # @example Returns a translation if the first argument is a symbol.
+ # class PrintHello < Hexx::Service
+ # def run
+ # puts t(:hello)
+ # end
# end
#
- # service = GetItem.new name: "Олег", family: "Рюрикович"
- # service.name # => "Олег"
+ # object = PrintHello.new
+ # object.run
+ # # => $ translation not found: en.activemodel.messages.models.test.name
#
- # @param [Array<Symbol, String>] params The list of allowed keys.
+ # @example Returns the string argument.
+ # class PrintHello < Hexx::Service
+ # def run
+ # puts t("hello")
+ # end
+ # end
+ #
+ # object = PrintHello.new
+ # object.run
+ # # => $ name
+ #
+ # @param [Symbol, String] text The text to be translated.
+ # @param [Hash] options ({}) The translation options.
+ # @return [String] The translation.
- # @!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.
+ include Helpers::Validations
+ # @api hide
+ private :validate!
+
+ # @!scope class
+ # @!method validates(attribute, options)
+ # @!visibility private
+ # Adds a standard validation for the attribute.
#
- # @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.
+ # @param [Symbol, String] attribute The name of the attribute to validate.
+ # @param [Hash] options The list of validation options.
+ # @see
+ # http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates
+ # ActiveModel validations APIdocs
- # @abstract
- # Runs the service object.
+ # @!scope class
+ # @!method validate(method, options)
+ # @!visibility private
+ # Adds a custom validation (calls given method).
#
- # The method does nothing. To be reloaded by a specific service class.
- def run
- end
+ # @param [Symbol, String] method The name of the validation method.
+ # @param [Hash] options The list of validation options.
+ # @see
+ # http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validate
+ # ActiveModel validations APIdocs
- # Makes private methods with given prefix public.
+ # @!scope instance
+ # @!method validate!
+ # @!visibility private
+ # Runs validations and raises <tt>Hexx::ServiceInvalid</tt>
+ # when a validation fails.
#
- # @example Opens private methods.
- # def GetItem < Hexx::Service
- # private
- # def on_success
- # publish :success
+ # @example (see Hexx::ServiceInvalid)
+ #
+ # @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.respond_to? :on_success
- # # => false
+ # service.run # => publishes :error notification
#
- # service_with_callbacks = service.with_callbacks
- # service_with_callbacks.respond_to? :on_success
- # # => true
+ # @raise [Hexx::ServiceInvalid] when the service object isn't valid.
+
+ include Helpers::Exceptions
+ # @api hide
+ private :escape, :on_error
+ private_class_method :raises
+
+ # @!scope class
+ # @!method raises(exceptions)
+ # @!visibility private
+ # Declares a list of specific +StandardError+ exceptions.
#
- # @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
+ # @example
+ # class Service < Hexx::Service
+ # raises :NotFound, :NotChanged
+ #
+ # def run
+ # run!
+ # rescue NotFound
+ # publish :not_found
+ # rescue NotChanged
+ # publish :not_changed
+ # rescue => err
+ # # works out any other (unspecified exceptions)
+ # publish :error
+ # else
+ # # works out the main scenario
+ # publish :success
+ # end
+ # end
+ #
+ # Service.const_defined? :NotFound # => true
+ # Service.const_defined? :NotChanged # => true
+ #
+ # exception = Service::NotFound.new
+ # exception.is_a? StandardError # => true
+ #
+ # @param [String, Symbol, Array<String, Symbol>] exceptions The list of
+ # specific +StandardError+ exceptions.
- private
+ # @!scope instance
+ # @!method escape
+ # @!visibility private
+ # The method re-raises +StandardError+ exceptions as a
+ # <tt>Hexx::ServiceInvalid</tt>.
+ #
+ # * rescues from a +StandardError+ exceptions
+ # * adds error message to the service
+ # * re-raises the <tt>Hexx::ServiceInvalid</tt> exception
+ #
+ # @example
+ # class GetItem < Hexx::Service
+ # def run
+ # escape { do_something_unsafe }
+ # rescue => err
+ # publish :error, err.messages
+ # end
+ # publish :success
+ # end
+ # end
+ #
+ # @yield the block.
+ # @raise [Hexx::ServiceInvalid] if the block raised the +StandardError+.
+ # @return the value returned by the block.
- attr_reader :params
+ # @!scope instance
+ # @!method on_error(messages)
+ # @!visibility private
+ # Raises the {Hexx::ServiceInvalid} exception, populated with given
+ # messages.
+ #
+ # @example
+ # class EditItem < Hexx::Service
+ #
+ # allow_params :id, :name
+ # # ...
+ #
+ # def find_item
+ # run_service GetItem, :on_item, id: id
+ # end
+ #
+ # def on_item_not_found(*, messages)
+ # on_error(messages) # Raises Hexx::ServiceInvalid
+ # end
+ # end
+ #
+ # @param [Array<Hexx::ServiceInvalid>] messages The list of error
+ # messages to be added to the exception.
+ # @raise [Hexx::ServiceInvalid] the exception.
- # @api hide
- # @return [Array<String>] Whitelist of parameters.
- def class_params
- self.class.send :params
+ # @!scope class
+ # @!method new(params = {})
+ # Constructs a service object with given parameters.
+ #
+ # @example (see Hexx::Service)
+ # @param [Hash] params ({}) The parameters of the service object to be
+ # assigned to the {#params} attribute.
+ # @return [Hexx::Service] The service object.
+
+ # @abstract
+ # Runs the service object.
+ def run
end
+ private
+
# The helper runs another service object and subscribes +self+ for the
# service object's notifications.
#
# @example
# class AddItem < Hexx::Service
@@ -238,100 +358,31 @@
service = service_class.new(options)
service.subscribe with_callbacks, prefix: prefix
service.run
end
- # @!method escape
+ # @api hide
+ # Makes private methods with given prefix public.
#
- # The method:
- # * rescues +StandardError+ exceptions
- # * adds error message to the service
- # * re-raises the <tt>Service::Invalid</tt> exception
- #
- # @example
- # class GetItem < Hexx::Service
- # def run
- # escape { do_something_unsafe }
- # rescue => err
- # publish :error, err.messages
- # end
+ # @example Opens private methods.
+ # def GetItem < Hexx::Service
+ # private
+ # def on_success
# publish :success
# end
# end
#
- # @yield the block.
- # @raise [Hexx::Service::Invalid] if the block raised the +StandardError+.
- # @return the value returned by the block.
- def escape
- yield
- rescue Invalid => err
- raise err
- rescue => err
- errors.add :base, err.message
- raise Invalid.new(self)
- 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
-
- attr_writer :messages
-
- # 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
+ # service.respond_to? :on_success
+ # # => false
#
- # @raise [Hexx::Service::Invalid] when the service object isn't valid.
- def validate!
- fail Invalid.new(self) unless valid?
- end
-
- def on_error(messages)
- messages.map(&:text).each { |text| errors.add :base, text }
- fail Invalid.new(self)
+ # 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
end
end