zero-rails-adapter 0.2.0 → 0.3.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.
@@ -37,13 +37,31 @@ module ZeroRailsAdapter
37
37
  jsonb: "z.json()"
38
38
  }.freeze
39
39
 
40
- attr_reader :models
40
+ attr_reader :models, :crud_models
41
+
42
+ def initialize(published_schema: nil, crud_models: nil, manual_relationships: nil)
43
+ schema = published_schema || ZeroRailsAdapter.configuration.published_schema.call
44
+ @published_schema = PublishedSchema.new(
45
+ schema,
46
+ zero_key: ZeroRailsAdapter.configuration.zero_key
47
+ )
48
+ @models = @published_schema.models.sort_by(&:table_name)
49
+ relationship_definitions = manual_relationships ||
50
+ ZeroRailsAdapter.configuration.relationship_provider.call
51
+ @manual_relationships = Array(relationship_definitions).map do |definition|
52
+ Relationship.from_definition(definition, published_schema: @published_schema)
53
+ end
54
+ requested_crud_models = Array(
55
+ crud_models || ZeroRailsAdapter.configuration.crud_model_provider.call
56
+ )
57
+ unpublished = requested_crud_models - models
58
+ if unpublished.any?
59
+ labels = unpublished.map { |model| model.respond_to?(:name) ? model.name : model.inspect }
60
+ raise UnsafePublicationError,
61
+ "CRUD models must also be published: #{labels.join(', ')}"
62
+ end
41
63
 
42
- def initialize(models: nil)
43
- @models = Array(models || ZeroRailsAdapter.configuration.model_provider.call)
44
- .select { |model| active_record_model?(model) }
45
- .uniq
46
- .sort_by(&:table_name)
64
+ @crud_models = requested_crud_models.uniq.sort_by(&:table_name)
47
65
  end
48
66
 
49
67
  def schema
@@ -75,7 +93,7 @@ module ZeroRailsAdapter
75
93
 
76
94
  def table_definitions
77
95
  models.map do |model|
78
- columns = model.columns.map do |column|
96
+ columns = columns_for(model).map do |column|
79
97
  " #{property_name(column.name)}: #{zero_type(model, column)},"
80
98
  end.join("\n")
81
99
  keys = primary_keys(model).map { |key| quote(key) }.join(", ")
@@ -91,22 +109,20 @@ module ZeroRailsAdapter
91
109
  end
92
110
 
93
111
  def relationship_definitions
94
- relationships.group_by(&:first).map do |model, definitions|
95
- kinds = definitions.map do |_source, reflection, _destination|
96
- reflection.collection? ? "many" : "one"
97
- end.uniq.sort.join(", ")
98
- entries = definitions.map do |_source, reflection, destination|
99
- source_fields, destination_fields =
100
- relationship_fields(model, reflection, destination)
101
- kind = reflection.collection? ? "many" : "one"
102
-
103
- [
104
- "#{property_name(reflection.name)}: #{kind}({",
105
- " sourceField: [#{source_fields.map { |field| quote(field) }.join(', ')}],",
106
- " destSchema: #{variable_name(destination)},",
107
- " destField: [#{destination_fields.map { |field| quote(field) }.join(', ')}],",
108
- "}),"
109
- ].join("\n")
112
+ relationships.group_by(&:source).map do |model, definitions|
113
+ kinds = definitions.map(&:kind).uniq.sort.join(", ")
114
+ entries = definitions.map do |relationship|
115
+ hops = relationship.hops.map do |hop|
116
+ [
117
+ "{",
118
+ " sourceField: [#{hop.source_fields.map { |field| quote(field) }.join(', ')}],",
119
+ " destSchema: #{variable_name(hop.destination)},",
120
+ " destField: [#{hop.destination_fields.map { |field| quote(field) }.join(', ')}],",
121
+ "}"
122
+ ].join("\n")
123
+ end.join(", ")
124
+
125
+ "#{property_name(relationship.name)}: #{relationship.kind}(#{hops}),"
110
126
  end.join("\n")
