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.
- 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
|