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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f3a7dee25a0ab1d7598ec72bbd71ec8807113d417cc758842cf4edd7b214d2a
4
- data.tar.gz: f81bd84c70a9d41a8db414ee9424d5c2e2d9533887cfaa1ea0b436094873bc9a
3
+ metadata.gz: 23083c50349b64586fcc01d57460032c08ab51eaf90af20591a49dfa16c477f2
4
+ data.tar.gz: '04495cb4fbf6f6c93bc24da26a7f889792a4b17498a04416b539b107b0aa2158'
5
5
  SHA512:
6
- metadata.gz: 8819827dfdffb0ae2bef29f3bbbeb944cd3270cfd1a7938037d487373123ce50e2a8f832c5687c6b75ef8fd6e2ebd2016689e4b7208752a1f4a15bafa6eac10d
7
- data.tar.gz: ba81595289f961c7c5f639d85fb16f7d05fe5b3d0aa3fcbeec1499030b7e56bbab75afc1cef58b6eafd5e6f115c3f2c4dbfb15ab29de8bc081095ccb4a8bd089
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"
@@ -1,3 +1,5 @@
1
+ require "foobara/common"
2
+
1
3
  module Foobara
2
4
  class Command
3
5
  class << self
@@ -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) || transform_command_class(command_class)
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 && command.is_a?(TransformedCommand) && command.class.requires_authentication != false
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::Model
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
- )
@@ -23,6 +23,7 @@ SimpleCov.start do
23
23
  end
24
24
 
25
25
  require "foobara/all"
26
+ require "foobara/command_connectors"
26
27
  require "foobara/entities_plumbing"
27
28
 
28
29
  RSpec.configure do |config|
@@ -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
@@ -1,6 +1,6 @@
1
1
  module Foobara
2
2
  class CommandConnector
3
- class Authenticator
3
+ module AuthenticatorMethods
4
4
  def relevant_entity_classes(_request)
5
5
  if to_type&.extends?(BuiltinTypes[:entity])
6
6
  NestedTransactionable.relevant_entity_classes_for_type(to_type)
@@ -23,6 +23,7 @@ SimpleCov.start do
23
23
  end
24
24
 
25
25
  require "foobara/all"
26
+ require "foobara/command_connectors"
26
27
 
27
28
  RSpec.configure do |config|
28
29
  # Need to do :all instead of :each because for specs that use .around,
@@ -5,6 +5,7 @@ module Foobara
5
5
  Model.on_reregister do
6
6
  GlobalDomain.foobara_each_command(&:handle_reregistered_types!)
7
7
  end
8
+ Foobara::DomainMapper.singleton_class.prepend(Foobara::ModelPlumbing::DomainMapperExtension)
8
9
  end
9
10
  end
10
11
  end
@@ -0,0 +1,13 @@
1
+ module Foobara
2
+ module ModelPlumbing
3
+ module DomainMapperExtension
4
+ def object_to_type(object)
5
+ if object.is_a?(::Class) && object < Foobara::Model
6
+ object.model_type
7
+ else
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -41,6 +41,10 @@ module Foobara
41
41
  end
42
42
  end
43
43
 
44
+ def project_installed?(project_symbol)
45
+ all_projects.key?(project_symbol)
46
+ end
47
+
44
48
  def install!
45
49
  self.is_installed = true
46
50
  all_projects.each_value(&:install!)
@@ -15,9 +15,23 @@ module Foobara
15
15
  type_declaration.is_deep_duped = true
16
16
  type_declaration
17
17
  else
18
- # :nocov:
19
- raise ArgumentError, "Cannot provide both block and declaration of #{args}"
20
- # :nocov:
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
@@ -1,6 +1,6 @@
1
1
  module Foobara
2
2
  module Version
3
- VERSION = "0.5.8".freeze
3
+ VERSION = "0.5.10".freeze
4
4
  MINIMUM_RUBY_VERSION = ">= 3.4.0".freeze
5
5
  end
6
6
  end
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.8
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/authenticator.rb
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