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.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.travis.yml +1 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile +8 -5
- data/lib/generators/rom/commands/templates/create.rb.erb +5 -7
- data/lib/generators/rom/commands/templates/update.rb.erb +5 -7
- data/lib/generators/rom/relation/templates/relation.rb.erb +6 -4
- data/lib/rom-rails.rb +0 -1
- data/lib/rom/rails/configuration.rb +0 -2
- data/lib/rom/rails/version.rb +1 -1
- data/rom-rails.gemspec +2 -4
- data/spec/dummy/app/commands/create_user.rb +10 -0
- data/spec/dummy/app/commands/update_user.rb +9 -0
- data/spec/dummy/app/controllers/users_controller.rb +12 -8
- data/spec/dummy/app/forms/user_form.rb +29 -9
- data/spec/dummy/app/mappers/task_mapper.rb +1 -1
- data/spec/dummy/app/mappers/user_mapper.rb +1 -1
- data/spec/dummy/app/models/user.rb +1 -1
- data/spec/dummy/app/relations/dummy_relation.rb +2 -1
- data/spec/dummy/app/relations/tasks.rb +2 -0
- data/spec/dummy/app/relations/users.rb +2 -0
- data/spec/dummy/lib/rom/test_adapter.rb +1 -1
- data/spec/features/users_spec.rb +1 -3
- data/spec/integration/activerecord_setup.rb +1 -1
- data/spec/integration/initializer_spec.rb +1 -3
- data/spec/integration/logger_spec.rb +1 -3
- data/spec/integration/user_commands_spec.rb +7 -7
- data/spec/integration/user_model_mapping_spec.rb +2 -4
- data/spec/lib/active_record/configuration_spec.rb +1 -2
- data/spec/lib/generators/commands_generator_spec.rb +7 -17
- data/spec/lib/generators/mapper_generator_spec.rb +1 -3
- data/spec/lib/generators/relation_generator_spec.rb +4 -8
- data/spec/lib/generators/repository_generator_spec.rb +1 -3
- data/spec/spec_helper.rb +1 -1
- metadata +12 -61
- data/lib/generators/rom/form/templates/base_form.rb.erb +0 -17
- data/lib/generators/rom/form/templates/edit_form.rb.erb +0 -20
- data/lib/generators/rom/form/templates/new_form.rb.erb +0 -20
- data/lib/generators/rom/form_generator.rb +0 -47
- data/lib/rom/rails/model/form.rb +0 -173
- data/lib/rom/rails/model/form/class_interface.rb +0 -457
- data/lib/rom/rails/model/form/error_proxy.rb +0 -53
- data/spec/dummy/app/forms/new_user_form.rb +0 -13
- data/spec/dummy/app/forms/update_user_form.rb +0 -11
- data/spec/integration/form_with_injected_commands_spec.rb +0 -37
- data/spec/integration/new_user_form_spec.rb +0 -19
- data/spec/lib/generators/form_generator_spec.rb +0 -140
- 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
|