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
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ViewModel::Migration::UnspecifiedVersionError < ViewModel::AbstractError
|
4
|
+
attr_reader :vm_name, :version
|
5
|
+
|
6
|
+
status 400
|
7
|
+
|
8
|
+
def initialize(vm_name, version)
|
9
|
+
@vm_name = vm_name
|
10
|
+
@version = version
|
11
|
+
super()
|
12
|
+
end
|
13
|
+
|
14
|
+
def detail
|
15
|
+
"Provided view for #{vm_name} at version #{version} does not match request"
|
16
|
+
end
|
17
|
+
|
18
|
+
def meta
|
19
|
+
{
|
20
|
+
viewmodel: vm_name,
|
21
|
+
version: version,
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ViewModel
|
4
|
+
class Migrator
|
5
|
+
class << self
|
6
|
+
def migrated_deep_schema_version(viewmodel_class, required_versions, include_referenced: true)
|
7
|
+
deep_schema_version = viewmodel_class.deep_schema_version(include_referenced: include_referenced)
|
8
|
+
|
9
|
+
if required_versions.present?
|
10
|
+
deep_schema_version = deep_schema_version.dup
|
11
|
+
|
12
|
+
required_versions.each do |required_vm_class, required_version|
|
13
|
+
name = required_vm_class.view_name
|
14
|
+
if deep_schema_version.has_key?(name)
|
15
|
+
deep_schema_version[name] = required_version
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
deep_schema_version
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(required_versions)
|
25
|
+
@paths = required_versions.each_with_object({}) do |(viewmodel_class, required_version), h|
|
26
|
+
if required_version != viewmodel_class.schema_version
|
27
|
+
path = viewmodel_class.migration_path(from: required_version, to: viewmodel_class.schema_version)
|
28
|
+
h[viewmodel_class.view_name] = path
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@versions = required_versions.each_with_object({}) do |(viewmodel_class, required_version), h|
|
33
|
+
h[viewmodel_class.view_name] = [required_version, viewmodel_class.schema_version]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def migrate!(node, references:)
|
38
|
+
case node
|
39
|
+
when Hash
|
40
|
+
if (type = node[ViewModel::TYPE_ATTRIBUTE])
|
41
|
+
version = node[ViewModel::VERSION_ATTRIBUTE]
|
42
|
+
|
43
|
+
if migrate_viewmodel!(type, version, node, references)
|
44
|
+
node[ViewModel::MIGRATED_ATTRIBUTE] = true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
node.each_value do |child|
|
49
|
+
migrate!(child, references: references)
|
50
|
+
end
|
51
|
+
when Array
|
52
|
+
node.each { |child| migrate!(child, references: references) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def migrate_viewmodel!(_view_name, _version, _view_hash, _references)
|
59
|
+
raise RuntimeError.new('abstract method')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class UpMigrator < Migrator
|
64
|
+
private
|
65
|
+
|
66
|
+
def migrate_viewmodel!(view_name, source_version, view_hash, references)
|
67
|
+
path = @paths[view_name]
|
68
|
+
return false unless path
|
69
|
+
|
70
|
+
# We assume that an unspecified source version is the same as the required
|
71
|
+
# version.
|
72
|
+
required_version, current_version = @versions[view_name]
|
73
|
+
|
74
|
+
unless source_version.nil? || source_version == required_version
|
75
|
+
raise ViewModel::Migration::UnspecifiedVersionError.new(view_name, source_version)
|
76
|
+
end
|
77
|
+
|
78
|
+
path.each do |migration|
|
79
|
+
migration.up(view_hash, references)
|
80
|
+
end
|
81
|
+
|
82
|
+
view_hash[ViewModel::VERSION_ATTRIBUTE] = current_version
|
83
|
+
|
84
|
+
true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# down migrations find a reverse path from the current schema version to the
|
89
|
+
# specific version requested by the client.
|
90
|
+
class DownMigrator < Migrator
|
91
|
+
private
|
92
|
+
|
93
|
+
def migrate_viewmodel!(view_name, _, view_hash, references)
|
94
|
+
path = @paths[view_name]
|
95
|
+
return false unless path
|
96
|
+
|
97
|
+
required_version, _current_version = @versions[view_name]
|
98
|
+
|
99
|
+
path.reverse_each do |migration|
|
100
|
+
migration.down(view_hash, references)
|
101
|
+
end
|
102
|
+
|
103
|
+
view_hash[ViewModel::VERSION_ATTRIBUTE] = required_version
|
104
|
+
|
105
|
+
true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/view_model/record.rb
CHANGED
@@ -10,6 +10,9 @@ class ViewModel::Record < ViewModel
|
|
10
10
|
attr_accessor :model
|
11
11
|
|
12
12
|
require 'view_model/record/attribute_data'
|
13
|
+
require 'view_model/migratable_view'
|
14
|
+
|
15
|
+
include ViewModel::MigratableView
|
13
16
|
|
14
17
|
class << self
|
15
18
|
attr_reader :_members
|
@@ -113,7 +116,7 @@ class ViewModel::Record < ViewModel
|
|
113
116
|
end
|
114
117
|
end
|
115
118
|
|
116
|
-
def resolve_viewmodel(
|
119
|
+
def resolve_viewmodel(_metadata, _view_hash, deserialize_context:)
|
117
120
|
self.for_new_model
|
118
121
|
end
|
119
122
|
|
@@ -178,6 +181,8 @@ class ViewModel::Record < ViewModel
|
|
178
181
|
@changed_attributes = []
|
179
182
|
@changed_nested_children = false
|
180
183
|
@changed_referenced_children = false
|
184
|
+
|
185
|
+
super()
|
181
186
|
end
|
182
187
|
|
183
188
|
# VM::Record identity matches the identity of its model. If the model has a
|
@@ -207,7 +212,7 @@ class ViewModel::Record < ViewModel
|
|
207
212
|
end
|
208
213
|
|
209
214
|
def serialize_view(json, serialize_context: self.class.new_serialize_context)
|
210
|
-
json.set!(ViewModel::ID_ATTRIBUTE,
|
215
|
+
json.set!(ViewModel::ID_ATTRIBUTE, self.id) if stable_id?
|
211
216
|
json.set!(ViewModel::TYPE_ATTRIBUTE, self.view_name)
|
212
217
|
json.set!(ViewModel::VERSION_ATTRIBUTE, self.class.schema_version)
|
213
218
|
|
@@ -306,12 +311,10 @@ class ViewModel::Record < ViewModel
|
|
306
311
|
# viewmodel), it's only desired for converting the value to and from wire
|
307
312
|
# format, so conversion is deferred to serialization time.
|
308
313
|
value = attr_data.map_value(value) do |v|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
"Could not serialize invalid value '#{vm_attr_name}': #{ex.message}")
|
314
|
-
end
|
314
|
+
attr_data.attribute_serializer.dump(v, json: true)
|
315
|
+
rescue IknowParams::Serializer::DumpError => ex
|
316
|
+
raise ViewModel::SerializationError.new(
|
317
|
+
"Could not serialize invalid value '#{vm_attr_name}': #{ex.message}")
|
315
318
|
end
|
316
319
|
end
|
317
320
|
|
@@ -339,12 +342,10 @@ class ViewModel::Record < ViewModel
|
|
339
342
|
end
|
340
343
|
when attr_data.using_serializer?
|
341
344
|
attr_data.map_value(serialized_value) do |sv|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
raise ViewModel::DeserializationError::Validation.new(vm_attr_name, reason, {}, blame_reference)
|
347
|
-
end
|
345
|
+
attr_data.attribute_serializer.load(sv)
|
346
|
+
rescue IknowParams::Serializer::LoadError => ex
|
347
|
+
reason = "could not be deserialized because #{ex.message}"
|
348
|
+
raise ViewModel::DeserializationError::Validation.new(vm_attr_name, reason, {}, blame_reference)
|
348
349
|
end
|
349
350
|
else
|
350
351
|
serialized_value
|
data/lib/view_model/reference.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ViewModel
|
2
4
|
# Key to identify a viewmodel with some kind of inherent ID (e.g. an ViewModel::ActiveRecord)
|
3
5
|
class Reference
|
@@ -22,7 +24,7 @@ class ViewModel
|
|
22
24
|
other.model_id == model_id
|
23
25
|
end
|
24
26
|
|
25
|
-
alias
|
27
|
+
alias eql? ==
|
26
28
|
|
27
29
|
def hash
|
28
30
|
[viewmodel_class, model_id].hash
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ViewModel
|
2
4
|
# A bucket for configuration, used for serializing and deserializing.
|
3
5
|
class References
|
@@ -17,14 +19,15 @@ class ViewModel
|
|
17
19
|
# under which the data is stored. If the data is not present, will compute
|
18
20
|
# it by calling the given block.
|
19
21
|
def add_reference(value)
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
ref = @ref_by_value[value]
|
23
|
+
|
24
|
+
unless ref.present?
|
23
25
|
ref = new_ref!(value)
|
24
26
|
@ref_by_value[value] = ref
|
25
27
|
@value_by_ref[ref] = value
|
26
|
-
ref
|
27
28
|
end
|
29
|
+
|
30
|
+
ref
|
28
31
|
end
|
29
32
|
|
30
33
|
def clear!
|
@@ -41,7 +44,7 @@ class ViewModel
|
|
41
44
|
hash = Digest::SHA256.base64digest("#{vm_ref.viewmodel_class.name}.#{vm_ref.model_id}")
|
42
45
|
"ref:h:#{hash}"
|
43
46
|
else
|
44
|
-
'ref:i:%
|
47
|
+
format('ref:i:%06<count>d', count: (@last_ref += 1))
|
45
48
|
end
|
46
49
|
end
|
47
50
|
end
|
data/lib/view_model/registry.rb
CHANGED
data/lib/view_model/schemas.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
require 'json_schema'
|
3
5
|
|
@@ -9,7 +11,8 @@ class ViewModel::Schemas
|
|
9
11
|
|
10
12
|
ID_SCHEMA =
|
11
13
|
{ 'oneOf' => [{ 'type' => 'integer' },
|
12
|
-
{ 'type' => 'string', 'format' => 'uuid' }] }
|
14
|
+
{ 'type' => 'string', 'format' => 'uuid' },] }.freeze
|
15
|
+
|
13
16
|
ID = JsonSchema.parse!(ID_SCHEMA)
|
14
17
|
|
15
18
|
VIEWMODEL_UPDATE_SCHEMA =
|
@@ -20,8 +23,9 @@ class ViewModel::Schemas
|
|
20
23
|
ViewModel::ID_ATTRIBUTE => ID_SCHEMA,
|
21
24
|
ViewModel::NEW_ATTRIBUTE => { 'type' => 'boolean' },
|
22
25
|
ViewModel::VERSION_ATTRIBUTE => { 'type' => 'integer' } },
|
23
|
-
'required' => [ViewModel::TYPE_ATTRIBUTE]
|
24
|
-
}
|
26
|
+
'required' => [ViewModel::TYPE_ATTRIBUTE],
|
27
|
+
}.freeze
|
28
|
+
|
25
29
|
VIEWMODEL_UPDATE = JsonSchema.parse!(VIEWMODEL_UPDATE_SCHEMA)
|
26
30
|
|
27
31
|
VIEWMODEL_REFERENCE_SCHEMA =
|
@@ -31,7 +35,8 @@ class ViewModel::Schemas
|
|
31
35
|
'properties' => { ViewModel::REFERENCE_ATTRIBUTE => { 'type' => 'string' } },
|
32
36
|
'additionalProperties' => false,
|
33
37
|
'required' => [ViewModel::REFERENCE_ATTRIBUTE],
|
34
|
-
}
|
38
|
+
}.freeze
|
39
|
+
|
35
40
|
VIEWMODEL_REFERENCE = JsonSchema.parse!(VIEWMODEL_REFERENCE_SCHEMA)
|
36
41
|
|
37
42
|
def self.verify_schema!(schema, value)
|
@@ -38,10 +38,10 @@ class ViewModel::SerializeContext < ViewModel::TraversalContext
|
|
38
38
|
|
39
39
|
while references.present?
|
40
40
|
extract_referenced_views!.each do |ref, value|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
next if serialized_refs.has_key?(ref)
|
42
|
+
|
43
|
+
serialized_refs[ref] = Jbuilder.new do |j|
|
44
|
+
ViewModel.serialize(value, j, serialize_context: reference_context)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
##
|
2
4
|
# Helpers useful for writing tests for viewmodel implementations
|
3
5
|
module ViewModel::TestHelpers
|
@@ -51,11 +53,13 @@ module ViewModel::TestHelpers
|
|
51
53
|
|
52
54
|
def assert_consistent_record(viewmodel, been_there: Set.new)
|
53
55
|
return if been_there.include?(viewmodel.model)
|
56
|
+
|
54
57
|
been_there << viewmodel.model
|
55
58
|
|
56
|
-
|
59
|
+
case viewmodel
|
60
|
+
when ViewModel::ActiveRecord
|
57
61
|
assert_model_represents_database(viewmodel.model, been_there: been_there)
|
58
|
-
|
62
|
+
when ViewModel::Record
|
59
63
|
viewmodel.class._members.each do |name, attribute_data|
|
60
64
|
if attribute_data.attribute_viewmodel
|
61
65
|
assert_consistent_record(viewmodel.send(name), been_there: been_there)
|
@@ -66,6 +70,7 @@ module ViewModel::TestHelpers
|
|
66
70
|
|
67
71
|
def assert_model_represents_database(model, been_there: Set.new)
|
68
72
|
return if been_there.include?(model)
|
73
|
+
|
69
74
|
been_there << model
|
70
75
|
|
71
76
|
refute(model.new_record?, 'model represents database entity')
|
@@ -83,7 +88,7 @@ module ViewModel::TestHelpers
|
|
83
88
|
next unless association.loaded?
|
84
89
|
|
85
90
|
case
|
86
|
-
when association.target
|
91
|
+
when association.target.nil?
|
87
92
|
assert_nil(database_model.association(reflection.name).target,
|
88
93
|
'in memory nil association matches database')
|
89
94
|
when reflection.collection?
|
@@ -1,10 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ViewModel::TestHelpers::ARVMBuilder
|
2
4
|
attr_reader :name, :model, :viewmodel, :namespace
|
3
5
|
|
4
6
|
# Building an ARVM requires three blocks, to define schema, model and
|
5
7
|
# viewmodel. Support providing these either in an spec argument or as a
|
6
8
|
# dsl-style builder.
|
7
|
-
Spec = Struct.new(:schema, :model, :viewmodel)
|
9
|
+
Spec = Struct.new(:schema, :model, :viewmodel)
|
10
|
+
class Spec
|
8
11
|
def initialize(schema:, model:, viewmodel:)
|
9
12
|
super(schema, model, viewmodel)
|
10
13
|
end
|
@@ -45,16 +48,16 @@ class ViewModel::TestHelpers::ARVMBuilder
|
|
45
48
|
instance_eval(&block)
|
46
49
|
end
|
47
50
|
|
48
|
-
raise
|
49
|
-
raise
|
50
|
-
raise
|
51
|
+
raise 'Model not created in ARVMBuilder' unless model
|
52
|
+
raise 'Schema not created in ARVMBuilder' unless model.table_exists?
|
53
|
+
raise 'ViewModel not created in ARVMBuilder' unless viewmodel || @no_viewmodel
|
51
54
|
|
52
55
|
# Force the realization of the view model into the library's lookup
|
53
56
|
# table. If this doesn't happen the library may have conflicting entries in
|
54
57
|
# the deferred table, and will allow viewmodels to leak between tests.
|
55
58
|
unless @no_viewmodel || !(@viewmodel < ViewModel::Record)
|
56
59
|
resolved = ViewModel::Registry.for_view_name(viewmodel.view_name)
|
57
|
-
raise
|
60
|
+
raise 'Failed to register expected new class!' unless resolved == @viewmodel
|
58
61
|
end
|
59
62
|
end
|
60
63
|
|
@@ -69,7 +72,7 @@ class ViewModel::TestHelpers::ARVMBuilder
|
|
69
72
|
private
|
70
73
|
|
71
74
|
def viewmodel_name
|
72
|
-
self.name +
|
75
|
+
self.name + 'View'
|
73
76
|
end
|
74
77
|
|
75
78
|
def define_schema(&block)
|
@@ -83,10 +86,11 @@ class ViewModel::TestHelpers::ARVMBuilder
|
|
83
86
|
|
84
87
|
def define_model(&block)
|
85
88
|
model_name = name
|
86
|
-
|
87
|
-
@model = Class.new(@model_base) do |
|
88
|
-
raise "Model already defined: #{model_name}" if
|
89
|
-
|
89
|
+
model_namespace = namespace
|
90
|
+
@model = Class.new(@model_base) do |_c|
|
91
|
+
raise "Model already defined: #{model_name}" if model_namespace.const_defined?(model_name, false)
|
92
|
+
|
93
|
+
model_namespace.const_set(model_name, self)
|
90
94
|
class_eval(&block)
|
91
95
|
reset_column_information
|
92
96
|
end
|
@@ -95,13 +99,15 @@ class ViewModel::TestHelpers::ARVMBuilder
|
|
95
99
|
|
96
100
|
def define_viewmodel(&block)
|
97
101
|
vm_name = viewmodel_name
|
98
|
-
|
99
|
-
@viewmodel = Class.new(@viewmodel_base) do |
|
100
|
-
raise "Viewmodel alreay defined: #{vm_name}" if
|
101
|
-
|
102
|
+
vm_namespace = namespace
|
103
|
+
@viewmodel = Class.new(@viewmodel_base) do |_c|
|
104
|
+
raise "Viewmodel alreay defined: #{vm_name}" if vm_namespace.const_defined?(vm_name, false)
|
105
|
+
|
106
|
+
vm_namespace.const_set(vm_name, self)
|
102
107
|
class_eval(&block)
|
103
108
|
end
|
104
|
-
raise
|
109
|
+
raise 'help help' if @viewmodel.name.nil?
|
110
|
+
|
105
111
|
@viewmodel
|
106
112
|
end
|
107
113
|
|
@@ -21,6 +21,7 @@ class ViewModel::TraversalContext
|
|
21
21
|
end
|
22
22
|
|
23
23
|
attr_reader :shared_context
|
24
|
+
|
24
25
|
delegate :access_control, :callbacks, to: :shared_context
|
25
26
|
|
26
27
|
def self.new_child(*args)
|
@@ -118,7 +119,7 @@ class ViewModel::TraversalContext
|
|
118
119
|
|
119
120
|
def nearest_root_viewmodel
|
120
121
|
if root?
|
121
|
-
raise RuntimeError.new(
|
122
|
+
raise RuntimeError.new('Attempted to find nearest root from a root context. This is probably not what you wanted.')
|
122
123
|
elsif parent_context.root?
|
123
124
|
parent_viewmodel
|
124
125
|
else
|