111
127
 
112
128
  [
@@ -119,7 +135,7 @@ module ZeroRailsAdapter
119
135
 
120
136
  def schema_export
121
137
  table_vars = models.map { |model| variable_name(model) }.join(", ")
122
- relation_vars = relationships.map(&:first).uniq.map do |model|
138
+ relation_vars = relationships.map(&:source).uniq.map do |model|
123
139
  relationship_variable_name(model)
124
140
  end.join(", ")
125
141
 
@@ -140,7 +156,7 @@ module ZeroRailsAdapter
140
156
  end
141
157
 
142
158
  def argument_schemas
143
- models.flat_map do |model|
159
+ crud_models.flat_map do |model|
144
160
  [
145
161
  zod_schema(model, :create),
146
162
  zod_schema(model, :update),
@@ -162,7 +178,7 @@ module ZeroRailsAdapter
162
178
  end
163
179
 
164
180
  def mutator_export
165
- tables = models.map do |model|
181
+ tables = crud_models.map do |model|
166
182
  variable = variable_name(model)
167
183
  table = property_name(model.table_name)
168
184
  create_values = create_insert_values(model)
@@ -193,8 +209,8 @@ module ZeroRailsAdapter
193
209
 
194
210
  def create_insert_values(model)
195
211
  additions = []
196
- additions << "created_at: now" if model.column_names.include?("created_at")
197
- additions << "updated_at: now" if model.column_names.include?("updated_at")
212
+ additions << "created_at: now" if published_column?(model, "created_at")
213
+ additions << "updated_at: now" if published_column?(model, "updated_at")
198
214
  defaulted_columns(model).each do |column|
199
215
  value = if generated_attribute_names(model, :create).include?(column.name)
200
216
  "args.#{property_name(column.name)} ?? #{typescript_default(column)}"
@@ -208,7 +224,7 @@ module ZeroRailsAdapter
208
224
 
209
225
  def update_insert_values(model)
210
226
  additions = []
211
- additions << "updated_at: Date.now()" if model.column_names.include?("updated_at")
227
+ additions << "updated_at: Date.now()" if published_column?(model, "updated_at")
212
228
  object_with_additions(additions)
213
229
  end
214
230
 
@@ -220,21 +236,24 @@ module ZeroRailsAdapter
220
236
 
221
237
  def mutation_columns(model, action)
222
238
  primary = primary_keys(model)
239
+ immutable = (primary + Array(model.primary_key).compact.map(&:to_s)).uniq
223
240
  case action
224
241
  when :create
225
242
  allowed = generated_attribute_names(model, action)
226
- model.columns.select do |column|
243
+ columns_for(model).select do |column|
227
244
  !timestamp_column?(column) &&
228
245
  (primary.include?(column.name) || allowed.include?(column.name))
229
246
  end
230
247
  when :update
231
248
  allowed = generated_attribute_names(model, action)
232
- model.columns.select do |column|
249
+ columns_for(model).select do |column|
233
250
  primary.include?(column.name) ||
234
- (!timestamp_column?(column) && allowed.include?(column.name))
251
+ (!immutable.include?(column.name) &&
252
+ !timestamp_column?(column) &&
253
+ allowed.include?(column.name))
235
254
  end
236
255
  when :destroy
237
- model.columns.select { |column| primary.include?(column.name) }
256
+ columns_for(model).select { |column| primary.include?(column.name) }
238
257
  end
239
258
  end
240
259
 
@@ -250,7 +269,7 @@ module ZeroRailsAdapter
250
269
 
251
270
  def defaulted_columns(model)
252
271
  primary = primary_keys(model)
253
- model.columns.select do |column|
272
+ columns_for(model).select do |column|
254
273
  !primary.include?(column.name) &&
255
274
  !timestamp_column?(column) &&
256
275
  !column.null &&
@@ -264,7 +283,7 @@ module ZeroRailsAdapter
264
283
  return column.null ? "#{type}.optional()" : type
265
284
  end
266
285
 
267
- type = enum_values(model, column)&.then do |values|
286
+ type = native_enum_values(model, column)&.then do |values|
268
287
  "enumeration<#{values.map { |value| quote(value) }.join(' | ')}>()"
269
288
  end
270
289
  type ||= mapped_type(SCHEMA_TYPES, model, column)
@@ -273,7 +292,7 @@ module ZeroRailsAdapter
273
292
  end
274
293
 
275
294
  def zod_type(model, column, action)
276
- values = enum_values(model, column)
295
+ values = native_enum_values(model, column)
277
296
  type = if column.respond_to?(:array) && column.array
278
297
  "z.array(#{mapped_type(ZOD_TYPES, model, column)})"
279
298
  elsif values
@@ -300,18 +319,24 @@ module ZeroRailsAdapter
300
319
  end
301
320
  end
302
321
 
303
- def enum_values(model, column)
322
+ def native_enum_values(model, column)
323
+ return unless column.type.to_sym == :enum
324
+
304
325
  definition = model.defined_enums[column.name]
305
- definition&.keys
326
+ return definition.keys if definition
327
+
328
+ enum_types = model.connection.enum_types.to_h
329
+ enum_types[column.sql_type] ||
330
+ raise(UnsupportedColumnTypeError,
331
+ "Cannot read PostgreSQL enum values for #{model.name}.#{column.name}")
306
332
  end
307
333
 
308
334
  def schema_imports
309
335
  imports = %w[createSchema table]
310
336
  imports << "relationships" if relationships.any?
311
- imports << "enumeration" if models.any? { |model| model.defined_enums.any? }
312
337
  models.each do |model|
313
- model.columns.each do |column|
314
- imports << if enum_values(model, column)
338
+ columns_for(model).each do |column|
339
+ imports << if native_enum_values(model, column)
315
340
  "enumeration"
316
341
  elsif column.respond_to?(:array) && column.array
317
342
  "json"
@@ -334,14 +359,42 @@ module ZeroRailsAdapter
334
359
  end
335
360
 
336
361
  def relationships
337
- @relationships ||= models.flat_map do |model|
338
- model.reflect_on_all_associations.filter_map do |reflection|
339
- next if reflection.polymorphic? || reflection.options[:through]
340
-
341
- destination = reflection.klass
342
- [model, reflection, destination] if models.include?(destination)
343
- rescue NameError
344
- nil
362
+ @relationships ||= begin
363
+ automatic = models.flat_map do |model|
364
+ model.reflect_on_all_associations.filter_map do |reflection|
365
+ next if reflection.polymorphic? || reflection.options[:through]
366
+
367
+ destination = reflection.klass
368
+ next unless models.include?(destination)
369
+
370
+ source_fields, destination_fields =
371
+ relationship_fields(model, reflection, destination)
372
+ if source_fields.all? { |field| published_column?(model, field) } &&
373
+ destination_fields.all? { |field| published_column?(destination, field) }
374
+ Relationship.new(
375
+ source: model,
376
+ name: reflection.name,
377
+ kind: reflection.collection? ? :many : :one,
378
+ hops: [{
379
+ source_fields:,
380
+ destination:,
381
+ destination_fields:
382
+ }],
383
+ published_schema: @published_schema
384
+ )
385
+ end
386
+ rescue NameError
387
+ nil
388
+ end
389
+ end
390
+
391
+ manual_keys = @manual_relationships.map do |relationship|
392
+ [relationship.source, relationship.name]
393
+ end
394
+ (automatic.reject do |relationship|
395
+ manual_keys.include?([relationship.source, relationship.name])
396
+ end + @manual_relationships).sort_by do |relationship|
397
+ [relationship.source.table_name, relationship.name]
345
398
  end
346
399
  end
347
400
  end
@@ -366,10 +419,15 @@ module ZeroRailsAdapter
366
419
  end
367
420
 
368
421
  def primary_keys(model)
369
- keys = Array(model.primary_key).compact.map(&:to_s)
370
- return keys if keys.any?
422
+ @published_schema.zero_keys_for(model)
423
+ end
424
+
425
+ def columns_for(model)
426
+ @published_schema.columns_for(model)
427
+ end
371
428
 
372
- raise UnsupportedColumnTypeError, "#{model.name} has no primary key"
429
+ def published_column?(model, name)
430
+ columns_for(model).any? { |column| column.name == name.to_s }
373
431
  end
374
432
 
375
433
  def relationship_variable_name(model)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ZeroRailsAdapter
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.1"
5
5
  end
@@ -11,6 +11,8 @@ require_relative "zero_rails_adapter/errors"
11
11
  require_relative "zero_rails_adapter/identity"
12
12
  require_relative "zero_rails_adapter/context"
13
13
  require_relative "zero_rails_adapter/configuration"
14
+ require_relative "zero_rails_adapter/published_schema"
15
+ require_relative "zero_rails_adapter/relationship"
14
16
  require_relative "zero_rails_adapter/request_verifiers/api_key"
15
17
  require_relative "zero_rails_adapter/registry"
16
18
  require_relative "zero_rails_adapter/mutator"
@@ -18,6 +20,7 @@ require_relative "zero_rails_adapter/mutation"
18
20
  require_relative "zero_rails_adapter/request"
19
21
  require_relative "zero_rails_adapter/crud/dispatcher"
20
22
  require_relative "zero_rails_adapter/type_script/generator"
23
+ require_relative "zero_rails_adapter/postgresql/publication_generator"
21
24
  require_relative "zero_rails_adapter/storage/zero_schema"
22
25
  require_relative "zero_rails_adapter/processor"
23
26
  require_relative "zero_rails_adapter/engine"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zero-rails-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zero Rails Adapter contributors
@@ -35,6 +35,7 @@ executables: []
35
35
  extensions: []
36
36
  extra_rdoc_files: []
37
37
  files:
38
+ - CHANGELOG.md
38
39
  - LICENSE.txt
39
40
  - README.md
40
41
  - app/controllers/zero_rails_adapter/mutations_controller.rb
@@ -46,6 +47,7 @@ files:
46
47
  - lib/generators/zero_rails_adapter/install/templates/initializer.rb
47
48
  - lib/generators/zero_rails_adapter/mutator/mutator_generator.rb
48
49
  - lib/generators/zero_rails_adapter/mutator/templates/mutator.rb
50
+ - lib/generators/zero_rails_adapter/publication/publication_generator.rb
49
51
  - lib/generators/zero_rails_adapter/typescript/typescript_generator.rb
50
52
  - lib/zero_rails_adapter.rb
51
53
  - lib/zero_rails_adapter/configuration.rb
@@ -56,8 +58,11 @@ files:
56
58
  - lib/zero_rails_adapter/identity.rb
57
59
  - lib/zero_rails_adapter/mutation.rb
58
60
  - lib/zero_rails_adapter/mutator.rb
61
+ - lib/zero_rails_adapter/postgresql/publication_generator.rb
59
62
  - lib/zero_rails_adapter/processor.rb
63
+ - lib/zero_rails_adapter/published_schema.rb
60
64
  - lib/zero_rails_adapter/registry.rb
65
+ - lib/zero_rails_adapter/relationship.rb
61
66
  - lib/zero_rails_adapter/request.rb
62
67
  - lib/zero_rails_adapter/request_verifiers/api_key.rb
63
68
  - lib/zero_rails_adapter/storage/zero_schema.rb
@@ -83,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
88
  - !ruby/object:Gem::Version
84
89
  version: '0'
85
90
  requirements: []
86
- rubygems_version: 4.0.10
91
+ rubygems_version: 4.0.16
87
92
  specification_version: 4
88
93
  summary: A Rails 8 Active Record adapter for Rocicorp Zero
89
94
  test_files: []