service_objects 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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