service_objects 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.metrics +5 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +2 -0
  6. data/.travis.yml +4 -0
  7. data/.yardopts +3 -0
  8. data/Gemfile +3 -0
  9. data/Guardfile +16 -0
  10. data/LICENSE +21 -0
  11. data/README.md +461 -0
  12. data/Rakefile +17 -0
  13. data/config/metrics/STYLEGUIDE +230 -0
  14. data/config/metrics/cane.yml +5 -0
  15. data/config/metrics/churn.yml +6 -0
  16. data/config/metrics/flay.yml +2 -0
  17. data/config/metrics/metric_fu.yml +14 -0
  18. data/config/metrics/pippi.yml +3 -0
  19. data/config/metrics/reek.yml +1 -0
  20. data/config/metrics/roodi.yml +24 -0
  21. data/config/metrics/rubocop.yml +75 -0
  22. data/config/metrics/saikuro.yml +3 -0
  23. data/config/metrics/simplecov.yml +6 -0
  24. data/config/metrics/yardstick.yml +37 -0
  25. data/lib/service_objects/base.rb +43 -0
  26. data/lib/service_objects/helpers/dependable.rb +63 -0
  27. data/lib/service_objects/helpers/exceptions.rb +64 -0
  28. data/lib/service_objects/helpers/messages.rb +96 -0
  29. data/lib/service_objects/helpers/parameterized.rb +85 -0
  30. data/lib/service_objects/helpers/parameters.rb +71 -0
  31. data/lib/service_objects/helpers/validations.rb +54 -0
  32. data/lib/service_objects/invalid.rb +55 -0
  33. data/lib/service_objects/listener.rb +97 -0
  34. data/lib/service_objects/message.rb +117 -0
  35. data/lib/service_objects/null.rb +26 -0
  36. data/lib/service_objects/utils/normal_hash.rb +34 -0
  37. data/lib/service_objects/version.rb +9 -0
  38. data/lib/service_objects.rb +12 -0
  39. data/service_objects.gemspec +28 -0
  40. data/spec/spec_helper.rb +15 -0
  41. data/spec/tests/base_spec.rb +43 -0
  42. data/spec/tests/helpers/dependable_spec.rb +77 -0
  43. data/spec/tests/helpers/exceptions_spec.rb +112 -0
  44. data/spec/tests/helpers/messages_spec.rb +64 -0
  45. data/spec/tests/helpers/parameterized_spec.rb +136 -0
  46. data/spec/tests/helpers/parameters_spec.rb +71 -0
  47. data/spec/tests/helpers/validations_spec.rb +60 -0
  48. data/spec/tests/invalid_spec.rb +69 -0
  49. data/spec/tests/listener_spec.rb +50 -0
  50. data/spec/tests/message_spec.rb +191 -0
  51. data/spec/tests/null_spec.rb +17 -0
  52. data/spec/tests/utils/normal_hash_spec.rb +16 -0
  53. metadata +182 -0
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+ require "wisper"
3
+
4
+ require "service_objects/helpers/messages"
5
+ require "service_objects/helpers/validations"
6
+ require "service_objects/helpers/exceptions"
7
+ require "service_objects/helpers/dependable"
8
+ require "service_objects/helpers/parameters"
9
+ require "service_objects/helpers/parameterized"
10
+
11
+ module ServiceObjects
12
+
13
+ # Base class for service objects
14
+ #
15
+ # @example
16
+ # AddItem = Class.new(ServiceObjects::Base)
17
+ #
18
+ # @see http://www.rubydoc.info/github/krisleech/wisper
19
+ # 'wisper' gem by Kris Leech
20
+ # @see http://apidock.com/rails/v4.1.8/ActiveModel/Validations
21
+ # ActiveModel::Validations
22
+ class Base
23
+
24
+ extend Helpers::Dependable
25
+ extend Helpers::Parameterized
26
+
27
+ include Helpers::Exceptions
28
+ include Helpers::Validations
29
+ include Wisper::Publisher
30
+ # @!parse include ServiceObjects::Helpers::Parameters
31
+ # @!parse include ServiceObjects::Helpers::Messages
32
+ # @!parse include ActiveModel::Validations
33
+
34
+ # @abstract
35
+ # Runs service object
36
+ #
37
+ # @return [undefined]
38
+ def run
39
+ end
40
+
41
+ end # class Base
42
+
43
+ end # module ServiceObjects
@@ -0,0 +1,63 @@
1
+ # encoding: utf-8
2
+
3
+ module ServiceObjects
4
+
5
+ module Helpers
6
+
7
+ # Features for service dependencies declaration
8
+ #
9
+ # @note
10
+ # A target class should be **extended** by the module
11
+ module Dependable
12
+
13
+ # Declares the dependency setter and getter
14
+ #
15
+ # @example
16
+ # class AddFoo
17
+ # extend ServiceObjects::Helpers::Dependable
18
+ #
19
+ # depends_on :get_item, default: GetItem
20
+ # end
21
+ #
22
+ # service = AddFoo.new
23
+ # service.get_item
24
+ # # => GetItem
25
+ #
26
+ # # Depencency injection
27
+ # service.get_item = FindItem
28
+ # service.get_item
29
+ # # => FindItem
30
+ #
31
+ # # Resetting to default
32
+ # service.get_item = nil
33
+ # service.get_item
34
+ # # => GetItem
35
+ #
36
+ # @example Set to NULL Object by default
37
+ # class AddFoo
38
+ # extend ServiceObjects::Helpers::Dependable
39
+ #
40
+ # depends_on :get_item
41
+ # end
42
+ #
43
+ # service = AddFoo.new
44
+ # service.get_item
45
+ # # => <ServiceObjects::NULL>
46
+ #
47
+ # @param [#to_sym] name
48
+ # the name for the dependency
49
+ # @param [BaseObject] default (ServiceObjects::NULL)
50
+ # default implementation for the dependency
51
+ #
52
+ # @return [:depends_on]
53
+ # the name of the method
54
+ def depends_on(name, default: NULL)
55
+ attr_writer name
56
+ define_method(name) { instance_eval("@#{ name }") || default }
57
+ end
58
+
59
+ end # Dependable
60
+
61
+ end # module Helpers
62
+
63
+ end # module ServiceObjects
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+
3
+ module ServiceObjects
4
+
5
+ module Helpers
6
+
7
+ # Features for escaping from runtime errors
8
+ #
9
+ # @note
10
+ # A target class should **include** the module
11
+ module Exceptions
12
+
13
+ # Re-raises standard errors as <tt>ServiceObjects::Invalid</tt>
14
+ #
15
+ # Mutates the current object by adding error messages
16
+ #
17
+ # @example
18
+ # class MyClass
19
+ # includes ServiceObjects::Helpers::Exceptions
20
+ # end
21
+ #
22
+ # begin
23
+ # MyClass.new.escape { fail StandardError.new "foo" }
24
+ # rescue => err
25
+ # puts err.class.name
26
+ # puts messages
27
+ # end
28
+ #
29
+ # # => ServiceObjects::Invalid
30
+ # # => [<ServiceObject::Message type="error" text="foo" ...>]
31
+ #
32
+ # @yield the block
33
+ #
34
+ # @raise [ServiceObjects::Invalid]
35
+ # if the block raises +StandardError+
36
+ #
37
+ # @return [Object] the value returned by the block
38
+ def escape
39
+ yield if block_given?
40
+ rescue => error
41
+ collect_messages_from error
42
+ raise Invalid.new(self)
43
+ end
44
+
45
+ private
46
+
47
+ # @!parse include ServiceObjects::Helpers::Messages
48
+ def self.included(klass)
49
+ klass.include Messages
50
+ end
51
+
52
+ def collect_messages_from(error)
53
+ if error.is_a? Invalid
54
+ messages.concat(error.messages) if error.object != self
55
+ else
56
+ add_message type: "error", text: error.message
57
+ end
58
+ end
59
+
60
+ end # module Exceptions
61
+
62
+ end # module Helpers
63
+
64
+ end # module ServiceObjects
@@ -0,0 +1,96 @@
1
+ # encoding: utf-8
2
+ require "extlib"
3
+
4
+ module ServiceObjects
5
+
6
+ # Contains helper objecs for the base service
7
+ module Helpers
8
+
9
+ # Features for collecting service messages
10
+ #
11
+ # @note
12
+ # A target class should **include** the module
13
+ module Messages
14
+
15
+ # Translates the text in the current scope with given options
16
+ #
17
+ # The method uses I18n.t library method.
18
+ #
19
+ # @example Returns a translation for symbolic argument
20
+ # class Test
21
+ # include ServiceObjects::Helpers::Messages
22
+ # end
23
+ #
24
+ # MyClass.new.translate :item
25
+ # # => translation not found: en.activemodel.messages.models.test.item
26
+ #
27
+ # @example Converts non-symbolic argument to a string.
28
+ # class Test
29
+ # include ServiceObjects::Helpers::Messages
30
+ # end
31
+ #
32
+ # MyClass.new.translate 1
33
+ # # => "1"
34
+ #
35
+ # @param [#to_s] text
36
+ # @param [Hash] options ({})
37
+ #
38
+ # @return [String]
39
+ def translate(text, options = {})
40
+ return text.to_s unless text.is_a? Symbol
41
+ I18n.t text, Utils::NormalHash.from(options).merge(scope: __scope__)
42
+ end
43
+
44
+ # A list of object messages
45
+ #
46
+ # @return [Array<ServiceObjects::Message>]
47
+ def messages
48
+ @messages ||= []
49
+ end
50
+
51
+ # Translates the text and adds a new message to the list of {#messages}
52
+ #
53
+ # @example
54
+ # class MyClass
55
+ # include ServiceObjects::Helpers::Messages
56
+ # end
57
+ #
58
+ # object = MyClass.new
59
+ # object.add_message type: "foo", text: "bar", priority: 2.5
60
+ # object.messages
61
+ # # => [<ServiceObjects::Message type="foo" text="bar" priority=2.5>]
62
+ #
63
+ # @param [Hash] options
64
+ # the list of options for the message and its translation
65
+ #
66
+ # @option options [#to_s] :text
67
+ # the text of a new message to be translated via {#translate} method
68
+ # @option options [#to_s] :type
69
+ # the type of a new message
70
+ # @option options [#to_f] :priority
71
+ # optional priority fo a new message
72
+ #
73
+ # @return [Array<ServiceObjects::Message>] The updated {#messages}
74
+ def add_message(options)
75
+ params = Utils::NormalHash.from(options)
76
+ type = params.delete :type
77
+ priority = params.delete :priority
78
+ text = translate params.delete(:text), params
79
+ messages << Message.new(text: text, type: type, priority: priority)
80
+ end
81
+
82
+ private
83
+
84
+ def __scope__
85
+ @__scope__ ||= [:activemodel, :messages, :models, __class_name__.to_sym]
86
+ end
87
+
88
+ def __class_name__
89
+ self.class.name.split("::").map(&:snake_case).join("/")
90
+ end
91
+
92
+ end # module Messages
93
+
94
+ end # module Helpers
95
+
96
+ end # module Services
@@ -0,0 +1,85 @@
1
+ # encoding: utf-8
2
+
3
+ module ServiceObjects
4
+
5
+ module Helpers
6
+
7
+ # Features for service parameters declaration
8
+ #
9
+ # @note
10
+ # A target class should be **extended** by the module
11
+ module Parameterized
12
+
13
+ # Whitelists {.new} options assigned to params
14
+ #
15
+ # Mutates the current class by adding the corresponding attribute
16
+ # for every parameter.
17
+ #
18
+ # @example Whitelists options
19
+ # class AddFoo
20
+ # extend ServiceObjects::Helpers::Parameterized
21
+ #
22
+ # allows_params :foo
23
+ # end
24
+ #
25
+ # service = AddFoo.new foo: "foo", bar: "baz"
26
+ # service.params # => { foo: "foo" }
27
+ #
28
+ # @example Adds attributes as aliases to corresponding params
29
+ # class AddFoo
30
+ # extend ServiceObjects::Helpers::Parameterized
31
+ #
32
+ # allows_params :foo
33
+ # end
34
+ #
35
+ # service = AddFoo.new foo: "foo"
36
+ # service.foo # => "foo"
37
+ #
38
+ # service.params[:foo] = "bar"
39
+ # service.foo # => "bar"
40
+ #
41
+ # service.foo = "baz"
42
+ # service.foo # => "baz"
43
+ # service.params[:foo] # => "baz"
44
+ #
45
+ # @param [Array<#to_sym>] list
46
+ #
47
+ # @return [Array<Symbol>]
48
+ # the list of arguments
49
+ def allows_params(*list)
50
+ @whitelist = list.flatten.map(&:to_sym).freeze
51
+ __attr_params__
52
+
53
+ whitelist
54
+ end
55
+
56
+ private
57
+
58
+ # @!parse include ServiceObjects::Helpers::Parameters
59
+ def self.extended(klass)
60
+ klass.include Parameters
61
+ super
62
+ end
63
+
64
+ def __attr_params__
65
+ whitelist.each(&method(:__attr_param_accessor__))
66
+ end
67
+
68
+ def __attr_param_accessor__(name)
69
+ __attr_param_reader__ name
70
+ __attr_param_writer__ name
71
+ end
72
+
73
+ def __attr_param_reader__(name)
74
+ define_method(name) { params[name] }
75
+ end
76
+
77
+ def __attr_param_writer__(name)
78
+ define_method("#{ name }=") { |value| params[name] = value }
79
+ end
80
+
81
+ end # module Parameters
82
+
83
+ end # module Helpers
84
+
85
+ end # module ServiceObjects
@@ -0,0 +1,71 @@
1
+ # encoding: utf-8
2
+
3
+ module ServiceObjects
4
+
5
+ module Helpers
6
+
7
+ # Features for whitelisting service options
8
+ #
9
+ # @note
10
+ # A target class should **include** the module
11
+ module Parameters
12
+
13
+ # @!scope class
14
+ # @!attribute [r] whitelist
15
+ # Returns the list of allowed parameters
16
+ #
17
+ # @return [Array<Symbol>]
18
+
19
+ # @!attribute [r] params
20
+ # Service object parameters
21
+ #
22
+ # @return [Hash]
23
+ attr_reader :params
24
+
25
+ # Service object initializer
26
+ #
27
+ # @example Normalizing options
28
+ # class AddFoo
29
+ # include ServiceObjects::Helpers::Parameters
30
+ #
31
+ # @whitelist = [:foo]
32
+ # end
33
+ #
34
+ # service = AddFoo.new("foo" => { "bar" => "baz" })
35
+ # service.params # => { foo: { bar: "baz" } }
36
+ #
37
+ # @example Whitelisting options
38
+ # class AddFoo
39
+ # include ServiceObjects::Helpers::Parameters
40
+ #
41
+ # @whitelist = [:foo]
42
+ # end
43
+ #
44
+ # service = AddFoo.new(foo: "bar", bar: "baz")
45
+ # service.params # => { foo: "bar" }
46
+ #
47
+ # @param [Hash] options
48
+ #
49
+ # @return [undefined]
50
+ def initialize(options = {})
51
+ @params = Utils::NormalHash.from(options).slice(*__whitelist__)
52
+ end
53
+
54
+ private
55
+
56
+ def self.included(klass)
57
+ klass
58
+ .singleton_class
59
+ .send(:define_method, :whitelist) { @whitelist ||= [] }
60
+ super
61
+ end
62
+
63
+ def __whitelist__
64
+ @whitelist ||= self.class.send :whitelist
65
+ end
66
+
67
+ end # module Parameters
68
+
69
+ end # module Helpers
70
+
71
+ end # module ServiceObjects
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+ require "active_model"
3
+
4
+ module ServiceObjects
5
+
6
+ module Helpers
7
+
8
+ # Features for service attributes validation
9
+ #
10
+ # @note
11
+ # A target class should **include** the module
12
+ #
13
+ # @see http://apidock.com/rails/v4.1.8/ActiveModel/Validations
14
+ # ActiveModel::Validations
15
+ module Validations
16
+
17
+ # @!method valid?
18
+ # Runs validations and checks if the object is valid
19
+ # @return [Boolean]
20
+
21
+ # Raises <tt>ServiceObjects::Invalid</tt> when {#valid?} method fails
22
+ #
23
+ # Mutates the current object by populating its messages
24
+ # with errors, added by {#valid?}
25
+ #
26
+ # @raise [ServiceObjects::Invalid]
27
+ # when a validation fails
28
+ #
29
+ # @return [self] (not changed)
30
+ # when a validation passes
31
+ def validate!
32
+ return self if valid?
33
+ __errors__.each { |text| add_message text: text, type: "error" }
34
+ fail Invalid.new(self)
35
+ end
36
+
37
+ private
38
+
39
+ # @!parse include ServiceObjects::Helpers::Messages
40
+ # @!parse include ActiveModel::Validations
41
+ def self.included(klass)
42
+ klass.include ActiveModel::Validations, Messages
43
+ super
44
+ end
45
+
46
+ def __errors__
47
+ errors.messages.values.flatten
48
+ end
49
+
50
+ end # module Validations
51
+
52
+ end # module Helpers
53
+
54
+ end # module ServiceObjects