telephone 0.1.0 → 1.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 +4 -4
- data/README.md +79 -1
- data/lib/telephone/locale/de.yml +5 -0
- data/lib/telephone/locale/en.yml +5 -0
- data/lib/telephone/locale/es.yml +5 -0
- data/lib/telephone/locale/fr.yml +5 -0
- data/lib/telephone/locale/it.yml +5 -0
- data/lib/telephone/locale/ja.yml +5 -0
- data/lib/telephone/locale/ko.yml +5 -0
- data/lib/telephone/locale/nl.yml +5 -0
- data/lib/telephone/locale/pt.yml +5 -0
- data/lib/telephone/locale/ru.yml +5 -0
- data/lib/telephone/locale/zh-CN.yml +5 -0
- data/lib/telephone/service.rb +79 -12
- data/lib/telephone/validators/type_validator.rb +25 -0
- data/lib/telephone/version.rb +1 -1
- data/lib/telephone.rb +5 -0
- metadata +30 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 45fbcb9aaecf2dc2d66072e93b97b168c2a505f6d4e0cc1ed08cfad4e8267bb2
|
|
4
|
+
data.tar.gz: 4f3992fbc6840c3d624fadb7de3faa56f3952ba4b4b5eae7cde0b61ecdf343f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ef63c3786eb25e545259ed90894ace7a67af3098028ebcdfc9206aa7bd995729dafa3ed5533a8109bd42696255216efb4db84e53fd9160304f8c7c868862dde5
|
|
7
|
+
data.tar.gz: 3a9419a284e42bde8b0de7bd8517a4dc7986e9f59b8c685a41059c8e5d81660d36c4aea2d3c3bba4610213211d243fceb2007103f2d79ac7f1fadea9be673431
|
data/README.md
CHANGED
|
@@ -94,6 +94,48 @@ You can also give a default value for an argument.
|
|
|
94
94
|
argument :name, default: "Benjamin"
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
Defaults can also be callable (procs or lambdas) that are evaluated at runtime. Callable defaults have access to other attributes and are processed in definition order:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
class GreetingService < ApplicationService
|
|
101
|
+
argument :first_name, default: "John"
|
|
102
|
+
argument :last_name, default: "Doe"
|
|
103
|
+
argument :full_name, default: -> { "#{first_name} #{last_name}" }
|
|
104
|
+
argument :created_at, default: -> { Time.current }
|
|
105
|
+
|
|
106
|
+
def call
|
|
107
|
+
"Hello, #{full_name}!"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
GreetingService.call.result #=> "Hello, John Doe!"
|
|
112
|
+
GreetingService.call(first_name: "Jane").result #=> "Hello, Jane Doe!"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Arguments can also be validated against a type. When `type:` is provided, the argument is checked with `is_a?` before the service runs. `nil` is allowed unless the argument is also marked as `required:`.
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
class UpdateUserService < ApplicationService
|
|
119
|
+
argument :user, type: User, required: true
|
|
120
|
+
|
|
121
|
+
def call
|
|
122
|
+
user.update!(status: "active")
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
s = UpdateUserService.call(user: "not a user")
|
|
127
|
+
s.success? #=> false
|
|
128
|
+
s.errors.full_messages #=> ["User must be a User"]
|
|
129
|
+
s.result #=> nil
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Type validation works with any class or module:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
argument :tags, type: Array
|
|
136
|
+
argument :metadata, type: Enumerable
|
|
137
|
+
```
|
|
138
|
+
|
|
97
139
|
### Validations
|
|
98
140
|
|
|
99
141
|
Since `Telephone::Service` includes `ActiveModel::Model`, you can define validations in the same way you would for an ActiveRecord model.
|
|
@@ -107,13 +149,49 @@ def admin_user?
|
|
|
107
149
|
end
|
|
108
150
|
```
|
|
109
151
|
|
|
110
|
-
If a validation fails, the service object will not execute and return `nil` as the result of
|
|
152
|
+
If a validation fails, the service object will not execute and return `nil` as the result of the call. You can check the status of the service object by calling `success?`.
|
|
111
153
|
|
|
112
154
|
```ruby
|
|
113
155
|
s = SomeService.call
|
|
114
156
|
s.success? #=> false
|
|
115
157
|
```
|
|
116
158
|
|
|
159
|
+
#### Internationalization
|
|
160
|
+
|
|
161
|
+
Type validation errors are translated through I18n. Telephone ships with translations for:
|
|
162
|
+
|
|
163
|
+
* `en` — English
|
|
164
|
+
* `es` — Spanish
|
|
165
|
+
* `fr` — French
|
|
166
|
+
* `de` — German
|
|
167
|
+
* `pt` — Portuguese
|
|
168
|
+
* `ja` — Japanese
|
|
169
|
+
* `zh-CN` — Chinese (Simplified)
|
|
170
|
+
* `it` — Italian
|
|
171
|
+
* `nl` — Dutch
|
|
172
|
+
* `ru` — Russian
|
|
173
|
+
* `ko` — Korean
|
|
174
|
+
|
|
175
|
+
In a Rails app, these are used automatically based on the current locale:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
I18n.locale = :es
|
|
179
|
+
|
|
180
|
+
s = UpdateUserService.call(user: "not a user")
|
|
181
|
+
s.errors.full_messages #=> ["User debe ser un User"]
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
You can also override the default message in your own locale files:
|
|
185
|
+
|
|
186
|
+
```yaml
|
|
187
|
+
# config/locales/telephone.en.yml
|
|
188
|
+
en:
|
|
189
|
+
activemodel:
|
|
190
|
+
errors:
|
|
191
|
+
messages:
|
|
192
|
+
invalid_type: "must be an instance of %{type}"
|
|
193
|
+
```
|
|
194
|
+
|
|
117
195
|
## Best Practices
|
|
118
196
|
|
|
119
197
|
Service objects are a great way to keep your code DRY and encapsulate business logic. As a rule of thumb, try to keep your service objects to a single responsibility. If you find yourself dealing with very complex logic, consider breaking it up into smaller services.
|
data/lib/telephone/service.rb
CHANGED
|
@@ -16,6 +16,34 @@ module Telephone
|
|
|
16
16
|
# Telephone::Service.call(foo: bar).result # "baz"
|
|
17
17
|
attr_accessor :result
|
|
18
18
|
|
|
19
|
+
##
|
|
20
|
+
# Primary responsibility of initialize is to instantiate the
|
|
21
|
+
# attributes of the service object with the expected values.
|
|
22
|
+
def initialize(attributes = {})
|
|
23
|
+
attributes = attributes.transform_keys(&:to_sym)
|
|
24
|
+
|
|
25
|
+
attributes.each do |key, value|
|
|
26
|
+
send("#{key}=", value)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
self.class.defaults.each do |key, value|
|
|
30
|
+
next if attributes.key?(key)
|
|
31
|
+
|
|
32
|
+
resolved = if value.is_a?(Proc)
|
|
33
|
+
instance_exec(&value)
|
|
34
|
+
elsif value.respond_to?(:call)
|
|
35
|
+
value.call
|
|
36
|
+
else
|
|
37
|
+
value
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
send("#{key}=", resolved)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
super
|
|
44
|
+
yield self if block_given?
|
|
45
|
+
end
|
|
46
|
+
|
|
19
47
|
##
|
|
20
48
|
# Determines whether or not the action of the service
|
|
21
49
|
# object was successful.
|
|
@@ -28,27 +56,55 @@ module Telephone
|
|
|
28
56
|
errors.empty?
|
|
29
57
|
end
|
|
30
58
|
|
|
59
|
+
def call
|
|
60
|
+
self.result = __call if valid?
|
|
61
|
+
self
|
|
62
|
+
end
|
|
63
|
+
|
|
31
64
|
class << self
|
|
32
65
|
##
|
|
33
66
|
# Defines a getter/setter for a service object argument. This also allows you
|
|
34
67
|
# to pass in a default, or set the argument to "required" to add a validation
|
|
35
68
|
# that runs before executing the block.
|
|
36
69
|
#
|
|
70
|
+
# The default value can be a static value or any callable object (Proc, lambda,
|
|
71
|
+
# method, or any object that responds to #call) that will be evaluated at
|
|
72
|
+
# runtime when the service is instantiated.
|
|
73
|
+
#
|
|
74
|
+
# Callable defaults are evaluated in the context of the service instance,
|
|
75
|
+
# so they can access other attributes. They are processed in definition order,
|
|
76
|
+
# meaning a callable can depend on any argument defined before it.
|
|
77
|
+
#
|
|
78
|
+
# To store a Proc as the actual value, wrap it in another lambda:
|
|
79
|
+
# argument :my_proc, default: -> { -> { puts "hi" } }
|
|
80
|
+
#
|
|
81
|
+
# You can also pass +type:+ to validate that the argument is an instance of
|
|
82
|
+
# the given class or module. Nil values are allowed unless the argument is
|
|
83
|
+
# also marked as required.
|
|
84
|
+
#
|
|
37
85
|
# @example
|
|
38
86
|
# class SomeService < Telephone::Service
|
|
39
|
-
# argument :
|
|
40
|
-
# argument :
|
|
87
|
+
# argument :first_name, default: "John"
|
|
88
|
+
# argument :last_name, default: "Doe"
|
|
89
|
+
# argument :full_name, default: -> { "#{first_name} #{last_name}" }
|
|
90
|
+
# argument :timestamp, default: -> { DateTime.current }
|
|
91
|
+
# argument :user, type: User
|
|
41
92
|
#
|
|
42
93
|
# def call
|
|
43
|
-
# puts
|
|
44
|
-
# puts
|
|
94
|
+
# puts full_name
|
|
95
|
+
# puts timestamp
|
|
45
96
|
# end
|
|
46
97
|
# end
|
|
47
|
-
def argument(arg, default: nil, required: false)
|
|
48
|
-
|
|
49
|
-
send(:
|
|
98
|
+
def argument(arg, default: nil, required: false, type: nil)
|
|
99
|
+
arg = arg.to_sym
|
|
100
|
+
send(:attr_accessor, arg)
|
|
101
|
+
send(:validates, arg, presence: true) if required
|
|
102
|
+
|
|
103
|
+
if type
|
|
104
|
+
send(:validates_with, Telephone::TypeValidator, attributes: arg, with: type, allow_nil: true)
|
|
105
|
+
end
|
|
50
106
|
|
|
51
|
-
defaults[arg
|
|
107
|
+
defaults[arg] = default
|
|
52
108
|
end
|
|
53
109
|
|
|
54
110
|
##
|
|
@@ -65,10 +121,21 @@ module Telephone
|
|
|
65
121
|
#
|
|
66
122
|
# @example
|
|
67
123
|
# Telephone::Service.call(foo: bar)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
124
|
+
# Telephone::Service.call({ foo: bar })
|
|
125
|
+
def call(*args, **kwargs)
|
|
126
|
+
new(args.extract_options!.merge(kwargs)).call
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
##
|
|
130
|
+
# When the subclass overwrites the #call method, reassign it to #__call.
|
|
131
|
+
# This allows us to still control what happens in the instance level of #call.
|
|
132
|
+
def method_added(method_name)
|
|
133
|
+
if method_name == :call
|
|
134
|
+
alias_method :__call, :call
|
|
135
|
+
send(:remove_method, :call)
|
|
136
|
+
else
|
|
137
|
+
super
|
|
138
|
+
end
|
|
72
139
|
end
|
|
73
140
|
end
|
|
74
141
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_model"
|
|
4
|
+
|
|
5
|
+
module Telephone
|
|
6
|
+
class TypeValidator < ActiveModel::EachValidator
|
|
7
|
+
def validate_each(record, attribute, value)
|
|
8
|
+
expected_type = options[:with]
|
|
9
|
+
|
|
10
|
+
return if value.is_a?(expected_type)
|
|
11
|
+
|
|
12
|
+
record.errors.add(
|
|
13
|
+
attribute,
|
|
14
|
+
:invalid_type,
|
|
15
|
+
**options.except(:with).merge(type: type_name(expected_type))
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def type_name(type)
|
|
22
|
+
type.name || type.to_s
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/telephone/version.rb
CHANGED
data/lib/telephone.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: telephone
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Benjamin Hargett
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 2026-06-08 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activemodel
|
|
@@ -24,6 +23,20 @@ dependencies:
|
|
|
24
23
|
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
25
|
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: i18n
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
27
40
|
description: Utility class for creating and calling service objects.
|
|
28
41
|
email: hargettbenjamin@gmail.com
|
|
29
42
|
executables: []
|
|
@@ -32,7 +45,19 @@ extra_rdoc_files: []
|
|
|
32
45
|
files:
|
|
33
46
|
- README.md
|
|
34
47
|
- lib/telephone.rb
|
|
48
|
+
- lib/telephone/locale/de.yml
|
|
49
|
+
- lib/telephone/locale/en.yml
|
|
50
|
+
- lib/telephone/locale/es.yml
|
|
51
|
+
- lib/telephone/locale/fr.yml
|
|
52
|
+
- lib/telephone/locale/it.yml
|
|
53
|
+
- lib/telephone/locale/ja.yml
|
|
54
|
+
- lib/telephone/locale/ko.yml
|
|
55
|
+
- lib/telephone/locale/nl.yml
|
|
56
|
+
- lib/telephone/locale/pt.yml
|
|
57
|
+
- lib/telephone/locale/ru.yml
|
|
58
|
+
- lib/telephone/locale/zh-CN.yml
|
|
35
59
|
- lib/telephone/service.rb
|
|
60
|
+
- lib/telephone/validators/type_validator.rb
|
|
36
61
|
- lib/telephone/version.rb
|
|
37
62
|
homepage: https://github.com/bharget/telephone
|
|
38
63
|
licenses: []
|
|
@@ -40,7 +65,6 @@ metadata:
|
|
|
40
65
|
bug_tracker_uri: https://github.com/bharget/telephone/issues
|
|
41
66
|
changelog_uri: https://github.com/bharget/telephone/blob/master/CHANGELOG.md
|
|
42
67
|
source_code_uri: https://github.com/bharget/telephone
|
|
43
|
-
post_install_message:
|
|
44
68
|
rdoc_options: []
|
|
45
69
|
require_paths:
|
|
46
70
|
- lib
|
|
@@ -48,7 +72,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
48
72
|
requirements:
|
|
49
73
|
- - ">="
|
|
50
74
|
- !ruby/object:Gem::Version
|
|
51
|
-
version: 2.
|
|
75
|
+
version: 3.2.0
|
|
52
76
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
77
|
requirements:
|
|
54
78
|
- - ">="
|
|
@@ -56,8 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
56
80
|
version: '0'
|
|
57
81
|
requirements:
|
|
58
82
|
- none
|
|
59
|
-
rubygems_version: 3.
|
|
60
|
-
signing_key:
|
|
83
|
+
rubygems_version: 3.6.3
|
|
61
84
|
specification_version: 4
|
|
62
85
|
summary: Utility class for creating and calling service objects.
|
|
63
86
|
test_files: []
|