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