service_objects 0.0.2 → 0.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 06222ac3c2f9261f41273db8c895a31223a3ef29
4
- data.tar.gz: 4e4ea02438691b0d12bdd91841caa3d96799bc7e
3
+ metadata.gz: e743a38993231e20c909e567e8f743b43527ea3f
4
+ data.tar.gz: 491a38180eccf6a4b8b884d23c6a9d9b84064ab4
5
5
  SHA512:
6
- metadata.gz: 1d780885f32cee67e8759d31a9dfd9851506bfe6294bac74dade103fba8fa90a56be2749cce93706ecb4a963bbf986950ef40abf6baebb2218a069917b6a7602
7
- data.tar.gz: e3356c69b601603fce0b240e4534c568fbe1739eacaca7c658c240829d1740a757f8956e8947bd4e46422220bcd915171d95a2549aac6f6a6518759caa4700f4
6
+ metadata.gz: fed24fa452deb99ef88a2a8325cc01fea50b91169578b1189c1634a8abcaab984f9d0594caa803feed6fbfb727a59028431b13d8d38791ad0a3d4e1c10154567
7
+ data.tar.gz: 20c5f8aa9fbca82761bd42df214cb95404c336f16dc3fce588b3d4396c589241b1444b5519d6b82e3280f62c211ab5f49785d7113c8a89a61bc38a4b2a3a83e5
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ *.gem
2
+ *.lock
3
+ .bundle/
4
+ .yardoc/
5
+ coverage/
6
+ doc/
7
+ log/
8
+ pkg/
9
+ tmp/
data/Gemfile CHANGED
@@ -2,4 +2,4 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- gem "hexx-suit", "~> 2.0", group: :metrics if RUBY_ENGINE == "ruby"
5
+ gem "hexx-suit", "~> 2.0", group: :metrics if RUBY_ENGINE == "ruby"
data/Guardfile CHANGED
@@ -4,6 +4,8 @@ guard :rspec, cmd: "bundle exec rspec" do
4
4
 
5
5
  watch("lib/service_objects.rb") { "spec" }
6
6
 
7
+ watch("lib/service_objects/cli/*.*") { "spec/tests/cli_spec.rb" }
8
+
7
9
  watch(%r{^lib/service_objects/(.+)\.rb$}) do |m|
8
10
  "spec/tests/#{ m[1] }_spec.rb"
9
11
  end
data/README.md CHANGED
@@ -433,6 +433,20 @@ service.send :add_message type: "excuse", text: :not_informed, subject: "issue"
433
433
  # => [<Message text="I haven't been informed on the issue" ...>]
