service_objects 0.0.2 → 0.1.0

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