rom-rails 0.9.0 → 1.0.0.beta1

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