foobara-autocrud 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []