mr 0.35.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +22 -0
  5. data/README.md +29 -0
  6. data/bench/all.rb +4 -0
  7. data/bench/factory.rb +68 -0
  8. data/bench/fake_record.rb +174 -0
  9. data/bench/model.rb +201 -0
  10. data/bench/read_model.rb +191 -0
  11. data/bench/results/factory.txt +21 -0
  12. data/bench/results/fake_record.txt +37 -0
  13. data/bench/results/model.txt +44 -0
  14. data/bench/results/read_model.txt +46 -0
  15. data/bench/setup.rb +132 -0
  16. data/lib/mr.rb +11 -0
  17. data/lib/mr/after_commit.rb +49 -0
  18. data/lib/mr/after_commit/fake_record.rb +39 -0
  19. data/lib/mr/after_commit/record.rb +48 -0
  20. data/lib/mr/after_commit/record_procs_methods.rb +82 -0
  21. data/lib/mr/factory.rb +82 -0
  22. data/lib/mr/factory/config.rb +240 -0
  23. data/lib/mr/factory/model_factory.rb +103 -0
  24. data/lib/mr/factory/model_stack.rb +28 -0
  25. data/lib/mr/factory/read_model_factory.rb +104 -0
  26. data/lib/mr/factory/record_factory.rb +130 -0
  27. data/lib/mr/factory/record_stack.rb +219 -0
  28. data/lib/mr/fake_query.rb +53 -0
  29. data/lib/mr/fake_record.rb +58 -0
  30. data/lib/mr/fake_record/associations.rb +257 -0
  31. data/lib/mr/fake_record/attributes.rb +168 -0
  32. data/lib/mr/fake_record/persistence.rb +116 -0
  33. data/lib/mr/json_field.rb +180 -0
  34. data/lib/mr/json_field/fake_record.rb +31 -0
  35. data/lib/mr/json_field/record.rb +38 -0
  36. data/lib/mr/model.rb +67 -0
  37. data/lib/mr/model/associations.rb +161 -0
  38. data/lib/mr/model/configuration.rb +67 -0
  39. data/lib/mr/model/fields.rb +177 -0
  40. data/lib/mr/model/persistence.rb +79 -0
  41. data/lib/mr/query.rb +126 -0
  42. data/lib/mr/read_model.rb +83 -0
  43. data/lib/mr/read_model/data.rb +38 -0
  44. data/lib/mr/read_model/fields.rb +218 -0
  45. data/lib/mr/read_model/query_expression.rb +188 -0
  46. data/lib/mr/read_model/querying.rb +214 -0
  47. data/lib/mr/read_model/set_querying.rb +82 -0
  48. data/lib/mr/read_model/subquery.rb +98 -0
  49. data/lib/mr/record.rb +35 -0
  50. data/lib/mr/test_helpers.rb +229 -0
  51. data/lib/mr/type_converter.rb +85 -0
  52. data/lib/mr/version.rb +3 -0
  53. data/log/.gitkeep +0 -0
  54. data/mr.gemspec +29 -0
  55. data/test/helper.rb +21 -0
  56. data/test/support/db.rb +10 -0
  57. data/test/support/factory.rb +13 -0
  58. data/test/support/factory/area.rb +6 -0
  59. data/test/support/factory/comment.rb +14 -0
  60. data/test/support/factory/image.rb +6 -0
  61. data/test/support/factory/user.rb +6 -0
  62. data/test/support/models/area.rb +58 -0
  63. data/test/support/models/comment.rb +60 -0
  64. data/test/support/models/image.rb +53 -0
  65. data/test/support/models/user.rb +96 -0
  66. data/test/support/read_model/querying.rb +150 -0
  67. data/test/support/read_models/comment_with_user_data.rb +27 -0
  68. data/test/support/read_models/set_data.rb +49 -0
  69. data/test/support/read_models/subquery_data.rb +41 -0
  70. data/test/support/read_models/user_with_area_data.rb +15 -0
  71. data/test/support/schema.rb +39 -0
  72. data/test/support/setup_test_db.rb +10 -0
  73. data/test/system/factory/model_factory_tests.rb +87 -0
  74. data/test/system/factory/model_stack_tests.rb +30 -0
  75. data/test/system/factory/record_factory_tests.rb +84 -0
  76. data/test/system/factory/record_stack_tests.rb +51 -0
  77. data/test/system/factory_tests.rb +32 -0
  78. data/test/system/read_model_tests.rb +199 -0
  79. data/test/system/with_model_tests.rb +275 -0
  80. data/test/unit/after_commit/fake_record_tests.rb +110 -0
  81. data/test/unit/after_commit/record_procs_methods_tests.rb +177 -0
  82. data/test/unit/after_commit/record_tests.rb +134 -0
  83. data/test/unit/after_commit_tests.rb +113 -0
  84. data/test/unit/factory/config_tests.rb +651 -0
  85. data/test/unit/factory/model_factory_tests.rb +473 -0
  86. data/test/unit/factory/model_stack_tests.rb +97 -0
  87. data/test/unit/factory/read_model_factory_tests.rb +195 -0
  88. data/test/unit/factory/record_factory_tests.rb +446 -0
  89. data/test/unit/factory/record_stack_tests.rb +549 -0
  90. data/test/unit/factory_tests.rb +213 -0
  91. data/test/unit/fake_query_tests.rb +137 -0
  92. data/test/unit/fake_record/associations_tests.rb +585 -0
  93. data/test/unit/fake_record/attributes_tests.rb +265 -0
  94. data/test/unit/fake_record/persistence_tests.rb +239 -0
  95. data/test/unit/fake_record_tests.rb +106 -0
  96. data/test/unit/json_field/fake_record_tests.rb +75 -0
  97. data/test/unit/json_field/record_tests.rb +80 -0
  98. data/test/unit/json_field_tests.rb +302 -0
  99. data/test/unit/model/associations_tests.rb +346 -0
  100. data/test/unit/model/configuration_tests.rb +92 -0
  101. data/test/unit/model/fields_tests.rb +278 -0
  102. data/test/unit/model/persistence_tests.rb +114 -0
  103. data/test/unit/model_tests.rb +137 -0
  104. data/test/unit/query_tests.rb +300 -0
  105. data/test/unit/read_model/data_tests.rb +56 -0
  106. data/test/unit/read_model/fields_tests.rb +416 -0
  107. data/test/unit/read_model/query_expression_tests.rb +381 -0
  108. data/test/unit/read_model/querying_tests.rb +613 -0
  109. data/test/unit/read_model/set_querying_tests.rb +149 -0
  110. data/test/unit/read_model/subquery_tests.rb +242 -0
  111. data/test/unit/read_model_tests.rb +187 -0
  112. data/test/unit/record_tests.rb +45 -0
  113. data/test/unit/test_helpers_tests.rb +431 -0
  114. data/test/unit/type_converter_tests.rb +207 -0
  115. metadata +285 -0
