iknow_view_models 3.1.6 → 3.2.2
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/.circleci/config.yml +6 -6
- data/.rubocop.yml +18 -0
- data/Appraisals +6 -6
- data/Gemfile +6 -2
- data/Rakefile +5 -5
- data/gemfiles/rails_5_2.gemfile +5 -5
- data/gemfiles/rails_6_0.gemfile +9 -0
- data/iknow_view_models.gemspec +40 -38
- data/lib/iknow_view_models.rb +9 -7
- data/lib/iknow_view_models/version.rb +1 -1
- data/lib/view_model.rb +31 -17
- data/lib/view_model/access_control.rb +5 -2
- data/lib/view_model/access_control/composed.rb +10 -9
- data/lib/view_model/access_control/open.rb +2 -0
- data/lib/view_model/access_control/read_only.rb +2 -0
- data/lib/view_model/access_control/tree.rb +11 -6
- data/lib/view_model/access_control_error.rb +4 -1
- data/lib/view_model/active_record.rb +13 -12
- data/lib/view_model/active_record/association_data.rb +3 -2
- data/lib/view_model/active_record/association_manipulation.rb +6 -4
- data/lib/view_model/active_record/cache.rb +114 -34
- data/lib/view_model/active_record/cache/cacheable_view.rb +2 -2
- data/lib/view_model/active_record/collection_nested_controller.rb +3 -3
- data/lib/view_model/active_record/controller.rb +68 -1
- data/lib/view_model/active_record/controller_base.rb +4 -1
- data/lib/view_model/active_record/nested_controller_base.rb +1 -0
- data/lib/view_model/active_record/update_context.rb +8 -6
- data/lib/view_model/active_record/update_data.rb +32 -30
- data/lib/view_model/active_record/update_operation.rb +17 -13
- data/lib/view_model/active_record/visitor.rb +0 -1
- data/lib/view_model/after_transaction_runner.rb +2 -2
- data/lib/view_model/callbacks.rb +3 -1
- data/lib/view_model/controller.rb +13 -3
- data/lib/view_model/deserialization_error.rb +15 -12
- data/lib/view_model/error.rb +12 -10
- data/lib/view_model/error_view.rb +3 -1
- data/lib/view_model/migratable_view.rb +78 -0
- data/lib/view_model/migration.rb +48 -0
- data/lib/view_model/migration/no_path_error.rb +26 -0
- data/lib/view_model/migration/one_way_error.rb +24 -0
- data/lib/view_model/migration/unspecified_version_error.rb +24 -0
- data/lib/view_model/migrator.rb +108 -0
- data/lib/view_model/record.rb +15 -14
- data/lib/view_model/reference.rb +3 -1
- data/lib/view_model/references.rb +8 -5
- data/lib/view_model/registry.rb +1 -1
- data/lib/view_model/schemas.rb +9 -4
- data/lib/view_model/serialization_error.rb +4 -1
- data/lib/view_model/serialize_context.rb +4 -4
- data/lib/view_model/test_helpers.rb +8 -3
- data/lib/view_model/test_helpers/arvm_builder.rb +21 -15
- data/lib/view_model/traversal_context.rb +2 -1
- data/nix/dependencies.nix +5 -0
- data/nix/gem/generate.rb +2 -1
- data/shell.nix +8 -3
- data/test/.rubocop.yml +14 -0
- data/test/helpers/arvm_test_models.rb +12 -9
- data/test/helpers/arvm_test_utilities.rb +5 -3
- data/test/helpers/controller_test_helpers.rb +55 -32
- data/test/helpers/match_enumerator.rb +1 -0
- data/test/helpers/query_logging.rb +2 -1
- data/test/helpers/test_access_control.rb +5 -3
- data/test/helpers/viewmodel_spec_helpers.rb +88 -22
- data/test/unit/view_model/access_control_test.rb +144 -144
- data/test/unit/view_model/active_record/alias_test.rb +15 -13
- data/test/unit/view_model/active_record/belongs_to_test.rb +40 -39
- data/test/unit/view_model/active_record/cache_test.rb +68 -31
- data/test/unit/view_model/active_record/cloner_test.rb +67 -63
- data/test/unit/view_model/active_record/controller_test.rb +113 -65
- data/test/unit/view_model/active_record/counter_test.rb +10 -9
- data/test/unit/view_model/active_record/customization_test.rb +59 -58
- data/test/unit/view_model/active_record/has_many_test.rb +112 -111
- data/test/unit/view_model/active_record/has_many_through_poly_test.rb +15 -14
- data/test/unit/view_model/active_record/has_many_through_test.rb +33 -38
- data/test/unit/view_model/active_record/has_one_test.rb +37 -36
- data/test/unit/view_model/active_record/migration_test.rb +161 -0
- data/test/unit/view_model/active_record/namespacing_test.rb +19 -17
- data/test/unit/view_model/active_record/poly_test.rb +44 -45
- data/test/unit/view_model/active_record/shared_test.rb +30 -28
- data/test/unit/view_model/active_record/version_test.rb +9 -7
- data/test/unit/view_model/active_record_test.rb +72 -72
- data/test/unit/view_model/callbacks_test.rb +19 -15
- data/test/unit/view_model/controller_test.rb +4 -2
- data/test/unit/view_model/record_test.rb +92 -97
- data/test/unit/view_model/traversal_context_test.rb +4 -5
- data/test/unit/view_model_test.rb +18 -16
- metadata +36 -12
- data/.travis.yml +0 -31
- data/appveyor.yml +0 -22
- data/gemfiles/rails_6_0_beta.gemfile +0 -9
@@ -33,11 +33,11 @@ module ViewModel::AfterTransactionRunner
|
|
33
33
|
if connection.transaction_open?
|
34
34
|
connection.add_transaction_record(self)
|
35
35
|
else
|
36
|
-
|
36
|
+
before_commit
|
37
|
+
after_commit
|
37
38
|
end
|
38
39
|
end
|
39
40
|
|
40
|
-
|
41
41
|
# Override to tie to a specific connection.
|
42
42
|
def connection
|
43
43
|
ActiveRecord::Base.connection
|
data/lib/view_model/callbacks.rb
CHANGED
@@ -79,7 +79,7 @@ module ViewModel::Callbacks
|
|
79
79
|
end
|
80
80
|
SRC
|
81
81
|
else
|
82
|
-
def self.create(callbacks, view, context)
|
82
|
+
def self.create(callbacks, view, context) # rubocop:disable Lint/NestedMethodDefinition
|
83
83
|
self.new(callbacks, view, context)
|
84
84
|
end
|
85
85
|
end
|
@@ -105,6 +105,7 @@ module ViewModel::Callbacks
|
|
105
105
|
define_singleton_method(:class_callbacks) { base_callbacks }
|
106
106
|
define_singleton_method(:all_callbacks) do |&block|
|
107
107
|
return to_enum(__method__) unless block
|
108
|
+
|
108
109
|
block.call(base_callbacks)
|
109
110
|
end
|
110
111
|
end
|
@@ -115,6 +116,7 @@ module ViewModel::Callbacks
|
|
115
116
|
subclass.define_singleton_method(:class_callbacks) { subclass_callbacks }
|
116
117
|
subclass.define_singleton_method(:all_callbacks) do |&block|
|
117
118
|
return to_enum(__method__) unless block
|
119
|
+
|
118
120
|
super(&block)
|
119
121
|
block.call(subclass_callbacks)
|
120
122
|
end
|
@@ -88,6 +88,7 @@ module ViewModel::Controller
|
|
88
88
|
if data.blank?
|
89
89
|
raise ViewModel::Error.new(status: 400, detail: "No data submitted: #{data.inspect}")
|
90
90
|
end
|
91
|
+
|
91
92
|
data.map { |el| _extract_param_hash(el) }
|
92
93
|
else
|
93
94
|
_extract_param_hash(data)
|
@@ -129,9 +130,18 @@ module ViewModel::Controller
|
|
129
130
|
# untouched. Requires a MultiJson adapter other than ActiveSupport's
|
130
131
|
# (modified) JsonGem.
|
131
132
|
class CompiledJson
|
132
|
-
def initialize(s)
|
133
|
-
|
134
|
-
|
133
|
+
def initialize(s)
|
134
|
+
@s = s
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_json(*_args)
|
138
|
+
@s
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_s
|
142
|
+
@s
|
143
|
+
end
|
144
|
+
|
135
145
|
undef_method :as_json
|
136
146
|
end
|
137
147
|
|
@@ -15,13 +15,14 @@ class ViewModel
|
|
15
15
|
unless nodes.all? { |n| n.viewmodel_class == first }
|
16
16
|
raise ArgumentError.new("All nodes must be of the same type for #{self.class.name}")
|
17
17
|
end
|
18
|
+
|
18
19
|
first
|
19
20
|
end
|
20
21
|
|
21
22
|
# A collection of DeserializationErrors
|
22
23
|
class Collection < ViewModel::AbstractErrorCollection
|
23
|
-
title
|
24
|
-
code
|
24
|
+
title 'Error(s) occurred during deserialization'
|
25
|
+
code 'DeserializationError.Collection'
|
25
26
|
|
26
27
|
def detail
|
27
28
|
"Error(s) occurred during deserialization: #{cause_details}"
|
@@ -33,7 +34,7 @@ class ViewModel
|
|
33
34
|
class InvalidRequest < DeserializationError
|
34
35
|
# Abstract
|
35
36
|
status 400
|
36
|
-
title
|
37
|
+
title 'Invalid request'
|
37
38
|
end
|
38
39
|
|
39
40
|
# There has been an unexpected internal failure of the ViewModel library.
|
@@ -145,6 +146,7 @@ class ViewModel
|
|
145
146
|
# association.
|
146
147
|
class InvalidAssociationType < InvalidRequest
|
147
148
|
attr_reader :association, :target_type
|
149
|
+
|
148
150
|
def initialize(association, target_type, node)
|
149
151
|
@association = association
|
150
152
|
@target_type = target_type
|
@@ -198,7 +200,7 @@ class ViewModel
|
|
198
200
|
end
|
199
201
|
|
200
202
|
def detail
|
201
|
-
errors = missing_nodes.map(&:to_s).join(
|
203
|
+
errors = missing_nodes.map(&:to_s).join(', ')
|
202
204
|
"Couldn't find requested member node(s) in association '#{association}': "\
|
203
205
|
"#{errors}"
|
204
206
|
end
|
@@ -218,7 +220,7 @@ class ViewModel
|
|
218
220
|
end
|
219
221
|
|
220
222
|
def detail
|
221
|
-
"Duplicate views for the same '#{type}' specified: "+ nodes.map(&:to_s).join(
|
223
|
+
"Duplicate views for the same '#{type}' specified: " + nodes.map(&:to_s).join(', ')
|
222
224
|
end
|
223
225
|
|
224
226
|
def meta
|
@@ -235,14 +237,14 @@ class ViewModel
|
|
235
237
|
end
|
236
238
|
|
237
239
|
def detail
|
238
|
-
"Multiple parents attempted to claim the same owned '#{association_name}' reference: " + nodes.map(&:to_s).join(
|
240
|
+
"Multiple parents attempted to claim the same owned '#{association_name}' reference: " + nodes.map(&:to_s).join(', ')
|
239
241
|
end
|
240
242
|
end
|
241
243
|
|
242
244
|
class ParentNotFound < NotFound
|
243
245
|
def detail
|
244
|
-
|
245
|
-
nodes.map(&:to_s).join(
|
246
|
+
'Could not resolve release from previous parent for the following owned viewmodel(s): ' +
|
247
|
+
nodes.map(&:to_s).join(', ')
|
246
248
|
end
|
247
249
|
end
|
248
250
|
|
@@ -284,7 +286,7 @@ class ViewModel
|
|
284
286
|
|
285
287
|
class ReadOnlyType < DeserializationError
|
286
288
|
status 400
|
287
|
-
detail
|
289
|
+
detail 'Deserialization not defined for view type'
|
288
290
|
end
|
289
291
|
|
290
292
|
class InvalidAttributeType < InvalidRequest
|
@@ -326,7 +328,7 @@ class ViewModel
|
|
326
328
|
status 400
|
327
329
|
|
328
330
|
def detail
|
329
|
-
errors = nodes.map(&:to_s).join(
|
331
|
+
errors = nodes.map(&:to_s).join(', ')
|
330
332
|
"Optimistic lock failure updating nodes: #{errors}"
|
331
333
|
end
|
332
334
|
end
|
@@ -408,8 +410,9 @@ class ViewModel
|
|
408
410
|
|
409
411
|
private
|
410
412
|
|
411
|
-
QUOTED_IDENTIFIER = /\A"(?:[^"]|"")+"
|
412
|
-
UNQUOTED_IDENTIFIER = /\A(?:\p{Alpha}|_)(?:\p{Alnum}|_)
|
413
|
+
QUOTED_IDENTIFIER = /\A"(?:[^"]|"")+"/.freeze
|
414
|
+
UNQUOTED_IDENTIFIER = /\A(?:\p{Alpha}|_)(?:\p{Alnum}|_)*/.freeze
|
415
|
+
|
413
416
|
def parse_identifier(stream)
|
414
417
|
if (identifier = stream.slice!(UNQUOTED_IDENTIFIER))
|
415
418
|
identifier
|
data/lib/view_model/error.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Abstract base for renderable errors in ViewModel-based APIs. Errors of this
|
2
4
|
# type will be caught by ViewModel controllers and rendered in a standard format
|
3
5
|
# by ViewModel::ErrorView, which loosely follows errors in JSON-API.
|
@@ -6,7 +8,7 @@ class ViewModel::AbstractError < StandardError
|
|
6
8
|
# Brief DSL for quickly defining constant attribute values in subclasses
|
7
9
|
[:detail, :status, :title, :code].each do |attribute|
|
8
10
|
define_method(attribute) do |x|
|
9
|
-
define_method(attribute){ x }
|
11
|
+
define_method(attribute) { x }
|
10
12
|
end
|
11
13
|
end
|
12
14
|
end
|
@@ -25,7 +27,7 @@ class ViewModel::AbstractError < StandardError
|
|
25
27
|
|
26
28
|
# Human-readable reason for use displaying this error.
|
27
29
|
def detail
|
28
|
-
|
30
|
+
'ViewModel::AbstractError'
|
29
31
|
end
|
30
32
|
|
31
33
|
# HTTP status code most appropriate for this error
|
@@ -40,7 +42,7 @@ class ViewModel::AbstractError < StandardError
|
|
40
42
|
|
41
43
|
# Unique symbol identifying this error type
|
42
44
|
def code
|
43
|
-
|
45
|
+
'ViewModel.AbstractError'
|
44
46
|
end
|
45
47
|
|
46
48
|
# Additional information specific to this error type.
|
@@ -74,8 +76,6 @@ class ViewModel::AbstractError < StandardError
|
|
74
76
|
|
75
77
|
protected
|
76
78
|
|
77
|
-
|
78
|
-
|
79
79
|
def format_references(viewmodel_refs)
|
80
80
|
viewmodel_refs.map do |viewmodel_ref|
|
81
81
|
format_reference(viewmodel_ref)
|
@@ -85,7 +85,7 @@ class ViewModel::AbstractError < StandardError
|
|
85
85
|
def format_reference(viewmodel_ref)
|
86
86
|
{
|
87
87
|
ViewModel::TYPE_ATTRIBUTE => viewmodel_ref.viewmodel_class.view_name,
|
88
|
-
ViewModel::ID_ATTRIBUTE => viewmodel_ref.model_id
|
88
|
+
ViewModel::ID_ATTRIBUTE => viewmodel_ref.model_id,
|
89
89
|
}
|
90
90
|
end
|
91
91
|
end
|
@@ -100,12 +100,13 @@ class ViewModel::AbstractErrorWithBlame < ViewModel::AbstractError
|
|
100
100
|
unless @nodes.all? { |n| n.is_a?(ViewModel::Reference) }
|
101
101
|
raise ArgumentError.new("#{self.class.name}: 'blame_nodes' must all be of type ViewModel::Reference")
|
102
102
|
end
|
103
|
+
|
103
104
|
super()
|
104
105
|
end
|
105
106
|
|
106
107
|
def meta
|
107
108
|
{
|
108
|
-
nodes: format_references(nodes)
|
109
|
+
nodes: format_references(nodes),
|
109
110
|
}
|
110
111
|
end
|
111
112
|
end
|
@@ -117,8 +118,9 @@ class ViewModel::AbstractErrorCollection < ViewModel::AbstractError
|
|
117
118
|
def initialize(causes)
|
118
119
|
@causes = Array.wrap(causes)
|
119
120
|
unless @causes.present?
|
120
|
-
raise ArgumentError.new(
|
121
|
+
raise ArgumentError.new('A collection must have at least one cause')
|
121
122
|
end
|
123
|
+
|
122
124
|
super()
|
123
125
|
end
|
124
126
|
|
@@ -151,7 +153,7 @@ class ViewModel::AbstractErrorCollection < ViewModel::AbstractError
|
|
151
153
|
protected
|
152
154
|
|
153
155
|
def cause_details
|
154
|
-
causes.map(&:detail).join(
|
156
|
+
causes.map(&:detail).join('; ')
|
155
157
|
end
|
156
158
|
end
|
157
159
|
|
@@ -180,7 +182,7 @@ end
|
|
180
182
|
class ViewModel::Error < ViewModel::AbstractError
|
181
183
|
attr_reader :detail, :status, :title, :code, :meta
|
182
184
|
|
183
|
-
def initialize(status: 400, detail:
|
185
|
+
def initialize(status: 400, detail: 'ViewModel Error', title: nil, code: nil, meta: {})
|
184
186
|
@detail = detail
|
185
187
|
@status = status
|
186
188
|
@title = title
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'view_model/record'
|
2
4
|
|
3
5
|
# ViewModel for rendering ViewModel::AbstractErrors
|
@@ -10,7 +12,7 @@ class ViewModel::ErrorView < ViewModel::Record
|
|
10
12
|
def serialize_view(json, serialize_context: nil)
|
11
13
|
json.set! :class, exception.class.name
|
12
14
|
json.backtrace exception.backtrace
|
13
|
-
if cause = exception.cause
|
15
|
+
if (cause = exception.cause)
|
14
16
|
json.cause do
|
15
17
|
json.set! :class, cause.class.name
|
16
18
|
json.backtrace cause.backtrace
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'view_model/migration'
|
4
|
+
require 'view_model/migrator'
|
5
|
+
|
6
|
+
require 'rgl/adjacency'
|
7
|
+
require 'rgl/dijkstra'
|
8
|
+
|
9
|
+
module ViewModel::MigratableView
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
class_methods do
|
13
|
+
def inherited(base)
|
14
|
+
super
|
15
|
+
base.initialize_as_migratable_view
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize_as_migratable_view
|
19
|
+
@migrations_lock = Monitor.new
|
20
|
+
@migration_classes = {}
|
21
|
+
@migration_paths = {}
|
22
|
+
@realized_migration_paths = true
|
23
|
+
end
|
24
|
+
|
25
|
+
def migration_path(from:, to:)
|
26
|
+
@migrations_lock.synchronize do
|
27
|
+
realize_paths! unless @realized_migration_paths
|
28
|
+
|
29
|
+
migrations = @migration_paths.fetch([from, to]) do
|
30
|
+
raise ViewModel::Migration::NoPathError.new(self, from, to)
|
31
|
+
end
|
32
|
+
|
33
|
+
migrations
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# Define a migration on this viewmodel
|
40
|
+
def migrates(from:, to:, &block)
|
41
|
+
@migrations_lock.synchronize do
|
42
|
+
builder = ViewModel::Migration::Builder.new
|
43
|
+
builder.instance_exec(&block)
|
44
|
+
@migration_classes[[from, to]] = builder.build!
|
45
|
+
@realized_migration_paths = false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Internal: find and record possible paths to the current schema version.
|
50
|
+
def realize_paths!
|
51
|
+
@migration_paths.clear
|
52
|
+
|
53
|
+
graph = RGL::DirectedAdjacencyGraph.new
|
54
|
+
|
55
|
+
# Add edges backwards, as we care about paths from the latest version
|
56
|
+
@migration_classes.each_key do |from, to|
|
57
|
+
graph.add_edge(to, from)
|
58
|
+
end
|
59
|
+
|
60
|
+
paths = graph.dijkstra_shortest_paths(Hash.new { 1 }, self.schema_version)
|
61
|
+
|
62
|
+
paths.each do |target_version, path|
|
63
|
+
next if path.length == 1
|
64
|
+
|
65
|
+
# Store the path forwards rather than backwards
|
66
|
+
path_migration_classes = path.reverse.each_cons(2).map do |from, to|
|
67
|
+
@migration_classes.fetch([from, to])
|
68
|
+
end
|
69
|
+
|
70
|
+
key = [target_version, schema_version]
|
71
|
+
|
72
|
+
@migration_paths[key] = path_migration_classes.map(&:new)
|
73
|
+
end
|
74
|
+
|
75
|
+
@realized_paths = true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ViewModel::Migration
|
4
|
+
require 'view_model/migration/no_path_error'
|
5
|
+
require 'view_model/migration/one_way_error'
|
6
|
+
require 'view_model/migration/unspecified_version_error'
|
7
|
+
|
8
|
+
def up(view, _references)
|
9
|
+
raise ViewModel::Migration::OneWayError.new(view[ViewModel::TYPE_ATTRIBUTE], :up)
|
10
|
+
end
|
11
|
+
|
12
|
+
def down(view, _references)
|
13
|
+
raise ViewModel::Migration::OneWayError.new(view[ViewModel::TYPE_ATTRIBUTE], :down)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Tiny DSL for defining migration classes
|
17
|
+
class Builder
|
18
|
+
def initialize
|
19
|
+
@up_block = nil
|
20
|
+
@down_block = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def build!
|
24
|
+
migration = Class.new(ViewModel::Migration)
|
25
|
+
migration.define_method(:up, &@up_block) if @up_block
|
26
|
+
migration.define_method(:down, &@down_block) if @down_block
|
27
|
+
migration
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def up(&block)
|
33
|
+
check_signature!(block)
|
34
|
+
@up_block = block
|
35
|
+
end
|
36
|
+
|
37
|
+
def down(&block)
|
38
|
+
check_signature!(block)
|
39
|
+
@down_block = block
|
40
|
+
end
|
41
|
+
|
42
|
+
def check_signature!(block)
|
43
|
+
unless block.arity == 2
|
44
|
+
raise RuntimeError.new('Illegal signature for migration method, must be (view, references)')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ViewModel::Migration::NoPathError < ViewModel::AbstractError
|
4
|
+
attr_reader :vm_name, :from, :to
|
5
|
+
|
6
|
+
status 400
|
7
|
+
|
8
|
+
def initialize(viewmodel, from, to)
|
9
|
+
@vm_name = viewmodel.view_name
|
10
|
+
@from = from
|
11
|
+
@to = to
|
12
|
+
super()
|
13
|
+
end
|
14
|
+
|
15
|
+
def detail
|
16
|
+
"No migration path for #{vm_name} from #{from} to #{to}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def meta
|
20
|
+
{
|
21
|
+
viewmodel: vm_name,
|
22
|
+
from: from,
|
23
|
+
to: to,
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ViewModel::Migration::OneWayError < ViewModel::AbstractError
|
4
|
+
attr_reader :vm_name, :direction
|
5
|
+
|
6
|
+
status 400
|
7
|
+
|
8
|
+
def initialize(vm_name, direction)
|
9
|
+
@vm_name = vm_name
|
10
|
+
@direction = direction
|
11
|
+
super()
|
12
|
+
end
|
13
|
+
|
14
|
+
def detail
|
15
|
+
"One way migration for #{vm_name} cannot be migrated #{direction}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def meta
|
19
|
+
{
|
20
|
+
viewmodel: vm_name,
|
21
|
+
direction: direction,
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|