434
434
  ```
435
435
 
436
+ ## Scaffolding
437
+
438
+ Use CLI command to scaffold a service object with its specification and translations:
439
+
440
+ ```
441
+ service new my_service
442
+ ```
443
+
444
+ To see available options run the command with `-h` option:
445
+
446
+ ```
447
+ service new -h
448
+ ```
449
+
436
450
  ## Compatibility
437
451
 
438
452
  Tested under MRI rubies >= 2.1
data/bin/service ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ require "service_objects/cli"
3
+
4
+ # Scaffolders runner from the command line
5
+ class CLI < Thor
6
+
7
+ register(
8
+ ServiceObjects::CLI,
9
+ "new",
10
+ "new NAME[ -d dependency{Default}][ -p option][ -n notification]" \
11
+ "[ -l en[ ru]][ -f folder]",
12
+ "Scaffolds a service object with a specification and translations."
13
+ )
14
+
15
+ end # class CLI
16
+
17
+ CLI.start
@@ -23,9 +23,7 @@ Style/ClassAndModuleChildren:
23
23
  - '**/*_spec.rb'
24
24
 
25
25
  Style/Documentation:
26
- Exclude:
27
- - '**/version.rb'
28
- - '**/*_spec.rb'
26
+ Enabled: false
29
27
 
30
28
  Style/EmptyLinesAroundBlockBody:
31
29
  Enabled: false
@@ -1,12 +1,14 @@
1
1
  # encoding: utf-8
2
- require "service_objects/null"
3
- require "service_objects/utils/normal_hash"
4
- require "service_objects/listener"
5
- require "service_objects/message"
6
- require "service_objects/invalid"
7
- require "service_objects/base"
8
2
 
9
3
  # The namespace for the 'service_objects' gem code
10
4
  module ServiceObjects
11
5
 
6
+ require_relative "service_objects/version"
7
+ require_relative "service_objects/null"
8
+ require_relative "service_objects/utils/normal_hash"
9
+ require_relative "service_objects/listener"
10
+ require_relative "service_objects/message"
11
+ require_relative "service_objects/invalid"
12
+ require_relative "service_objects/base"
13
+
12
14
  end # module ServiceObjects
@@ -1,15 +1,10 @@
1
1
  # encoding: utf-8
2
2
  require "wisper"
3
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
4
  module ServiceObjects
12
5
 
6
+ require_relative "helpers"
7
+
13
8
  # Base class for service objects
14
9
  #
15
10
  # @example
@@ -0,0 +1,117 @@
1
+ # encoding: utf-8
2
+ require "hexx-cli"
3
+
4
+ module ServiceObjects
5
+
6
+ require_relative "parsers"
7
+
8
+ # Scaffolds a service object with a specification and translations
9
+ class CLI < Hexx::CLI::Base
10
+
11
+ # @private
12
+ def self.source_root
13
+ ::File.expand_path "../cli", __FILE__
14
+ end
15
+
16
+ desc "Scaffolds a service object with a specification and translations."
17
+ namespace :new
18
+
19
+ argument(
20
+ :name,
21
+ banner: "NAME",
22
+ desc: "The name of the service",
23
+ type: :string
24
+ )
25
+
26
+ class_option(
27
+ :dependencies,
28
+ aliases: %w(-d),
29
+ banner: "name{value}",
30
+ default: %w(),
31
+ desc: "The list of dependencies for the service object",
32
+ type: :array
33
+ )
34
+
35
+ class_option(
36
+ :params,
37
+ aliases: %w(-p),
38
+ default: %w(),
39
+ desc: "The list of service object's parameters",
40
+ type: :array
41
+ )
42
+
43
+ class_option(
44
+ :notifications,
45
+ aliases: %w(-n),
46
+ default: %w(),
47
+ desc: "The list of notifications, published by a service",
48
+ type: :array
49
+ )
50
+
51
+ class_option(
52
+ :locales,
53
+ aliases: %w(-l),
54
+ default: %w(en ru),
55
+ desc: "The list of locales (languages) for message translations",
56
+ type: :array
57
+ )
58
+
59
+ class_option(
60
+ :folder,
61
+ aliases: %w(-f),
62
+ default: %w(services),
63
+ desc: "The folder namespace for the service",
64
+ type: :array
65
+ )
66
+
67
+ # @private
68
+ def add_specification
69
+ template "spec.erb", "spec/tests/#{ service.path }_spec.rb"
70
+ end
71
+
72
+ # @private
73
+ def add_service
74
+ template "service.erb", "app/#{ service.path }.rb"
75
+ end
76
+
77
+ # @private
78
+ def add_locales
79
+ locales.each do |lang|
80
+ @locale = lang
81
+ template "locale.erb", "config/locales/#{ service.path }/#{ lang }.yml"
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def service
88
+ @service ||= Hexx::CLI::Name.new [options[:folder], name].join("/")
89
+ end
90
+
91
+ def project
92
+ @project ||= Hexx::CLI::Name.new ::File.basename(destination_root)
93
+ end
94
+
95
+ def params
96
+ @params ||= options[:params].map(&:downcase)
97
+ end
98
+
99
+ def locales
100
+ @locales ||= options[:locales].map(&:downcase)
101
+ end
102
+
103
+ def notifications
104
+ @notifications ||=
105
+ options[:notifications]
106
+ .map(&Parsers::Notification.method(:new))
107
+ end
108
+
109
+ def dependencies
110
+ @dependencies ||=
111
+ options[:dependencies]
112
+ .map(&Parsers::Dependency.method(:new))
113
+ end
114
+
115
+ end # class CLI
116
+
117
+ end # module ServiceObjects
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+ # Translations for errors and other messages, generated by services of type:
3
+ # <%= service.type %>
4
+ ---
5
+ # <%= @locale %>:
6
+ # activemodel:
7
+ # errors:
8
+ # models:
9
+ # <%= project.path %>/<%= service.path %>:
10
+ # attributes:
11
+ # base:
12
+ <% params.sort.each do |item| -%>
13
+ # <%= item %>:
14
+ <% end -%>
15
+ # messages:
16
+ # models:
17
+ # <%= project.path %>/<%= service.path %>:
18
+ <% notifications.select(&:publish_messages?).each do |item| -%>
19
+ # <%= item.name %>: @todo
20
+ <% end -%>
@@ -0,0 +1,125 @@
1
+ # encoding: utf-8
2
+ <% tabs = 0 -%>
3
+ <% project.namespaces.each do |item| -%>
4
+
5
+ <%= " " * tabs %>module <%= item %>
6
+ <% tabs += 1 -%>
7
+ <% end -%>
8
+
9
+ <%= " " * tabs %>module <%= project.const %>
10
+ <% tabs += 1 -%>
11
+ <% service.namespaces.each do |item| -%>
12
+
13
+ <%= " " * tabs %>module <%= item %>
14
+ <% tabs += 1 -%>
15
+ <% end -%>
16
+
17
+ <%= " " * tabs %># @todo: describe what the service does
18
+ <% if dependencies.any? -%>
19
+ <%= " " * tabs %>#
20
+ <%= " " * tabs %># Uses other services:
21
+ <% dependencies.each do |item| -%>
22
+ <%= " " * tabs %># * <%= item.name %> (<%= item.type %>)
23
+ <%= " " * tabs %># @todo: describe what is it for
24
+ <% end -%>
25
+ <% end -%>
26
+ <%= " " * tabs %>#
27
+ <%= " " * tabs %># @example
28
+ <%= " " * tabs %># service = <%= service.type %>.new(
29
+ <% params.each do |item| -%>
30
+ <%= " " * tabs %># <%= item %>: @todo,
31
+ <% end -%>
32
+ <%= " " * tabs %># )
33
+ <%= " " * tabs %># service.subscribe listener
34
+ <%= " " * tabs %># service.run
35
+ <%= " " * tabs %>#
36
+ <%= " " * tabs %># @param [Hash] params
37
+ <% params.each do |item| -%>
38
+ <%= " " * tabs %># @option params [@todo] :<%= item %>
39
+ <%= " " * tabs %># @todo: describe the option
40
+ <% end -%>
41
+ <%= " " * tabs %>#
42
+ <% notifications.each do |item| -%>
43
+ <%= " " * tabs %># @publish <%= item.name %>(<%= item.args.join(", ") %>)
44
+ <%= " " * tabs %># @todo: describe the condition
45
+ <% end -%>
46
+ <%= " " * tabs %>class <%= service.const %> < ServiceObjects::Base
47
+ <% dependencies.each do |item| -%>
48
+
49
+ <%= " " * tabs %> # @!attribute <%= item.name %>
50
+ <%= " " * tabs %> # @todo: describe the dependency
51
+ <%= " " * tabs %> # @return [Class]
52
+ <%= " " * tabs %> depends_on <%= item.name %>, default: <%= item.type %>
53
+ <% end -%>
54
+ <% if params.any? -%>
55
+
56
+ <%= " " * tabs %> # Params definition
57
+ <%= " " * tabs %> allows_params <%= params.map { |item| ":" << item }.join(", ") %>
58
+ <% end -%>
59
+ <% if notifications.any? -%>
60
+
61
+ <%= " " * tabs %> # Internal exceptions
62
+ <% notifications.reject(&:error?).to_a[1..-1].to_a.each do |item| -%>
63
+ <%= " " * tabs %> <%= item.exception %> = Class.new Invalid
64
+ <% end -%>
65
+ <% end -%>
66
+
67
+ <%= " " * tabs %> # Runs the service and publishes its results
68
+ <%= " " * tabs %> #
69
+ <%= " " * tabs %> # @example (see <%= project.type %>::<%= service.type %>)
70
+ <%= " " * tabs %> #
71
+ <%= " " * tabs %> # @publish (see <%= project.type %>::<%= service.type %>)
72
+ <%= " " * tabs %> #
73
+ <%= " " * tabs %> # @return [undefined]
74
+ <%= " " * tabs %> def run
75
+ <%= " " * tabs %> run!
76
+ <% notifications.reject(&:error?).to_a[1..-1].to_a.each do |item| -%>
77
+ <%= " " * tabs %> rescue <%= item.exception %>
78
+ <%= " " * tabs %> publish :<%= item.name %>, <%= item.args.join(", ") %>
79
+ <% end -%>
80
+ <%= " " * tabs %> rescue Invalid => err
81
+ <%= " " * tabs %> publish :error, err.messages
82
+ <%= " " * tabs %> else
83
+ <% if (item = notifications.reject(&:error?).first) -%>
84
+ <%= " " * tabs %> publish :<%= item.name %>, <%= item.args.join(", ") %>
85
+ <% else -%>
86
+ <%= " " * tabs %> # publish :something
87
+ <% end -%>
88
+ <%= " " * tabs %> end
89
+
90
+ <%= " " * tabs %> private
91
+
92
+ <%= " " * tabs %> def run!
93
+ <%= " " * tabs %> end
94
+ <% notifications.flat_map(&:non_messages).uniq.each do |item| -%>
95
+
96
+ <%= " " * tabs %> def <%= item %>
97
+ <%= " " * tabs %> end
98
+ <% end -%>
99
+ <% dependencies.each do |item| -%>
100
+
101
+ <%= " " * tabs %> # The listener of notifications from {#<%= item.name %>}
102
+ <%= " " * tabs %> class <%= item.listener %> < ServiceObjects::Listener
103
+
104
+ <%= " " * tabs %> # Callback to be called when no other callback received
105
+ <%= " " * tabs %> # @return [undefined]
106
+ <%= " " * tabs %> def otherwise
107
+ <%= " " * tabs %> end
108
+
109
+ <%= " " * tabs %> end # class <%= item.listener %>
110
+ <% end -%>
111
+
112
+ <%= " " * tabs %>end # class <%= service.const %>
113
+ <% service.namespaces.reverse.each do |item| -%>
114
+
115
+ <% tabs -= 1 -%>
116
+ <%= " " * tabs -%>end # module <%= item %>
117
+ <% end -%>
118
+
119
+ <% tabs -= 1 -%>
120
+ <%= " " * tabs -%>end # module <%= project.const %>
121
+ <% project.namespaces.reverse.each do |item| -%>
122
+
123
+ <% tabs -= 1 -%>
124
+ <%= " " * tabs -%>end # module <%= item %>
125
+ <% end -%>
@@ -0,0 +1,87 @@
1
+ # encoding: utf-8
2
+ require "service_objects/rspec"
3
+
4
+ describe <%= project.type %>::<%= service.type %> do
5
+
6
+ # defines helpers and matcher for testing services:
7
+ #
8
+ # Helper arguments:
9
+ # * params ({} by default)
10
+ # * service
11
+ # * listener - it is subscribed for the service
12
+ #
13
+ # Helper methods:
14
+ # * service_double(*args) { |*args| ... }
15
+ #
16
+ # Matchers:
17
+ # * correspond_to(hash)
18
+ # * be_notified_on(notification, *args)
19
+ #
20
+ include ServiceObjects::RSpec
21
+ <% dependencies.each do |item| -%>
22
+
23
+ # describe "#<%= item.name %>" do
24
+
25
+ # it "is set to <%= item.type %>" do
26
+ # expect(subject.<%= item.name %>).to eq <%= item.type %>
27
+ # end
28
+
29
+ # it "is injectable" do
30
+ # injection = double
31
+ # expect { subject.<%= item.name %> = injection)
32
+ # .to change { subject.<%= item.name %> }.to injection
33
+ # end
34
+
35
+ # end # describe #<%= item.name %>
36
+ <% end -%>
37
+
38
+ describe "#run" do
39
+ <% dependencies.each do |item| -%>
40
+
41
+ # shared_context "#<%= item.name %> publishes @todo" do |options|
42
+
43
+ # before do
44
+ # injection = service_double(@todo) { publish @todo }
45
+ # inject :<%= item.name %>, with: options, constucts: injection
46
+ # end
47
+
48
+ # end # shared_context
49
+ <% end -%>
50
+ <% notifications.each do |item| -%>
51
+
52
+ # shared_examples "@todo" do
53
+
54
+ # it "[@todo]" do
55
+ # expect { service.run }
56
+ # .to change { @todo }
57
+ # .from(@todo)
58
+ # .to(@todo)
59
+ # end
60
+
61
+ # it "publishes :<%= item.name %>" do
62
+ # expect(listener)
63
+ # .to receive(:<%= item.name %>) do |<%= item.args.join(", ") %>|
64
+ <% item.args.each do |arg| -%>
65
+ # expect(<%= arg %>).to eq @todo
66
+ <% end -%>
67
+ # end
68
+ # service.run
69
+ # end
70
+
71
+ # end
72
+ <% end -%>
73
+
74
+ # context "when @todo" do
75
+
76
+ # before { params.merge! "@todo" => @todo }
77
+ <% dependencies.each do |item| -%>
78
+ # include_context "#<%= item.name %> publishes @todo", @todo
79
+ <% end -%>
80
+
81
+ # it_behaves_like "@todo"
82
+
83
+ # end
84
+
85
+ end
86
+
87
+ end # describe <%= project.type %>::<%= service.type %>
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ module ServiceObjects
4
+
5
+ # Contains helper objecs for the base service
6
+ module Helpers
7
+
8
+ require_relative "helpers/messages"
9
+ require_relative "helpers/validations"
10
+ require_relative "helpers/exceptions"
11
+ require_relative "helpers/dependable"
12
+ require_relative "helpers/parameters"
13
+ require_relative "helpers/parameterized"
14
+
15
+ end # module Helpers
16
+
17
+ end # module ServiceObjects
@@ -3,7 +3,6 @@ require "extlib"
3
3
 
4
4
  module ServiceObjects
5
5
 
6
- # Contains helper objecs for the base service
7
6
  module Helpers
8
7
 
9
8
  # Features for collecting service messages
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ module ServiceObjects
4
+
5
+ # Collection of CLI arguments parsers
6
+ module Parsers
7
+
8
+ require_relative "parsers/dependency"
9
+ require_relative "parsers/notification"
10
+
11
+ end # module ServiceObjects
12
+
13
+ end # module Parsers
@@ -0,0 +1,69 @@
1
+ # encoding: utf-8
2
+ require "hexx-cli"
3
+
4
+ module ServiceObjects
5
+
6
+ module Parsers
7
+
8
+ # Parses and decorates a string, describing a notification
9
+ #
10
+ # @example
11
+ # result = ServiceObjects::Parsers::Dependency.new "add_item{get_item}"
12
+ # result.name # => add_item
13
+ # result.type # => GetItem
14
+ # result.listener # => AddItemListener
15
+ class Dependency
16
+
17
+ # @!scope class
18
+ # @!method new(source)
19
+ # Constructs the object from source
20
+ #
21
+ # @param [#to_s] source
22
+ #
23
+ # @return [Hexx::Services::Parsers::Dependency]
24
+ def initialize(source)
25
+ @source = source.to_s
26
+ end
27
+
28
+ # The name for the dependency default implementation
29
+ #
30
+ # @return [String]
31
+ def name
32
+ @name ||= injector.item
33
+ end
34
+
35
+ # The type for the dependency default implementation
36
+ #
37
+ # @return [String]
38
+ def type
39
+ @type ||= injection.type
40
+ end
41
+
42
+ # The listener name for the dependency
43
+ #
44
+ # @return [String]
45
+ def listener
46
+ @listener ||= "#{ injector.const }Listener"
47
+ end
48
+
49
+ private
50
+
51
+ attr_reader :source
52
+
53
+ def matcher
54
+ @matcher ||= source.match(/^(.+){(.+)}$/)
55
+ end
56
+
57
+ def injector
58
+ @injector ||= Hexx::CLI::Name.new matcher[1]
59
+ end
60
+
61
+ def injection
62
+ @injection ||= Hexx::CLI::Name.new matcher[2]
63
+ end
64
+
65
+ end # class Dependency
66
+
67
+ end # module Parsers
68
+
69
+ end # module ServiceObjects
@@ -0,0 +1,85 @@
1
+ # encoding: utf-8
2
+ require "extlib"
3
+
4
+ module ServiceObjects
5
+
6
+ module Parsers
7
+
8
+ # Parses and decorates a string, describing a notification
9
+ #
10
+ # @example
11
+ # result = ServiceObjects::Parsers::Notification.new "FOund:iTem:messages"
12
+ # result.name # => found
13
+ # result.arguments # => ["item", "messages"]
14
+ # result.non_messages # => ["item"]
15
+ # result.error? # => false
16
+ # result.publish_messages? # => true
17
+ class Notification
18
+
19
+ # @!scope class
20
+ # @!method new(source)
21
+ # Initializes the object from source string
22
+ #
23
+ # @param [#to_s] source
24
+ #
25
+ # @return [ServiceObjects::Parsers::Notification]
26
+ def initialize(source)
27
+ @source = source.to_s
28
+ end
29
+
30
+ # The name for the parsed value
31
+ # @return [String]
32
+ def name
33
+ @name ||= list.first
34
+ end
35
+
36
+ # The type of exception to be raised and catched by service object
37
+ # @return [String]
38
+ def exception
39
+ @exception ||= name.camel_case
40
+ end
41
+
42
+ # The list of published values
43
+ # @return [Array<String>]
44
+ def args
45
+ @args ||= Array(list[1..-1])
46
+ end
47
+
48
+ # The list of pulished values except for messages
49
+ # @return [Array<String>]
50
+ def non_messages
51
+ @non_messages ||= args.reject { |item| item == "messages" }
52
+ end
53
+
54
+ # Checks whether a notification reports an error
55
+ # @return [Boolean]
56
+ def error?
57
+ name == "error"
58
+ end
59
+
60
+ # Checks whether a notification publishes messages
61
+ # @return [Boolean]
62
+ def publish_messages?
63
+ args.include? "messages"
64
+ end
65
+
66
+ # Returns value to order notifications by
67
+ # @return [0] if a notification is successful
68
+ # @return [1] if a notification is an error
69
+ def order
70
+ @order = error? ? 1 : 0
71
+ end
72
+
73
+ private
74
+
75
+ attr_reader :source
76
+
77
+ def list
78
+ @list ||= source.scan(/\w+/).map(&:snake_case)
79
+ end
80
+
81
+ end # module ServiceObjects
82
+
83
+ end # module Parsers
84
+
85
+ end # class Notification
@@ -0,0 +1,75 @@
1
+ # encoding: utf-8
2
+ require "rspec/mocks"
3
+
4
+ module ServiceObjects
5
+
6
+ # Collection of helpers and matchers for testing services
7
+ #
8
+ # @example
9
+ # require "service_objects/rspec"
10
+ # describe MyService do
11
+ # include ServiceObjects::RSpec
12
+ # end
13
+ module RSpec
14
+ include ::RSpec::Mocks::ExampleMethods
15
+
16
+ # Params to be given to service object constructor
17
+ #
18
+ # @return [Hash]
19
+ def params
20
+ @params ||= {}
21
+ end
22
+
23
+ # The service object of described_class, that subscribed the listener
24
+ #
25
+ # @return [ServiceObjects::Base]
26
+ def service
27
+ @service ||= described_class.new(params).subscribe(listener)
28
+ end
29
+
30
+ # The spy object to listen a service's notifications
31
+ #
32
+ # @return [::RSpec::Mocks::Double]
33
+ def listener
34
+ spy
35
+ end
36
+
37
+ # Makes given dependency to construct object when called with params
38
+ #
39
+ # @example
40
+ # before { inject(:get_item, with: { name: :foo }, constructs: foo) }
41
+ # service.get_item.new(name: :foo) # => foo
42
+ #
43
+ # @return [undefined]
44
+ def inject(dependency, with: {}, constructs: service_double)
45
+ klass = Class.new ServiceObjects::Base
46
+ allow(klass).to receive(:new).with(with).and_return constructs
47
+ allow(service).to receive(dependency).and_return klass
48
+ end
49
+
50
+ # Mock for another services, the service under test depends from
51
+ #
52
+ # The block contains the code to be executed on service #run.
53
+ #
54
+ # @example
55
+ # let(:another_service) { service_double(:foo) { |name| publish name } }
56
+ # another_service.run
57
+ # # another_service sends :foo to its listeners
58
+ #
59
+ # @param [Array<Object>] args
60
+ # the list of arguments for the block
61
+ # @param [Proc] block
62
+ # the block to be yielded by #run method
63
+ #
64
+ # @return [ServiceObjects::Base]
65
+ def service_double(*args, &block)
66
+ object = ServiceObjects::Base.new
67
+ allow(object).to receive(:run) do
68
+ object.instance_exec(*args, &block) if block_given?
69
+ end
70
+ object
71
+ end
72
+
73
+ end
74
+
75
+ end # module ServiceObjects
@@ -4,6 +4,6 @@ module ServiceObjects
4
4
 
5
5
  # The semantic version of the module.
6
6
  # @see http://semver.org/ Semantic versioning 2.0
7
- VERSION = "0.0.2".freeze
7
+ VERSION = "0.1.0".freeze
8
8
 
9
9
  end # module ServiceObjects
@@ -15,6 +15,7 @@ Gem::Specification.new do |gem|
15
15
 
16
16
  gem.require_paths = ["lib"]
17
17
  gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
18
+ gem.executables = ["service"]
18
19
  gem.test_files = Dir["spec/**/*.rb"]
19
20
  gem.extra_rdoc_files = Dir["README.md", "LICENSE"]
20
21
 
@@ -23,6 +24,7 @@ Gem::Specification.new do |gem|
23
24
  gem.add_runtime_dependency "extlib", "~> 0.9"
24
25
  gem.add_runtime_dependency "naught", "~> 1.0"
25
26
  gem.add_runtime_dependency "wisper", "~> 1.6"
27
+ gem.add_runtime_dependency "hexx-cli", "~> 0.0"
26
28
  gem.add_development_dependency "hexx-rspec", "~> 0.3"
27
29
 
28
30
  end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ describe "$ service new", :sandbox, :capture do
4
+
5
+ let(:argv) { %w(foo) }
6
+
7
+ before { try_in_sandbox { `service new #{ argv.join(" ") }` } }
8
+
9
+ it "runs a scaffolder" do
10
+ %w(
11
+ app/services/foo.rb
12
+ config/locales/services/foo/en.yml
13
+ config/locales/services/foo/ru.yml
14
+ spec/tests/services/foo_spec.rb
15
+ ).each { |file| expect(file).to be_present_in_sandbox }
16
+ end
17
+
18
+ end # describe $ service new
@@ -0,0 +1,179 @@
1
+ # encoding: utf-8
2
+ require "service_objects/cli"
3
+
4
+ describe ServiceObjects::CLI, :sandbox, :capture do
5
+
6
+ subject { try_in_sandbox { described_class.start options } }
7
+
8
+ shared_examples "adding an object" do
9
+
10
+ let(:file) { "app/#{ folder }/foo.rb" }
11
+ let(:content) { read_in_sandbox(file) }
12
+
13
+ it "[creates file]" do
14
+ expect(file).to be_present_in_sandbox
15
+ end
16
+
17
+ it "[declares a service]" do
18
+ expect(content).to include "class Foo < ServiceObjects::Base"
19
+ end
20
+
21
+ end # examples
22
+
23
+ shared_examples "adding a specification" do
24
+
25
+ it "[creates file]" do
26
+ expect("spec/tests/#{ folder }/foo_spec.rb").to be_present_in_sandbox
27
+ end
28
+
29
+ end # examples
30
+
31
+ shared_examples "adding translations" do |locales|
32
+
33
+ it "[creates files]" do
34
+ locales.each do |locale|
35
+ file = "config/locales/#{ folder }/foo/#{ locale }.yml"
36
+ expect(file).to be_present_in_sandbox
37
+ end
38
+ end
39
+
40
+ it "[adds content]" do
41
+ locales.each do |locale|
42
+ file = "config/locales/#{ folder }/foo/#{ locale }.yml"
43
+ content = read_in_sandbox(file)
44
+
45
+ expect(content).to include("# #{ locale }:")
46
+ expect(content).to include("#{ folder }/foo:")
47
+ end
48
+ end
49
+
50
+ end # examples
51
+
52
+ shared_examples "using namespaces" do |modules|
53
+
54
+ let(:file) { "app/#{ folder }/foo.rb" }
55
+ let(:content) { read_in_sandbox(file) }
56
+
57
+ it "[adds modules]" do
58
+ modules.each do |item|
59
+ expect(content).to include "module #{ item }"
60
+ end
61
+ end
62
+
63
+ end # examples
64
+
65
+ context "foo" do
66
+
67
+ let(:options) { %w(foo) }
68
+ let(:folder) { "services" }
69
+
70
+ before { subject }
71
+
72
+ it_behaves_like "adding an object"
73
+ it_behaves_like "using namespaces", %w(Services)
74
+ it_behaves_like "adding a specification"
75
+ it_behaves_like "adding translations", %w(en ru)
76
+
77
+ end # context
78
+
79
+ context "foo -p bar baz" do
80
+
81
+ let(:options) { %w(foo -p bar baz) }
82
+ let(:folder) { "services" }
83
+
84
+ before { subject }
85
+
86
+ it_behaves_like "adding an object"
87
+ it_behaves_like "using namespaces", %w(Services)
88
+ it_behaves_like "adding a specification"
89
+ it_behaves_like "adding translations", %w(en ru)
90
+
91
+ it "uses parameters" do
92
+ content = read_in_sandbox("app/services/foo.rb")
93
+ expect(content).to include "allows_params :bar, :baz"
94
+ end
95
+
96
+ end # context
97
+
98
+ context "foo -n foo:bar:baz" do
99
+
100
+ let(:options) { %w(foo -n foo:bar:baz cad:cam:messages) }
101
+ let(:folder) { "services" }
102
+
103
+ before { subject }
104
+
105
+ it_behaves_like "adding an object"
106
+ it_behaves_like "using namespaces", %w(Services)
107
+ it_behaves_like "adding a specification"
108
+ it_behaves_like "adding translations", %w(en ru)
109
+
110
+ it "uses notifications" do
111
+ content = read_in_sandbox("app/services/foo.rb")
112
+ expect(content).to include "publish :foo, bar, baz"
113
+ end
114
+
115
+ end # context
116
+
117
+ context "foo -f bar baz" do
118
+
119
+ let(:options) { %w(foo -f bar baz) }
120
+ let(:folder) { "bar/baz" }
121
+
122
+ before { subject }
123
+
124
+ it_behaves_like "adding an object"
125
+ it_behaves_like "using namespaces", %w(Bar Baz)
126
+ it_behaves_like "adding a specification"
127
+ it_behaves_like "adding translations", %w(en ru)
128
+
129
+ end # context
130
+
131
+ context "foo -f bar/baz" do
132
+
133
+ let(:options) { %w(foo -f bar/baz) }
134
+ let(:folder) { "bar/baz" }
135
+
136
+ before { subject }
137
+
138
+ it_behaves_like "adding an object"
139
+ it_behaves_like "using namespaces", %w(Bar Baz)
140
+ it_behaves_like "adding a specification"
141
+ it_behaves_like "adding translations", %w(en ru)
142
+
143
+ end # context
144
+
145
+ context "foo -l jp ua" do
146
+
147
+ let(:options) { %w(foo -l jp ua) }
148
+ let(:folder) { "services" }
149
+
150
+ before { subject }
151
+
152
+ it_behaves_like "adding an object"
153
+ it_behaves_like "using namespaces", %w(Services)
154
+ it_behaves_like "adding a specification"
155
+ it_behaves_like "adding translations", %w(jp ua)
156
+
157
+ end # context
158
+
159
+ context "foo -d get_item{AddItem}" do
160
+
161
+ let(:options) { %w(foo -d get_item{AddItem}) }
162
+ let(:folder) { "services" }
163
+
164
+ before { subject }
165
+
166
+ it_behaves_like "adding an object"
167
+ it_behaves_like "using namespaces", %w(Services)
168
+ it_behaves_like "adding a specification"
169
+ it_behaves_like "adding translations", %w(en ru)
170
+
171
+ it "uses dependencies" do
172
+ content = read_in_sandbox("app/services/foo.rb")
173
+ expect(content).to include "depends_on get_item, default: AddItem"
174
+ expect(content).to include "GetItemListener"
175
+ end
176
+
177
+ end # context
178
+
179
+ end # describe
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+ require "service_objects/parsers/dependency"
3
+
4
+ describe ServiceObjects::Parsers::Dependency do
5
+
6
+ subject { described_class.new "Get_item{add/item.do.chain}" }
7
+
8
+ describe "#name" do
9
+
10
+ it "returns the dependency name in snake case" do
11
+ expect(subject.name).to eq "get_item"
12
+ end
13
+ end
14
+
15
+ describe "#type" do
16
+
17
+ it "returns the dependency type as is" do
18
+ expect(subject.type).to eq "Add::Item.do.chain"
19
+ end
20
+ end
21
+
22
+ describe "#listener" do
23
+
24
+ it "returns the dependency listener name" do
25
+ expect(subject.listener).to eq "GetItemListener"
26
+ end
27
+ end
28
+
29
+ end # describe ServiceObjects::Parsers::Dependency
@@ -0,0 +1,84 @@
1
+ # encoding: utf-8
2
+ require "service_objects/parsers/notification"
3
+
4
+ describe ServiceObjects::Parsers::Notification do
5
+
6
+ describe "#name" do
7
+
8
+ it "returns the name of the notification" do
9
+ subject = described_class.new "not_found:item:messages"
10
+ expect(subject.name).to eq "not_found"
11
+ end
12
+
13
+ end # describe #name
14
+
15
+ describe "#exception" do
16
+
17
+ it "returns the name of the exception" do
18
+ subject = described_class.new "not_found:item:messages"
19
+ expect(subject.exception).to eq "NotFound"
20
+ end
21
+
22
+ end # describe #exception
23
+
24
+ describe "#args" do
25
+
26
+ it "returns all notification arguments" do
27
+ subject = described_class.new "success:messages:info:item"
28
+ expect(subject.args).to eq %w(messages info item)
29
+ end
30
+
31
+ end # describe #args" do
32
+
33
+ describe "#non_messages" do
34
+
35
+ it "returns all args except for messages" do
36
+ subject = described_class.new "success:messages:info:item"
37
+ expect(subject.non_messages).to eq %w(info item)
38
+ end
39
+
40
+ end # describe #non_messages
41
+
42
+ describe "#publish_messages?" do
43
+
44
+ it "returns true if messages published" do
45
+ subject = described_class.new "found:item:messages"
46
+ expect(subject.publish_messages?).to eq true
47
+ end
48
+
49
+ it "returns false unless messages published" do
50
+ subject = described_class.new "found:item:info"
51
+ expect(subject.publish_messages?).to eq false
52
+ end
53
+
54
+ end # describe #publish_messages?
55
+
56
+ describe "#error?" do
57
+
58
+ it "returns true for errors" do
59
+ subject = described_class.new "error:messages"
60
+ expect(subject).to be_error
61
+ end
62
+
63
+ it "returns false otherwise" do
64
+ subject = described_class.new "found:messages"
65
+ expect(subject).not_to be_error
66
+ end
67
+
68
+ end # describe #error?
69
+
70
+ describe "#order" do
71
+
72
+ it "returns 1 for errors" do
73
+ subject = described_class.new "error:messages"
74
+ expect(subject.order).to eq 1
75
+ end
76
+
77
+ it "returns 0 otherwise" do
78
+ subject = described_class.new "found:messages"
79
+ expect(subject.order).to eq 0
80
+ end
81
+
82
+ end # describe #order
83
+
84
+ end # describe ServiceObjects::Parsers::Notification
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+ require "service_objects/rspec"
3
+
4
+ describe ServiceObjects::RSpec do
5
+
6
+ let(:test_class) { Class.new(Object).__send__(:include, described_class) }
7
+ subject { test_class.new }
8
+
9
+ describe "#params" do
10
+
11
+ it "returns a hash" do
12
+ expect(subject.params).to eq({})
13
+ end
14
+
15
+ end # describe #params
16
+
17
+ describe "listener" do
18
+
19
+ it "should be spy" do
20
+ expect(subject.listener).to be_kind_of ::RSpec::Mocks::Double
21
+ end
22
+
23
+ end # describe #listener
24
+
25
+ describe "#service" do
26
+
27
+ let(:service_class) { ServiceObjects::Base }
28
+ before { allow(subject).to receive(:described_class) { service_class } }
29
+
30
+ it "returns the object of described class" do
31
+ expect(subject.service).to be_kind_of(service_class)
32
+ end
33
+
34
+ it "initializes the object with given params" do
35
+ allow(subject).to receive(:params) { { foo: :bar } }
36
+
37
+ expect(service_class)
38
+ .to receive(:new)
39
+ .with(subject.params)
40
+ .and_call_original
41
+ subject.service
42
+ end
43
+
44
+ it "subscribes the listener" do
45
+ listener = double
46
+ allow(subject).to receive(:listener) { listener }
47
+
48
+ expect(service_class)
49
+ .to receive_message_chain(:new, :subscribe)
50
+ .with(listener)
51
+ subject.service
52
+ end
53
+
54
+ end # describe #service
55
+
56
+ describe "#service_double" do
57
+
58
+ it "returns a service object" do
59
+ expect(subject.service_double).to be_kind_of ServiceObjects::Base
60
+ end
61
+
62
+ it "runs given block in the service's scope" do
63
+ service_double = subject.service_double(:foo) { |name| publish name }
64
+ expect(service_double).to receive(:publish).with(:foo)
65
+ service_double.run
66
+ end
67
+
68
+ end # describe #service_double
69
+
70
+ describe "#inject" do
71
+
72
+ let(:service_class) { Class.new ServiceObjects::Base }
73
+ let(:injection) { double }
74
+ let(:params) { double }
75
+
76
+ before { allow(subject).to receive(:described_class) { service_class } }
77
+
78
+ it "makes service dependency to construct given object" do
79
+ subject.inject :foo, with: params, constructs: injection
80
+
81
+ expect(subject.service.foo.new(params)).to eq injection
82
+ end
83
+
84
+ end # describe #inject
85
+
86
+ end # describe ServiceObjects::RSpec
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: service_objects
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kozin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-16 00:00:00.000000000 Z
11
+ date: 2015-03-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: hexx-cli
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: hexx-rspec
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -83,13 +97,15 @@ dependencies:
83
97
  description: Base class for objects, that implements both the Interactor and Observer