@@ -0,0 +1,116 @@
1
+ require 'active_model'
2
+ require 'active_record'
3
+ require 'active_record/validations'
4
+ require 'much-plugin'
5
+ require 'mr/factory'
6
+ require 'mr/fake_record/attributes'
7
+
8
+ module MR; end
9
+ module MR::FakeRecord
10
+
11
+ module Persistence
12
+ include MuchPlugin
13
+
14
+ plugin_included do
15
+ include MR::FakeRecord::Attributes
16
+ extend TransactionMethods
17
+ extend ClassMethods
18
+ include InstanceMethods
19
+
20
+ attribute :id, :primary_key
21
+ end
22
+
23
+ # this is broken into a separate module so `FakeRecord` can extend it and
24
+ # provide easy access to the `transaction` method (for stubbing, etc)
25
+ module TransactionMethods
26
+
27
+ # ActiveRecord methods
28
+
29
+ def transaction
30
+ begin
31
+ yield if block_given?
32
+ rescue ActiveRecord::Rollback
33
+ # activerecord swallows rollback exceptions, they are only intended as a
34
+ # mechanism to rollback the transaction
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ module ClassMethods
41
+
42
+ # ActiveRecord methods
43
+
44
+ # this is needed to raise ActiveRecord::RecordInvalid
45
+ def human_attribute_name(attribute, options = {})
46
+ options[:default] || attribute.to_s.split('.').last
47
+ end
48
+
49
+ end
50
+
51
+ module InstanceMethods
52
+
53
+ # ActiveRecord methods
54
+
55
+ def save!
56
+ raise ActiveRecord::RecordInvalid.new(self) unless self.valid?
57
+ self.id ||= MR::Factory.primary_key(self.class)
58
+ current_time = CurrentTime.new
59
+ self.created_at ||= current_time if self.respond_to?(:created_at=)
60
+ if self.respond_to?(:updated_at=) && !self.updated_at_changed?
61
+ self.updated_at = current_time
62
+ end
63
+ self.saved_attributes = self.attributes.dup
64
+ @save_called = true
65
+ end
66
+
67
+ def destroy
68
+ @destroyed = true
69
+ end
70
+
71
+ def transaction(&block)
72
+ self.class.transaction(&block)
73
+ end
74
+
75
+ def new_record?
76
+ !self.id
77
+ end
78
+
79
+ def destroyed?
80
+ !!@destroyed
81
+ end
82
+
83
+ def errors
84
+ @errors ||= ActiveModel::Errors.new(self)
85
+ end
86
+
87
+ def valid?
88
+ self.errors.empty?
89
+ end
90
+
91
+ # Non-ActiveRecord methods
92
+
93
+ def save_called
94
+ @save_called = false if @save_called.nil?
95
+ @save_called
96
+ end
97
+
98
+ def reset_save_called
99
+ @save_called = false
100
+ end
101
+
102
+ end
103
+
104
+ module CurrentTime
105
+ def self.new
106
+ if ActiveRecord::Base.default_timezone == :utc
107
+ Time.now.utc
108
+ else
109
+ Time.now
110
+ end
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+ end
@@ -0,0 +1,180 @@
1
+ require 'much-plugin'
2
+
3
+ require 'mr/json_field/fake_record'
4
+ require 'mr/json_field/record'
5
+
6
+ module MR
7
+
8
+ module JsonField
9
+ include MuchPlugin
10
+
11
+ DEFAULT_ENCODER = proc{ |value| ::JSON.dump(value) }
12
+ DEFAULT_DECODER = proc{ |value| ::JSON.load(value) }
13
+
14
+ def self.encoder; @encoder ||= DEFAULT_ENCODER; end
15
+ def self.encoder=(new_value); @encoder = new_value; end
16
+
17
+ def self.decoder; @decoder ||= DEFAULT_DECODER; end
18
+ def self.decoder=(new_value); @decoder = new_value; end
19
+
20
+ def self.encode(value)
21
+ self.encoder.call(value)
22
+ rescue StandardError => exception
23
+ raise InvalidJSONError, exception.message
24
+ end
25
+
26
+ def self.decode(value)
27
+ self.decoder.call(value)
28
+ rescue StandardError => exception
29
+ raise InvalidJSONError, exception.message
30
+ end
31
+
32
+ # this can be used with `MR::Model` or `MR::ReadModel`, so it doesn't
33
+ # include either by default
34
+ plugin_included do
35
+ extend ClassMethods
36
+ end
37
+
38
+ module ClassMethods
39
+
40
+ def json_field(field_name, options = nil)
41
+ json_field_reader(field_name, options)
42
+ json_field_writer(field_name, options)
43
+ end
44
+
45
+ def json_field_reader(field_name, options = nil)
46
+ options ||= {}
47
+ field_name = field_name.to_s
48
+ ivar_name = "@#{field_name}"
49
+ source_field_name = (options[:source] || "#{field_name}_json").to_s
50
+
51
+ if source_field_name == field_name
52
+ raise ArgumentError, "the field name and source cannot be the same"
53
+ end
54
+
55
+ define_method(field_name) do
56
+ if !(cached_value = self.instance_variable_get(ivar_name)).nil?
57
+ return cached_value
58
+ else
59
+ source_value = self.send(source_field_name)
60
+ return source_value if source_value.nil?
61
+
62
+ value = begin
63
+ MR::JsonField.decode(source_value)
64
+ rescue InvalidJSONError => exception
65
+ message = "can't decode `#{field_name}` JSON field (from " \
66
+ "`#{source_field_name}`): #{exception.message}"
67
+ raise exception.class, message, caller
68
+ end
69
+
70
+ # cache the decoded value in the ivar
71
+ self.instance_variable_set(ivar_name, value)
72
+
73
+ value
74
+ end
75
+ end
76
+
77
+ (self.json_field_readers << field_name).uniq!
78
+ (self.json_field_source_fields << source_field_name).uniq!
79
+ end
80
+
81
+ def json_field_writer(field_name, options = nil)
82
+ options ||= {}
83
+ field_name = field_name.to_s.strip
84
+ ivar_name = "@#{field_name}"
85
+ source_field_name = (options[:source] || "#{field_name}_json").to_s.strip
86
+
87
+ if source_field_name == field_name
88
+ raise ArgumentError, "the field name and source cannot be the same"
89
+ end
90
+
91
+ define_method("#{field_name}=") do |new_value|
92
+ encoded_value = if !new_value.nil?
93
+ begin
94
+ MR::JsonField.encode(new_value)
95
+ rescue InvalidJSONError => exception
96
+ message = "can't encode value for `#{field_name}` JSON field: " \
97
+ "#{exception.message}"
98
+ raise exception.class, message, caller
99
+ end
100
+ else
101
+ nil
102
+ end
103
+ self.send("#{source_field_name}=", encoded_value)
104
+
105
+ # reset the ivar so its value will be calculated again when read
106
+ self.instance_variable_set(ivar_name, nil)
107
+
108
+ new_value
109
+ end
110
+
111
+ (self.json_field_writers << field_name).uniq!
112
+ (self.json_field_source_fields << source_field_name).uniq!
113
+ end
114
+
115
+ def json_field_readers; @json_field_readers ||= []; end
116
+ def json_field_writers; @json_field_writers ||= []; end
117
+
118
+ def json_field_accessors
119
+ self.json_field_readers & self.json_field_writers
120
+ end
121
+
122
+ def json_field_source_fields
123
+ @json_field_source_fields ||= []
124
+ end
125
+
126
+ end
127
+
128
+ module TestHelpers
129
+ include MuchPlugin
130
+
131
+ plugin_included do
132
+ include InstanceMethods
133
+
134
+ require 'mr/factory'
135
+ end
136
+
137
+ module InstanceMethods
138
+
139
+ def assert_json_field(subject, field_name, options = nil)
140
+ assert_json_field_reader(subject, field_name, options)
141
+ assert_json_field_writer(subject, field_name, options)
142
+ end
143
+
144
+ def assert_json_field_reader(subject, field_name, options = nil)
145
+ options ||= {}
146
+ source_field_name = options[:source] || "#{field_name}_json"
147
+
148
+ # set a value to read if it's `nil`
149
+ if subject.send(source_field_name).nil?
150
+ encoded_value = MR::JsonField.encode({
151
+ MR::Factory.string => MR::Factory.string
152
+ })
153
+ subject.send("#{source_field_name}=", encoded_value)
154
+ end
155
+
156
+ assert_respond_to "#{field_name}", subject
157
+ exp = MR::JsonField.decode(subject.send"#{source_field_name}")
158
+ assert_equal exp, subject.send("#{field_name}")
159
+ end
160
+
161
+ def assert_json_field_writer(subject, field_name, options = nil)
162
+ options ||= {}
163
+ source_field_name = options[:source] || "#{field_name}_json"
164
+
165
+ assert_respond_to "#{field_name}=", subject
166
+ new_value = { MR::Factory.string => MR::Factory.string }
167
+ subject.send("#{field_name}=", new_value)
168
+ exp = MR::JsonField.encode(new_value)
169
+ assert_equal exp, subject.send("#{source_field_name}")
170
+ end
171
+
172
+ end
173
+
174
+ end
175
+
176
+ InvalidJSONError = Class.new(StandardError)
177
+
178
+ end
179
+
180
+ end
@@ -0,0 +1,31 @@
1
+ require 'much-plugin'
2
+
3
+ require 'mr/fake_record'
4
+
5
+ module MR; end
6
+ module MR::JsonField
7
+
8
+ module FakeRecord
9
+ include MuchPlugin
10
+
11
+ plugin_included do
12
+ include MR::FakeRecord
13
+ include InstanceMethods
14
+ end
15
+
16
+ module InstanceMethods
17
+
18
+ # this mimics the `JsonField::Record` mixin, doing the same logic to
19
+ # ensure that the source fields match the json fields
20
+ def save!
21
+ self.model.class.json_field_accessors.each do |field_name|
22
+ self.model.send("#{field_name}=", self.model.send(field_name))
23
+ end
24
+ super
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,38 @@
1
+ require 'much-plugin'
2
+
3
+ require 'mr/record'
4
+
5
+ module MR; end
6
+ module MR::JsonField
7
+
8
+ module Record
9
+ include MuchPlugin
10
+
11
+ plugin_included do
12
+ include MR::Record
13
+ include InstanceMethods
14
+
15
+ before_save :json_field_sync_all_json_fields_on_save
16
+ end
17
+
18
+ module InstanceMethods
19
+
20
+ private
21
+
22
+ # this makes it so changes that are done to the JSON object are persisted
23
+ # to the DB without having to re-write the entire field, for example if
24
+ # the json field is a hash then a change to an individual key's value
25
+ # needs to get re-encoded and stored in the source field, since the json
26
+ # field writer wasn't used it won't get encoded, this callback fixes the
27
+ # issue by ensuring all json fields get synced before we save the record
28
+ def json_field_sync_all_json_fields_on_save
29
+ self.model.class.json_field_accessors.each do |field_name|
30
+ self.model.send("#{field_name}=", self.model.send(field_name))
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,67 @@
1
+ require 'much-plugin'
2
+ require 'mr/model/associations'
3
+ require 'mr/model/configuration'
4
+ require 'mr/model/fields'
5
+ require 'mr/model/persistence'
6
+
7
+ module MR
8
+
9
+ module Model
10
+ include MuchPlugin
11
+
12
+ plugin_included do
13
+ include MR::Model::Configuration
14
+ include MR::Model::Fields
15
+ include MR::Model::Associations
16
+ include MR::Model::Persistence
17
+ extend ClassMethods
18
+ include InstanceMethods
19
+ end
20
+
21
+ module ClassMethods
22
+
23
+ def find(id)
24
+ self.new(self.record_class.find(id))
25
+ end
26
+
27
+ def all
28
+ self.record_class.all.map{ |record| self.new(record) }
29
+ end
30
+
31
+ end
32
+
33
+ module InstanceMethods
34
+
35
+ def initialize(*args)
36
+ field_values = args.pop if args.last.kind_of?(Hash)
37
+ set_record(args.first || self.record_class.new)
38
+ (field_values || {}).each do |name, value|
39
+ self.send("#{name}=", value)
40
+ end
41
+ end
42
+
43
+ def ==(other)
44
+ other.kind_of?(self.class) ? self.record == other.record : super
45
+ end
46
+
47
+ def eql?(other)
48
+ other.kind_of?(self.class) ? self.record.eql?(other.record) : super
49
+ end
50
+
51
+ def hash
52
+ record.hash
53
+ end
54
+
55
+ def inspect
56
+ object_hex = (self.object_id << 1).to_s(16)
57
+ fields_inspect = self.class.fields.map do |field|
58
+ "@#{field.name}=#{field.read(record).inspect}"
59
+ end.sort.join(" ")
60
+ "#<#{self.class}:0x#{object_hex} #{fields_inspect}>"
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,161 @@
1
+ require 'much-plugin'
2
+ require 'mr/model/configuration'
3
+
4
+ module MR; end
5
+ module MR::Model
6
+
7
+ module Associations
8
+ include MuchPlugin
9
+
10
+ plugin_included do
11
+ include MR::Model::Configuration
12
+ extend ClassMethods
13
+ end
14
+
15
+ module ClassMethods
16
+
17
+ def associations
18
+ @associations ||= MR::Model::AssociationSet.new
19
+ end
20
+
21
+ def belongs_to(*names)
22
+ names.each do |name|
23
+ self.associations.add_belongs_to(name, self)
24
+ end
25
+ end
26
+
27
+ def polymorphic_belongs_to(*names)
28
+ names.each do |name|
29
+ self.associations.add_polymorphic_belongs_to(name, self)
30
+ end
31
+ end
32
+
33
+ def has_one(*names)
34
+ names.each do |name|
35
+ self.associations.add_has_one(name, self)
36
+ end
37
+ end
38
+
39
+ def has_many(*names)
40
+ names.each do |name|
41
+ self.associations.add_has_many(name, self)
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ class AssociationSet
50
+ attr_reader :belongs_to, :polymorphic_belongs_to
51
+ attr_reader :has_one, :has_many
52
+
53
+ def initialize
54
+ @belongs_to = []
55
+ @polymorphic_belongs_to = []
56
+ @has_one = []
57
+ @has_many = []
58
+ end
59
+
60
+ def add_belongs_to(name, model_class)
61
+ association = BelongsToAssociation.new(name)
62
+ association.define_accessor_on(model_class)
63
+ @belongs_to << association
64
+ end
65
+
66
+ def add_polymorphic_belongs_to(name, model_class)
67
+ association = PolymorphicBelongsToAssociation.new(name)
68
+ association.define_accessor_on(model_class)
69
+ @polymorphic_belongs_to << association
70
+ end
71
+
72
+ def add_has_one(name, model_class)
73
+ association = HasOneAssociation.new(name)
74
+ association.define_accessor_on(model_class)
75
+ @has_one << association
76
+ end
77
+
78
+ def add_has_many(name, model_class)
79
+ association = HasManyAssociation.new(name)
80
+ association.define_accessor_on(model_class)
81
+ @has_many << association
82
+ end
83
+
84
+ end
85
+
86
+ class Association
87
+ attr_reader :name
88
+ attr_reader :reader_method_name, :writer_method_name
89
+
90
+ def initialize(name)
91
+ @name = name.to_s
92
+ @reader_method_name = @name
93
+ @writer_method_name = "#{@name}="
94
+ @association_reader_name = @name
95
+ @association_writer_name = "#{@name}="
96
+ end
97
+
98
+ def define_accessor_on(model_class)
99
+ association = self
100
+ model_class.class_eval do
101
+
102
+ define_method(association.reader_method_name) do
103
+ association.read(record)
104
+ end
105
+
106
+ define_method(association.writer_method_name) do |value|
107
+ begin
108
+ association.write(value, self, record){ |m| m.record }
109
+ rescue BadAssociationValueError => exception
110
+ raise ArgumentError, exception.message, caller
111
+ end
112
+ end
113
+
114
+ end
115
+ end
116
+ end
117
+
118
+ class OneToOneAssociation < Association
119
+ def read(record)
120
+ if associated_record = record.send(@association_reader_name)
121
+ associated_record.model_class.new(associated_record)
122
+ end
123
+ end
124
+
125
+ def write(value, model, record, &block)
126
+ raise BadAssociationValueError.new(value) if value && !value.kind_of?(MR::Model)
127
+ associated_record = model.instance_exec(value, &block) if value
128
+ record.send(@association_writer_name, associated_record)
129
+ value
130
+ end
131
+ end
132
+
133
+ class OneToManyAssociation < Association
134
+ def read(record)
135
+ (record.send(@association_reader_name) || []).map do |associated_record|
136
+ associated_record.model_class.new(associated_record)
137
+ end
138
+ end
139
+
140
+ def write(values, model, record, &block)
141
+ associated_records = [*values].compact.map do |value|
142
+ raise BadAssociationValueError.new(value) if !value.kind_of?(MR::Model)
143
+ model.instance_exec(value, &block)
144
+ end
145
+ record.send(@association_writer_name, associated_records)
146
+ values
147
+ end
148
+ end
149
+
150
+ BelongsToAssociation = Class.new(OneToOneAssociation)
151
+ PolymorphicBelongsToAssociation = Class.new(BelongsToAssociation)
152
+ HasOneAssociation = Class.new(OneToOneAssociation)
153
+ HasManyAssociation = Class.new(OneToManyAssociation)
154
+
155
+ class BadAssociationValueError < RuntimeError
156
+ def initialize(value)
157
+ super "#{value.inspect} is not a kind of MR::Model"
158
+ end
159
+ end
160
+
161
+ end