rom-rails 0.9.0 → 1.0.0.beta1

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.travis.yml +1 -0
  4. data/CHANGELOG.md +14 -0
  5. data/Gemfile +8 -5
  6. data/lib/generators/rom/commands/templates/create.rb.erb +5 -7
  7. data/lib/generators/rom/commands/templates/update.rb.erb +5 -7
  8. data/lib/generators/rom/relation/templates/relation.rb.erb +6 -4
  9. data/lib/rom-rails.rb +0 -1
  10. data/lib/rom/rails/configuration.rb +0 -2
  11. data/lib/rom/rails/version.rb +1 -1
  12. data/rom-rails.gemspec +2 -4
  13. data/spec/dummy/app/commands/create_user.rb +10 -0
  14. data/spec/dummy/app/commands/update_user.rb +9 -0
  15. data/spec/dummy/app/controllers/users_controller.rb +12 -8
  16. data/spec/dummy/app/forms/user_form.rb +29 -9
  17. data/spec/dummy/app/mappers/task_mapper.rb +1 -1
  18. data/spec/dummy/app/mappers/user_mapper.rb +1 -1
  19. data/spec/dummy/app/models/user.rb +1 -1
  20. data/spec/dummy/app/relations/dummy_relation.rb +2 -1
  21. data/spec/dummy/app/relations/tasks.rb +2 -0
  22. data/spec/dummy/app/relations/users.rb +2 -0
  23. data/spec/dummy/lib/rom/test_adapter.rb +1 -1
  24. data/spec/features/users_spec.rb +1 -3
  25. data/spec/integration/activerecord_setup.rb +1 -1
  26. data/spec/integration/initializer_spec.rb +1 -3
  27. data/spec/integration/logger_spec.rb +1 -3
  28. data/spec/integration/user_commands_spec.rb +7 -7
  29. data/spec/integration/user_model_mapping_spec.rb +2 -4
  30. data/spec/lib/active_record/configuration_spec.rb +1 -2
  31. data/spec/lib/generators/commands_generator_spec.rb +7 -17
  32. data/spec/lib/generators/mapper_generator_spec.rb +1 -3
  33. data/spec/lib/generators/relation_generator_spec.rb +4 -8
  34. data/spec/lib/generators/repository_generator_spec.rb +1 -3
  35. data/spec/spec_helper.rb +1 -1
  36. metadata +12 -61
  37. data/lib/generators/rom/form/templates/base_form.rb.erb +0 -17
  38. data/lib/generators/rom/form/templates/edit_form.rb.erb +0 -20
  39. data/lib/generators/rom/form/templates/new_form.rb.erb +0 -20
  40. data/lib/generators/rom/form_generator.rb +0 -47
  41. data/lib/rom/rails/model/form.rb +0 -173
  42. data/lib/rom/rails/model/form/class_interface.rb +0 -457
  43. data/lib/rom/rails/model/form/error_proxy.rb +0 -53
  44. data/spec/dummy/app/forms/new_user_form.rb +0 -13
  45. data/spec/dummy/app/forms/update_user_form.rb +0 -11
  46. data/spec/integration/form_with_injected_commands_spec.rb +0 -37
  47. data/spec/integration/new_user_form_spec.rb +0 -19
  48. data/spec/lib/generators/form_generator_spec.rb +0 -140
  49. data/spec/unit/form_spec.rb +0 -366