84
98
  design patterns.
85
99
  email: andrew.kozin@gmail.com
86
- executables: []
100
+ executables:
101
+ - service
87
102
  extensions: []
88
103
  extra_rdoc_files:
89
104
  - README.md
90
105
  - LICENSE
91
106
  files:
92
107
  - ".coveralls.yml"
108
+ - ".gitignore"
93
109
  - ".metrics"
94
110
  - ".rspec"
95
111
  - ".rubocop.yml"
@@ -100,6 +116,7 @@ files:
100
116
  - LICENSE
101
117
  - README.md
102
118
  - Rakefile
119
+ - bin/service
103
120
  - config/metrics/STYLEGUIDE
104
121
  - config/metrics/cane.yml
105
122
  - config/metrics/churn.yml
@@ -114,6 +131,11 @@ files:
114
131
  - config/metrics/yardstick.yml
115
132
  - lib/service_objects.rb
116
133
  - lib/service_objects/base.rb
134
+ - lib/service_objects/cli.rb
135
+ - lib/service_objects/cli/locale.erb
136
+ - lib/service_objects/cli/service.erb
137
+ - lib/service_objects/cli/spec.erb
138
+ - lib/service_objects/helpers.rb
117
139
  - lib/service_objects/helpers/dependable.rb
118
140
  - lib/service_objects/helpers/exceptions.rb
