service_object 0.1.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 449e4bf91c536fb0279d4120a6ab3b268c3f5c13
4
+ data.tar.gz: 9e9117fc1345ddb731825f6c876a00b933af9bf3
5
+ SHA512:
6
+ metadata.gz: 2560a175d5fc0c6294a60b8e7e62f08bc22ee2dc3205f5b2ffd2a02bbb9394c94137790aff891a314ce314c61b40d1c491c01ccc930f246b414fb222f44f3f8f
7
+ data.tar.gz: 68c2148bf83ed99ff2503b399497dee84da2164a5aae1df713a688fd64fff5cfe83fe1a506563a8597ef6f9b26743994c4529ee4ef8fc2e288169c161c78b3ce
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format=doc
2
+ --color
@@ -0,0 +1,20 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ - 2.3.4
5
+ - 2.4.1
6
+
7
+ gemfile:
8
+ - gemfiles/4.0.gemfile
9
+ - gemfiles/4.1.gemfile
10
+ - gemfiles/4.2.gemfile
11
+ - gemfiles/5.0.gemfile
12
+ - gemfiles/5.1.gemfile
13
+
14
+ matrix:
15
+ fast_finish: true
16
+ allow_failures:
17
+ - rvm: ruby-head
18
+
19
+ sudo: false
20
+ cache: bundler
@@ -0,0 +1,19 @@
1
+ appraise "4.0" do
2
+ gem "rails", "~> 4.0.0"
3
+ end
4
+
5
+ appraise "4.1" do
6
+ gem "rails", "~> 4.1.0"
7
+ end
8
+
9
+ appraise "4.2" do
10
+ gem "rails", "~> 4.2.0"
11
+ end
12
+
13
+ appraise "5.0" do
14
+ gem "rails", "~> 5.0.0"
15
+ end
16
+
17
+ appraise "5.1" do
18
+ gem "rails", "~> 5.1.0"
19
+ end
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
@@ -0,0 +1,20 @@
1
+ Copyright 2014- Yukio Mizuta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,205 @@
1
+ # ServiceObject for Ruby on Rails
2
+
3
+ A mini gem to make it easy for you to have service objects in Rails.
4
+
5
+ This gem allows your controllers to control only flows and encapsulates detailed logic in services.
6
+
7
+ ```ruby
8
+ def some_action_on_book
9
+ service = CreateMyBookService.new(params[:isbn])
10
+ service.run do |s|
11
+ s.get_info_from_isbn_api
12
+ s.get_availability_with_library
13
+ s.save_my_book
14
+ end
15
+ if service.result
16
+ render json: { result: 'success', message: 'Successfully saved the book data' }
17
+ else
18
+ render json: { result: 'failure', messages: service.error_messages }
19
+ end
20
+ end
21
+ ```
22
+
23
+ [Rails Cast (Pro): #398 Service Objects](http://railscasts.com/episodes/398-service-objects)
24
+
25
+ [![Code Climate](https://codeclimate.com/github/untidy-hair/service_object/badges/gpa.svg)](https://codeclimate.com/github/untidy-hair/service_object)
26
+ [![Build Status](https://travis-ci.org/untidy-hair/service_object.svg?branch=ci-setup)](https://travis-ci.org/untidy-hair/service_object)
27
+
28
+ ## Install
29
+ In your Rails application Gemfile, add this line and do 'bundle install'
30
+ ```ruby
31
+ gem 'service_object'
32
+ ```
33
+
34
+ ## Usage (What you need to know)
35
+ 1. How to implement your code
36
+ - Inherit ServiceObject::Base in your service class.
37
+ - Your service class needs to call `super` in its initializer for now.
38
+ - Define your methods in the service.
39
+ - Make your service method raise an error whenever it fails.
40
+ (By default, ServiceObject::Base\#result will return false and ServiceObject::Base\#error_messages will have error message.)
41
+ - For error cases, override and customize ServiceObject::Base\#process_exception(e) to handle exceptions in your own way.
42
+ As a best practice, process only expected errors but re-raise unexpected errors. (See Sample 2 below)
43
+ By default, all `StandardError` will be caught and the error messages will be stored in `@errors` that thus will be accessible through `#error_messages`.
44
+ - Implement your controller with `run` or `execute` method and put your service methods inside the block.
45
+
46
+ 2. Interfaces for controllers
47
+ - ServiceObject::Base\#run(#execute): Put your service methods in order in the \#run block. (See samples below.)
48
+ - ServiceObject::Base\#result: If all the service process goes well, it returns true.
49
+ Otherwise it returns false.
50
+ - ServiceObject::Base\#error_messages: Returns an array of error messages.
51
+
52
+ 3. Other utility methods
53
+ - ServiceObject::Base\#logger is available (delegated to Rails.logger)
54
+ - ServiceObject::Base\#transaction is available (delegated to ActiveRecord::Base.transaction)
55
+ - ServiceObject::Base\#flattened_active_model_error(private) changes ActiveModel error
56
+ to a string so that the error gets easy to add to `@errors`
57
+
58
+
59
+ ## Sample 1
60
+
61
+ ### Controller
62
+ Your controller will be well-readable and the flow is easy to understand.
63
+ You can use \#result or \#error_messages to know the result of your service.
64
+
65
+ ```ruby
66
+ def some_action_on_book
67
+ service = CreateMyBookService.new(params[:isbn])
68
+ service.run do |s|
69
+ s.get_info_from_isbn_api
70
+ s.get_availability_with_library
71
+ s.save_my_book
72
+ end
73
+ if service.result
74
+ render json: { result: 'success', message: 'Successfully saved the book data' }
75
+ else
76
+ render json: { result: 'failure', messages: service.error_messages }
77
+ end
78
+ end
79
+ ```
80
+
81
+ ### Service
82
+ Inherit ServiceObject::Base and implement business logic into methods.
83
+ When something goes wrong, throw an error from inside your method in service.
84
+ The process stops there and the error will be added to service.errors (available through service.error_messages) and service.result returns false automatically.
85
+ ```ruby
86
+ class CreateMyBookService < ServiceObject::Base
87
+ def initialize(isbn)
88
+ super # This is necessary
89
+ @isbn = isbn
90
+ @book_info = nil
91
+ @my_book = MyBook.new # ActiveRecord Model
92
+ @isbn_api = IsbnApi.new(@isbn) # Non-AR Model
93
+ @library_api = LibraryApi.new # Non-AR Model
94
+ end
95
+
96
+ def get_info_from_isbn_api
97
+ @book_info = @isbn_api.get_all_info
98
+ end
99
+
100
+ def get_availability_with_library
101
+ @availability = @library_api.get_availability(@isbn)
102
+ rescue Net::HTTPError => e
103
+ # You can re-throw you own error, too.
104
+ raise YourError, 'Failed to get availability from library'
105
+ end
106
+
107
+ def save_my_book
108
+ @my_book.update!(
109
+ available: @availability,
110
+ name: @book_info.title,
111
+ author: @book_info.author,
112
+ isbn: @isbn
113
+ )
114
+ end
115
+ end
116
+ ```
117
+
118
+ ## Sample 2
119
+
120
+ A sample with DB transaction and rollback process.
121
+
122
+ ### Controller
123
+ ```ruby
124
+ def some_action_on_content_file
125
+ service = UploadContentService.new(content_params, session[:user_id])
126
+ service.execute do |s| # execute is alias of #run
127
+ s.upload_file
128
+ s.transaction do
129
+ s.save_content_data
130
+ s.update_user
131
+ end
132
+ end
133
+
134
+ if service.executed_successfully? # executed_successfully is alias of #result
135
+ render json: { result: 'success', message: 'Successfully uploaded your content' }
136
+ else
137
+ render json: { result: 'failure', messages: service.error_messages }
138
+ end
139
+ end
140
+ ```
141
+
142
+ ### Service
143
+
144
+ ```ruby
145
+ class UploadContentService < ServiceObject::Base
146
+ def initialize(params, user_id)
147
+ super
148
+ @content = Content.new(params)
149
+ @file = ContentFile.new(params[:file_path])
150
+ @user = User.find(user_id)
151
+ end
152
+
153
+ def upload_file
154
+ raise YourFileError, "File Type needs to be one of #{ContentFile::TYPES.join('/')}" unless @file.allowed_file_type?
155
+ @file.build_permission_info
156
+ @file.queue_upload_job! # Let's assume this throws ContentFile::YourOwnEnqueueError when failing.
157
+ end
158
+
159
+ def save_content_data
160
+ @content.update!(active: true)
161
+ end
162
+
163
+ def update_user
164
+ @user.contents_counter += 1 # This is just a contrived sample. Use counter_cache
165
+ @user.save!
166
+ end
167
+
168
+ # Custom error process by overriding the originally defined #process_exception.
169
+ def process_exception(e)
170
+ if e.is_a? ActiveRecord::ActiveRecordError
171
+ # When DB persistence fails, revert uploading file, too.
172
+ rollback_uploaded_file
173
+ @errors.add flattened_active_model_error(e.record) # This method is provided for convenience
174
+ elsif e.class.in? [ContentFile::YourOwnEnqueueError, YourFileError]
175
+ # This is still known error.
176
+ logger.warn 'File upload had an issue.'
177
+ @errors.add e.message
178
+ else
179
+ # Other errors are unexpected, so let the system fail by re-raising the error.
180
+ raise e
181
+ end
182
+ end
183
+
184
+ def rollback_uploaded_file
185
+ if @file.respond_to?(:queued?) && @file.queued?
186
+ @file.delete_queue_job
187
+ end
188
+ end
189
+ end
190
+ ```
191
+
192
+ ## More Flow Controls (hooks)
193
+ - Override `before_run` method if there are pre-processes that you don't want to show in your controller.
194
+ - Override `after_run` method if there are post-processes that you don't want to show in your controller.
195
+
196
+ ## To Do
197
+ - Integration tests / Tests for each use case
198
+
199
+ ## Credits
200
+
201
+ ServiceObject is written by untidyhair(Yukio Mizuta).
202
+
203
+ ## License
204
+
205
+ [MIT-LICENSE](MIT-LICENSE)
@@ -0,0 +1,15 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ Bundler::GemHelper.install_tasks
8
+
9
+ require 'rspec/core'
10
+ require 'rspec/core/rake_task'
11
+ RSpec::Core::RakeTask.new(:spec) do |spec|
12
+ spec.pattern = FileList['spec/**/*_spec.rb']
13
+ end
14
+
15
+ task default: :spec
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 4.0.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 4.1.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 4.2.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 5.0.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 5.1.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,2 @@
1
+ require 'service_object/base'
2
+ require 'service_object/errors'
@@ -0,0 +1,129 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+ module ServiceObject
3
+ # Service object base class which provides interfaces to controllers so that
4
+ # they can access the result of service processing and its errors if any.
5
+ # Uses ServiceObject::Errors as the error container.
6
+ class Base
7
+ # @return [ServiceObject::Errors] Errors object of the current service
8
+ attr_reader :errors
9
+ delegate :logger, to: :Rails
10
+
11
+ def initialize(*args)
12
+ @result = true
13
+ @errors = Errors.new
14
+ end
15
+
16
+ # This runs your logic without exposing unessential processes to your controllers.
17
+ # @return [true, false]
18
+ #
19
+ # Examples:
20
+ # # Controller
21
+ # def some_action_on_book
22
+ # service = CreateMyBookService.new(params[:isbn])
23
+ # service.run do |s|
24
+ # s.get_info_from_isbn_api
25
+ # s.get_availability_with_library
26
+ # s.save_my_book
27
+ # end
28
+ # if service.result
29
+ # render json: { result: 'success', message: 'Successfully saved the book data' }
30
+ # else
31
+ # render json: { result: 'failure', messages: service.error_messages }
32
+ # end
33
+ # end
34
+ #
35
+ # # Service
36
+ # class CreateMyBookService < ServiceObject::Base
37
+ # def initialize(isbn)
38
+ # super # This is necessary
39
+ # @isbn = isbn
40
+ # @book_info = nil
41
+ # @my_book = MyBook.new # ActiveRecord Model
42
+ # @isbn_api = IsbnApi.new(@isbn) # Non-AR Model
43
+ # @library_api = LibraryApi.new # Non-AR Model
44
+ # end
45
+ #
46
+ # def get_info_from_isbn_api
47
+ # @book_info = @isbn_api.get_all_info
48
+ # end
49
+ #
50
+ # def get_availability_with_library
51
+ # @availability = @library_api.get_availability(@isbn)
52
+ # rescue Net::HTTPError => e
53
+ # # You can re-throw you own error, too.
54
+ # raise YourError, 'Failed to get availability from library'
55
+ # end
56
+ #
57
+ # def save_my_book
58
+ # @my_book.update!(
59
+ # available: @availability,
60
+ # name: @book_info.title,
61
+ # author: @book_info.author,
62
+ # isbn: @isbn
63
+ # )
64
+ # end
65
+ # end
66
+ #
67
+ def run
68
+ before_run
69
+ yield self
70
+ after_run
71
+ @result
72
+ rescue => e
73
+ process_exception(e)
74
+ @result = false
75
+ end
76
+ alias execute run
77
+
78
+ # Error messages of the service process so far
79
+ # @return [Array] Array of error messages
80
+ def error_messages
81
+ @errors.full_messages
82
+ end
83
+
84
+ # Check if the service process is going well or not so far
85
+ # @return [true, false]
86
+ def result
87
+ @result && @errors.empty?
88
+ end
89
+ alias executed_successfully? result
90
+ alias ran_successfully? result
91
+
92
+ # Shorthand for ActiveRecord::Base.transaction
93
+ def transaction(&block)
94
+ self.class.transaction(&block)
95
+ end
96
+
97
+ class << self
98
+
99
+ # Shorthand for ActiveRecord::Base.transaction
100
+ def transaction(&block)
101
+ ActiveRecord::Base.transaction(&block)
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ # Override this method when there are other pre-processes that you don't want to show in controller.
108
+ def before_run; end
109
+
110
+ # Override this method when there are other post-processes that you don't want to show in controller.
111
+ def after_run; end
112
+
113
+ # @param e [StandardError]
114
+ # This puts all StandardError encountered into @errors, which will be available through #error_messages.
115
+ # If you want to specify special behaviors for each error type such as some rollback process or error logging,
116
+ # please override this method. (See README.md Sample 2.)
117
+ def process_exception(e)
118
+ @errors.add "#{e.class}: #{e.message}"
119
+ end
120
+
121
+ # Change activemodel errors into a string to be added to service errors
122
+ # @param active_model [ActiveModel] ActiveModel Object
123
+ # whose error messages are to be flattened
124
+ # @return [String] Flattened string error message
125
+ def flattened_active_model_error(active_model)
126
+ "#{active_model.class}: #{active_model.errors.full_messages.join(', ')}"
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,72 @@
1
+ require 'active_support/core_ext/array/conversions'
2
+ require 'delegate'
3
+
4
+ module ServiceObject
5
+
6
+ # Provides a customized +Array+ to contain errors that happen in service layer.
7
+ # Also provides a utility APIs to handle errors well in controllers.
8
+ # (All array methods are available by delegation, too.)
9
+ # errs = ServiceObject::Errors.new
10
+ # errs.add 'Something is wrong.'
11
+ # errs.add 'Another is wrong.'
12
+ # errs.messages
13
+ # => ['Something is wrong.','Another is wrong.']
14
+ # errs.full_messages
15
+ # => ['Something is wrong.','Another is wrong.']
16
+ # errs.to_xml
17
+ # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<errors>\n <error>Something is wrong.</error>\n
18
+ # <error>Another is wrong.</error>\n</errors>\n"
19
+ # errs.empty?
20
+ # => false
21
+ # errs.clear
22
+ # => []
23
+ # errs.empty?
24
+ # => true
25
+ class Errors < Delegator
26
+ # @return [Array<String>] Messages of the current errors
27
+ attr_reader :messages
28
+
29
+ def initialize
30
+ @messages = []
31
+ end
32
+
33
+ # @private
34
+ def __getobj__ # :nodoc:
35
+ @messages
36
+ end
37
+
38
+ # Returns all the current error messages
39
+ # @return [Array<String>] Messages of the current errors
40
+ def full_messages
41
+ messages
42
+ end
43
+
44
+ # Adds a new error message to the current error messages
45
+ # @param message [String] New error message to add
46
+ def add(message)
47
+ @messages << message
48
+ end
49
+
50
+ # Generates XML format errors
51
+ # errs = ServiceObject::Errors.new
52
+ # errs.add 'Something is wrong.'
53
+ # errs.add 'Another is wrong.'
54
+ # errs.to_xml
55
+ # =>
56
+ # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
57
+ # <errors>
58
+ # <error>Something is wrong.</error>
59
+ # <error>Another is wrong.</error>
60
+ # </errors>
61
+ # @return [String] XML format string
62
+ def to_xml(options={})
63
+ super({ root: "errors", skip_types: true }.merge!(options))
64
+ end
65
+
66
+ # Generates duplication of the message
67
+ # @return [Array<String>]
68
+ def as_json
69
+ messages.dup
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,3 @@
1
+ module ServiceObject
2
+ VERSION = "0.1.2"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :service_object do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,24 @@
1
+ $:.push File.expand_path('../lib', __FILE__)
2
+
3
+ require 'service_object/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'service_object'
7
+ s.version = ServiceObject::VERSION
8
+ s.authors = ['Yukio Mizuta']
9
+ s.email = ['untidyhair@gmail.com']
10
+ s.homepage = ''
11
+ s.summary = 'Small but powerful service objects/service layers library for Rails application.'
12
+ s.description = 'Not only does it let you code complicated business logic easier, but it also helps you keep controllers well-readable and models loose-coupled to each other.'
13
+ s.license = 'MIT'
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_dependency 'rails', '>= 4.0.0'
21
+ s.add_development_dependency 'pry-rails'
22
+ s.add_development_dependency 'rspec'
23
+ s.add_development_dependency 'appraisal'
24
+ end
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ class DummyService < ServiceObject::Base; end
4
+
5
+ describe ServiceObject::Base do
6
+ subject { DummyService.new }
7
+
8
+ specify 'The errors attribute is a ServiceObject::Errors instance' do
9
+ expect(subject.errors).to be_a ServiceObject::Errors
10
+ end
11
+
12
+ specify 'The result attribute is set as true after instantiation' do
13
+ expect(subject.result).to be true
14
+ end
15
+
16
+ describe '#error_messages' do
17
+ it 'returns @errors full_messages' do
18
+ expect(subject.error_messages).to eq subject.errors.full_messages
19
+ subject.errors.add 'Error Message 1'
20
+ expect(subject.error_messages).to eq subject.errors.full_messages
21
+ subject.errors.add 'Error Message 2'
22
+ expect(subject.error_messages).to eq subject.errors.full_messages
23
+ end
24
+ end
25
+
26
+ describe '#result' do
27
+ context 'When @result is true and @errors are empty' do
28
+ it 'returns true' do
29
+ expect(subject.result).to be true
30
+ end
31
+ end
32
+
33
+ context 'When @result is false' do
34
+ it 'returns false' do
35
+ subject.instance_variable_set(:@result, false)
36
+ expect(subject.result).to be false
37
+ end
38
+ end
39
+
40
+ context 'When @result is true but @errors are not empty' do
41
+ it 'returns false' do
42
+ subject.errors.add 'Error added!'
43
+ expect(subject.result).to be false
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '-#flattened_active_model_error' do
49
+ it 'returns error info of given active model instance as string' do
50
+ class DummyActiveModel
51
+ include ActiveModel::Model
52
+ attr_reader :name, :age
53
+ validates :name, presence: true
54
+ validates :age, numericality: { only_integer: true }
55
+ end
56
+ dummy_model = DummyActiveModel.new
57
+ dummy_model.valid?
58
+ expect(subject.__send__(:flattened_active_model_error, dummy_model)).
59
+ to eq 'DummyActiveModel: Name can\'t be blank, Age is not a number'
60
+ end
61
+ end
62
+
63
+ # ToDo: How to expect messages with a block
64
+ describe '.transaction' do
65
+ it 'delegates to ActiveRecord::Base.transaction' do
66
+ expect(ActiveRecord::Base).to receive(:transaction)
67
+ described_class.transaction { 'hoge' }
68
+ end
69
+ end
70
+
71
+ describe '#transaction' do
72
+ it 'delegates to .transaction' do
73
+ expect(described_class).to receive(:transaction)
74
+ subject.transaction { 'hoge' }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe ServiceObject::Errors do
4
+ # This example is not comprehensive
5
+ it 'delegates to @messages' do
6
+ messages = subject.messages
7
+ [:unshift, :push, :<<].each do |method|
8
+ expect(messages).to receive(method).with(100)
9
+ subject.__send__(method, 100)
10
+ end
11
+ end
12
+
13
+ describe '#add' do
14
+ it 'adds new error(s) to @messages' do
15
+ subject.add 'Error 1'
16
+ expect(subject.messages).to eq ['Error 1']
17
+ subject.add 'Error 2'
18
+ expect(subject.messages).to eq ['Error 1', 'Error 2']
19
+ end
20
+ end
21
+
22
+ describe '#full_message' do
23
+ it 'returns the same value as @messages' do
24
+ subject.add 'Error 1'
25
+ expect(subject.full_messages).to eq subject.messages
26
+ subject.add 'Error 2'
27
+ expect(subject.full_messages).to eq subject.messages
28
+ end
29
+ end
30
+
31
+ describe '#to_xml' do
32
+ it 'returns a xml format string' do
33
+ subject.add 'Something went wrong.'
34
+ subject.add 'Another error happened.'
35
+ expect(subject.to_xml).to eq <<-XML
36
+ <?xml version=\"1.0\" encoding=\"UTF-8\"?>
37
+ <errors>
38
+ <error>Something went wrong.</error>
39
+ <error>Another error happened.</error>
40
+ </errors>
41
+ XML
42
+ end
43
+ end
44
+
45
+ describe '#as_json' do
46
+ before { subject.add 'Test error happened' }
47
+ it 'returns messages as an Array' do
48
+ expect(subject.as_json).to eq subject.messages
49
+ expect(subject.as_json).to be_a Array
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+ require 'rails/all'
5
+ require 'service_object'
6
+
7
+ # Read support files if any
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
9
+
10
+ RSpec.configure do |config|
11
+ config.before(:suite) do
12
+ I18n.enforce_available_locales = false
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: service_object
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Yukio Mizuta
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-04-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 4.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 4.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: appraisal
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Not only does it let you code complicated business logic easier, but
70
+ it also helps you keep controllers well-readable and models loose-coupled to each
71
+ other.
72
+ email:
73
+ - untidyhair@gmail.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - Appraisals
81
+ - Gemfile
82
+ - MIT-LICENSE
83
+ - README.md
84
+ - Rakefile
85
+ - gemfiles/4.0.gemfile
86
+ - gemfiles/4.1.gemfile
87
+ - gemfiles/4.2.gemfile
88
+ - gemfiles/5.0.gemfile
89
+ - gemfiles/5.1.gemfile
90
+ - lib/service_object.rb
91
+ - lib/service_object/base.rb
92
+ - lib/service_object/errors.rb
93
+ - lib/service_object/version.rb
94
+ - lib/tasks/service_object_tasks.rake
95
+ - service_object.gemspec
96
+ - spec/service_object/base_spec.rb
97
+ - spec/service_object/errors_spec.rb
98
+ - spec/spec_helper.rb
99
+ homepage: ''
100
+ licenses:
101
+ - MIT
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.6.12
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: Small but powerful service objects/service layers library for Rails application.
123
+ test_files:
124
+ - spec/service_object/base_spec.rb
125
+ - spec/service_object/errors_spec.rb
126
+ - spec/spec_helper.rb