@@ -1,457 +0,0 @@
1
- require 'dry/core/class_builder'
2
-
3
- module ROM
4
- module Model
5
- class Form
6
- module ClassInterface
7
- # Return param handler class
8
- #
9
- # This class is used to process input params coming from a request and
10
- # it's being created using `input` API
11
- #
12
- # @example
13
- #
14
- # class MyForm < ROM::Model::Form
15
- # input do
16
- # attribute :name, String
17
- # end
18
- # end
19
- #
20
- # MyForm.attributes # => MyForm::Attributes
21
- #
22
- # # process input params
23
- # attributes = MyForm.attributes[name: 'Jane']
24
- #
25
- # @return [Class]
26
- #
27
- # @api public
28
- attr_reader :attributes
29
-
30
- # Return attributes validator
31
- #
32
- # @example
33
- # class MyForm < ROM::Model::Form
34
- # input do
35
- # attribute :name, String
36
- # end
37
- #
38
- # validations do
39
- # validates :name, presence: true
40
- # end
41
- # end
42
- #
43
- # attributes = MyForm.attributes[name: nil]
44
- # MyForm::Validator.call(attributes) # raises validation error
45
- #
46
- # @return [Class]
47
- #
48
- # @api public
49
- attr_reader :validator
50
-
51
- # Return model class
52
- #
53
- # @return [Class]
54
- #
55
- # @api public
56
- attr_reader :model
57
-
58
- # relation => command name mapping used to generate commands automatically
59
- #
60
- # @return [Hash]
61
- #
62
- # @api private
63
- attr_reader :self_commands
64
-
65
- # A list of relation names for which commands should be injected from
66
- # the rom env automatically.
67
- #
68
- # This is used only when a given form re-uses existing commands
69
- #
70
- # @return [Hash]
71
- #
72
- # @api private
73
- attr_reader :injectible_commands
74
-
75
- # Copy input attributes, validator and model to the descendant
76
- #
77
- # @api private
78
- def inherited(klass)
79
- klass.inject_commands_for(*injectible_commands) if injectible_commands
80
- klass.commands(*self_commands) if self_commands
81
- input_blocks.each { |block| klass.input(readers: false, &block) }
82
- validation_blocks.each { |block| klass.validations(&block) }
83
- super
84
- end
85
-
86
- # Set key for the model that is handled by a form object
87
- #
88
- # This defaults to [:id]
89
- #
90
- # @example
91
- # class MyForm < ROM::Model::Form
92
- # key [:user_id]
93
- # end
94
- #
95
- # @return [Array<Symbol>]
96
- #
97
- # @api public
98
- def key(*keys)
99
- if keys.any? && !@key
100
- @key = keys
101
- attr_reader(*keys)
102
- elsif !@key
103
- @key = [:id]
104
- attr_reader :id
105
- elsif keys.any?
106
- @key = keys
107
- end
108
- @key
109
- end
110
-
111
- # Specify what commands should be generated for a form object
112
- #
113
- # @example
114
- # class MyForm < ROM::Model::Form
115
- # commands users: :create
116
- # end
117
- #
118
- # @param [Hash] relation => command name map
119
- #
120
- # @return [self]
121
- #
122
- # @api public
123
- def commands(names)
124
- names.each { |relation, _action| attr_reader(relation) }
125
- @self_commands = names
126
- self
127
- end
128
-
129
- # Specify input params handler class
130
- #
131
- # This uses Virtus DSL
132
- #
133
- # @example
134
- # class MyForm < ROM::Model::Form
135
- # input do
136
- # set_model_name 'User'
137
- #
138
- # attribute :name, String
139
- # attribute :age, Integer
140
- # end
141
- # end
142
- #
143
- # MyForm.build(name: 'Jane', age: 21).attributes
144
- # # => #<MyForm::Attributes:0x007f821f863d48 @name="Jane", @age=21>
145
- #
146
- # @return [self]
147
- #
148
- # @api public
149
- def input(options = {}, &block)
150
- readers = options.fetch(:readers) { true }
151
- define_attributes!(block)
152
- define_attribute_readers! if readers
153
- define_model!
154
- self
155
- end
156
-
157
- # Specify attribute validator class
158
- #
159
- # This uses ActiveModel::Validations DSL
160
- #
161
- # @example
162
- # class MyForm < ROM::Model::Form
163
- # input do
164
- # set_model_name 'User'
165
- #
166
- # attribute :name, String
167
- # attribute :age, Integer
168
- # end
169
- #
170
- # validations do
171
- # validates :name, :age, presence: true
172
- # end
173
- # end
174
- #
175
- # form = MyForm.build(name: 'Jane', age: nil)
176
- # # => #<MyForm::Attributes:0x007f821f863d48 @name="Jane", @age=21>
177
- # form.validate! # raises
178
- #
179
- # @return [self]
180
- #
181
- # @api public
182
- def validations(&block)
183
- define_validator!(block)
184
- self
185
- end
186
-
187
- # Inject specific commands from the rom env
188
- #
189
- # This can be used when the env has re-usable commands
190
- #
191
- # @example
192
- # class MyForm < ROM::Model::Form
193
- # inject_commands_for :users
194
- # end
195
- #
196
- # @api public
197
- def inject_commands_for(*names)
198
- @injectible_commands = names
199
- names.each { |name| attr_reader(name) }
200
- self
201
- end
202
-
203
- # Build a form object using input params and options
204
- #
205
- # @example
206
- # class MyForm < ROM::Model::Form
207
- # input do
208
- # set_model_name 'User'
209
- #
210
- # attribute :name, String
211
- # attribute :age, Integer
212
- # end
213
- # end
214
- #
215
- # # form for a new object
216
- # form = MyForm.build(name: 'Jane')
217
- #
218
- # # form for a persisted object
219
- # form = MyForm.build({ name: 'Jane' }, id: 1)
220
- #
221
- # @return [Model::Form]
222
- #
223
- # @api public
224
- def build(input = {}, options = {})
225
- commands =
226
- if mappings
227
- command_registry.each_with_object({}) { |(relation, registry), h|
228
- mapper = mappings[relation]
229
-
230
- h[relation] =
231
- if mapper
232
- registry.as(mapper)
233
- else
234
- registry
235
- end
236
- }
237
- else
238
- command_registry
239
- end
240
- new(input, options.merge(commands))
241
- end
242
-
243
- private
244
-
245
- # retrieve a list of reserved method names
246
- #
247
- # @return [Array<Symbol>]
248
- #
249
- # @api private
250
- def reserved_attributes
251
- ROM::Model::Form.public_instance_methods
252
- end
253
-
254
- # @return [Hash<Symbol=>ROM::CommandRegistry>]
255
- #
256
- # @api private
257
- def command_registry
258
- @command_registry ||= setup_command_registry
259
- end
260
-
261
- # input block stored to be used in inherited hook
262
- #
263
- # @return [Proc]
264
- #
265
- # @api private
266
- def input_blocks
267
- @input_blocks ||= []
268
- end
269
-
270
- # validation blocks stored to be used in inherited hook
271
- #
272
- # @return [Proc]
273
- #
274
- # @api private
275
- def validation_blocks
276
- @validation_blocks ||= []
277
- end
278
-
279
- # Create attribute handler class
280
- #
281
- # @return [Class]
282
- #
283
- # @api private
284
- def define_attributes!(block)
285
- input_blocks << block
286
- @attributes = Dry::Core::ClassBuilder.new(name: "#{name}::Attributes", parent: Object).call { |klass|
287
- klass.send(:include, ROM::Model::Attributes)
288
- }
289
- input_blocks.each do |input_block|
290
- @attributes.class_eval(&input_block)
291
- end
292
-
293
- update_const(:Attributes, @attributes)
294
- end
295
-
296
- # Define attribute readers for the form
297
- #
298
- # This is very unfortunate but rails `form_for` and friends require
299
- # the object to provide attribute values, hence we need to expose those
300
- # using the form object itself.
301
- #
302
- # @return [Class]
303
- #
304
- # @api private
305
- def define_attribute_readers!
306
- reserved = reserved_attributes
307
- @attributes.attribute_set.each do |attribute|
308
- if reserved.include?(attribute.name)
309
- raise(
310
- ArgumentError,
311
- "#{attribute.name} attribute is in conflict with #{self}##{attribute.name}"
312
- )
313
- end
314
-
315
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
316
- def #{attribute.name}
317
- attributes[:#{attribute.name}]
318
- end
319
- RUBY
320
- end
321
- end
322
-
323
- # Create model class
324
- #
325
- # Model instance represents an entity that will be persisted or was
326
- # already persisted and will be updated.
327
- #
328
- # This object is returned via `Form#to_model` which rails uses internally
329
- # in many places to figure out what to do.
330
- #
331
- # Model object provides two crucial pieces of information: whether or not
332
- # something was persisted and its primary key value
333
- #
334
- # @return [Class]
335
- #
336
- # @api private
337
- def define_model!
338
- @model = Dry::Core::ClassBuilder.new(name: "#{name}::Model", parent: @attributes).call { |klass|
339
- klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
340
- def persisted?
341
- to_key.any?
342
- end
343
-
344
- def to_key
345
- to_h.values_at(#{key.map(&:inspect).join(', ')}).compact
346
- end
347
- RUBY
348
- }
349
- key.each { |name| @model.attribute(name) }
350
-
351
- update_const(:Model, @model)
352
- end
353
-
354
- # Define attribute validator class
355
- #
356
- # @return [Class]
357
- #
358
- # @api private
359
- def define_validator!(block)
360
- validation_blocks << block
361
- @validator = Dry::Core::ClassBuilder.new(name: "#{name}::Validator", parent: Object).call { |klass|
362
- klass.send(:include, ROM::Model::Validator)
363
- }
364
- validation_blocks.each { |validation| @validator.class_eval(&validation) }
365
- update_const(:Validator, @validator)
366
- end
367
-
368
- # Shortcut to global ROM env
369
- #
370
- # @return [ROM::Env]
371
- #
372
- # @api private
373
- def rom
374
- ROM.env
375
- end
376
-
377
- # Return identifier of the default adapter
378
- #
379
- # TODO: we need an interface for that in ROM
380
- #
381
- # @return [Symbol]
382
- #
383
- # @api private
384
- def adapter
385
- ROM.adapters.keys.first
386
- end
387
-
388
- # Generate a command registry hash which will be auto-injected to a form
389
- # object.
390
- #
391
- # @return [Hash<Symbol=>ROM::CommandRegistry>]
392
- #
393
- # @api private
394
- def setup_command_registry
395
- commands = {}
396
-
397
- if self_commands
398
- self_commands.each do |rel_name, name|
399
- command = build_command(name, rel_name)
400
- elements = { name => command }
401
- options =
402
- if rom.mappers.key?(rel_name)
403
- { mappers: rom.mappers[rel_name] }
404
- else
405
- {}
406
- end
407
-
408
- commands[rel_name] = CommandRegistry.new(rel_name, elements, options)
409
- end
410
- end
411
-
412
- if injectible_commands
413
- injectible_commands.each do |relation|
414
- commands[relation] = rom.command(relation)
415
- end
416
- end
417
-
418
- commands
419
- end
420
-
421
- # Build a command object with a specific name
422
- #
423
- # @param [Symbol] name The name of the command
424
- # @param [Symbol] rel_name The name of the command's relation
425
- #
426
- # @return [ROM::Command]
427
- #
428
- # @api private
429
- def build_command(name, rel_name)
430
- klass = ConfigurationDSL::Command.build_class(name, rel_name, adapter: adapter)
431
-
432
- klass.result :one
433
-
434
- relation = rom.relations[rel_name]
435
- gateway = rom.gateways[relation.gateway]
436
- gateway.extend_command_class(klass, relation.dataset)
437
-
438
- klass.send(:include, Command.relation_methods_mod(relation.class))
439
-
440
- klass.build(relation)
441
- end
442
-
443
- # Silently update a constant, replacing any existing definition without
444
- # warning
445
- #
446
- # @param [Symbol] name the name of the constant
447
- # @param [Class] klass class to assign
448
- #
449
- # @api private
450
- def update_const(name, klass)
451
- remove_const(name) if const_defined?(name, false)
452
- const_set(name, klass)
453
- end
454
- end
455
- end
456
- end
457
- end