119
141
  - lib/service_objects/helpers/messages.rb
@@ -124,11 +146,17 @@ files:
124
146
  - lib/service_objects/listener.rb
125
147
  - lib/service_objects/message.rb
126
148
  - lib/service_objects/null.rb
149
+ - lib/service_objects/parsers.rb
150
+ - lib/service_objects/parsers/dependency.rb
151
+ - lib/service_objects/parsers/notification.rb
152
+ - lib/service_objects/rspec.rb
127
153
  - lib/service_objects/utils/normal_hash.rb
128
154
  - lib/service_objects/version.rb
129
155
  - service_objects.gemspec
130
156
  - spec/spec_helper.rb
131
157
  - spec/tests/base_spec.rb
158
+ - spec/tests/bin/service_spec.rb
159
+ - spec/tests/cli_spec.rb
132
160
  - spec/tests/helpers/dependable_spec.rb
133
161
  - spec/tests/helpers/exceptions_spec.rb
134
162
  - spec/tests/helpers/messages_spec.rb
@@ -139,6 +167,9 @@ files:
139
167
  - spec/tests/listener_spec.rb
140
168
  - spec/tests/message_spec.rb
141
169
  - spec/tests/null_spec.rb
170
+ - spec/tests/parsers/dependency_spec.rb
171
+ - spec/tests/parsers/notification_spec.rb
172
+ - spec/tests/rspec_spec.rb
142
173
  - spec/tests/utils/normal_hash_spec.rb
143
174
  homepage: https://github.com/nepalez/service_objects
144
175
  licenses:
@@ -168,8 +199,11 @@ test_files:
168
199
  - spec/spec_helper.rb
169
200
  - spec/tests/listener_spec.rb
170
201
  - spec/tests/message_spec.rb
202
+ - spec/tests/cli_spec.rb
171
203
  - spec/tests/null_spec.rb
172
204
  - spec/tests/base_spec.rb
205
+ - spec/tests/parsers/dependency_spec.rb
206
+ - spec/tests/parsers/notification_spec.rb
173
207
  - spec/tests/invalid_spec.rb
174
208
  - spec/tests/helpers/parameterized_spec.rb
175
209
  - spec/tests/helpers/parameters_spec.rb
@@ -178,4 +212,6 @@ test_files:
178
212
  - spec/tests/helpers/exceptions_spec.rb
179
213
  - spec/tests/helpers/dependable_spec.rb
180
214
  - spec/tests/utils/normal_hash_spec.rb
215
+ - spec/tests/bin/service_spec.rb
216
+ - spec/tests/rspec_spec.rb
181
217
  has_rdoc: