foobara 0.0.34 → 0.0.35
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/CHANGELOG.md +2 -1
- data/README.md +467 -6
- data/projects/domain/src/domain_mapper.rb +4 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f2cf6302b2afba48242f5cc283a4029ebb6337cccc367a633752cd9866c69d69
|
|
4
|
+
data.tar.gz: 05b4068afedd82c0712e977d9810ebc1bcacb9c06ab90205e1985966f5ece820
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 48722536d6aee98bebaad5934153c5d46223857343292054f6f053c80a7784d187f853514bb3271831c6158056490326731501d2a890d51a386c6626cbd76932
|
|
7
|
+
data.tar.gz: 1371ff6d6b3a81b4cf7228f6cd15a70265955da87dca6bcc580a938217f2443d3add0f8aaad85e705cca00cb101cb8587723360ada4f8a9b765895400d4b44e6
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
<!-- TOC -->
|
|
3
2
|
* [What is/Why Foobara?](#what-iswhy-foobara)
|
|
4
3
|
* [Commands](#commands)
|
|
@@ -19,6 +18,7 @@
|
|
|
19
18
|
* [Async Command Connectors](#async-command-connectors)
|
|
20
19
|
* [Scheduler Command Connectors](#scheduler-command-connectors)
|
|
21
20
|
* [Intermediate Foobara](#intermediate-foobara)
|
|
21
|
+
* [Metadata manifests for discoverability](#metadata-manifests-for-discoverability)
|
|
22
22
|
* [Remote Commands](#remote-commands)
|
|
23
23
|
* [Subcommands](#subcommands)
|
|
24
24
|
* [Custom Errors](#custom-errors)
|
|
@@ -979,34 +979,495 @@ Great! We can now move commands, types, etc, around between systems without need
|
|
|
979
979
|
errors work the same way:
|
|
980
980
|
|
|
981
981
|
```
|
|
982
|
-
|
|
982
|
+
> puts FindCapybara.run(id: "asdf").errors_sentence
|
|
983
|
+
At id: Cannot cast "asdf" to an integer. Expected it to be a Integer, or be a string of digits optionally with a minus sign in front
|
|
983
984
|
```
|
|
984
985
|
|
|
985
986
|
### Subcommands
|
|
986
987
|
|
|
987
|
-
|
|
988
|
+
Inevitably, we'll want one high-level domain operation to be able to invoke another high-level domain operation. And
|
|
989
|
+
because Foobara commands are the public interfaces to our systems/subsystems, we really really want to be able to do
|
|
990
|
+
this when a command needs a behavior from another domain as an implementation detail.
|
|
991
|
+
|
|
992
|
+
Let's create a command that calls another. Remember our `Add` command from earlier?
|
|
993
|
+
Let's implement a contrived Subtract command that is implemented using Add:
|
|
994
|
+
|
|
995
|
+
```ruby
|
|
996
|
+
|
|
997
|
+
class Subtract < Foobara::Command
|
|
998
|
+
inputs do
|
|
999
|
+
operand1 :integer, :required
|
|
1000
|
+
operand2 :integer, :required
|
|
1001
|
+
end
|
|
1002
|
+
|
|
1003
|
+
result :integer
|
|
1004
|
+
|
|
1005
|
+
depends_on Add
|
|
1006
|
+
|
|
1007
|
+
def execute
|
|
1008
|
+
subtract_operands
|
|
1009
|
+
|
|
1010
|
+
difference
|
|
1011
|
+
end
|
|
1012
|
+
|
|
1013
|
+
attr_accessor :difference
|
|
1014
|
+
|
|
1015
|
+
def subtract_operands
|
|
1016
|
+
self.difference = run_subcommand!(Add, operand1:, operand2: -operand2)
|
|
1017
|
+
end
|
|
1018
|
+
end
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
We call our subcommand using `#run_subcommand!`. This will run the command and return the result. If an error occurs,
|
|
1022
|
+
the errors from Add will be appended to our errors for Subtract, causing it to fail.
|
|
1023
|
+
|
|
1024
|
+
Note that we declare that Subtract depends on Add. We do this using `.depends_on`. This helps in a few ways.
|
|
1025
|
+
For one, Subtract.possible_errors can include errors that might happen in Add.
|
|
1026
|
+
This allows us to see a command dependencies using graphing tools and what-not and enforce a unidirectional
|
|
1027
|
+
dependency graph of commands.
|
|
1028
|
+
|
|
1029
|
+
Let's play with it:
|
|
1030
|
+
|
|
1031
|
+
```ruby
|
|
1032
|
+
> Subtract.run!(operand1: 5, operand2: 2)
|
|
1033
|
+
==> 3
|
|
1034
|
+
```
|
|
1035
|
+
|
|
1036
|
+
We get the answer we expected!
|
|
1037
|
+
|
|
1038
|
+
A little bit advanced but let's look at the possible errors for Subtract:
|
|
1039
|
+
|
|
1040
|
+
```irb
|
|
1041
|
+
> Subtract.possible_errors.map(&:key).map(&:to_s).sort
|
|
1042
|
+
==>
|
|
1043
|
+
["add>data.cannot_cast",
|
|
1044
|
+
"add>data.missing_required_attribute",
|
|
1045
|
+
"add>data.operand1.cannot_cast",
|
|
1046
|
+
"add>data.operand1.missing_required_attribute",
|
|
1047
|
+
"add>data.operand2.cannot_cast",
|
|
1048
|
+
"add>data.operand2.missing_required_attribute",
|
|
1049
|
+
"add>data.unexpected_attributes",
|
|
1050
|
+
"data.cannot_cast",
|
|
1051
|
+
"data.missing_required_attribute",
|
|
1052
|
+
"data.operand1.cannot_cast",
|
|
1053
|
+
"data.operand1.missing_required_attribute",
|
|
1054
|
+
"data.operand2.cannot_cast",
|
|
1055
|
+
"data.operand2.missing_required_attribute",
|
|
1056
|
+
"data.unexpected_attributes"]
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
We can see some errors from Add here. Note: we actually know in this case that we don't expect these errors to occur.
|
|
1060
|
+
We could filter these out to improve the information to the outside world/tooling/generators but that's beyond
|
|
1061
|
+
the intermediate level.
|
|
988
1062
|
|
|
989
1063
|
### Custom Errors
|
|
990
1064
|
|
|
1065
|
+
Speaking of errors, an intermediate Foobara skill is defining a custom error.
|
|
1066
|
+
|
|
991
1067
|
#### Input Errors
|
|
992
1068
|
|
|
993
|
-
|
|
1069
|
+
Let's make a DivideByZeroError as an example. First, let's make a command that would use it. Q: can you
|
|
1070
|
+
guess which command we're going to make next? A: Divide!
|
|
1071
|
+
|
|
1072
|
+
```ruby
|
|
1073
|
+
class Divide < Foobara::Command
|
|
1074
|
+
inputs do
|
|
1075
|
+
dividend :integer, :required
|
|
1076
|
+
divisor :integer, :required
|
|
1077
|
+
end
|
|
1078
|
+
|
|
1079
|
+
result :integer
|
|
1080
|
+
|
|
1081
|
+
depends_on Subtract
|
|
1082
|
+
|
|
1083
|
+
def execute
|
|
1084
|
+
initialize_quotient_to_zero
|
|
1085
|
+
make_operands_positive_and_determine_if_result_is_negative
|
|
1086
|
+
|
|
1087
|
+
until dividend_less_than_divisor?
|
|
1088
|
+
increment_quotient
|
|
1089
|
+
subtract_divisor_from_dividend
|
|
1090
|
+
end
|
|
1091
|
+
|
|
1092
|
+
negate_quotient if negative_result?
|
|
1093
|
+
|
|
1094
|
+
quotient
|
|
1095
|
+
end
|
|
1096
|
+
|
|
1097
|
+
attr_accessor :negative_result, :quotient
|
|
1098
|
+
|
|
1099
|
+
def make_operands_positive_and_determine_if_result_is_negative
|
|
1100
|
+
self.negative_result = false
|
|
1101
|
+
|
|
1102
|
+
if dividend < 0
|
|
1103
|
+
self.dividend = -dividend
|
|
1104
|
+
self.negative_result = !negative_result
|
|
1105
|
+
end
|
|
1106
|
+
|
|
1107
|
+
if divisor < 0
|
|
1108
|
+
self.divisor = -divisor
|
|
1109
|
+
self.negative_result = !negative_result
|
|
1110
|
+
end
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
def negate_quotient
|
|
1114
|
+
self.quotient = -quotient
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
def dividend_less_than_divisor?
|
|
1118
|
+
dividend < divisor
|
|
1119
|
+
end
|
|
1120
|
+
|
|
1121
|
+
def negative_result?
|
|
1122
|
+
negative_result
|
|
1123
|
+
end
|
|
1124
|
+
|
|
1125
|
+
def increment_quotient
|
|
1126
|
+
self.quotient += 1
|
|
1127
|
+
end
|
|
1128
|
+
|
|
1129
|
+
def subtract_divisor_from_dividend
|
|
1130
|
+
self.dividend = run_subcommand!(Subtract, operand1: dividend, operand2: divisor)
|
|
1131
|
+
end
|
|
1132
|
+
|
|
1133
|
+
def initialize_quotient_to_zero
|
|
1134
|
+
self.quotient = 0
|
|
1135
|
+
end
|
|
1136
|
+
|
|
1137
|
+
attr_writer :dividend, :divisor
|
|
1138
|
+
|
|
1139
|
+
def dividend
|
|
1140
|
+
@dividend || super
|
|
1141
|
+
end
|
|
1142
|
+
|
|
1143
|
+
def divisor
|
|
1144
|
+
@divisor || super
|
|
1145
|
+
end
|
|
1146
|
+
end
|
|
1147
|
+
```
|
|
1148
|
+
|
|
1149
|
+
This one is pretty long because it has a more complex algorithm. Note how the #execute method
|
|
1150
|
+
has a self-documenting form of the algorithm in it. That is a good best-practice when it comes to commands.
|
|
1151
|
+
In a real project, this would be encapsulating a high-level domain operation. Having various levels
|
|
1152
|
+
of abstraction of the algorithm mixed together can harm our ability to see what the domain
|
|
1153
|
+
operation entails at a high-level.
|
|
1154
|
+
|
|
1155
|
+
Let's play with it:
|
|
1156
|
+
|
|
1157
|
+
```irb
|
|
1158
|
+
> Divide.run!(dividend: 6, divisor: 7)
|
|
1159
|
+
==> 0
|
|
1160
|
+
> Divide.run!(dividend: 8, divisor: 7)
|
|
1161
|
+
==> 1
|
|
1162
|
+
> Divide.run!(dividend: 49, divisor: 7)
|
|
1163
|
+
==> 7
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
We get the expected integer division results. However, if we pass
|
|
1167
|
+
0 as the divisor, it will hang forever.
|
|
1168
|
+
|
|
1169
|
+
Time for our custom error!
|
|
1170
|
+
|
|
1171
|
+
```ruby
|
|
1172
|
+
class Divide < Foobara::Command
|
|
1173
|
+
possible_input_error :divisor, :divide_by_zero, message: "Cannot divide by zero"
|
|
1174
|
+
|
|
1175
|
+
...
|
|
1176
|
+
```
|
|
1177
|
+
|
|
1178
|
+
This is one way we can express a custom error for associated with a specific input.
|
|
1179
|
+
|
|
1180
|
+
Let's try it out!
|
|
1181
|
+
|
|
1182
|
+
```irb
|
|
1183
|
+
> outcome = Divide.run(dividend: 49, divisor: 0)
|
|
1184
|
+
==> #<Foobara::Outcome:0x00007f504d178e38...
|
|
1185
|
+
> outcome.success?
|
|
1186
|
+
==> false
|
|
1187
|
+
> outcome.errors_hash
|
|
1188
|
+
==>
|
|
1189
|
+
{"data.divisor.divide_by_zero"=>
|
|
1190
|
+
{:key=>"data.divisor.divide_by_zero",
|
|
1191
|
+
:path=>[:divisor],
|
|
1192
|
+
:runtime_path=>[],
|
|
1193
|
+
:category=>:data,
|
|
1194
|
+
:symbol=>:divide_by_zero,
|
|
1195
|
+
:message=>"Cannot divide by zero",
|
|
1196
|
+
:context=>{},
|
|
1197
|
+
:is_fatal=>false}}
|
|
1198
|
+
> outcome.errors_sentence
|
|
1199
|
+
==> "Cannot divide by zero"
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
And we can see the error in the command's list of possible errors:
|
|
1203
|
+
|
|
1204
|
+
```irb
|
|
1205
|
+
> Divide.possible_errors.map(&:key).map(&:to_s).grep /zero/
|
|
1206
|
+
==> ["data.divisor.divide_by_zero"]
|
|
1207
|
+
```
|
|
1208
|
+
|
|
1209
|
+
And of course, as expected, tooling has access to information about this error and the command's possible error through manifest
|
|
1210
|
+
metadata:
|
|
1211
|
+
|
|
1212
|
+
```irb
|
|
1213
|
+
> Foobara.manifest[:command][:Divide][:possible_errors]["data.divisor.divide_by_zero"][:error]
|
|
1214
|
+
==> "Divide::DivideByZeroError"
|
|
1215
|
+
> Foobara.manifest[:error][:"Divide::DivideByZeroError"][:parent]
|
|
1216
|
+
==> [:command, "Divide"]
|
|
1217
|
+
```
|
|
1218
|
+
|
|
1219
|
+
There's an alternative way to express these custom errors:
|
|
1220
|
+
|
|
1221
|
+
```ruby
|
|
1222
|
+
class Divide < Foobara::Command
|
|
1223
|
+
class DivideByZeroError < Foobara::DataError
|
|
1224
|
+
def message
|
|
1225
|
+
"Cannot divide by zero"
|
|
1226
|
+
end
|
|
1227
|
+
end
|
|
1228
|
+
|
|
1229
|
+
possible_input_error :divisor, DivideByZeroError
|
|
1230
|
+
```
|
|
1231
|
+
|
|
1232
|
+
Both do the same thing.
|
|
994
1233
|
|
|
995
1234
|
#### Runtime Errors
|
|
996
1235
|
|
|
997
|
-
|
|
1236
|
+
Often, you a command will have to fail due to an error that isn't related to a specific input. For these situations,
|
|
1237
|
+
you want a runtime error. Let's convert our DivideByZeroError to a runtime error just for demonstration purposes:
|
|
1238
|
+
|
|
1239
|
+
```ruby
|
|
1240
|
+
class Divide < Foobara::Command
|
|
1241
|
+
possible_error :divide_by_zero, message: "Cannot divide by zero"
|
|
1242
|
+
|
|
1243
|
+
def execute
|
|
1244
|
+
validate_divisor
|
|
1245
|
+
|
|
1246
|
+
...
|
|
1247
|
+
end
|
|
1248
|
+
|
|
1249
|
+
def validate_divisor
|
|
1250
|
+
if divisor == 0
|
|
1251
|
+
add_runtime_error DivideByZeroError
|
|
1252
|
+
end
|
|
1253
|
+
end
|
|
1254
|
+
|
|
1255
|
+
...
|
|
1256
|
+
```
|
|
1257
|
+
|
|
1258
|
+
And let's try it out:
|
|
1259
|
+
|
|
1260
|
+
```irb
|
|
1261
|
+
> outcome = Divide.run(dividend: 49, divisor: 0)
|
|
1262
|
+
==> #<Foobara::Outcome:0x00007f030fe3b8b8...
|
|
1263
|
+
> outcome.success?
|
|
1264
|
+
==> false
|
|
1265
|
+
> outcome.errors_sentence
|
|
1266
|
+
==> "Cannot divide by zero"
|
|
1267
|
+
> outcome.errors_hash
|
|
1268
|
+
==>
|
|
1269
|
+
{"runtime.divide_by_zero"=>
|
|
1270
|
+
{:key=>"runtime.divide_by_zero",
|
|
1271
|
+
:path=>[],
|
|
1272
|
+
:runtime_path=>[],
|
|
1273
|
+
:category=>:runtime,
|
|
1274
|
+
:symbol=>:divide_by_zero,
|
|
1275
|
+
:message=>"Cannot divide by zero",
|
|
1276
|
+
:context=>{},
|
|
1277
|
+
:is_fatal=>false}}
|
|
1278
|
+
```
|
|
1279
|
+
|
|
1280
|
+
Very similar behavior to before but this time it's a runtime error.
|
|
998
1281
|
|
|
999
1282
|
## Advanced Foobara
|
|
1000
1283
|
|
|
1001
1284
|
### Domain Mappers
|
|
1002
1285
|
|
|
1286
|
+
We should really move our various commands into their proper orgs/domains now for the remaining advanced/expert
|
|
1287
|
+
examples.
|
|
1288
|
+
|
|
1289
|
+
In an integer_math_server.rb file, let's put Add/Subtract/Divide and expose them via HTTP:
|
|
1290
|
+
|
|
1291
|
+
```ruby
|
|
1292
|
+
#!/usr/bin/env ruby
|
|
1293
|
+
|
|
1294
|
+
require "foobara/rack_connector"
|
|
1295
|
+
require "rackup/server"
|
|
1296
|
+
|
|
1297
|
+
module FoobaraDemo
|
|
1298
|
+
foobara_organization!
|
|
1299
|
+
|
|
1300
|
+
module IntegerMath
|
|
1301
|
+
foobara_domain!
|
|
1302
|
+
|
|
1303
|
+
class Add < Foobara::Command
|
|
1304
|
+
...
|
|
1305
|
+
end
|
|
1306
|
+
|
|
1307
|
+
|
|
1308
|
+
command_connector = Foobara::CommandConnectors::Http::Rack.new
|
|
1309
|
+
command_connector.connect(FoobaraDemo)
|
|
1310
|
+
|
|
1311
|
+
Rackup::Server.start(app: command_connector)
|
|
1312
|
+
```
|
|
1313
|
+
|
|
1314
|
+
Note: here we have just connected the entire organization. This is just a lazy way for us to expose all commands.
|
|
1315
|
+
|
|
1316
|
+
Let's do the same for our capybara commands.
|
|
1317
|
+
In a capy_cafe_server.rb file, let's put CreateCapybara/IncrementAge/FindCapybara and expose them via HTTP:
|
|
1318
|
+
|
|
1319
|
+
```ruby
|
|
1320
|
+
#!/usr/bin/env ruby
|
|
1321
|
+
|
|
1322
|
+
require "foobara/local_files_crud_driver"
|
|
1323
|
+
require "foobara/rack_connector"
|
|
1324
|
+
require "rackup/server"
|
|
1325
|
+
|
|
1326
|
+
crud_driver = Foobara::LocalFilesCrudDriver.new
|
|
1327
|
+
Foobara::Persistence.default_crud_driver = crud_driver
|
|
1328
|
+
|
|
1329
|
+
module FoobaraDemo
|
|
1330
|
+
foobara_organization!
|
|
1331
|
+
|
|
1332
|
+
module CapyCafe
|
|
1333
|
+
foobara_domain!
|
|
1334
|
+
|
|
1335
|
+
class Capybara < Foobara::Entity
|
|
1336
|
+
...
|
|
1337
|
+
|
|
1338
|
+
command_connector = Foobara::CommandConnectors::Http::Rack.new
|
|
1339
|
+
|
|
1340
|
+
command_connector.connect(FoobaraDemo)
|
|
1341
|
+
|
|
1342
|
+
Rackup::Server.start(app: command_connector, Port: 9293)
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
We'll start this one on 9293 since it will have the same URL as our integer math server.
|
|
1346
|
+
|
|
1347
|
+
Now, let's come up with a contrived use-case for a domain mapper. Let's say there's some information about capybaras
|
|
1348
|
+
in some other model in some other domain that we could import into our CapyCafe domain.
|
|
1349
|
+
|
|
1350
|
+
Let's code up such a domain/model in yet another file called capy_cafe_import.rb,
|
|
1351
|
+
let's set import our other two domains:
|
|
1352
|
+
|
|
1353
|
+
```ruby
|
|
1354
|
+
#!/usr/bin/env ruby
|
|
1355
|
+
|
|
1356
|
+
require "foobara/remote_imports"
|
|
1357
|
+
|
|
1358
|
+
[9292, 9293].each do |port|
|
|
1359
|
+
Foobara::RemoteImports::ImportCommand.run!(manifest_url: "http://localhost:#{port}/manifest")
|
|
1360
|
+
end
|
|
1361
|
+
```
|
|
1362
|
+
|
|
1363
|
+
And now let's define an Animal model that could be imported into our CapyCafe domain as a Capybara record:
|
|
1364
|
+
|
|
1365
|
+
```ruby
|
|
1366
|
+
module FoobaraDemo
|
|
1367
|
+
module AnimalHouse
|
|
1368
|
+
foobara_domain!
|
|
1369
|
+
|
|
1370
|
+
class Animal < Foobara::Model
|
|
1371
|
+
attributes do
|
|
1372
|
+
first_name :string
|
|
1373
|
+
last_name :string
|
|
1374
|
+
birthday :date
|
|
1375
|
+
species :symbol, one_of: %i[capybara cat tartigrade]
|
|
1376
|
+
end
|
|
1377
|
+
end
|
|
1378
|
+
end
|
|
1379
|
+
end
|
|
1380
|
+
```
|
|
1381
|
+
|
|
1382
|
+
And now let's define a domain mapper that knows how to map an AnimalHouse::Animal to a CapyCafe::Capybara:
|
|
1383
|
+
|
|
1384
|
+
```ruby
|
|
1385
|
+
module FoobaraDemo
|
|
1386
|
+
module CapyCafe
|
|
1387
|
+
foobara_depends_on AnimalHouse
|
|
1388
|
+
|
|
1389
|
+
class AnimalToCapybara < Foobara::DomainMapper
|
|
1390
|
+
from AnimalHouse::Animal
|
|
1391
|
+
to CreateCapybara
|
|
1392
|
+
|
|
1393
|
+
def map(animal)
|
|
1394
|
+
age = birthday_to_age(animal.birthday)
|
|
1395
|
+
|
|
1396
|
+
{
|
|
1397
|
+
name: "#{animal.first_name} #{animal.last_name}",
|
|
1398
|
+
age:
|
|
1399
|
+
}
|
|
1400
|
+
end
|
|
1401
|
+
|
|
1402
|
+
def birthday_to_age(birthday)
|
|
1403
|
+
today = Date.today
|
|
1404
|
+
age = today.year - birthday.year
|
|
1405
|
+
birthday_this_year = Date.new(birthday.year + age, birthday.month, birthday.day)
|
|
1406
|
+
|
|
1407
|
+
today < birthday_this_year ? age - 1 : age
|
|
1408
|
+
end
|
|
1409
|
+
end
|
|
1410
|
+
end
|
|
1411
|
+
end
|
|
1412
|
+
```
|
|
1413
|
+
|
|
1414
|
+
Note: that we have a bit of an unusual architecture here: we are defining CapyCafe commands in two different systems.
|
|
1415
|
+
A point of Foobara is that regardless of how these commands are distributed calling code doesn't change as this
|
|
1416
|
+
distribution changes.
|
|
1417
|
+
|
|
1418
|
+
Normally, we wouldn't make use of a domain mapper in isolation. Like everything else, it should be used in the context
|
|
1419
|
+
of a command. But we can play with it directly:
|
|
1420
|
+
|
|
1421
|
+
```irb
|
|
1422
|
+
|
|
1423
|
+
```
|
|
1424
|
+
|
|
1425
|
+
```ruby
|
|
1426
|
+
class ImportAnimal < Foobara::Command
|
|
1427
|
+
class NotACapybara < Foobara::DataError
|
|
1428
|
+
context species: :symbol, animal: AnimalHouse::Animal
|
|
1429
|
+
|
|
1430
|
+
def message
|
|
1431
|
+
"Can only import a capybara not a #{species}"
|
|
1432
|
+
end
|
|
1433
|
+
end
|
|
1434
|
+
|
|
1435
|
+
inputs animal: AnimalHouse::Animal
|
|
1436
|
+
result Capybara
|
|
1437
|
+
|
|
1438
|
+
possible_input_error :animal, NotACapybara
|
|
1439
|
+
|
|
1440
|
+
depends_on CreateCapybara
|
|
1441
|
+
|
|
1442
|
+
def execute
|
|
1443
|
+
create_capybara
|
|
1444
|
+
|
|
1445
|
+
capybara
|
|
1446
|
+
end
|
|
1447
|
+
|
|
1448
|
+
attr_accessor :capybara
|
|
1449
|
+
|
|
1450
|
+
def validate
|
|
1451
|
+
species = animal.species
|
|
1452
|
+
|
|
1453
|
+
unless species == :capybara
|
|
1454
|
+
add_input_error :animal, NotACapybara, animal: animal, species: species
|
|
1455
|
+
end
|
|
1456
|
+
end
|
|
1457
|
+
|
|
1458
|
+
def create_capybara
|
|
1459
|
+
self.capybara = run_mapped_subcommand!(CreateCapybara, animal)
|
|
1460
|
+
end
|
|
1461
|
+
end
|
|
1462
|
+
```
|
|
1463
|
+
|
|
1003
1464
|
TODO
|
|
1004
1465
|
|
|
1005
1466
|
### Code Generators
|
|
1006
1467
|
|
|
1007
1468
|
#### Generating a new Foobara Ruby project
|
|
1008
1469
|
#### Generating a new Foobara Typescript/React project
|
|
1009
|
-
####
|
|
1470
|
+
#### Generating commands, models, entities, types, domains, organizations, etc...
|
|
1010
1471
|
|
|
1011
1472
|
TODO
|
|
1012
1473
|
|
|
@@ -140,11 +140,15 @@ module Foobara
|
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
def call(from_value)
|
|
143
|
+
from_value = from_type.process_value!(from_value)
|
|
144
|
+
|
|
143
145
|
mapped_value = map(from_value)
|
|
144
146
|
|
|
145
147
|
to_type.process_value!(mapped_value)
|
|
146
148
|
end
|
|
147
149
|
|
|
150
|
+
# TODO: can we make _from_value passed to #initialize instead? That way it doesn't have to be passed around
|
|
151
|
+
# between various helper methods
|
|
148
152
|
def map(_from_value)
|
|
149
153
|
# :nocov:
|
|
150
154
|
raise "subclass repsonsibility"
|