foobara 0.5.8 → 0.5.10
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 +8 -0
- data/lib/foobara/all.rb +7 -0
- data/projects/command/lib/foobara/command.rb +2 -0
- data/projects/command/src/command_pattern_implementation.rb +0 -1
- data/projects/command_connectors/src/command_connector.rb +15 -4
- data/projects/command_connectors/src/transformed_command.rb +6 -1
- data/projects/command_connectors/src/transformers/auth_errors_transformer.rb +1 -1
- data/projects/domain_mapper/src/domain_mapper.rb +1 -3
- data/projects/entities_plumbing/lib/foobara/entities_plumbing.rb +25 -6
- data/projects/entities_plumbing/spec/spec_helper.rb +1 -0
- data/projects/entities_plumbing/spec/transaction_spec.rb +534 -0
- data/projects/entities_plumbing/src/extensions/{authenticator.rb → authenticator_methods.rb} +1 -1
- data/projects/manifest/spec/spec_helper.rb +1 -0
- data/projects/model_plumbing/lib/foobara/model_plumbing.rb +1 -0
- data/projects/model_plumbing/src/domain_mapper_extension.rb +13 -0
- data/projects/typesystem/projects/builtin_types/src/attributes/supported_transformers/ignore_unexpected_attributes.rb +25 -0
- data/projects/typesystem/projects/project/src/foobara.rb +4 -0
- data/projects/typesystem/projects/type_declarations/src/type_builder.rb +17 -3
- data/projects/typesystem/spec/builtin_types/attributes_spec.rb +15 -0
- data/version.rb +1 -1
- metadata +6 -3
- /data/projects/{command/src → entities_plumbing/src/extensions}/command_pattern_implementation/concerns/entities.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 23083c50349b64586fcc01d57460032c08ab51eaf90af20591a49dfa16c477f2
|
|
4
|
+
data.tar.gz: '04495cb4fbf6f6c93bc24da26a7f889792a4b17498a04416b539b107b0aa2158'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 23af9c5f5cd3ac5bdfe282151b13d412377c8ff9b9fa87f2a9093e3bbe6d748d6e40845cdd7ef5062710e8db892c28dcccb6e8c358f087a68e785ef9953d6a7b
|
|
7
|
+
data.tar.gz: 3669d063aa1f94ff023464841e9b5cab65fafb82290aeaae170564d13aa3659f6b012fc5ec71471f781e6b236f78e33aa5643d465aa0687f9bfcabfe4df2dfaf
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
# [0.5.10] - 2026-04-17
|
|
2
|
+
|
|
3
|
+
- Add a ignore_unexpected_attributes transformer to builtin attributes type
|
|
4
|
+
|
|
5
|
+
# [0.5.9] - 2026-03-06
|
|
6
|
+
|
|
7
|
+
- Do not require an allowed rule for builtin-in commands when using requires_allowed_rule: true
|
|
8
|
+
|
|
1
9
|
# [0.5.8] - 2026-03-04
|
|
2
10
|
|
|
3
11
|
- Provide a way for allowed rules to be declared independently of connecting commands
|
data/lib/foobara/all.rb
CHANGED
|
@@ -11,8 +11,15 @@ module Foobara
|
|
|
11
11
|
# TODO: delete this and make Rubocop exception in .rubocop.yml
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
require "foobara/delegate"
|
|
15
|
+
require "foobara/state_machine"
|
|
16
|
+
require "foobara/builtin_types"
|
|
17
|
+
require "foobara/domain"
|
|
18
|
+
require "foobara/command"
|
|
19
|
+
# TODO: make it so these are fully decoupled, optional, and not required here
|
|
14
20
|
require "foobara/model_attribute_helpers"
|
|
15
21
|
require "foobara/model_plumbing"
|
|
22
|
+
require "foobara/entities_plumbing"
|
|
16
23
|
# Shouldn't this be optional? Maybe use autoload feature somehow?
|
|
17
24
|
require "foobara/in_memory_crud_driver"
|
|
18
25
|
require "foobara/domain_mapper"
|
|
@@ -30,7 +30,6 @@ module Foobara
|
|
|
30
30
|
include CommandPatternImplementation::Concerns::Callbacks
|
|
31
31
|
include CommandPatternImplementation::Concerns::StateMachine
|
|
32
32
|
include CommandPatternImplementation::Concerns::Transactions
|
|
33
|
-
include CommandPatternImplementation::Concerns::Entities
|
|
34
33
|
include CommandPatternImplementation::Concerns::Subcommands
|
|
35
34
|
include CommandPatternImplementation::Concerns::DomainMappers
|
|
36
35
|
include CommandPatternImplementation::Concerns::Reflection
|
|
@@ -413,8 +413,6 @@ module Foobara
|
|
|
413
413
|
raise NoCommandFoundError.new(message: "Could not find command registered for #{full_command_name}")
|
|
414
414
|
# :nocov:
|
|
415
415
|
end
|
|
416
|
-
|
|
417
|
-
transformed_command_class
|
|
418
416
|
else
|
|
419
417
|
action = case action
|
|
420
418
|
when "describe_type", "manifest", "describe_command"
|
|
@@ -433,8 +431,15 @@ module Foobara
|
|
|
433
431
|
command_class = find_builtin_command_class(command_name)
|
|
434
432
|
full_command_name = command_class.full_command_name
|
|
435
433
|
|
|
436
|
-
transformed_command_from_name(full_command_name) ||
|
|
434
|
+
transformed_command_class = transformed_command_from_name(full_command_name) ||
|
|
435
|
+
transform_command_class(command_class)
|
|
436
|
+
|
|
437
|
+
if transformed_command_class < TransformedCommand
|
|
438
|
+
transformed_command_class.builtin = true
|
|
439
|
+
end
|
|
437
440
|
end
|
|
441
|
+
|
|
442
|
+
transformed_command_class
|
|
438
443
|
end
|
|
439
444
|
|
|
440
445
|
def request_to_command_inputs(request)
|
|
@@ -640,8 +645,14 @@ module Foobara
|
|
|
640
645
|
|
|
641
646
|
def run_command(request)
|
|
642
647
|
command = request.command
|
|
648
|
+
command_class = request.command_class
|
|
643
649
|
|
|
644
|
-
if requires_allowed_rule &&
|
|
650
|
+
if requires_allowed_rule &&
|
|
651
|
+
command.is_a?(TransformedCommand) &&
|
|
652
|
+
# The != false looks odd and must be that we want to distinguish between the default (nil)
|
|
653
|
+
# and explicitly not requiring auth
|
|
654
|
+
command.class.requires_authentication != false &&
|
|
655
|
+
!command_class.builtin?
|
|
645
656
|
unless command.allowed_rule
|
|
646
657
|
raise NoAllowedRuleGivenError,
|
|
647
658
|
"Must connect #{command.full_command_name} with an `allowed_if:` " \
|
|
@@ -21,7 +21,8 @@ module Foobara
|
|
|
21
21
|
:requires_authentication,
|
|
22
22
|
:authenticator,
|
|
23
23
|
:subclassed_in_namespace,
|
|
24
|
-
:suffix
|
|
24
|
+
:suffix,
|
|
25
|
+
:builtin
|
|
25
26
|
|
|
26
27
|
def subclass(
|
|
27
28
|
command_class,
|
|
@@ -546,6 +547,10 @@ module Foobara
|
|
|
546
547
|
end
|
|
547
548
|
end
|
|
548
549
|
|
|
550
|
+
def builtin?
|
|
551
|
+
builtin
|
|
552
|
+
end
|
|
553
|
+
|
|
549
554
|
private
|
|
550
555
|
|
|
551
556
|
def processors_to_manifest_symbols(processors)
|
|
@@ -12,7 +12,7 @@ module Foobara
|
|
|
12
12
|
map = map.merge(possible_error.key.to_s => possible_error)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
if transformed_command.allowed_rule
|
|
15
|
+
if transformed_command.allowed_rule && transformed_command.allowed_rule.symbol != :always
|
|
16
16
|
possible_error = PossibleError.new(CommandConnector::NotAllowedError)
|
|
17
17
|
map = map.merge(possible_error.key.to_s => possible_error)
|
|
18
18
|
end
|
|
@@ -95,9 +95,7 @@ module Foobara
|
|
|
95
95
|
def object_to_type(object)
|
|
96
96
|
if object
|
|
97
97
|
if object.is_a?(::Class)
|
|
98
|
-
if object < Foobara::
|
|
99
|
-
object.model_type
|
|
100
|
-
elsif object < Foobara::Command
|
|
98
|
+
if object < Foobara::Command
|
|
101
99
|
object.inputs_type
|
|
102
100
|
else
|
|
103
101
|
domain.foobara_type_from_declaration(object)
|
|
@@ -1,12 +1,31 @@
|
|
|
1
|
-
require "foobara/command_connectors"
|
|
2
|
-
|
|
3
1
|
module Foobara
|
|
4
2
|
module EntitiesPlumbing
|
|
3
|
+
class << self
|
|
4
|
+
def install!
|
|
5
|
+
CommandPatternImplementation.include CommandPatternImplementation::Concerns::Entities
|
|
6
|
+
|
|
7
|
+
if Foobara.project_installed?("command_connectors")
|
|
8
|
+
# :nocov:
|
|
9
|
+
install_command_connector_extension
|
|
10
|
+
# :nocov:
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def new_project_added(new_project)
|
|
15
|
+
if new_project.symbol == "command_connectors"
|
|
16
|
+
install_command_connector_extension
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def install_command_connector_extension
|
|
21
|
+
CommandConnector.singleton_class.prepend(
|
|
22
|
+
Foobara::EntitiesPlumbing::CommandConnectorsExtension::ClassMethods
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
CommandConnector::Authenticator.include CommandConnector::AuthenticatorMethods
|
|
26
|
+
end
|
|
27
|
+
end
|
|
5
28
|
end
|
|
6
29
|
end
|
|
7
30
|
|
|
8
31
|
Foobara.project("entities_plumbing", project_path: "#{__dir__}/../..")
|
|
9
|
-
|
|
10
|
-
Foobara::CommandConnector.singleton_class.prepend(
|
|
11
|
-
Foobara::EntitiesPlumbing::CommandConnectorsExtension::ClassMethods
|
|
12
|
-
)
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
RSpec.describe Foobara::CommandPatternImplementation::Concerns::Entities do
|
|
2
|
+
describe "#run" do
|
|
3
|
+
after do
|
|
4
|
+
Foobara.reset_alls
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
let(:user_base) do
|
|
8
|
+
Foobara::Persistence::EntityBase.new(
|
|
9
|
+
"user_base",
|
|
10
|
+
entity_attributes_crud_driver: Foobara::Persistence::CrudDrivers::InMemory.new
|
|
11
|
+
).tap do |base|
|
|
12
|
+
Foobara::Persistence.register_base(base)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
let(:read_command) do
|
|
17
|
+
stub_class(:ReadEmployee, Foobara::Command) do
|
|
18
|
+
# TODO: does this work with Employee instead of :Employee ?
|
|
19
|
+
inputs employee: { type: :Employee, required: true }
|
|
20
|
+
|
|
21
|
+
load_all
|
|
22
|
+
|
|
23
|
+
result Employee
|
|
24
|
+
|
|
25
|
+
def execute
|
|
26
|
+
employee
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
let(:employee_id) do
|
|
32
|
+
Foobara::Persistence.transaction(Employee, User, mode: :use_existing) do
|
|
33
|
+
Employee.all[0].id
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
before do
|
|
38
|
+
Foobara::Persistence.default_crud_driver = Foobara::Persistence::CrudDrivers::InMemory.new
|
|
39
|
+
|
|
40
|
+
stub_class :Base, Foobara::Entity do
|
|
41
|
+
abstract
|
|
42
|
+
|
|
43
|
+
attributes id: :integer
|
|
44
|
+
|
|
45
|
+
primary_key :id
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
stub_class :User, Base do
|
|
49
|
+
attributes name: { type: :string, required: true }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
Foobara::Persistence.register_entity(user_base, User, table_name: "users")
|
|
53
|
+
|
|
54
|
+
stub_class(:Person, Base) do
|
|
55
|
+
abstract
|
|
56
|
+
|
|
57
|
+
attributes user: User
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# TODO: how to support self-referential models??
|
|
61
|
+
stub_class(:Applicant, Person) do
|
|
62
|
+
attributes do
|
|
63
|
+
is_active :boolean, default: true
|
|
64
|
+
friends [User]
|
|
65
|
+
attrs do
|
|
66
|
+
foo :symbol
|
|
67
|
+
bar [:integer]
|
|
68
|
+
duckfoo :duck
|
|
69
|
+
duckbar [:duck]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
stub_class(:Package, Base) do
|
|
75
|
+
attributes applicants: [Applicant],
|
|
76
|
+
is_active: :boolean
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
stub_class(:Assignment, Base) do
|
|
80
|
+
attributes package: Package
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
stub_class(:Employee, Person) do
|
|
84
|
+
attributes do
|
|
85
|
+
assignments [Assignment], default: []
|
|
86
|
+
past_assignments [Assignment]
|
|
87
|
+
priority_assignment Assignment
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
association :past_users, "past_assignments.#.package.applicants.#.user"
|
|
91
|
+
association :priority_package, :"priority_assignment.package"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
applicant_users = []
|
|
95
|
+
employee_users = []
|
|
96
|
+
|
|
97
|
+
User.transaction do
|
|
98
|
+
5.times do |i|
|
|
99
|
+
applicant_users << User.create(name: "applicant user#{i}")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
5.times do |i|
|
|
103
|
+
employee_users << User.create(name: "employee user#{i}")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
Foobara::Persistence.transaction(Employee, User) do
|
|
108
|
+
applicants = []
|
|
109
|
+
employees = []
|
|
110
|
+
|
|
111
|
+
5.times do |i|
|
|
112
|
+
applicants << Applicant.create(user: applicant_users[i])
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
5.times do |i|
|
|
116
|
+
employees << Employee.create(user: employee_users[i])
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
7.times do |i|
|
|
120
|
+
package = Package.create(is_active: i < 3, applicants: [applicants[i % 3]])
|
|
121
|
+
|
|
122
|
+
assignment = Assignment.create(package:)
|
|
123
|
+
|
|
124
|
+
if i < 5
|
|
125
|
+
employees[i % 4].assignments += [assignment]
|
|
126
|
+
|
|
127
|
+
if i.even?
|
|
128
|
+
employees[(i % 4) + 1].assignments += [assignment]
|
|
129
|
+
end
|
|
130
|
+
else
|
|
131
|
+
employees[0].past_assignments ||= []
|
|
132
|
+
employees[0].past_assignments += [assignment]
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it "can find the record" do
|
|
139
|
+
expect(read_command.run!(employee: employee_id).id).to eq(employee_id)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
context "with multiple records" do
|
|
143
|
+
let(:read_command) do
|
|
144
|
+
stub_class(:ReadEmployee, Foobara::Command) do
|
|
145
|
+
# TODO: does this work with Employee instead of :Employee ?
|
|
146
|
+
inputs employee: { type: :Employee, required: true },
|
|
147
|
+
employee_array: [Employee]
|
|
148
|
+
|
|
149
|
+
load_all
|
|
150
|
+
|
|
151
|
+
result [Employee]
|
|
152
|
+
|
|
153
|
+
def execute
|
|
154
|
+
[employee, *employee_array]
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
let!(:employee2_id) do
|
|
160
|
+
Employee.transaction do
|
|
161
|
+
Employee.create
|
|
162
|
+
end.id
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
let!(:employee3_id) do
|
|
166
|
+
Employee.transaction do
|
|
167
|
+
Employee.create
|
|
168
|
+
end.id
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "can find the records" do
|
|
172
|
+
inputs = { employee: employee_id, employee_array: [employee2_id, employee3_id] }
|
|
173
|
+
expect(read_command.run!(inputs).map(&:id)).to eq([employee_id, employee2_id, employee3_id])
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
context "when given primary key for record that doesnt exist" do
|
|
178
|
+
it "is not success" do
|
|
179
|
+
outcome = read_command.run(employee: 100)
|
|
180
|
+
expect(outcome).to_not be_success
|
|
181
|
+
|
|
182
|
+
errors = outcome.errors
|
|
183
|
+
|
|
184
|
+
expect(errors.size).to eq(1)
|
|
185
|
+
|
|
186
|
+
error = errors.first
|
|
187
|
+
|
|
188
|
+
expect(error.class.name).to eq("ReadEmployee::EmployeeNotFoundError")
|
|
189
|
+
expect(error.key).to eq("runtime.employee_not_found")
|
|
190
|
+
|
|
191
|
+
expect(error.symbol).to be(:employee_not_found)
|
|
192
|
+
|
|
193
|
+
context = error.context
|
|
194
|
+
|
|
195
|
+
expect(context[:entity_class]).to eq("Employee")
|
|
196
|
+
expect(context[:criteria]).to eq(100)
|
|
197
|
+
expect(context[:data_path]).to eq("employee")
|
|
198
|
+
|
|
199
|
+
expect(error.entity_class).to eq("Employee")
|
|
200
|
+
expect(error.criteria).to eq(100)
|
|
201
|
+
expect(error.data_path).to eq("employee")
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
context "when given non-castable primary key" do
|
|
206
|
+
it "is not success" do
|
|
207
|
+
outcome = read_command.run(employee: "asdf")
|
|
208
|
+
expect(outcome).to_not be_success
|
|
209
|
+
|
|
210
|
+
errors = outcome.errors
|
|
211
|
+
|
|
212
|
+
expect(errors.size).to eq(1)
|
|
213
|
+
|
|
214
|
+
error = errors.first
|
|
215
|
+
|
|
216
|
+
expect(error.key).to eq("data.employee.cannot_cast")
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
context "when input is good but a different runtime error occurs" do
|
|
221
|
+
let(:read_command) do
|
|
222
|
+
error_class = stub_class(:SomeRuntimeError, Foobara::RuntimeError) do
|
|
223
|
+
class << self
|
|
224
|
+
def context_type_declaration
|
|
225
|
+
{}
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
stub_class(:ReadEmployee, Foobara::Command) do
|
|
231
|
+
# TODO: does this work with Employee instead of :Employee ?
|
|
232
|
+
inputs employee: { type: :Employee, required: true }
|
|
233
|
+
|
|
234
|
+
load_all
|
|
235
|
+
|
|
236
|
+
possible_error error_class
|
|
237
|
+
|
|
238
|
+
result Employee
|
|
239
|
+
|
|
240
|
+
define_method :execute do
|
|
241
|
+
employee.assignments = []
|
|
242
|
+
add_runtime_error(error_class.new(message: "asdf", context: {}))
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
it "is not success and rolls back" do
|
|
248
|
+
old_assignments = Foobara::Persistence.transaction(Employee, User, mode: :use_existing) do
|
|
249
|
+
Employee.load(employee_id).assignments
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
expect(old_assignments).to_not be_empty
|
|
253
|
+
|
|
254
|
+
outcome = read_command.run(employee: employee_id)
|
|
255
|
+
expect(outcome).to_not be_success
|
|
256
|
+
|
|
257
|
+
errors = outcome.error_collection
|
|
258
|
+
|
|
259
|
+
expect(errors.size).to eq(1)
|
|
260
|
+
|
|
261
|
+
error = errors.first
|
|
262
|
+
|
|
263
|
+
expect(error.class.name).to eq("SomeRuntimeError")
|
|
264
|
+
|
|
265
|
+
Foobara::Persistence.transaction(Employee, User, mode: :use_existing) do
|
|
266
|
+
expect(Employee.load(employee_id).assignments).to eq(old_assignments)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
describe "manifest" do
|
|
272
|
+
it "includes entity dependencies" do
|
|
273
|
+
expect(Foobara.manifest[:type][:Employee][:deep_depends_on]).to eq(
|
|
274
|
+
[
|
|
275
|
+
"Assignment",
|
|
276
|
+
"User",
|
|
277
|
+
"Package",
|
|
278
|
+
"Applicant"
|
|
279
|
+
]
|
|
280
|
+
)
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
describe "create command" do
|
|
285
|
+
context "with entity class as inputs" do
|
|
286
|
+
let(:create_command) do
|
|
287
|
+
stub_class(:CreateUser, Foobara::Command) do
|
|
288
|
+
inputs User.attributes_type
|
|
289
|
+
result User
|
|
290
|
+
|
|
291
|
+
# TODO: automatically include result type if it's an entity to avoid this in such cases
|
|
292
|
+
depends_on_entities User
|
|
293
|
+
|
|
294
|
+
def execute
|
|
295
|
+
create_user
|
|
296
|
+
|
|
297
|
+
user
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
attr_accessor :user
|
|
301
|
+
|
|
302
|
+
def create_user
|
|
303
|
+
self.user = User.create(inputs)
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
it "can create an user" do
|
|
309
|
+
expect(create_command.types_depended_on).to include(User.entity_type)
|
|
310
|
+
command = create_command.new(name: "Some Name")
|
|
311
|
+
outcome = command.run
|
|
312
|
+
|
|
313
|
+
expect(outcome).to be_success
|
|
314
|
+
expect(command.user).to be_a(User)
|
|
315
|
+
|
|
316
|
+
result = outcome.result
|
|
317
|
+
expect(result.name).to eq("Some Name")
|
|
318
|
+
expect(result.id).to be_a(Integer)
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
context "with normal attributes inputs" do
|
|
323
|
+
let(:create_command) do
|
|
324
|
+
stub_class(:CreateUser, Foobara::Command) do
|
|
325
|
+
# TODO: does this work with User instead of :User ?
|
|
326
|
+
# We can't come up with a cleaner way to do this?
|
|
327
|
+
inputs user: User
|
|
328
|
+
result User.entity_type
|
|
329
|
+
|
|
330
|
+
def execute
|
|
331
|
+
user
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
it "can create an user" do
|
|
337
|
+
command = create_command.new(user: { name: "Some Name" })
|
|
338
|
+
outcome = Foobara::Persistence.transaction(Employee, User, mode: :use_existing) do
|
|
339
|
+
command.run
|
|
340
|
+
end
|
|
341
|
+
expect(outcome).to be_success
|
|
342
|
+
expect(command.user).to be_a(User)
|
|
343
|
+
|
|
344
|
+
result = outcome.result
|
|
345
|
+
expect(result.name).to eq("Some Name")
|
|
346
|
+
expect(result.id).to be_a(Integer)
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
describe "update atom command" do
|
|
352
|
+
context "with helper method result for inputs" do
|
|
353
|
+
let(:update_command) do
|
|
354
|
+
stub_class(:UpdateApplicant, Foobara::Command) do
|
|
355
|
+
# TODO: does this work with User instead of :User ?
|
|
356
|
+
# We can't come up with a cleaner way to do this?
|
|
357
|
+
inputs Applicant.attributes_for_atom_update(require_primary_key: true)
|
|
358
|
+
result Applicant # seems like we should just use nil?
|
|
359
|
+
|
|
360
|
+
def execute
|
|
361
|
+
update_applicant
|
|
362
|
+
|
|
363
|
+
applicant
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
attr_accessor :applicant
|
|
367
|
+
|
|
368
|
+
def load_records
|
|
369
|
+
self.applicant = Applicant.load(id)
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def update_applicant
|
|
373
|
+
inputs.each_pair do |attribute_name, value|
|
|
374
|
+
applicant.write_attribute(attribute_name, value)
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
let(:applicant) { Applicant.create(user:, is_active: true) }
|
|
381
|
+
let(:user) { User.create(name: "first user") }
|
|
382
|
+
|
|
383
|
+
it "can update an applicant" do
|
|
384
|
+
applicant_id = Applicant.transaction { applicant }.id
|
|
385
|
+
|
|
386
|
+
Applicant.transaction do
|
|
387
|
+
expect {
|
|
388
|
+
update_command.run!(id: applicant_id, is_active: false)
|
|
389
|
+
}.to change { Applicant.load_aggregate(applicant_id).is_active }.from(true).to(false)
|
|
390
|
+
|
|
391
|
+
expect {
|
|
392
|
+
update_command.run!(id: applicant_id, is_active: true)
|
|
393
|
+
}.to change { Applicant.load(applicant_id).is_active }.from(false).to(true)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
new_user_id = User.transaction(skip_dependent_transactions: true) { User.create(name: "second user") }.id
|
|
397
|
+
|
|
398
|
+
Applicant.transaction do
|
|
399
|
+
expect {
|
|
400
|
+
update_command.run!(id: applicant_id, user: new_user_id)
|
|
401
|
+
}.to change { Applicant.load(applicant_id).user.name }.from("first user").to("second user")
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
describe "update aggregate command" do
|
|
408
|
+
context "with helper method result for inputs" do
|
|
409
|
+
let(:update_command) do
|
|
410
|
+
stub_class(:UpdateApplicantAggregate, Foobara::Command) do
|
|
411
|
+
# TODO: does this work with User instead of :User ?
|
|
412
|
+
# We can't come up with a cleaner way to do this?
|
|
413
|
+
inputs Applicant.attributes_for_aggregate_update(require_primary_key: true)
|
|
414
|
+
result Applicant # seems like we should just use nil?
|
|
415
|
+
|
|
416
|
+
def execute
|
|
417
|
+
update_applicant
|
|
418
|
+
|
|
419
|
+
applicant
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
attr_accessor :applicant
|
|
423
|
+
|
|
424
|
+
def load_records
|
|
425
|
+
self.applicant = Applicant.load(id)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def update_applicant
|
|
429
|
+
applicant.update_aggregate(inputs)
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
let(:applicant) do
|
|
435
|
+
Applicant.create(
|
|
436
|
+
user:,
|
|
437
|
+
is_active: true,
|
|
438
|
+
attrs: {
|
|
439
|
+
foo: :fooooo,
|
|
440
|
+
bar: [1, 2, 3, 4],
|
|
441
|
+
duckfoo: "asdfasdf",
|
|
442
|
+
duckbar: ["asdf", 5]
|
|
443
|
+
}
|
|
444
|
+
)
|
|
445
|
+
end
|
|
446
|
+
let(:user) { User.create(name: "old name") }
|
|
447
|
+
|
|
448
|
+
it "can update an applicant" do
|
|
449
|
+
applicant_id = Applicant.transaction { applicant }.id
|
|
450
|
+
user_id = User.transaction { user }.id
|
|
451
|
+
|
|
452
|
+
expect {
|
|
453
|
+
update_command.run!(id: applicant_id, is_active: false)
|
|
454
|
+
}.to change {
|
|
455
|
+
Applicant.transaction { Applicant.load(applicant_id).is_active }
|
|
456
|
+
}.from(true).to(false)
|
|
457
|
+
|
|
458
|
+
expect {
|
|
459
|
+
update_command.run!(id: applicant_id, is_active: true)
|
|
460
|
+
}.to change {
|
|
461
|
+
Applicant.transaction { Applicant.load(applicant_id).is_active }
|
|
462
|
+
}.from(false).to(true)
|
|
463
|
+
|
|
464
|
+
Applicant.transaction do
|
|
465
|
+
update_command.run!(
|
|
466
|
+
id: applicant_id,
|
|
467
|
+
user: { name: "new name" },
|
|
468
|
+
attrs: {
|
|
469
|
+
foo: :food,
|
|
470
|
+
bar: [10, 11],
|
|
471
|
+
duckfoo: "df",
|
|
472
|
+
duckbar: ["db", "asdf"]
|
|
473
|
+
}
|
|
474
|
+
)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
Applicant.transaction do
|
|
478
|
+
applicant = Applicant.load(applicant_id)
|
|
479
|
+
expect(applicant.user.name).to eq("new name")
|
|
480
|
+
expect(applicant.user.id).to eq(user_id)
|
|
481
|
+
expect(applicant.attrs).to eq(
|
|
482
|
+
foo: :food,
|
|
483
|
+
bar: [10, 11],
|
|
484
|
+
duckfoo: "df",
|
|
485
|
+
duckbar: ["db", "asdf"]
|
|
486
|
+
)
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
describe "delete command" do
|
|
493
|
+
let(:delete_command) do
|
|
494
|
+
stub_class(:DeleteApplicant, Foobara::Command) do
|
|
495
|
+
# TODO: does this work with User instead of :User ?
|
|
496
|
+
# We can't come up with a cleaner way to do this?
|
|
497
|
+
inputs applicant: Applicant
|
|
498
|
+
result Applicant
|
|
499
|
+
|
|
500
|
+
load_all
|
|
501
|
+
|
|
502
|
+
def execute
|
|
503
|
+
delete_applicant
|
|
504
|
+
|
|
505
|
+
applicant
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def delete_applicant
|
|
509
|
+
applicant.hard_delete!
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
let(:applicant) { Applicant.create(user:) }
|
|
515
|
+
let(:user) { User.create(name: "old name") }
|
|
516
|
+
|
|
517
|
+
it "deletes the applicant" do
|
|
518
|
+
applicant_id = Applicant.transaction { applicant }.id
|
|
519
|
+
|
|
520
|
+
Applicant.transaction do
|
|
521
|
+
expect {
|
|
522
|
+
delete_command.run!(applicant: applicant_id)
|
|
523
|
+
}.to change(Applicant, :count).by(-1)
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
Applicant.transaction do
|
|
527
|
+
expect {
|
|
528
|
+
Applicant.load(applicant_id)
|
|
529
|
+
}.to raise_error(Foobara::Entity::NotFoundError)
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Foobara
|
|
2
|
+
module BuiltinTypes
|
|
3
|
+
module Attributes
|
|
4
|
+
module SupportedTransformers
|
|
5
|
+
class IgnoreUnexpectedAttributes < Value::Transformer
|
|
6
|
+
class << self
|
|
7
|
+
def requires_parent_declaration_data?
|
|
8
|
+
true
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def transform(attributes_hash)
|
|
13
|
+
element_type_declarations = parent_declaration_data[:element_type_declarations]
|
|
14
|
+
expected_attributes = element_type_declarations.keys
|
|
15
|
+
unexpected_attributes = attributes_hash.keys - expected_attributes
|
|
16
|
+
|
|
17
|
+
return attributes_hash if unexpected_attributes.empty?
|
|
18
|
+
|
|
19
|
+
attributes_hash.slice(*expected_attributes)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -15,9 +15,23 @@ module Foobara
|
|
|
15
15
|
type_declaration.is_deep_duped = true
|
|
16
16
|
type_declaration
|
|
17
17
|
else
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
args -= [:attributes]
|
|
19
|
+
supported_processor_symbols = BuiltinTypes[:attributes].all_supported_processor_classes.map(&:symbol)
|
|
20
|
+
|
|
21
|
+
if args.all? { supported_processor_symbols.include?(it) }
|
|
22
|
+
handler = Foobara::GlobalDomain.foobara_type_builder.handler_for_class(
|
|
23
|
+
TypeDeclarations::Handlers::ExtendAttributesTypeDeclaration
|
|
24
|
+
)
|
|
25
|
+
type_declaration = handler.desugarize(TypeDeclaration.new(block))
|
|
26
|
+
|
|
27
|
+
args.each { |processor_symbol| type_declaration[processor_symbol] = true }
|
|
28
|
+
|
|
29
|
+
type_declaration
|
|
30
|
+
else
|
|
31
|
+
# :nocov:
|
|
32
|
+
raise ArgumentError, "Cannot provide both block and declaration of #{args}"
|
|
33
|
+
# :nocov:
|
|
34
|
+
end
|
|
21
35
|
end
|
|
22
36
|
else
|
|
23
37
|
case args.size
|
|
@@ -472,4 +472,19 @@ RSpec.describe Foobara::BuiltinTypes::Attributes do
|
|
|
472
472
|
end
|
|
473
473
|
end
|
|
474
474
|
end
|
|
475
|
+
|
|
476
|
+
describe "ignore_unexpected_attributes" do
|
|
477
|
+
let(:type) do
|
|
478
|
+
Foobara::Domain.current.foobara_type_from_declaration :ignore_unexpected_attributes do
|
|
479
|
+
a :integer, :required
|
|
480
|
+
b :string
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
it "excludes unexpected attributes" do
|
|
485
|
+
h = { a: "1", b: "2", c: "3" }
|
|
486
|
+
|
|
487
|
+
expect(type.process_value!(h)).to eq(a: 1, b: "2")
|
|
488
|
+
end
|
|
489
|
+
end
|
|
475
490
|
end
|
data/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: foobara
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Miles Georgi
|
|
@@ -86,7 +86,6 @@ files:
|
|
|
86
86
|
- projects/command/src/command_pattern_implementation/concerns/callbacks.rb
|
|
87
87
|
- projects/command/src/command_pattern_implementation/concerns/command_data.rb
|
|
88
88
|
- projects/command/src/command_pattern_implementation/concerns/domain_mappers.rb
|
|
89
|
-
- projects/command/src/command_pattern_implementation/concerns/entities.rb
|
|
90
89
|
- projects/command/src/command_pattern_implementation/concerns/errors.rb
|
|
91
90
|
- projects/command/src/command_pattern_implementation/concerns/errors_type.rb
|
|
92
91
|
- projects/command/src/command_pattern_implementation/concerns/inputs.rb
|
|
@@ -337,8 +336,10 @@ files:
|
|
|
337
336
|
- projects/entities_plumbing/spec/spec_helper.rb
|
|
338
337
|
- projects/entities_plumbing/spec/support/rubyprof.rb
|
|
339
338
|
- projects/entities_plumbing/spec/support/term_trap.rb
|
|
339
|
+
- projects/entities_plumbing/spec/transaction_spec.rb
|
|
340
340
|
- projects/entities_plumbing/src/command_connectors_extension.rb
|
|
341
|
-
- projects/entities_plumbing/src/extensions/
|
|
341
|
+
- projects/entities_plumbing/src/extensions/authenticator_methods.rb
|
|
342
|
+
- projects/entities_plumbing/src/extensions/command_pattern_implementation/concerns/entities.rb
|
|
342
343
|
- projects/manifest/Guardfile
|
|
343
344
|
- projects/manifest/lib/foobara/manifest.rb
|
|
344
345
|
- projects/manifest/spec/associations_spec.rb
|
|
@@ -365,6 +366,7 @@ files:
|
|
|
365
366
|
- projects/manifest/src/type.rb
|
|
366
367
|
- projects/manifest/src/type_declaration.rb
|
|
367
368
|
- projects/model_plumbing/lib/foobara/model_plumbing.rb
|
|
369
|
+
- projects/model_plumbing/src/domain_mapper_extension.rb
|
|
368
370
|
- projects/model_plumbing/src/model_plumbing.rb
|
|
369
371
|
- projects/typesystem/projects/builtin_types/lib/foobara/builtin_types.rb
|
|
370
372
|
- projects/typesystem/projects/builtin_types/src/README.md
|
|
@@ -383,6 +385,7 @@ files:
|
|
|
383
385
|
- projects/typesystem/projects/builtin_types/src/attributes/supported_transformers/defaults/type_declaration_extension/extend_attributes_type_declaration/desugarizers/symbolize_defaults.rb
|
|
384
386
|
- projects/typesystem/projects/builtin_types/src/attributes/supported_transformers/defaults/type_declaration_extension/extend_attributes_type_declaration/type_declaration_validators/hash_with_symbolic_keys.rb
|
|
385
387
|
- projects/typesystem/projects/builtin_types/src/attributes/supported_transformers/defaults/type_declaration_extension/extend_attributes_type_declaration/type_declaration_validators/valid_attribute_names.rb
|
|
388
|
+
- projects/typesystem/projects/builtin_types/src/attributes/supported_transformers/ignore_unexpected_attributes.rb
|
|
386
389
|
- projects/typesystem/projects/builtin_types/src/attributes/supported_validators/required.rb
|
|
387
390
|
- projects/typesystem/projects/builtin_types/src/attributes/supported_validators/required/type_declaration_extension/extend_attributes_type_declaration/desugarizers/alphabetize_required.rb
|
|
388
391
|
- projects/typesystem/projects/builtin_types/src/attributes/supported_validators/required/type_declaration_extension/extend_attributes_type_declaration/desugarizers/move_required_from_element_types_to_root.rb
|
|
File without changes
|