service_objects_rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dfd8b349fec82efeaba05ceebd23dceb3f5b6b72840442cc7e4e5d22e219c89b
4
+ data.tar.gz: fcda7ca5e0417fcc890dc1e8867d6019af4c0dabf75d8b507065ab6b38fcf3ae
5
+ SHA512:
6
+ metadata.gz: c03cda7db0b3b27378a6eadb576e13b5fb022b8a4fa333a8bddf103b81c101319e8cdf0c8683451e859478f4c9f71ed8204447c047fe3a88a8d456f28fecb86c
7
+ data.tar.gz: 0554bf7e6ec1b43463c2928b5378fc0e0e608677c70f29ce54befc120e744c313908d90ef6fd9b24d929e4c4f42533cd78dc1b20146fb5fbb5f97be216a7a82a
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (23-Feb-22)
4
+
5
+ Initial gem skeleton and basic functionality.
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem "rspec_junit_formatter"
7
+ gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby]
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Matthew
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # Service Objects for Rails
2
+
3
+ [![mattwd7](https://circleci.com/gh/mattwd7/service_objects_rails.svg?style=svg)](https://app.circleci.com/pipelines/github/mattwd7/service_objects_rails?filter=all)
4
+ [![Test Coverage](https://codecov.io/gh/mattwd7/service_objects_rails/graph/badge.svg)](https://codecov.io/gh/mattwd7/service_objects_rails)
5
+
6
+ A lightweight, high-speed service layer for Rails applications.
7
+
8
+ ## Motivation
9
+ Gone are the days of fat controllers and fat models! Service objects (sometimes known as Commands) enable an important service layer in your Rails applications to keep your code readable, reusable, and testable. Service objects help to facilitate a separation of concerns and the single responsibility principle. Here is an example of a service object that adds a new user to a table in a poker application:
10
+
11
+ ```
12
+ module Services
13
+ class AddPlayerToTable
14
+ include Services::Base
15
+
16
+ def initialize(user_id:, table_id:)
17
+ @user = User.find(user_id)
18
+ @table = Table.find(table_id)
19
+ end
20
+
21
+ def call
22
+ confirm_player_has_available_funds
23
+ assign_player_to_table
24
+ queue_player_for_next_deal
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :user, :table
30
+
31
+ def confirm_player_has_available_funds
32
+ # business logic
33
+ end
34
+
35
+ def assign_player_to_table
36
+ # business logic
37
+ end
38
+
39
+ def queue_player_for_next_deal
40
+ # business logic
41
+ end
42
+ end
43
+ end
44
+ ```
45
+ Without this Service Object, our Rails models like User, Table, and Queue would require a knowledge of associated models and their logic, consequently bloating model classes and their tests. Instead, this Service Object orchestrates the necessary objects to perform the singular task of adding a player to a poker table.
46
+
47
+ ## Installation
48
+ Include the gem in your Gemfile:
49
+ ```
50
+ gem "service_objects_rails"
51
+ ```
52
+
53
+ and install:
54
+ ```
55
+ bundle install
56
+ ```
57
+
58
+ Then generate boilerplate directories and gem configuration files with
59
+ ```
60
+ rails generate services:install
61
+ ```
62
+
63
+ This will create the `app/namespaces/services` directory which will hold the namespaced service objects you create and use in your application.
64
+
65
+ ## Quick start
66
+ ```
67
+ ## generate a service
68
+
69
+ rails g services:new AddPlayerToTable user_id table_id
70
+
71
+ ## write out and test the service
72
+
73
+ module Services
74
+ class AddPlayerToTable
75
+ include Services::Base
76
+
77
+ def initialize(user_id:, table_id:)
78
+ @user = User.find(user_id)
79
+ @table = Table.find(table_id)
80
+ end
81
+
82
+ def call
83
+ confirm_player_has_available_funds
84
+ assign_player_to_table
85
+ queue_player_for_next_deal
86
+ end
87
+
88
+ private
89
+
90
+ attr_reader :user, :table
91
+
92
+ def confirm_player_has_available_funds
93
+ # business logic
94
+ end
95
+
96
+ def assign_player_to_table
97
+ # business logic
98
+ end
99
+
100
+ def queue_player_for_next_deal
101
+ # business logic
102
+ end
103
+ end
104
+ end
105
+
106
+ ## use the service
107
+
108
+ # app/controllers/tables_controller.rb
109
+ class TablesController < ApplicationController
110
+ def join_table
111
+ service_response = Services::AddPlayerToTable.call(
112
+ user_id: params[:user_id],
113
+ table_id: params[:table_id]
114
+ )
115
+
116
+ if service_response.success?
117
+ render :show, status: 204
118
+ else
119
+ render json: { errors: service_response.errors.full_messages }
120
+ end
121
+ end
122
+ end
123
+ ```
124
+
125
+ ## Usage
126
+ All service objects have the same skeleton: an `initialize` method, a `call` method, and an included `Services::Base` mixin. The `Services::Base` mixin enforces strict, predictable usage of service objects such that they
127
+
128
+ 1. Are always called with named arguemnts
129
+ 1. Can never be publicly instantiated; only `call`ed
130
+ 1. Always return an instance of a Services::Response object
131
+
132
+ It is a best-practice to name service objects in the format <ACTION><NOUN> to abstract the responsibilty and function of the service at the highest level, e.g. `Services::AddPlayerToTable` as opposed to `Services::NewPlayerTable`.
133
+
134
+ ### Using the Generator
135
+ To quickly whip up a new service object and corresponding spec, use the provided generator:
136
+ ```
137
+ rails generate services:new <MyNewServiceName> <named_arg_1> <named_arg_2> ...
138
+ ```
139
+
140
+ An example of using the generator:
141
+ ```
142
+ rails g services:new AddPlayerToTable user_id table_id
143
+
144
+ # app/namespaces/services/add_player_to_table.rb
145
+ module Services
146
+ class AddPlayerToTable
147
+ include ServiceObject::Base
148
+
149
+ def initialize(user_id:, table_id:,)
150
+ @user_id = user_id
151
+ @table_id = table_id
152
+ end
153
+
154
+ def call
155
+ end
156
+
157
+ private
158
+
159
+ attr_reader :user_id, :table_id
160
+ end
161
+ end
162
+
163
+
164
+ # spec/namespaces/services/add_player_to_table_spec.rb
165
+ include "rails_helper"
166
+
167
+ RSpec.describe Services::AddPlayerToTable do
168
+ subject(:service_response) do
169
+ described_class.call(user_id: user_id, table_id: table_id)
170
+ end
171
+
172
+ let(:user_id) { nil }
173
+ let(:table_id) { nil }
174
+
175
+ it "does what it says" do
176
+ end
177
+
178
+ it "returns what is expected" do
179
+ end
180
+ end
181
+ ```
@@ -0,0 +1,24 @@
1
+ require "rails/generators"
2
+
3
+ module Services
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("../templates", __dir__)
7
+
8
+ desc "Creates the file structure for Service objects, " \
9
+ "generates a config files, and generates the ServiceBase module."
10
+
11
+ def copy_config
12
+ template(
13
+ "service_objects_rails_config.rb",
14
+ "#{Rails.root}/config/service_objects_rails.rb"
15
+ )
16
+ end
17
+
18
+ def setup_file_structure
19
+ empty_directory("#{Rails.root}/app/namespaces/services")
20
+ empty_directory("#{Rails.root}/spec/namespaces/services")
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ require "rails/generators"
2
+
3
+ module Services
4
+ module Generators
5
+ class NewGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("../templates", __dir__)
7
+
8
+ argument :service_name, type: :string
9
+ argument :service_args, type: :array, default: []
10
+
11
+ desc "Creates a new Service Object and corresponding spec file"
12
+
13
+ def create_new_service
14
+ template(
15
+ "new_service.erb",
16
+ "#{Rails.root}/app/namespaces/services/#{service_filepath}.rb"
17
+ )
18
+ end
19
+
20
+ def create_new_service_spec
21
+ template(
22
+ "new_service_spec.erb",
23
+ "#{Rails.root}/spec/namespaces/services/#{service_filepath}_spec.rb"
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ def service_filepath
30
+ service_name.gsub(/::/, "/")
31
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
32
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
33
+ .tr("-", "_")
34
+ .downcase
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,16 @@
1
+ module Services
2
+ class <%= @service_name %>
3
+ include Services::Base
4
+
5
+ def initialize(<%= @service_args.map { |arg| "#{arg}:" }.join(", ") %>)
6
+ <%= @service_args.map { |arg| "@#{arg} = #{arg}" }.join("\n ") %>
7
+ end
8
+
9
+ def call
10
+ end
11
+
12
+ private
13
+
14
+ attr_reader <%= @service_args.map { |arg| ":#{arg}" }.join(", ") %>
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ require "rails_helper"
2
+
3
+ RSpec.describe Services::<%= @service_name %> do
4
+ subject(:service_response) do
5
+ described_class.call<%= "(\n #{@service_args.map { |arg| "#{arg}: #{arg}" }.join(",\n ")}\n )" if @service_args.any? %>
6
+ end
7
+
8
+ <%= @service_args.map { |arg| "let(:#{arg}) { nil }" }.join("\n ") %>
9
+
10
+ it "does what it should"
11
+
12
+ it "returns what it ought to"
13
+ end
@@ -0,0 +1,12 @@
1
+ require "service_objects_rails"
2
+
3
+ ServiceObjectsRails.config do |c|
4
+ # The Services::Response class is always returned after
5
+ # executing the #call on a Service. This object includes
6
+ # goodies like graceful error handling and returning with the
7
+ # object(s) upon which the Service operated in response.body.
8
+ # However, some consider it good practice to simply return the
9
+ # object(s) a service operated on. Set this to false to avoid
10
+ # Services::Response altogether.
11
+ # c.return_service_response_object = false
12
+ end
@@ -0,0 +1,16 @@
1
+ require "services/base"
2
+ require "services/version"
3
+
4
+ module ServiceObjectsRails
5
+ class << self
6
+ attr_writer :return_service_response_object
7
+
8
+ def config
9
+ yield self
10
+ end
11
+
12
+ def return_service_response_object
13
+ @return_service_response_object.nil? ? true : @return_service_response_object
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ require "services/response"
2
+
3
+ module Services::Base
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+
7
+ base.private_class_method :new
8
+ base.send(:attr_reader, :response)
9
+ end
10
+
11
+ module ClassMethods
12
+ def call(**args)
13
+ service = new(**args)
14
+ service.instance_variable_set(:@response, Services::Response.new)
15
+
16
+ if ServiceObjectsRails.return_service_response_object
17
+ service.response.body = service.call
18
+ service.response
19
+ else
20
+ service.call
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ module Services
2
+ class Response
3
+ attr_accessor :body
4
+ attr_reader :errors
5
+
6
+ def initialize
7
+ @body = nil
8
+ @errors = []
9
+ end
10
+
11
+ def add_errors(*errors)
12
+ @errors.concat(Array(errors))
13
+ end
14
+
15
+ def success?
16
+ errors.empty?
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module Services
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,38 @@
1
+ $LOAD_PATH << File.expand_path("lib", __dir__)
2
+ require "services/version"
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "service_objects_rails"
6
+ gem.version = Services::VERSION
7
+ gem.authors = ["Matthew Dick"]
8
+ gem.email = ["mattwd7@gmail.com"]
9
+ gem.summary = "A lightweight, high-speed service layer for Rails applications"
10
+ gem.description = "A lightweight, high-speed service layer for Rails applications"
11
+ gem.homepage = "https://github.com/mattwd7/service_objects_rails"
12
+ gem.license = "MIT"
13
+ gem.platform = Gem::Platform::RUBY
14
+ gem.required_ruby_version = ">= 2.5.0"
15
+ gem.require_path = "lib"
16
+ gem.files = Dir.glob("lib/**/*") + [
17
+ "service_objects_rails.gemspec",
18
+ "Gemfile",
19
+ "CHANGELOG.md",
20
+ "LICENSE.txt",
21
+ "README.md"
22
+ ]
23
+
24
+ gem.add_development_dependency "codecov", "~> 0.6"
25
+ gem.add_development_dependency "dotenv", "~> 2.7"
26
+ gem.add_development_dependency "factory_bot_rails", "~> 6.2"
27
+ gem.add_development_dependency "pry", "~> 0.14"
28
+ gem.add_development_dependency "rake", "~> 13.0"
29
+ gem.add_development_dependency "rails", "~> 7.0"
30
+ gem.add_development_dependency "rspec-rails", "~> 5.1"
31
+ gem.add_development_dependency "rspec", "~> 3.11"
32
+ gem.add_development_dependency "rubocop-performance", "~> 1.13"
33
+ gem.add_development_dependency "rubocop-rails", "~> 2.13"
34
+ gem.add_development_dependency "rubocop-rspec", "~> 2.8"
35
+ gem.add_development_dependency "rubocop", "~> 1.25"
36
+ gem.add_development_dependency "simplecov", "~> 0.21"
37
+ gem.add_development_dependency "standard", "~> 1.7"
38
+ end
metadata ADDED
@@ -0,0 +1,253 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: service_objects_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Matthew Dick
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-03-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: codecov
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dotenv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: factory_bot_rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '6.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '6.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.14'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.14'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '7.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '7.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '5.1'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '5.1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.11'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.11'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop-performance
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.13'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.13'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop-rails
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '2.13'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '2.13'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubocop-rspec
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '2.8'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '2.8'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rubocop
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '1.25'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '1.25'
181
+ - !ruby/object:Gem::Dependency
182
+ name: simplecov
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '0.21'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '0.21'
195
+ - !ruby/object:Gem::Dependency
196
+ name: standard
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '1.7'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '1.7'
209
+ description: A lightweight, high-speed service layer for Rails applications
210
+ email:
211
+ - mattwd7@gmail.com
212
+ executables: []
213
+ extensions: []
214
+ extra_rdoc_files: []
215
+ files:
216
+ - CHANGELOG.md
217
+ - Gemfile
218
+ - LICENSE.txt
219
+ - README.md
220
+ - lib/generators/services/install_generator.rb
221
+ - lib/generators/services/new_generator.rb
222
+ - lib/generators/templates/new_service.erb
223
+ - lib/generators/templates/new_service_spec.erb
224
+ - lib/generators/templates/service_objects_rails_config.rb
225
+ - lib/service_objects_rails.rb
226
+ - lib/services/base.rb
227
+ - lib/services/response.rb
228
+ - lib/services/version.rb
229
+ - service_objects_rails.gemspec
230
+ homepage: https://github.com/mattwd7/service_objects_rails
231
+ licenses:
232
+ - MIT
233
+ metadata: {}
234
+ post_install_message:
235
+ rdoc_options: []
236
+ require_paths:
237
+ - lib
238
+ required_ruby_version: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - ">="
241
+ - !ruby/object:Gem::Version
242
+ version: 2.5.0
243
+ required_rubygems_version: !ruby/object:Gem::Requirement
244
+ requirements:
245
+ - - ">="
246
+ - !ruby/object:Gem::Version
247
+ version: '0'
248
+ requirements: []
249
+ rubygems_version: 3.3.8
250
+ signing_key:
251
+ specification_version: 4
252
+ summary: A lightweight, high-speed service layer for Rails applications
253
+ test_files: []