foobara-autocrud 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 21073f778402366246fec16fac613de11e675c23dc7072b17c310fca6f1f6270
4
+ data.tar.gz: 1bd0c5b4cbf7938583548a901ff0d1d9d509c221689448c9347343743f637ceb
5
+ SHA512:
6
+ metadata.gz: 29bf2ae6eb556a3054202145c425498bb0e263e3d5e1458828c737f785af4a87295feb1481d958264af7944cb38f0faf625f20b2a0dc6e51f773152e7f49adf3
7
+ data.tar.gz: 5b1ee6bd9c2513e426d8cfcf0c2f49151f1e4427790fec4dd88364474b2a5cc64cdde6f32933543a6128318fa6d03341eb8572c8d66bb903cb5aaa3354441a36
data/.rspec ADDED
@@ -0,0 +1,5 @@
1
+ --format progress
2
+ --color
3
+ --require spec_helper
4
+ --order defined
5
+ --seed 0
data/.rubocop.yml ADDED
@@ -0,0 +1,12 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.2
3
+ NewCops: enable
4
+
5
+ inherit_gem:
6
+ foobara-rubocop-rules:
7
+ - rules/*
8
+
9
+ Naming/FileName:
10
+ inherit_mode:
11
+ merge:
12
+ - Exclude
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.1
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ ## [0.0.1] - 2025-01-06
2
+
3
+ - Bump Ruby to 3.4.1
4
+
5
+ ## [0.0.0] - 2023-10-12
6
+
7
+ - Project birth
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard :rspec, all_after_pass: true, all_on_start: true, cmd: "bundle exec rspec", failed_mode: :focus do
2
+ watch(%r{^spec/(.+)_spec\.rb$})
3
+ watch(%r{^(lib|src)/(.+)\.rb$}) { "spec/" }
4
+ watch(%r{^spec/spec_helper.rb$}) { "spec/" }
5
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023-2024 Miles Georgi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # Autocrud
2
+
3
+ TODO: Delete this and the text below, and describe your gem
4
+
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library
6
+ into a gem. Put your Ruby code in the file `lib/autocrud`. To experiment with that code, run `bin/console` for an
7
+ interactive prompt.
8
+
9
+ ## Installation
10
+
11
+ TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it
12
+ to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with
13
+ instructions to install your gem from git if you don't plan to release to RubyGems.org.
14
+
15
+ Install the gem and add to the application's Gemfile by executing:
16
+
17
+ $ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
18
+
19
+ If bundler is not being used to manage dependencies, install the gem by executing:
20
+
21
+ $ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can
30
+ also run `bin/console` for an interactive prompt that will allow you to experiment.
31
+
32
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
33
+ version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,
34
+ push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
35
+
36
+ ## Contributing
37
+
38
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/autocrud. This project is
39
+ intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to
40
+ the [code of conduct](https://github.com/[USERNAME]/autocrud/blob/main/CODE_OF_CONDUCT.md).
41
+
42
+ ## License
43
+
44
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
45
+
46
+ ## Code of Conduct
47
+
48
+ Everyone interacting in the Autocrud project's codebases, issue trackers, chat rooms and mailing lists is expected to
49
+ follow the [code of conduct](https://github.com/[USERNAME]/autocrud/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ require "rubocop/rake_task"
7
+
8
+ RuboCop::RakeTask.new
9
+
10
+ task default: %i[spec rubocop]
@@ -0,0 +1,9 @@
1
+ require "foobara/all"
2
+
3
+ module Foobara
4
+ module Autocrud
5
+ foobara_domain!
6
+ end
7
+
8
+ Util.require_directory("#{__dir__}/../../src")
9
+ end
data/src/autocrud.rb ADDED
@@ -0,0 +1,27 @@
1
+ require "foobara/all"
2
+
3
+ module Foobara
4
+ module Autocrud
5
+ foobara_domain!
6
+
7
+ class << self
8
+ attr_accessor :base
9
+
10
+ def install!
11
+ raise NoBaseSetError unless base
12
+
13
+ base.register_entity_class(PersistedType, table_name: :persisted_types)
14
+
15
+ PersistedType.transaction do
16
+ PersistedType.all do |persisted_type|
17
+ BuildType.run!(
18
+ type_declaration: persisted_type.type_declaration,
19
+ type_symbol: persisted_type.type_symbol,
20
+ domain: persisted_type.full_domain_name
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ module Foobara
2
+ module Autocrud
3
+ class AutocrudCommand < Foobara::Command
4
+ def validate
5
+ validate_base!
6
+ end
7
+
8
+ def validate_base!
9
+ raise NoBaseSetError unless Autocrud.base
10
+ end
11
+ end
12
+ end
13
+ end
data/src/build_type.rb ADDED
@@ -0,0 +1,64 @@
1
+ module Foobara
2
+ module Autocrud
3
+ class BuildType < AutocrudCommand
4
+ # TODO: maybe add an input for controlling if/which commands are created?
5
+ inputs do
6
+ type_declaration :associative_array, :required
7
+ domain :duck
8
+ type_symbol :symbol, :allow_nil
9
+ end
10
+
11
+ depends_on CreateCommands
12
+
13
+ def execute
14
+ determine_domain
15
+ build_type
16
+ register_type_if_needed
17
+ create_autocrud_commands_if_needed
18
+
19
+ type
20
+ end
21
+
22
+ attr_accessor :domain, :type
23
+
24
+ def determine_domain
25
+ domain = inputs[:domain]
26
+
27
+ # TODO: remove global domain from here...
28
+ self.domain = Domain.to_domain(domain)
29
+ rescue Domain::NoSuchDomain => e
30
+ if domain.is_a?(::String) || domain.is_a?(::Symbol)
31
+ self.domain = Domain.create(domain)
32
+ else
33
+ # :nocov:
34
+ raise e
35
+ # :nocov:
36
+ end
37
+ end
38
+
39
+ def build_type
40
+ self.type = (domain || GlobalDomain).foobara_type_from_declaration(type_declaration)
41
+ end
42
+
43
+ def register_type_if_needed
44
+ if type.registered?
45
+ if type_symbol && type_symbol.to_sym != type.type_symbol
46
+ # :nocov:
47
+ raise "Type symbol mismatch: #{type_symbol} versus #{type.type_symbol}"
48
+ # :nocov:
49
+ end
50
+ else
51
+ type.type_symbol = type_symbol
52
+ type.foobara_parent_namespace ||= domain
53
+ type.foobara_parent_namespace.foobara_register(type)
54
+ end
55
+ end
56
+
57
+ def create_autocrud_commands_if_needed
58
+ if type.extends?(BuiltinTypes[:entity])
59
+ run_subcommand!(CreateCommands, entity_class: type.target_class)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,507 @@
1
+ module Foobara
2
+ module Autocrud
3
+ # TODO: autocrud commands!
4
+ # commands:
5
+ #
6
+ # CreateUser
7
+ # UpdateUserAtom
8
+ # UpdateUserAggregate
9
+ # can records be created in this situation?? or only updated?
10
+ # HardDeleteUser
11
+ # AppendToUserRatings
12
+ # RemoveFromUserRatings
13
+ # FindUser
14
+ # FindUserBy
15
+ # QueryUser
16
+ #
17
+ # types:
18
+ #
19
+ # User
20
+ # UserAttributes
21
+ # UserCreateAttributes
22
+ # UserUpdateAtomAttributes
23
+ # remove all required and defaults
24
+ # primary key required
25
+ # UserUpdateAggregateAttributes
26
+ # convert all associations to their XUpdateAggregateAttributes types??
27
+ # UserPrimaryKeyType ??
28
+ # if primary key created by db
29
+ # no primary key in UserCreateAttributes
30
+ # if primary key created externally
31
+ # primary key in UserCreateAttributes and is required
32
+ # TODO: consider moving helper methods here into their own commands?
33
+ class CreateCommands < Foobara::Command
34
+ ALLOWED_COMMANDS = %i[
35
+ create
36
+ update_atom
37
+ update_aggregate
38
+ hard_delete
39
+ find
40
+ find_by
41
+ query
42
+ query_all
43
+ append
44
+ ].freeze
45
+
46
+ inputs do
47
+ # TODO: give a way to specify union types? We would like an array of one_of: or one_of: or :all here.
48
+ # But no way to specify that.
49
+ # TODO: append and remove commands should be split up
50
+ # TODO: append and remove commands should be able to be narrowed down by association but inputs currently
51
+ # just supports creating all append/remove commands for all associations.
52
+ commands :duck
53
+ # TODO: give a way to specify subclass of Foobara::Entity here
54
+ entity_class Class
55
+ end
56
+
57
+ def execute
58
+ create_commands
59
+
60
+ created_commands
61
+ end
62
+
63
+ attr_writer :created_commands
64
+
65
+ def create_commands
66
+ commands_to_create.each do |command_symbol|
67
+ method = if command_symbol == :append
68
+ "create_append_commands"
69
+ else
70
+ "create_#{command_symbol}_command"
71
+ end
72
+
73
+ created_commands << send(method)
74
+ end
75
+
76
+ self.created_commands = created_commands.flatten
77
+ end
78
+
79
+ def created_commands
80
+ @created_commands ||= []
81
+ end
82
+
83
+ def commands_to_create
84
+ @commands_to_create ||= if commands == :all || commands.nil?
85
+ ALLOWED_COMMANDS
86
+ else
87
+ Util.array(commands)
88
+ end
89
+ end
90
+
91
+ # rubocop:disable Lint/NestedMethodDefinition
92
+ def create_update_atom_command
93
+ entity_class = self.entity_class
94
+ domain = entity_class.domain
95
+ command_name = [*domain.scoped_full_path, "Update#{entity_class.entity_type.scoped_short_name}Atom"].join("::")
96
+
97
+ Util.make_class command_name, Foobara::Command do
98
+ define_method :entity_class do
99
+ entity_class
100
+ end
101
+
102
+ # TODO: make this work with just inputs :UserAttributesForAtomUpdate
103
+ # Should this be moved to this project instead of living in entities?
104
+ inputs entity_class.attributes_for_atom_update
105
+ result entity_class # seems like we should just use nil?
106
+
107
+ def execute
108
+ update_record
109
+
110
+ record
111
+ end
112
+
113
+ attr_accessor :record
114
+
115
+ def load_records
116
+ self.record = entity_class.load(id)
117
+ end
118
+
119
+ def update_record
120
+ inputs.each_pair do |attribute_name, value|
121
+ record.write_attribute(attribute_name, value)
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ def create_update_aggregate_command
128
+ entity_class = self.entity_class
129
+ domain = entity_class.domain
130
+ command_name = [*domain.scoped_full_path,
131
+ "Update#{entity_class.entity_type.scoped_short_name}Aggregate"].join("::")
132
+
133
+ Util.make_class command_name, Foobara::Command do
134
+ define_method :entity_class do
135
+ entity_class
136
+ end
137
+
138
+ # TODO: does this work with User instead of :User ?
139
+ # We can't come up with a cleaner way to do this?
140
+ inputs entity_class.attributes_for_aggregate_update
141
+ result entity_class # seems like we should just use nil?
142
+
143
+ def execute
144
+ update_record
145
+
146
+ record
147
+ end
148
+
149
+ attr_accessor :record
150
+
151
+ def load_records
152
+ self.record = entity_class.load(id)
153
+ end
154
+
155
+ def update_record
156
+ record.update_aggregate(inputs)
157
+ end
158
+ end
159
+ end
160
+
161
+ def create_create_command
162
+ entity_class = self.entity_class
163
+ domain = entity_class.domain
164
+ command_name = [*domain.scoped_full_path, "Create#{entity_class.entity_type.scoped_short_name}"].join("::")
165
+
166
+ Util.make_class(command_name, Foobara::Command) do
167
+ define_method :entity_class do
168
+ entity_class
169
+ end
170
+
171
+ # TODO: does this work with User instead of :User ?
172
+ # We can't come up with a cleaner way to do this?
173
+ # TODO: we should be allowed to just pass the type instead of transforming it to declaration_data
174
+ inputs entity_class.attributes_type
175
+ result entity_class
176
+
177
+ def execute
178
+ create_record
179
+
180
+ record
181
+ end
182
+
183
+ attr_accessor :record
184
+
185
+ def create_record
186
+ self.record = entity_class.create(inputs)
187
+ end
188
+ end
189
+ end
190
+
191
+ def create_hard_delete_command
192
+ entity_class = self.entity_class
193
+ domain = entity_class.domain
194
+ command_name = [*domain.scoped_full_path, "HardDelete#{entity_class.entity_type.scoped_short_name}"].join("::")
195
+
196
+ Util.make_class(command_name, Foobara::Command) do
197
+ singleton_class.define_method :record_method_name do
198
+ @record_method_name ||= Util.underscore(entity_class.entity_type.scoped_short_name)
199
+ end
200
+
201
+ foobara_delegate :record_method_name, to: :class
202
+
203
+ def record
204
+ send(record_method_name)
205
+ end
206
+
207
+ # TODO: does this work with User instead of :User ?
208
+ # We can't come up with a cleaner way to do this?
209
+ # TODO: make this work with entity classes!! no reason not to and very inconvenient
210
+ inputs Util.underscore(entity_class.entity_type.scoped_short_name) => entity_class
211
+ result entity_class
212
+
213
+ load_all
214
+
215
+ def execute
216
+ delete_record
217
+
218
+ record
219
+ end
220
+
221
+ def delete_record
222
+ record.hard_delete!
223
+ end
224
+ end
225
+ end
226
+
227
+ def create_find_command
228
+ entity_class = self.entity_class
229
+ domain = entity_class.domain
230
+ command_name = [*domain.scoped_full_path, "Find#{entity_class.entity_type.scoped_short_name}"].join("::")
231
+
232
+ Util.make_class(command_name, Foobara::Command) do
233
+ define_method :entity_class do
234
+ entity_class
235
+ end
236
+
237
+ inputs entity_class.primary_key_attribute => entity_class.primary_key_type
238
+ result entity_class
239
+
240
+ possible_error Entity::NotFoundError
241
+
242
+ def execute
243
+ load_record
244
+
245
+ record
246
+ end
247
+
248
+ attr_accessor :record
249
+
250
+ def load_record
251
+ self.record = entity_class.load(record_id)
252
+ rescue Entity::NotFoundError => e
253
+ add_runtime_error e
254
+ end
255
+
256
+ def primary_key_attribute
257
+ entity_class.primary_key_attribute
258
+ end
259
+
260
+ def record_id
261
+ inputs[primary_key_attribute]
262
+ end
263
+ end
264
+ end
265
+
266
+ def create_find_by_command
267
+ entity_class = self.entity_class
268
+ domain = entity_class.domain
269
+ command_name = [*domain.scoped_full_path, "Find#{entity_class.entity_type.scoped_short_name}By"].join("::")
270
+
271
+ Util.make_class(command_name, Foobara::Command) do
272
+ define_method :entity_class do
273
+ entity_class
274
+ end
275
+
276
+ # TODO: can't use attributes: :attributes but should be able to.
277
+ inputs entity_class.attributes_for_find_by
278
+ result entity_class
279
+
280
+ possible_error Entity::NotFoundError
281
+
282
+ def execute
283
+ load_record
284
+
285
+ record
286
+ end
287
+
288
+ attr_accessor :record
289
+
290
+ def load_record
291
+ self.record = entity_class.find_by(inputs)
292
+
293
+ unless record
294
+ add_runtime_error Entity::NotFoundError.new(inputs, entity_class:)
295
+ end
296
+ end
297
+ end
298
+ end
299
+
300
+ def create_query_command
301
+ entity_class = self.entity_class
302
+ domain = entity_class.domain
303
+ command_name = [*domain.scoped_full_path, "Query#{entity_class.entity_type.scoped_short_name}"].join("::")
304
+
305
+ Util.make_class(command_name, Foobara::Command) do
306
+ define_method :entity_class do
307
+ entity_class
308
+ end
309
+
310
+ # TODO: can't use attributes: :attributes but should be able to.
311
+ inputs entity_class.attributes_for_find_by
312
+ result [entity_class]
313
+
314
+ def execute
315
+ run_query
316
+
317
+ records
318
+ end
319
+
320
+ attr_accessor :records
321
+
322
+ def run_query
323
+ self.records = entity_class.find_many_by(inputs)
324
+ end
325
+ end
326
+ end
327
+
328
+ def create_query_all_command
329
+ entity_class = self.entity_class
330
+ domain = entity_class.domain
331
+ command_name = [*domain.scoped_full_path, "QueryAll#{entity_class.entity_type.scoped_short_name}"].join("::")
332
+
333
+ Util.make_class(command_name, Foobara::Command) do
334
+ define_method :entity_class do
335
+ entity_class
336
+ end
337
+
338
+ # TODO: can't use attributes: :attributes but should be able to.
339
+ inputs({})
340
+ result [entity_class]
341
+
342
+ def execute
343
+ run_query
344
+
345
+ records
346
+ end
347
+
348
+ attr_accessor :records
349
+
350
+ def run_query
351
+ self.records = entity_class.all
352
+ end
353
+ end
354
+ end
355
+
356
+ def create_append_commands
357
+ commands = []
358
+
359
+ entity_class.associations.each_pair do |data_path, type|
360
+ data_path = DataPath.parse(data_path)
361
+ if data_path.simple_collection?
362
+ path = data_path.path[0..-2]
363
+ commands << create_append_command(path, type)
364
+ commands << create_remove_command(path, type)
365
+ end
366
+ end
367
+
368
+ commands
369
+ end
370
+
371
+ def create_append_command(path_to_collection, association_type)
372
+ entity_class = self.entity_class
373
+ start = path_to_collection.size - 2
374
+ start = 0 if start < 0
375
+ collection_name = path_to_collection[start..start + 1]
376
+ collection_name = collection_name.map { |part| Util.classify(part) }.join
377
+
378
+ domain = entity_class.domain
379
+ # TODO: group these by entity name?
380
+ command_name = [*domain.scoped_full_path,
381
+ "AppendTo#{entity_class.entity_type.scoped_short_name}#{collection_name}"].join("::")
382
+
383
+ entity_input_name = Util.underscore_sym(entity_class.entity_type.scoped_short_name)
384
+
385
+ Util.make_class(command_name, Foobara::Command) do
386
+ define_method :path_to_collection do
387
+ path_to_collection
388
+ end
389
+
390
+ define_method :entity_input_name do
391
+ entity_input_name
392
+ end
393
+
394
+ # TODO: can't use attributes: :attributes but should be able to.
395
+ # Allow a hash to create these these things?
396
+ inputs type: :attributes,
397
+ element_type_declarations: {
398
+ entity_input_name => entity_class,
399
+ element_to_append: association_type.target_class
400
+ },
401
+ required: [entity_input_name, :element_to_append]
402
+
403
+ result association_type.target_class
404
+
405
+ to_load entity_input_name
406
+
407
+ def execute
408
+ append_record_to_collection
409
+
410
+ element_to_append
411
+ end
412
+
413
+ attr_accessor :new_collection
414
+
415
+ def append_record_to_collection
416
+ collection = DataPath.value_at(path_to_collection, record)
417
+
418
+ self.new_collection = [*collection, element_to_append]
419
+
420
+ DataPath.set_value_at(record, new_collection, path_to_collection)
421
+ end
422
+
423
+ def record
424
+ inputs[entity_input_name]
425
+ end
426
+ end
427
+ end
428
+
429
+ def create_remove_command(path_to_collection, association_type)
430
+ entity_class = self.entity_class
431
+ start = path_to_collection.size - 2
432
+ start = 0 if start < 0
433
+ collection_name = path_to_collection[start..start + 1]
434
+ collection_name = collection_name.map { |part| Util.classify(part) }.join
435
+
436
+ domain = entity_class.domain
437
+ # TODO: group these by entity name?
438
+ command_name = [*domain.scoped_full_path,
439
+ "RemoveFrom#{entity_class.entity_type.scoped_short_name}#{collection_name}"].join("::")
440
+
441
+ entity_input_name = Util.underscore_sym(entity_class.entity_type.scoped_short_name)
442
+
443
+ Util.make_class(command_name, Foobara::Command) do
444
+ Util.make_class("#{command_name}::ElementNotInCollectionError", Foobara::RuntimeError) do
445
+ class << self
446
+ # TODO: make this the default
447
+ def context_type_declaration
448
+ {}
449
+ end
450
+ end
451
+ end
452
+
453
+ define_method :path_to_collection do
454
+ path_to_collection
455
+ end
456
+
457
+ define_method :entity_input_name do
458
+ entity_input_name
459
+ end
460
+
461
+ # TODO: can't use attributes: :attributes but should be able to.
462
+ # Allow a hash to create these these things?
463
+ inputs type: :attributes,
464
+ element_type_declarations: {
465
+ entity_input_name => entity_class,
466
+ element_to_remove: association_type.target_class
467
+ },
468
+ required: [entity_input_name, :element_to_remove]
469
+
470
+ result association_type.target_class
471
+
472
+ to_load entity_input_name
473
+
474
+ def execute
475
+ remove_record_from_collection
476
+
477
+ element_to_remove
478
+ end
479
+
480
+ attr_accessor :new_collection
481
+
482
+ def remove_record_from_collection
483
+ collection = DataPath.value_at(path_to_collection, record)
484
+
485
+ self.new_collection = collection.reject { |element| element == element_to_remove }
486
+
487
+ if collection == new_collection
488
+ add_runtime_error(
489
+ self.class::ElementNotInCollectionError.new(
490
+ message: "Element not in collection so can't remove it.",
491
+ context: {} # TODO: make this the default
492
+ )
493
+ )
494
+ end
495
+
496
+ DataPath.set_value_at(record, new_collection, path_to_collection)
497
+ end
498
+
499
+ def record
500
+ inputs[entity_input_name]
501
+ end
502
+ end
503
+ end
504
+ # rubocop:enable Lint/NestedMethodDefinition
505
+ end
506
+ end
507
+ end
@@ -0,0 +1,79 @@
1
+ require_relative "create_type"
2
+
3
+ module Foobara
4
+ module Autocrud
5
+ class CreateEntity < AutocrudCommand
6
+ depends_on CreateType
7
+
8
+ inputs do
9
+ name :string, :required
10
+ attributes_declaration :duck, :required
11
+ domain :duck
12
+ end
13
+
14
+ result :duck
15
+
16
+ def execute
17
+ determine_domain
18
+ desugarize_attributes_declaration
19
+
20
+ build_type_declaration
21
+ create_type
22
+
23
+ entity_class
24
+ end
25
+
26
+ attr_accessor :domain, :type_declaration, :type, :desugarized_attributes_declaration
27
+
28
+ def determine_domain
29
+ domain = inputs[:domain]
30
+
31
+ # TODO: remove global domain from here...
32
+ self.domain = Domain.to_domain(domain)
33
+ rescue Domain::NoSuchDomain => e
34
+ if domain.is_a?(::String) || domain.is_a?(::Symbol)
35
+ self.domain = Domain.create(domain)
36
+ else
37
+ # :nocov:
38
+ raise e
39
+ # :nocov:
40
+ end
41
+ end
42
+
43
+ def desugarize_attributes_declaration
44
+ self.desugarized_attributes_declaration = if attributes_declaration.is_a?(Proc)
45
+ TypeDeclarations::Dsl::Attributes.to_declaration(
46
+ &attributes_declaration
47
+ )
48
+ else
49
+ attributes_handler.desugarize(attributes_declaration)
50
+ end
51
+ end
52
+
53
+ def build_type_declaration
54
+ self.type_declaration = {
55
+ type: :entity,
56
+ attributes_declaration: desugarized_attributes_declaration,
57
+ name:,
58
+ primary_key: desugarized_attributes_declaration[:element_type_declarations].keys.first
59
+ }
60
+
61
+ if domain && domain != GlobalDomain
62
+ type_declaration[:model_module] = domain
63
+ end
64
+ end
65
+
66
+ def create_type
67
+ self.type = run_subcommand!(CreateType, type_declaration:)
68
+ end
69
+
70
+ def entity_class
71
+ type.target_class
72
+ end
73
+
74
+ def attributes_handler
75
+ domain.foobara_type_builder.handler_for_class(TypeDeclarations::Handlers::ExtendAttributesTypeDeclaration)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,39 @@
1
+ require_relative "build_type"
2
+
3
+ module Foobara
4
+ module Autocrud
5
+ class CreateType < AutocrudCommand
6
+ depends_on BuildType
7
+ depends_on_entity PersistedType
8
+
9
+ inputs do
10
+ type_declaration :associative_array, :required
11
+ domain :duck
12
+ type_symbol :symbol, :allow_nil
13
+ end
14
+
15
+ def execute
16
+ build_type
17
+ persist_type
18
+
19
+ type
20
+ end
21
+
22
+ attr_accessor :type
23
+
24
+ def build_type
25
+ self.type = run_subcommand!(BuildType, type_declaration:, domain:, type_symbol:)
26
+ end
27
+
28
+ def persist_type
29
+ PersistedType.create(
30
+ Util.remove_blank(
31
+ type_declaration: type.declaration_data,
32
+ type_symbol: type.type_symbol,
33
+ full_domain_name: type.foobara_domain.scoped_full_name
34
+ )
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,13 @@
1
+ module Foobara
2
+ module Autocrud
3
+ # TODO: delete this error once everything is implemented as commands
4
+ class NoBaseSetError < StandardError
5
+ def initialize
6
+ super(
7
+ "You need to set Foobara::Autocrud.base. " \
8
+ "Try `Autocrud.base = Foobara::Persistence.default_base` if you just want to use the default base."
9
+ )
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ module Foobara
2
+ module Autocrud
3
+ class PersistedType < Entity
4
+ attributes type: :attributes,
5
+ element_type_declarations: {
6
+ id: :integer,
7
+ type_declaration: :duck,
8
+ type_symbol: :symbol,
9
+ full_domain_name: :string
10
+ },
11
+ required: %i[type_declaration type_symbol]
12
+
13
+ primary_key :id
14
+ end
15
+ end
16
+ end
data/version.rb ADDED
@@ -0,0 +1,11 @@
1
+ module Foobara
2
+ module Autocrud
3
+ module Version
4
+ VERSION = "0.0.1".freeze
5
+
6
+ local_ruby_version = File.read("#{__dir__}/.ruby-version").chomp
7
+ local_ruby_version_minor = local_ruby_version[/\A(\d+\.\d+)\.\d+\z/, 1]
8
+ MINIMUM_RUBY_VERSION = ">= #{local_ruby_version_minor}.0".freeze
9
+ end
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: foobara-autocrud
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Miles Georgi
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-01-07 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: foobara
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ description: Persists entity declarations and exposes CRUD commands for those entities
27
+ automatically.
28
+ email:
29
+ - azimux@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".rspec"
35
+ - ".rubocop.yml"
36
+ - ".ruby-version"
37
+ - CHANGELOG.md
38
+ - Guardfile
39
+ - LICENSE.txt
40
+ - README.md
41
+ - Rakefile
42
+ - lib/foobara/autocrud.rb
43
+ - src/autocrud.rb
44
+ - src/autocrud_command.rb
45
+ - src/build_type.rb
46
+ - src/create_commands.rb
47
+ - src/create_entity.rb
48
+ - src/create_type.rb
49
+ - src/no_base_set_error.rb
50
+ - src/persisted_type.rb
51
+ - version.rb
52
+ homepage: https://github.com/foobara/autocrud
53
+ licenses:
54
+ - none yet
55
+ metadata:
56
+ homepage_uri: https://github.com/foobara/autocrud
57
+ source_code_uri: https://github.com/foobara/autocrud
58
+ changelog_uri: https://github.com/foobara/autocrud/CHANGELOG.md
59
+ rubygems_mfa_required: 'true'
60
+ rdoc_options: []
61
+ require_paths:
62
+ - "./lib"
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 3.4.0
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.6.2
75
+ specification_version: 4
76
+ summary: Persists entity declarations and exposes CRUD commands for those entities
77
+ automatically.
78
+ test_files: []