wrapper_based 1.1.0 → 2.0.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/.gitignore +2 -0
- data/.travis.yml +1 -1
- data/README.md +105 -62
- data/examples/acapella.rb +55 -0
- data/examples/background_job.rb +29 -0
- data/examples/dijkstra.rb +27 -17
- data/examples/http_api_client.rb +46 -0
- data/examples/money_transfer.rb +5 -11
- data/examples/toy_shop.rb +12 -17
- data/examples/users_facade.rb +23 -0
- data/lib/wrapper_based.rb +24 -4
- data/lib/wrapper_based/casting.rb +56 -0
- data/lib/wrapper_based/casting/director.rb +28 -0
- data/lib/wrapper_based/casting/pool.rb +22 -0
- data/lib/wrapper_based/casting/type.rb +31 -0
- data/lib/wrapper_based/context.rb +6 -44
- data/lib/wrapper_based/dci.rb +17 -3
- data/lib/wrapper_based/role_unknown.rb +8 -0
- data/lib/wrapper_based/{context/type_casting.rb → type_casting.rb} +3 -9
- data/lib/wrapper_based/version.rb +1 -1
- data/wrapper_based.gemspec +3 -1
- metadata +43 -10
- data/lib/wrapper_based/context/casting.rb +0 -31
- data/lib/wrapper_based/context/casting_director.rb +0 -20
- data/lib/wrapper_based/context/producer.rb +0 -21
- data/lib/wrapper_based/roles.rb +0 -64
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9905f3b4631008847a1ea6998fc79ce2d186727
|
4
|
+
data.tar.gz: bf443b8da55d7e0c32b7cdb99e09a8d47b585901
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2f5bc3f9544c492ed7a5307f3b5a57463e8ef12dcb9972998da421298ecc528e3f215097d33bf8cdc8ebb1275980ee32a77f70d908bec84ea22fbc08d6f89ec2
|
7
|
+
data.tar.gz: 9b42afaff1621d74441b3373ce7879bc2d167959b3fcdea5b7d46d6562a2df02395261b1591406a8f567596ef07daccfc1bc2a580ff359fd6ba072b281155be1
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -17,88 +17,137 @@ And then execute:
|
|
17
17
|
|
18
18
|
$ bundle
|
19
19
|
|
20
|
-
##
|
20
|
+
## DCI::Roles
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
Includes role components to classes.
|
23
|
+
|
24
|
+
### Usage
|
25
25
|
|
26
26
|
```ruby
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
class ApplicationController
|
28
|
+
include DCI::Roles(:current_user, logged: SignsUser)
|
29
|
+
|
30
|
+
before_action -> { with! logged: session }
|
31
|
+
helper_method :logged, :current_user
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def authenticate_user!
|
36
|
+
if logged.out?
|
37
|
+
flash[:error] = "You must be logged in to access this section"
|
38
|
+
redirect_to login_url
|
39
|
+
else
|
40
|
+
with_current_user! logged.user
|
41
|
+
end
|
31
42
|
end
|
32
43
|
end
|
33
44
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
45
|
+
class LoginController < ApplicationController
|
46
|
+
include DCI::Roles(signing: SignsUser, user_params: UserParams, login: HasEncryptedPassword)
|
47
|
+
|
48
|
+
before_action -> { with! signing: session, user_params: params.require(:user) }
|
49
|
+
before_action -> { with! login: signing.by(user_params.login) }
|
38
50
|
|
39
|
-
def
|
40
|
-
|
51
|
+
def new; end
|
52
|
+
|
53
|
+
def create
|
54
|
+
if login.authenticate(user_params.password)
|
55
|
+
signing.in! login
|
56
|
+
redirect_to login, notice: 'Welcome, you are now logged in.'
|
57
|
+
else
|
58
|
+
flash[:danger] = 'Invalid credentials.'
|
59
|
+
render :new, status: :unauthorized
|
60
|
+
end
|
41
61
|
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class LogoutController < ApplicationController
|
65
|
+
include DCI::Roles(signing: SignsUser)
|
42
66
|
|
43
|
-
|
44
|
-
|
67
|
+
before_action -> { with! signing: session }
|
68
|
+
|
69
|
+
def destroy
|
70
|
+
signing.out!
|
71
|
+
redirect_to root_url
|
45
72
|
end
|
46
73
|
end
|
47
74
|
|
48
|
-
class
|
49
|
-
def
|
75
|
+
class SignsUser < Struct.new(:signed)
|
76
|
+
def in!(user)
|
77
|
+
signed[:user_id] = user.id
|
78
|
+
end
|
50
79
|
|
51
|
-
def
|
52
|
-
|
80
|
+
def out!
|
81
|
+
signed.delete(:user_id)
|
82
|
+
@user = nil
|
53
83
|
end
|
54
84
|
|
55
|
-
def
|
56
|
-
|
85
|
+
def out?
|
86
|
+
signed[:user_id].nil? || in?
|
57
87
|
end
|
58
88
|
|
59
|
-
|
89
|
+
def in?
|
90
|
+
!user.nil?
|
91
|
+
end
|
60
92
|
|
61
|
-
def
|
62
|
-
|
63
|
-
map { |neighbor| to.shortest_path_from(neighbor, self) }.
|
64
|
-
min_by { |neighbor_path| city.distance_of neighbor_path }
|
93
|
+
def user
|
94
|
+
@user ||= by(id: signed[:user_id])
|
65
95
|
end
|
66
|
-
end
|
67
|
-
```
|
68
96
|
|
69
|
-
|
97
|
+
def by(**identification)
|
98
|
+
User.find_by identification
|
99
|
+
end
|
70
100
|
|
71
|
-
|
101
|
+
def with(**credentials)
|
102
|
+
User.new credentials
|
103
|
+
end
|
72
104
|
|
73
|
-
|
105
|
+
def up!(user)
|
106
|
+
user.save && user if User.validate_registering(user)
|
107
|
+
end
|
108
|
+
end
|
74
109
|
|
75
|
-
|
110
|
+
module UserParams
|
111
|
+
def registration
|
112
|
+
permit(:username, :email)
|
113
|
+
end
|
76
114
|
|
77
|
-
|
78
|
-
|
115
|
+
def login
|
116
|
+
permit(:username, :email)
|
117
|
+
end
|
118
|
+
|
119
|
+
def security
|
120
|
+
permit(:password, :password_confirmation, :code)
|
121
|
+
end
|
122
|
+
|
123
|
+
def password
|
124
|
+
self[:password]
|
125
|
+
end
|
126
|
+
end
|
79
127
|
```
|
80
128
|
|
81
|
-
|
129
|
+
## DCI::Context
|
82
130
|
|
83
|
-
|
131
|
+
### Usage
|
84
132
|
|
85
133
|
```ruby
|
86
|
-
|
134
|
+
class GiftToy < DCI::Context(:toy, gifter: Buyer, giftee: Recipient)
|
135
|
+
def call(toy = @toy)
|
136
|
+
gift = gifter.buy
|
137
|
+
giftee.receive gift
|
138
|
+
end
|
139
|
+
end
|
87
140
|
```
|
88
141
|
|
89
|
-
|
90
|
-
|
142
|
+
#### Context#to_proc
|
143
|
+
|
144
|
+
Returns call method as a Proc.
|
91
145
|
|
92
146
|
```ruby
|
93
|
-
|
94
|
-
['Batman', Superman', 'Wonder Woman'].each do |founder|
|
95
|
-
add_member.rebind(member: founder).(recruit: 'Supergirl')
|
96
|
-
end
|
147
|
+
['Card Wars', 'Ice Ninja Manual', 'Bacon'].map &GiftToy[gifter: 'Jake', giftee: 'Finn']
|
97
148
|
```
|
98
149
|
|
99
|
-
|
100
|
-
|
101
|
-
### klass#call(**params)
|
150
|
+
#### Context::call
|
102
151
|
|
103
152
|
A shortcut for instantiating the context by passing the collaborators and then executing the context call method.
|
104
153
|
|
@@ -112,21 +161,15 @@ Which is equivalent to:
|
|
112
161
|
Funds::TransferMoney.new(from: @account1, to: @account2, amount: 50).call
|
113
162
|
```
|
114
163
|
|
115
|
-
##
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
def song
|
126
|
-
"Everything is AWESOME!!!"
|
127
|
-
end
|
128
|
-
end
|
129
|
-
```
|
164
|
+
## Context Examples
|
165
|
+
[Money Transfer](examples/money_transfer.rb) |
|
166
|
+
[Djikstra](examples/djikstra.rb) |
|
167
|
+
[Toy Shop](examples/toy_shop.rb) |
|
168
|
+
[Acapella](examples/acapella.rb) |
|
169
|
+
[Background Job](examples/background_job.rb) |
|
170
|
+
[Facade](examples/users_facade.rb) |
|
171
|
+
[HTTP API Client](examples/http_api_client.rb) |
|
172
|
+
[all examples](examples)
|
130
173
|
|
131
174
|
## Development
|
132
175
|
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'wrapper_based'
|
2
|
+
|
3
|
+
module SoftSinging
|
4
|
+
def sing(lyrics)
|
5
|
+
puts "#{name}: #{lyrics.to_s.downcase}"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module LoudSinging
|
10
|
+
def sing(lyrics)
|
11
|
+
puts "#{name}: #{lyrics.to_s.upcase}!"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module NormalSinging
|
16
|
+
def sing(lyrics)
|
17
|
+
puts "#{name}: #{lyrics}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Trio < DCI::Context(normaly: NormalSinging, softly: SoftSinging, loudly: LoudSinging)
|
22
|
+
def initialize(singing = nil, normaly: singing, softly: singing, loudly: singing)
|
23
|
+
super(normaly: normaly, softly: softly, loudly: loudly)
|
24
|
+
end
|
25
|
+
|
26
|
+
def perform(song)
|
27
|
+
song.each_slice(3).each do |first, second, third|
|
28
|
+
softly.sing(first)
|
29
|
+
loudly.sing(second)
|
30
|
+
normaly.sing(third)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Duet < DCI::Context(female: SoftSinging, male: LoudSinging)
|
36
|
+
def initialize(singing = nil, female: singing, male: singing)
|
37
|
+
super(female: female, male: male)
|
38
|
+
end
|
39
|
+
|
40
|
+
def perform(song)
|
41
|
+
song.each_slice(2).each do |first, last|
|
42
|
+
female.sing(first)
|
43
|
+
male.sing(last)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
Singer = Struct.new(:name)
|
49
|
+
singer = Singer['Justin']
|
50
|
+
artist = Trio.new(singer)
|
51
|
+
|
52
|
+
words = ["Na", "Hey", "Baby", "Oh", "Ooh", "You"]
|
53
|
+
|
54
|
+
pop_song = words.permutation.flat_map(&:to_a).shuffle.first(20)
|
55
|
+
artist.perform pop_song
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# https://gist.github.com/fj/c67a81a8ded757ccc075
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module RangeQuery
|
5
|
+
def weekly(since, till = since + 1.week)
|
6
|
+
where(created_at: since...till)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class SendWeeklySummary < DCI::Context(:author, posts: RangeQuery)
|
11
|
+
def initialize(author) super(author: author, posts: author.posts) end
|
12
|
+
|
13
|
+
def perform since: Date.today.at_beginning_of_week, weekly_posts: posts.weekly(since)
|
14
|
+
weekly_digest = PostMailer.summary(weekly_posts, to: @author, title: "Your Posts this week")
|
15
|
+
weekly_digest.deliver unless weekly_posts.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
# load decoded job
|
19
|
+
def init_with(coder)
|
20
|
+
initialize Author.find coder['author_id']
|
21
|
+
end
|
22
|
+
|
23
|
+
# encode job
|
24
|
+
def encode_with(coder)
|
25
|
+
coder['author_id'] = @author.id
|
26
|
+
end
|
27
|
+
|
28
|
+
public :author
|
29
|
+
end
|
data/examples/dijkstra.rb
CHANGED
@@ -2,42 +2,52 @@ require_relative 'dijkstra/data'
|
|
2
2
|
# See https://github.com/RichOrElse/wrapper-based/blob/master/test/dijkstra_test.rb
|
3
3
|
|
4
4
|
module DestinationNode
|
5
|
-
def shortest_path_from(
|
6
|
-
return [self] if equal?
|
7
|
-
|
5
|
+
def shortest_path_from(node, finds_shortest)
|
6
|
+
return [self] if equal? node
|
7
|
+
finds_shortest.path from: node
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
Map
|
11
|
+
module Map
|
12
12
|
def distance_between(a, b)
|
13
13
|
@distances[Edge.new(a, b)]
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
17
|
-
|
16
|
+
def neighbors_of(node)
|
17
|
+
[south_neighbor_of(node), east_neighbor_of(node)].compact
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class FindsDistance < DCI::Context(city: Map)
|
22
|
+
def initialize(city) super(city: city) end
|
23
|
+
|
24
|
+
def between(from, to)
|
25
|
+
city.distance_between(from, to)
|
18
26
|
end
|
19
27
|
|
20
|
-
def
|
21
|
-
|
28
|
+
def of(path)
|
29
|
+
path.each_cons(2).inject(0) { |total_distance, (to, from)| total_distance + between(from, to) }
|
22
30
|
end
|
31
|
+
|
32
|
+
alias_method :call, :of
|
23
33
|
end
|
24
34
|
|
25
|
-
class
|
26
|
-
def initialize(city:, from: city.root, to: city.destination) super end
|
35
|
+
class FindsShortest < DCI::Context(:from, to: DestinationNode, city: Map, road_distance: FindsDistance)
|
36
|
+
def initialize(city:, from: city.root, to: city.destination, road_distance: city) super end
|
27
37
|
|
28
38
|
def distance
|
29
|
-
|
39
|
+
road_distance.of path
|
30
40
|
end
|
31
41
|
|
32
42
|
def path(from: @from)
|
33
|
-
|
43
|
+
city.neighbors_of(from).map(&to_shortest_path).min_by(&road_distance) << from
|
44
|
+
end
|
45
|
+
|
46
|
+
def call(neighbor = @from)
|
47
|
+
to.shortest_path_from(neighbor, self)
|
34
48
|
end
|
35
49
|
|
36
50
|
private
|
37
51
|
|
38
|
-
|
39
|
-
city.neighbors(near: current).
|
40
|
-
map { |neighbor| to.shortest_path_from(neighbor, self) }.
|
41
|
-
min_by { |neighbor_path| city.distance_of neighbor_path }
|
42
|
-
end
|
52
|
+
alias_method :to_shortest_path, :to_proc
|
43
53
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ServiceAPI
|
2
|
+
InvalidToken = Class.new(StandardError)
|
3
|
+
UnknownContact = Class.new(StandardError)
|
4
|
+
|
5
|
+
module Client
|
6
|
+
REQUEST_PATH = Rails.application.secrets.service_api_url + '/api/v2'
|
7
|
+
MAX_PER_PAGE = 100
|
8
|
+
|
9
|
+
def contacts(page: 1, per: MAX_PER_PAGE)
|
10
|
+
fail InvalidToken, 'Please provide valid token.' if service_api_token.blank?
|
11
|
+
url = REQUEST_PATH + '/contacts?' + { token: service_api_token, page: page, per: per }.to_query
|
12
|
+
JSON(RestClient.get url, header: { content_type: :json })
|
13
|
+
rescue RestClient::Unauthorized => error
|
14
|
+
fail InvalidToken, 'Please provide valid token.'
|
15
|
+
end
|
16
|
+
|
17
|
+
def send_message(composition)
|
18
|
+
fail InvalidToken, 'Please provide valid token.' if service_api_token.blank?
|
19
|
+
url = REQUEST_PATH + '/messages?' + { token: service_api_token }.to_query
|
20
|
+
RestClient.post(url, composition.to_json, { content_type: :json, accept: :json }).body
|
21
|
+
rescue RestClient::UnprocessableEntity, RestClient::NotFound => error
|
22
|
+
fail UnknownContact, 'Contact not recognized.'
|
23
|
+
rescue RestClient::Unauthorized => error
|
24
|
+
fail InvalidToken, 'Please provide valid token.'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module MessageComposingRecipient
|
29
|
+
def compose(body)
|
30
|
+
fail UnknownContact, 'Contact not recognized.' if blank?
|
31
|
+
{
|
32
|
+
recipients: ["contact-#{self}"],
|
33
|
+
message: { body: body }
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class SendsContactMessage < DCI::Context(:message, user: ServiceAPI::Client, to: ServiceAPI::MessageComposingRecipient)
|
40
|
+
def call(message = @message)
|
41
|
+
results = user.send_message to.compose(message)
|
42
|
+
return :success, results
|
43
|
+
rescue ServiceAPI::UnknownContact => error
|
44
|
+
return :failure, message: error.message
|
45
|
+
end
|
46
|
+
end
|
data/examples/money_transfer.rb
CHANGED
@@ -21,18 +21,16 @@ module Funds
|
|
21
21
|
end
|
22
22
|
|
23
23
|
class TransferMoney < DCI::Context(:amount, from: SourceAccount, to: DestinationAccount, events: Logging)
|
24
|
-
def initialize(amount: 0, events: [], **accounts)
|
25
|
-
super
|
26
|
-
end
|
24
|
+
def initialize(amount: 0, events: [], **accounts) super end
|
27
25
|
|
28
26
|
def withdraw(amount)
|
29
27
|
from.decrease_balance_by(amount)
|
30
|
-
events.log "Withdrew", amount, from
|
28
|
+
events.log "Withdrew", amount, @from
|
31
29
|
end
|
32
30
|
|
33
31
|
def deposit(amount)
|
34
32
|
to.increase_balance_by(amount)
|
35
|
-
events.log "Deposited", amount, to
|
33
|
+
events.log "Deposited", amount, @to
|
36
34
|
end
|
37
35
|
|
38
36
|
def transfer(amount)
|
@@ -42,13 +40,9 @@ module Funds
|
|
42
40
|
|
43
41
|
def call(amount: @amount)
|
44
42
|
transfer(amount)
|
45
|
-
return :success,
|
43
|
+
return :success, logging: @events, accounts: [@from, @to]
|
46
44
|
rescue Funds::Insufficient => error
|
47
|
-
return :failure,
|
48
|
-
end
|
49
|
-
|
50
|
-
def accounts
|
51
|
-
[@from, @to]
|
45
|
+
return :failure, message: error.message
|
52
46
|
end
|
53
47
|
end
|
54
48
|
end
|
data/examples/toy_shop.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
require 'wrapper_based'
|
2
|
+
|
1
3
|
# Data
|
2
4
|
|
3
5
|
Purchase = Struct.new(:toy, :buyer)
|
4
6
|
Deliver = Struct.new(:toy, :recipient, :purchase, :status)
|
5
7
|
|
6
|
-
#
|
8
|
+
# Interactions
|
7
9
|
|
8
|
-
module
|
10
|
+
module Shopper
|
9
11
|
def buy(toy)
|
10
12
|
Purchase.new toy, self
|
11
13
|
end
|
@@ -19,32 +21,25 @@ end
|
|
19
21
|
|
20
22
|
# Contexts
|
21
23
|
|
22
|
-
class
|
23
|
-
|
24
|
-
purchaser.as Recipient
|
24
|
+
class BuyToy < DCI::Context(shopper: Shopper, recipient: Recipient)
|
25
|
+
def initialize(shopper:, recipient: shopper) super end
|
25
26
|
|
26
|
-
def call(
|
27
|
-
|
28
|
-
|
27
|
+
def call(item)
|
28
|
+
bought = shopper.buy item
|
29
|
+
recipient.receive bought
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
32
|
-
|
33
|
-
class GiftToy < DCI::Context(:gifter, :giftee)
|
34
|
-
gifter.as Buyer
|
35
|
-
giftee.as Recipient
|
36
|
-
|
33
|
+
class GiftToy < DCI::Context(gifter: Shopper, giftee: Recipient)
|
37
34
|
def call(toy)
|
38
35
|
gift = gifter.buy toy
|
39
36
|
giftee.receive gift
|
40
37
|
end
|
41
38
|
end
|
42
39
|
|
43
|
-
|
44
|
-
|
45
|
-
finn_purchase_toy = PurchaseToy[purchaser: 'Finn']
|
40
|
+
finn_purchase_toy = BuyToy[shopper: 'Finn']
|
46
41
|
finn_purchase_toy.call 'Rusty sword'
|
47
42
|
finn_purchase_toy.('Armor of Zeldron')
|
48
43
|
finn_purchase_toy['The Enchiridion']
|
49
44
|
|
50
|
-
['Card Wars', 'Ice Ninja Manual', 'Bacon'].
|
45
|
+
puts ['Card Wars', 'Ice Ninja Manual', 'Bacon'].map &GiftToy[gifter: 'Jake', giftee: 'Finn']
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# See: https://medium.com/kkempin/facade-design-pattern-in-ruby-on-rails-710aa88326f
|
2
|
+
|
3
|
+
class UsersFacade < DCI::Context(:user, :users, :active_users, vip: VipUsersPresenter)
|
4
|
+
def initialize(user:, users: user.class, active_users: users.active, vip: active_users.vip) super end
|
5
|
+
|
6
|
+
def new_user
|
7
|
+
users.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def last_active_users
|
11
|
+
@last_active_users ||= active_users.order(:created_at).limit(10)
|
12
|
+
end
|
13
|
+
|
14
|
+
def vip_users
|
15
|
+
@vip_users ||= vip.users
|
16
|
+
end
|
17
|
+
|
18
|
+
def messages
|
19
|
+
@messages ||= user.messages
|
20
|
+
end
|
21
|
+
|
22
|
+
private :users, :active_users, :vip
|
23
|
+
end
|
data/lib/wrapper_based.rb
CHANGED
@@ -1,10 +1,30 @@
|
|
1
1
|
require "wrapper_based/version"
|
2
2
|
require "wrapper_based/context"
|
3
|
-
require "wrapper_based/
|
4
|
-
require "wrapper_based/
|
5
|
-
require "wrapper_based/
|
6
|
-
require "wrapper_based/
|
3
|
+
require "wrapper_based/casting"
|
4
|
+
require "wrapper_based/casting/type"
|
5
|
+
require "wrapper_based/casting/director"
|
6
|
+
require "wrapper_based/casting/pool"
|
7
7
|
require "wrapper_based/dci"
|
8
|
+
require "wrapper_based/type_casting"
|
9
|
+
require "wrapper_based/role_unknown"
|
8
10
|
|
9
11
|
module WrapperBased
|
12
|
+
def with!(**role_players)
|
13
|
+
role_players.each { |role, player| send :"with_#{role}!", player }
|
14
|
+
self
|
15
|
+
rescue NoMethodError => error
|
16
|
+
raise RoleUnknown.or(error)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
using TypeCasting
|
22
|
+
|
23
|
+
def cast_as(role, actor, &recast)
|
24
|
+
_components[role].as_role_played_by(actor, &recast)
|
25
|
+
end
|
26
|
+
|
27
|
+
def _components
|
28
|
+
@_components ||= {}
|
29
|
+
end
|
10
30
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module WrapperBased
|
2
|
+
RoleValueMissing = Class.new(StandardError)
|
3
|
+
|
4
|
+
class Casting < Module
|
5
|
+
def initialize
|
6
|
+
include WrapperBased
|
7
|
+
end
|
8
|
+
|
9
|
+
def casts(role)
|
10
|
+
role_writer = :"#{role}="
|
11
|
+
|
12
|
+
define_method(:"with_#{role}!") do |value|
|
13
|
+
send role_writer, yield(value)
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
private_reader role
|
18
|
+
private_writer role
|
19
|
+
end
|
20
|
+
|
21
|
+
def casts_as(role, &recast)
|
22
|
+
role_player = :"@#{role}"
|
23
|
+
|
24
|
+
define_method(:"with_#{role}!") do |actor|
|
25
|
+
_components[role] = cast_as(role, actor, &recast)
|
26
|
+
instance_variable_set(role_player, actor)
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
private_reader role
|
31
|
+
private_writer role
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def private_reader(role)
|
37
|
+
define_method(role) do
|
38
|
+
_components.fetch(role) { raise RoleValueMissing, "Role `#{role}' is missing.", caller }
|
39
|
+
end
|
40
|
+
|
41
|
+
private role
|
42
|
+
end
|
43
|
+
|
44
|
+
def private_writer(role)
|
45
|
+
writer = :"#{role}="
|
46
|
+
role_player = :"@#{role}"
|
47
|
+
|
48
|
+
define_method(writer) do |value|
|
49
|
+
_components[role] = value
|
50
|
+
instance_variable_set(role_player, value)
|
51
|
+
end
|
52
|
+
|
53
|
+
private writer
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module WrapperBased
|
2
|
+
TALENTLESS = -> actor { actor }
|
3
|
+
|
4
|
+
class Casting::Director < Struct.new(:casting, :talent_pool)
|
5
|
+
def initialize(talent_pool) super(Casting.new, talent_pool) end
|
6
|
+
|
7
|
+
def adds(role, talent = TALENTLESS)
|
8
|
+
case talent
|
9
|
+
when ::Proc, ::Method, ::Symbol
|
10
|
+
casting.casts role, &talent
|
11
|
+
when ::Class
|
12
|
+
casting.casts role, &talent.method(:new)
|
13
|
+
when ::Module
|
14
|
+
casting.casts_as role, &type_for(role).has(talent).method(:typecast)
|
15
|
+
else
|
16
|
+
throw :wrong_talent_type, [role, talent, "expected Module, Class, Proc, Method or Symbol"]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_proc
|
21
|
+
method(:adds).to_proc
|
22
|
+
end
|
23
|
+
|
24
|
+
def type_for(role)
|
25
|
+
Casting::Type.new(role, talent_pool)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module WrapperBased
|
2
|
+
class Casting::Pool
|
3
|
+
def initialize(wrapper_for)
|
4
|
+
@talent_pool = Hash.new do |cache, type_casting|
|
5
|
+
cache[type_casting] = wrapper_for[*type_casting]
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def casting_for(*supporting_roles, **leading_roles)
|
10
|
+
WrapperBased::Casting::Director.new(@talent_pool).tap do |director|
|
11
|
+
supporting_roles.each(&director)
|
12
|
+
leading_roles.each { |role, talent| director.adds role, talent }
|
13
|
+
end.casting
|
14
|
+
end
|
15
|
+
|
16
|
+
def context_for(*roles, &script)
|
17
|
+
klass = Class.new(Context).include casting_for(*roles)
|
18
|
+
klass.class_exec(&script) if block_given?
|
19
|
+
klass
|
20
|
+
end
|
21
|
+
end # Context class
|
22
|
+
end # WrapperBased module
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module WrapperBased
|
4
|
+
class Casting::Type
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
def initialize(name, talent_pool)
|
8
|
+
@name = name.to_sym
|
9
|
+
@talents = Set.new
|
10
|
+
@lookup = talent_pool
|
11
|
+
end
|
12
|
+
|
13
|
+
def has(talent)
|
14
|
+
@talents << talent
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](*where)
|
19
|
+
@lookup[where]
|
20
|
+
end
|
21
|
+
|
22
|
+
def cast_type(type)
|
23
|
+
self[type, *@talents]
|
24
|
+
end
|
25
|
+
|
26
|
+
def typecast(actor)
|
27
|
+
return actor if @talents.empty?
|
28
|
+
cast_type(actor.class).new actor
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,60 +1,22 @@
|
|
1
1
|
module WrapperBased
|
2
2
|
class Context
|
3
|
-
def initialize(
|
4
|
-
|
5
|
-
rebind where
|
6
|
-
end
|
7
|
-
|
8
|
-
def rebind(**where)
|
9
|
-
where.each { |role, player| public_send :"#{role}=", player }
|
10
|
-
self
|
3
|
+
def initialize(*role_players)
|
4
|
+
with!(*role_players)
|
11
5
|
end
|
12
6
|
|
13
7
|
def to_proc
|
14
8
|
method(:call).to_proc
|
15
9
|
end
|
16
10
|
|
17
|
-
def
|
18
|
-
|
11
|
+
def call(*)
|
12
|
+
raise NotImplementedError, "Context must implement a call method."
|
19
13
|
end
|
20
14
|
|
21
|
-
UnassignedRole = Class.new(StandardError)
|
22
|
-
|
23
15
|
class << self
|
24
16
|
alias_method :[], :new
|
25
17
|
|
26
|
-
def call(
|
27
|
-
new(
|
28
|
-
end
|
29
|
-
|
30
|
-
def cast_as(role, actor)
|
31
|
-
send(role).typecast(actor)
|
32
|
-
end
|
33
|
-
|
34
|
-
protected
|
35
|
-
|
36
|
-
def add_role(role, casting)
|
37
|
-
add_reader_for(role)
|
38
|
-
add_writer_for(role)
|
39
|
-
add_role_to_class role, casting
|
40
|
-
end
|
41
|
-
|
42
|
-
def add_reader_for(role)
|
43
|
-
define_method(role) do
|
44
|
-
@_casting_director_.fetch(role) { raise UnassignedRole, "Role '#{role}' is missing.", caller }
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def add_writer_for(role)
|
49
|
-
role_player = :"@#{role}"
|
50
|
-
define_method(:"#{role}=") do |actor|
|
51
|
-
instance_variable_set(role_player, actor)
|
52
|
-
@_casting_director_.cast_as role, actor
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def add_role_to_class(role, casting)
|
57
|
-
define_singleton_method(role) { casting }
|
18
|
+
def call(*role_players, &blk)
|
19
|
+
new(*role_players).call(&blk)
|
58
20
|
end
|
59
21
|
end # class methods
|
60
22
|
end # Context class
|
data/lib/wrapper_based/dci.rb
CHANGED
@@ -3,9 +3,23 @@ require "type_wrapper"
|
|
3
3
|
module DCI
|
4
4
|
Module = Class.new(TypeWrapper::Module)
|
5
5
|
|
6
|
-
@@
|
6
|
+
@@casting_pool = WrapperBased::Casting::Pool.new(TypeWrapper)
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
class << self
|
9
|
+
def Context(*roles, &block)
|
10
|
+
fail_on_wrong_talent_type { return @@casting_pool.context_for(*roles, &block) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def Roles(*roles)
|
14
|
+
fail_on_wrong_talent_type { return @@casting_pool.casting_for(*roles) }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def fail_on_wrong_talent_type(&blk)
|
20
|
+
key, wrong, expected = catch(:wrong_talent_type, &blk)
|
21
|
+
message = "'#{key}: #{wrong}' has wrong key value type #{wrong.class} (#{expected})"
|
22
|
+
raise TypeError, message, caller
|
23
|
+
end
|
10
24
|
end
|
11
25
|
end
|
@@ -1,23 +1,17 @@
|
|
1
1
|
require 'delegate'
|
2
2
|
|
3
3
|
module WrapperBased
|
4
|
-
module
|
4
|
+
module TypeCasting
|
5
5
|
refine Object do
|
6
6
|
def as_role_played_by(actor)
|
7
|
-
actor
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
refine NilClass do
|
12
|
-
def as_role_played_by(actor)
|
13
|
-
yield
|
7
|
+
yield actor
|
14
8
|
end
|
15
9
|
end
|
16
10
|
|
17
11
|
refine Delegator do
|
18
12
|
def as_role_played_by(actor)
|
19
13
|
return replace_role_player_with(actor) if actor.instance_of?(role_type)
|
20
|
-
yield
|
14
|
+
yield actor
|
21
15
|
end
|
22
16
|
|
23
17
|
def replace_role_player_with(actor)
|
data/wrapper_based.gemspec
CHANGED
@@ -21,9 +21,11 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
spec.required_ruby_version = ">= 2.1.0"
|
23
23
|
|
24
|
-
spec.add_runtime_dependency "type_wrapper", "~> 1.
|
24
|
+
spec.add_runtime_dependency "type_wrapper", "~> 1.3"
|
25
25
|
|
26
26
|
spec.add_development_dependency "bundler", "~> 1.15"
|
27
27
|
spec.add_development_dependency "rake", "~> 10.0"
|
28
28
|
spec.add_development_dependency "minitest", "~> 5.0"
|
29
|
+
spec.add_development_dependency "mocha", "~> 1.2.1"
|
30
|
+
spec.add_development_dependency "activesupport", "4.2.9"
|
29
31
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wrapper_based
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ritchie Paul Buitre
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: type_wrapper
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.3'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
26
|
+
version: '1.3'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +66,34 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '5.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mocha
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.2.1
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.2.1
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activesupport
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 4.2.9
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 4.2.9
|
69
97
|
description:
|
70
98
|
email:
|
71
99
|
- ritchie@richorelse.com
|
@@ -81,18 +109,23 @@ files:
|
|
81
109
|
- Rakefile
|
82
110
|
- bin/console
|
83
111
|
- bin/setup
|
112
|
+
- examples/acapella.rb
|
113
|
+
- examples/background_job.rb
|
84
114
|
- examples/dijkstra.rb
|
85
115
|
- examples/dijkstra/data.rb
|
116
|
+
- examples/http_api_client.rb
|
86
117
|
- examples/money_transfer.rb
|
87
118
|
- examples/toy_shop.rb
|
119
|
+
- examples/users_facade.rb
|
88
120
|
- lib/wrapper_based.rb
|
121
|
+
- lib/wrapper_based/casting.rb
|
122
|
+
- lib/wrapper_based/casting/director.rb
|
123
|
+
- lib/wrapper_based/casting/pool.rb
|
124
|
+
- lib/wrapper_based/casting/type.rb
|
89
125
|
- lib/wrapper_based/context.rb
|
90
|
-
- lib/wrapper_based/context/casting.rb
|
91
|
-
- lib/wrapper_based/context/casting_director.rb
|
92
|
-
- lib/wrapper_based/context/producer.rb
|
93
|
-
- lib/wrapper_based/context/type_casting.rb
|
94
126
|
- lib/wrapper_based/dci.rb
|
95
|
-
- lib/wrapper_based/
|
127
|
+
- lib/wrapper_based/role_unknown.rb
|
128
|
+
- lib/wrapper_based/type_casting.rb
|
96
129
|
- lib/wrapper_based/version.rb
|
97
130
|
- wrapper_based.gemspec
|
98
131
|
homepage: https://github.com/RichOrElse/wrapper-based/
|
@@ -115,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
148
|
version: '0'
|
116
149
|
requirements: []
|
117
150
|
rubyforge_project:
|
118
|
-
rubygems_version: 2.6.
|
151
|
+
rubygems_version: 2.6.13
|
119
152
|
signing_key:
|
120
153
|
specification_version: 4
|
121
154
|
summary: Wrapper Based DCI framework for OOP done right.
|
@@ -1,31 +0,0 @@
|
|
1
|
-
require 'set'
|
2
|
-
|
3
|
-
module WrapperBased
|
4
|
-
class Context::Casting
|
5
|
-
attr_reader :name
|
6
|
-
|
7
|
-
def initialize(name, wrappers)
|
8
|
-
@name = name.to_sym
|
9
|
-
@casting = Set.new
|
10
|
-
@wrappers = wrappers
|
11
|
-
end
|
12
|
-
|
13
|
-
def as(extention)
|
14
|
-
@casting << extention
|
15
|
-
self
|
16
|
-
end
|
17
|
-
|
18
|
-
def wrapper_for(*args)
|
19
|
-
@wrappers[args]
|
20
|
-
end
|
21
|
-
|
22
|
-
def cast_type(type)
|
23
|
-
wrapper_for type, *@casting
|
24
|
-
end
|
25
|
-
|
26
|
-
def typecast(actor)
|
27
|
-
return actor if @casting.empty?
|
28
|
-
cast_type(actor.class).new actor
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
require 'forwardable'
|
2
|
-
|
3
|
-
module WrapperBased
|
4
|
-
class Context::CastingDirector
|
5
|
-
extend Forwardable
|
6
|
-
using Context::TypeCasting
|
7
|
-
|
8
|
-
def initialize(context_class)
|
9
|
-
@casts = {}
|
10
|
-
@re = context_class
|
11
|
-
end
|
12
|
-
|
13
|
-
def cast_as(role, actor)
|
14
|
-
@casts[role] = @casts[role].as_role_played_by(actor) { recast_as(role, actor) }
|
15
|
-
end
|
16
|
-
|
17
|
-
def_delegator :@re, :cast_as, :recast_as
|
18
|
-
def_delegators :@casts, :fetch, :[]
|
19
|
-
end
|
20
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module WrapperBased
|
2
|
-
class Context
|
3
|
-
class Producer
|
4
|
-
def initialize(wrapper_for)
|
5
|
-
@wrapper_cache = Hash.new do |cache, type_casting|
|
6
|
-
cache[type_casting] = wrapper_for[*type_casting]
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
def produce(*supporting, **leading, &script)
|
11
|
-
wrappers = @wrapper_cache
|
12
|
-
roles = supporting.map(&:to_sym) | leading.keys
|
13
|
-
Class.new(Context) do
|
14
|
-
roles.each { |role| add_role role, Casting.new(role, wrappers) }
|
15
|
-
leading.each_pair { |role, trait| send(role).as trait }
|
16
|
-
class_eval(&script) unless script.nil?
|
17
|
-
end
|
18
|
-
end # produce method
|
19
|
-
end # Producer class
|
20
|
-
end # Context class
|
21
|
-
end # WrapperBased module
|
data/lib/wrapper_based/roles.rb
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
module WrapperBased
|
2
|
-
class Roles < Module
|
3
|
-
Unassigned = Class.new(StandardError)
|
4
|
-
|
5
|
-
def initialize(wrappers, *roles, **where, &block)
|
6
|
-
@casting_agent = (where.keys | roles.map(&:to_sym)).inject {|cast, role| cast[role] = Casting.new(role, wrappers) }
|
7
|
-
@casting_agent.keys.each { |role| add_role role }
|
8
|
-
where.each_pair {|role, trait| @casting[role].as trait }
|
9
|
-
define_casting_director(@casting_agent)
|
10
|
-
include InstanceMethods
|
11
|
-
end
|
12
|
-
|
13
|
-
def cast_as role, actor
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
def defing_casting_director(casting_agent)
|
18
|
-
define_method(:_casting_director_) { @_casting_director ||= CastingDirector.new(casting_agent) }
|
19
|
-
end
|
20
|
-
|
21
|
-
def cast_as(role, actor)
|
22
|
-
@casting[role].typecast actor
|
23
|
-
end
|
24
|
-
|
25
|
-
def add_role(role)
|
26
|
-
add_reader_for(role)
|
27
|
-
add_writer_for(role)
|
28
|
-
end
|
29
|
-
|
30
|
-
def add_reader_for(role)
|
31
|
-
define_method(role) do
|
32
|
-
_casting_director_.fetch(role) { raise Roles::Unassigned, "Role '#{role}' is missing.", caller }
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def add_writer_for(role)
|
37
|
-
role_player = :"@#{role}"
|
38
|
-
define_method(:"#{role}=") do |actor|
|
39
|
-
instance_variable_set(role_player, actor)
|
40
|
-
_casting_director_.cast_as role, actor
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def add_role_to_class(role, casting)
|
45
|
-
define_singleton_method(role) { casting }
|
46
|
-
end
|
47
|
-
|
48
|
-
def define_casting_director
|
49
|
-
roles = self
|
50
|
-
end
|
51
|
-
|
52
|
-
def included(base)
|
53
|
-
base.extend ClassMethods
|
54
|
-
end
|
55
|
-
|
56
|
-
module InstanceMethods
|
57
|
-
def rebind(**where, &block)
|
58
|
-
where.each { |role, player| public_send :"#{role}=", player }
|
59
|
-
instance_eval &block if block_given?
|
60
|
-
self
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|