mr 0.35.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 +7 -0
- data/.gitignore +19 -0
- data/Gemfile +13 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/bench/all.rb +4 -0
- data/bench/factory.rb +68 -0
- data/bench/fake_record.rb +174 -0
- data/bench/model.rb +201 -0
- data/bench/read_model.rb +191 -0
- data/bench/results/factory.txt +21 -0
- data/bench/results/fake_record.txt +37 -0
- data/bench/results/model.txt +44 -0
- data/bench/results/read_model.txt +46 -0
- data/bench/setup.rb +132 -0
- data/lib/mr.rb +11 -0
- data/lib/mr/after_commit.rb +49 -0
- data/lib/mr/after_commit/fake_record.rb +39 -0
- data/lib/mr/after_commit/record.rb +48 -0
- data/lib/mr/after_commit/record_procs_methods.rb +82 -0
- data/lib/mr/factory.rb +82 -0
- data/lib/mr/factory/config.rb +240 -0
- data/lib/mr/factory/model_factory.rb +103 -0
- data/lib/mr/factory/model_stack.rb +28 -0
- data/lib/mr/factory/read_model_factory.rb +104 -0
- data/lib/mr/factory/record_factory.rb +130 -0
- data/lib/mr/factory/record_stack.rb +219 -0
- data/lib/mr/fake_query.rb +53 -0
- data/lib/mr/fake_record.rb +58 -0
- data/lib/mr/fake_record/associations.rb +257 -0
- data/lib/mr/fake_record/attributes.rb +168 -0
- data/lib/mr/fake_record/persistence.rb +116 -0
- data/lib/mr/json_field.rb +180 -0
- data/lib/mr/json_field/fake_record.rb +31 -0
- data/lib/mr/json_field/record.rb +38 -0
- data/lib/mr/model.rb +67 -0
- data/lib/mr/model/associations.rb +161 -0
- data/lib/mr/model/configuration.rb +67 -0
- data/lib/mr/model/fields.rb +177 -0
- data/lib/mr/model/persistence.rb +79 -0
- data/lib/mr/query.rb +126 -0
- data/lib/mr/read_model.rb +83 -0
- data/lib/mr/read_model/data.rb +38 -0
- data/lib/mr/read_model/fields.rb +218 -0
- data/lib/mr/read_model/query_expression.rb +188 -0
- data/lib/mr/read_model/querying.rb +214 -0
- data/lib/mr/read_model/set_querying.rb +82 -0
- data/lib/mr/read_model/subquery.rb +98 -0
- data/lib/mr/record.rb +35 -0
- data/lib/mr/test_helpers.rb +229 -0
- data/lib/mr/type_converter.rb +85 -0
- data/lib/mr/version.rb +3 -0
- data/log/.gitkeep +0 -0
- data/mr.gemspec +29 -0
- data/test/helper.rb +21 -0
- data/test/support/db.rb +10 -0
- data/test/support/factory.rb +13 -0
- data/test/support/factory/area.rb +6 -0
- data/test/support/factory/comment.rb +14 -0
- data/test/support/factory/image.rb +6 -0
- data/test/support/factory/user.rb +6 -0
- data/test/support/models/area.rb +58 -0
- data/test/support/models/comment.rb +60 -0
- data/test/support/models/image.rb +53 -0
- data/test/support/models/user.rb +96 -0
- data/test/support/read_model/querying.rb +150 -0
- data/test/support/read_models/comment_with_user_data.rb +27 -0
- data/test/support/read_models/set_data.rb +49 -0
- data/test/support/read_models/subquery_data.rb +41 -0
- data/test/support/read_models/user_with_area_data.rb +15 -0
- data/test/support/schema.rb +39 -0
- data/test/support/setup_test_db.rb +10 -0
- data/test/system/factory/model_factory_tests.rb +87 -0
- data/test/system/factory/model_stack_tests.rb +30 -0
- data/test/system/factory/record_factory_tests.rb +84 -0
- data/test/system/factory/record_stack_tests.rb +51 -0
- data/test/system/factory_tests.rb +32 -0
- data/test/system/read_model_tests.rb +199 -0
- data/test/system/with_model_tests.rb +275 -0
- data/test/unit/after_commit/fake_record_tests.rb +110 -0
- data/test/unit/after_commit/record_procs_methods_tests.rb +177 -0
- data/test/unit/after_commit/record_tests.rb +134 -0
- data/test/unit/after_commit_tests.rb +113 -0
- data/test/unit/factory/config_tests.rb +651 -0
- data/test/unit/factory/model_factory_tests.rb +473 -0
- data/test/unit/factory/model_stack_tests.rb +97 -0
- data/test/unit/factory/read_model_factory_tests.rb +195 -0
- data/test/unit/factory/record_factory_tests.rb +446 -0
- data/test/unit/factory/record_stack_tests.rb +549 -0
- data/test/unit/factory_tests.rb +213 -0
- data/test/unit/fake_query_tests.rb +137 -0
- data/test/unit/fake_record/associations_tests.rb +585 -0
- data/test/unit/fake_record/attributes_tests.rb +265 -0
- data/test/unit/fake_record/persistence_tests.rb +239 -0
- data/test/unit/fake_record_tests.rb +106 -0
- data/test/unit/json_field/fake_record_tests.rb +75 -0
- data/test/unit/json_field/record_tests.rb +80 -0
- data/test/unit/json_field_tests.rb +302 -0
- data/test/unit/model/associations_tests.rb +346 -0
- data/test/unit/model/configuration_tests.rb +92 -0
- data/test/unit/model/fields_tests.rb +278 -0
- data/test/unit/model/persistence_tests.rb +114 -0
- data/test/unit/model_tests.rb +137 -0
- data/test/unit/query_tests.rb +300 -0
- data/test/unit/read_model/data_tests.rb +56 -0
- data/test/unit/read_model/fields_tests.rb +416 -0
- data/test/unit/read_model/query_expression_tests.rb +381 -0
- data/test/unit/read_model/querying_tests.rb +613 -0
- data/test/unit/read_model/set_querying_tests.rb +149 -0
- data/test/unit/read_model/subquery_tests.rb +242 -0
- data/test/unit/read_model_tests.rb +187 -0
- data/test/unit/record_tests.rb +45 -0
- data/test/unit/test_helpers_tests.rb +431 -0
- data/test/unit/type_converter_tests.rb +207 -0
- metadata +285 -0
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'much-plugin'
|
2
|
+
require 'mr/read_model/query_expression'
|
3
|
+
require 'mr/record'
|
4
|
+
require 'mr/query'
|
5
|
+
|
6
|
+
module MR::ReadModel
|
7
|
+
|
8
|
+
module Querying
|
9
|
+
include MuchPlugin
|
10
|
+
|
11
|
+
plugin_included do
|
12
|
+
extend ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
def relation
|
18
|
+
@relation ||= Relation.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def record_class
|
22
|
+
self.relation.from_record_class
|
23
|
+
end
|
24
|
+
|
25
|
+
def find(id, params = nil)
|
26
|
+
self.new(self.relation.build_for_find(id, params || {}).first!)
|
27
|
+
end
|
28
|
+
|
29
|
+
def query(params = nil)
|
30
|
+
MR::Query.new(self, self.relation.build_for_all(params || {}))
|
31
|
+
end
|
32
|
+
|
33
|
+
def count(*args)
|
34
|
+
self.query(*args).count
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_sql(params = nil)
|
38
|
+
self.relation.build_sql(params)
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_attr(column = nil)
|
42
|
+
self.relation.find_attr = column if !column.nil?
|
43
|
+
self.relation.find_attr
|
44
|
+
end
|
45
|
+
|
46
|
+
def select(*args, &block)
|
47
|
+
add_query_expression(:select, *args, &block)
|
48
|
+
rescue InvalidQueryExpressionError, ArgumentError => exception
|
49
|
+
raise ArgumentError, exception.message, caller
|
50
|
+
end
|
51
|
+
|
52
|
+
def from(record_class)
|
53
|
+
relation.from(record_class)
|
54
|
+
rescue ArgumentError => exception
|
55
|
+
raise ArgumentError, exception.message, caller
|
56
|
+
end
|
57
|
+
|
58
|
+
def from_subquery(&block)
|
59
|
+
relation.from_subquery(&block)
|
60
|
+
rescue ArgumentError => exception
|
61
|
+
raise ArgumentError, exception.message, caller
|
62
|
+
end
|
63
|
+
|
64
|
+
def joins(*args, &block)
|
65
|
+
add_query_expression(:joins, *args, &block)
|
66
|
+
rescue InvalidQueryExpressionError, ArgumentError => exception
|
67
|
+
raise ArgumentError, exception.message, caller
|
68
|
+
end
|
69
|
+
|
70
|
+
def inner_join_subquery(&block)
|
71
|
+
add_subquery_expression(:joins, :inner, &block)
|
72
|
+
rescue InvalidQueryExpressionError, ArgumentError => exception
|
73
|
+
raise ArgumentError, exception.message, caller
|
74
|
+
end
|
75
|
+
|
76
|
+
def left_outer_join_subquery(&block)
|
77
|
+
add_subquery_expression(:joins, :left, &block)
|
78
|
+
rescue InvalidQueryExpressionError, ArgumentError => exception
|
79
|
+
raise ArgumentError, exception.message, caller
|
80
|
+
end
|
81
|
+
alias :left_join_subquery :left_outer_join_subquery
|
82
|
+
|
83
|
+
def right_outer_join_subquery(&block)
|
84
|
+
add_subquery_expression(:joins, :right, &block)
|
85
|
+
rescue InvalidQueryExpressionError, ArgumentError => exception
|
86
|
+
raise ArgumentError, exception.message, caller
|
87
|
+
end
|
88
|
+
alias :right_join_subquery :right_outer_join_subquery
|
89
|
+
|
90
|
+
def full_outer_join_subquery(&block)
|
91
|
+
add_subquery_expression(:joins, :full, &block)
|
92
|
+
rescue InvalidQueryExpressionError, ArgumentError => exception
|
93
|
+
raise ArgumentError, exception.message, caller
|
94
|
+
end
|
95
|
+
alias :full_join_subquery :full_outer_join_subquery
|
96
|
+
|
97
|
+
def where(*args, &block)
|
98
|
+
add_merge_query_expression(:where, *args, &block)
|
99
|
+
rescue InvalidQueryExpressionError, ArgumentError => exception
|
100
|
+
raise ArgumentError, exception.message, caller
|
101
|
+
end
|
102
|
+
|
103
|
+
def order(*args, &block)
|
104
|
+
add_merge_query_expression(:order, *args, &block)
|
105
|
+
rescue InvalidQueryExpressionError, ArgumentError => exception
|
106
|
+
raise ArgumentError, exception.message, caller
|
107
|
+
end
|
108
|
+
|
109
|
+
def group(*args, &block)
|
110
|
+
add_query_expression(:group, *args, &block)
|
111
|
+
rescue InvalidQueryExpressionError, ArgumentError => exception
|
112
|
+
raise ArgumentError, exception.message, caller
|
113
|
+
end
|
114
|
+
|
115
|
+
def having(*args, &block)
|
116
|
+
add_query_expression(:having, *args, &block)
|
117
|
+
rescue InvalidQueryExpressionError, ArgumentError => exception
|
118
|
+
raise ArgumentError, exception.message, caller
|
119
|
+
end
|
120
|
+
|
121
|
+
def limit(*args, &block)
|
122
|
+
add_query_expression(:limit, *args, &block)
|
123
|
+
rescue InvalidQueryExpressionError, ArgumentError => exception
|
124
|
+
raise ArgumentError, exception.message, caller
|
125
|
+
end
|
126
|
+
|
127
|
+
def offset(*args, &block)
|
128
|
+
add_query_expression(:offset, *args, &block)
|
129
|
+
rescue InvalidQueryExpressionError, ArgumentError => exception
|
130
|
+
raise ArgumentError, exception.message, caller
|
131
|
+
end
|
132
|
+
|
133
|
+
def merge(*args, &block)
|
134
|
+
add_merge_query_expression(:merge, *args, &block)
|
135
|
+
rescue InvalidQueryExpressionError, ArgumentError => exception
|
136
|
+
raise ArgumentError, exception.message, caller
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def add_query_expression(type, *args, &block)
|
142
|
+
QueryExpression.new(type, *args, &block).tap do |expression|
|
143
|
+
relation.query_expressions << expression
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def add_merge_query_expression(type, *args, &block)
|
148
|
+
MergeQueryExpression.new(type, *args, &block).tap do |expression|
|
149
|
+
relation.query_expressions << expression
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def add_subquery_expression(type, *args, &block)
|
154
|
+
SubqueryExpression.new(type, *args, &block).tap do |expression|
|
155
|
+
relation.query_expressions << expression
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
class Relation
|
164
|
+
attr_reader :from_expression, :query_expressions, :set_expressions
|
165
|
+
attr_writer :find_attr
|
166
|
+
|
167
|
+
def initialize
|
168
|
+
@from_expression = NullFromExpression.new
|
169
|
+
@query_expressions = []
|
170
|
+
@set_expressions = []
|
171
|
+
end
|
172
|
+
|
173
|
+
def from(record_class)
|
174
|
+
@from_expression = FromExpression.new(record_class)
|
175
|
+
end
|
176
|
+
|
177
|
+
def from_subquery(&block)
|
178
|
+
@from_expression = FromSubqueryExpression.new(&block)
|
179
|
+
end
|
180
|
+
|
181
|
+
def from_record_class
|
182
|
+
self.from_expression.record_class
|
183
|
+
end
|
184
|
+
|
185
|
+
def find_attr
|
186
|
+
@find_attr ||= self.from_expression.default_find_attr
|
187
|
+
end
|
188
|
+
|
189
|
+
FIND_EXCLUDED_TYPES = [:where, :limit, :offset].freeze
|
190
|
+
def build_for_find(id, params = nil)
|
191
|
+
query_expressions = self.query_expressions.reject do |e|
|
192
|
+
FIND_EXCLUDED_TYPES.include?(e.type)
|
193
|
+
end
|
194
|
+
ar_relation = build_ar_relation_for_all(query_expressions, params)
|
195
|
+
ar_relation.where(self.find_attr => id).limit(1)
|
196
|
+
end
|
197
|
+
|
198
|
+
def build_for_all(params = nil)
|
199
|
+
build_ar_relation_for_all(self.query_expressions, params)
|
200
|
+
end
|
201
|
+
|
202
|
+
def build_sql(params = nil)
|
203
|
+
self.build_for_all(params).to_sql.strip
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
def build_ar_relation_for_all(query_expressions, params)
|
209
|
+
ar_relation = self.from_expression.ar_relation(params)
|
210
|
+
query_expressions.inject(ar_relation){ |r, e| e.apply_to(r, params) }
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'much-plugin'
|
2
|
+
require 'mr/read_model/querying'
|
3
|
+
|
4
|
+
module MR::ReadModel
|
5
|
+
|
6
|
+
module SetQuerying
|
7
|
+
include MuchPlugin
|
8
|
+
|
9
|
+
plugin_included do
|
10
|
+
include MR::ReadModel::Querying
|
11
|
+
extend ClassMethods
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
def relation
|
17
|
+
@relation ||= Relation.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def union(&block)
|
21
|
+
add_set_expression(:union, &block)
|
22
|
+
rescue ArgumentError => exception
|
23
|
+
raise ArgumentError, exception.message, caller
|
24
|
+
end
|
25
|
+
|
26
|
+
def union_all(&block)
|
27
|
+
add_set_expression(:union_all, &block)
|
28
|
+
rescue ArgumentError => exception
|
29
|
+
raise ArgumentError, exception.message, caller
|
30
|
+
end
|
31
|
+
|
32
|
+
def intersect(&block)
|
33
|
+
add_set_expression(:intersect, &block)
|
34
|
+
rescue ArgumentError => exception
|
35
|
+
raise ArgumentError, exception.message, caller
|
36
|
+
end
|
37
|
+
|
38
|
+
def intersect_all(&block)
|
39
|
+
add_set_expression(:intersect_all, &block)
|
40
|
+
rescue ArgumentError => exception
|
41
|
+
raise ArgumentError, exception.message, caller
|
42
|
+
end
|
43
|
+
|
44
|
+
def except(&block)
|
45
|
+
add_set_expression(:except, &block)
|
46
|
+
rescue ArgumentError => exception
|
47
|
+
raise ArgumentError, exception.message, caller
|
48
|
+
end
|
49
|
+
|
50
|
+
def except_all(&block)
|
51
|
+
add_set_expression(:except_all, &block)
|
52
|
+
rescue ArgumentError => exception
|
53
|
+
raise ArgumentError, exception.message, caller
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def add_set_expression(type, &block)
|
59
|
+
SetExpression.new(type, &block).tap do |expression|
|
60
|
+
relation.set_expressions << expression
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
class Relation < MR::ReadModel::Relation
|
67
|
+
attr_reader :set_expressions
|
68
|
+
|
69
|
+
def initialize
|
70
|
+
super
|
71
|
+
@set_expressions = []
|
72
|
+
end
|
73
|
+
|
74
|
+
def build_sql(params = nil)
|
75
|
+
sql = super(params)
|
76
|
+
self.set_expressions.inject(sql){ |s, e| e.combine_sql(s, params) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module MR; end
|
2
|
+
module MR::ReadModel
|
3
|
+
|
4
|
+
module Subquery
|
5
|
+
attr_reader :read_model_class
|
6
|
+
|
7
|
+
def initialize(&block)
|
8
|
+
self.instance_eval(&block)
|
9
|
+
if !self.read_model_class
|
10
|
+
raise ArgumentError, "invalid subquery - " \
|
11
|
+
"use `read_model` to define the subquery"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def read_model(&block)
|
16
|
+
@read_model_class = Class.new do
|
17
|
+
# TODO - fix circular require
|
18
|
+
require 'mr/read_model'
|
19
|
+
require 'mr/read_model/set_querying'
|
20
|
+
include MR::ReadModel
|
21
|
+
include MR::ReadModel::SetQuerying
|
22
|
+
end
|
23
|
+
@read_model_class.class_eval(&block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_sql(params = nil)
|
27
|
+
"(#{self.read_model_class.build_sql(params)})"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
module AliasSubquery
|
33
|
+
include Subquery
|
34
|
+
|
35
|
+
def alias_sql
|
36
|
+
@alias_sql ||= ""
|
37
|
+
end
|
38
|
+
|
39
|
+
def as(alias_name)
|
40
|
+
if alias_name.to_s.strip.empty?
|
41
|
+
raise ArgumentError, "alias can't be blank"
|
42
|
+
end
|
43
|
+
@alias_sql = "AS #{alias_name}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_sql(params = nil)
|
47
|
+
if self.alias_sql.to_s.strip.empty?
|
48
|
+
raise InvalidSubqueryError, "subquery must have an alias"
|
49
|
+
end
|
50
|
+
"#{super} #{self.alias_sql}".strip
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
class FromSubquery
|
56
|
+
include AliasSubquery
|
57
|
+
|
58
|
+
def record_class
|
59
|
+
self.read_model_class.record_class
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
class JoinSubquery
|
65
|
+
include AliasSubquery
|
66
|
+
|
67
|
+
DEFAULT_JOIN_SQL = 'JOIN'.freeze
|
68
|
+
JOIN_SQL = Hash.new(DEFAULT_JOIN_SQL).tap do |h|
|
69
|
+
h[:inner] = 'INNER JOIN'.freeze
|
70
|
+
h[:left] = 'LEFT OUTER JOIN'.freeze
|
71
|
+
h[:right] = 'RIGHT OUTER JOIN'.freeze
|
72
|
+
h[:full] = 'FULL OUTER JOIN'.freeze
|
73
|
+
end.freeze
|
74
|
+
|
75
|
+
attr_reader :join_sql
|
76
|
+
|
77
|
+
def initialize(type, &block)
|
78
|
+
@join_sql = JOIN_SQL[type]
|
79
|
+
super(&block)
|
80
|
+
end
|
81
|
+
|
82
|
+
def conditions_sql
|
83
|
+
@conditions_sql ||= ""
|
84
|
+
end
|
85
|
+
|
86
|
+
def on(conditions)
|
87
|
+
@conditions_sql = "ON #{conditions}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def build_sql(params = nil)
|
91
|
+
"#{self.join_sql} #{super} #{self.conditions_sql}".strip
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
InvalidSubqueryError = Class.new(RuntimeError)
|
97
|
+
|
98
|
+
end
|
data/lib/mr/record.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'much-plugin'
|
2
|
+
|
3
|
+
module MR
|
4
|
+
|
5
|
+
module Record
|
6
|
+
include MuchPlugin
|
7
|
+
|
8
|
+
plugin_included do
|
9
|
+
extend ClassMethods
|
10
|
+
include InstanceMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
module InstanceMethods
|
14
|
+
|
15
|
+
attr_writer :model
|
16
|
+
|
17
|
+
def model
|
18
|
+
@model ||= self.class.model_class.new(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def model_class
|
22
|
+
self.class.model_class
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
|
29
|
+
attr_accessor :model_class
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
require 'mr/fake_record'
|
2
|
+
require 'mr/model'
|
3
|
+
|
4
|
+
module MR
|
5
|
+
|
6
|
+
module TestHelpers
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def model_reset_save_called(model, &block)
|
10
|
+
fake_record = model.record
|
11
|
+
if !fake_record.kind_of?(MR::FakeRecord)
|
12
|
+
raise ArgumentError, "model must be using a fake record"
|
13
|
+
end
|
14
|
+
yield model if block
|
15
|
+
fake_record.reset_save_called
|
16
|
+
end
|
17
|
+
|
18
|
+
def assert_association_saved(model, association, expected_value)
|
19
|
+
with_backtrace(caller) do
|
20
|
+
AssociationSavedAssertion.new(model, association, expected_value).run(self)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def assert_not_association_saved(model, association, expected_value)
|
25
|
+
with_backtrace(caller) do
|
26
|
+
AssociationNotSavedAssertion.new(model, association, expected_value).run(self)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def assert_model_destroyed(model)
|
31
|
+
with_backtrace(caller) do
|
32
|
+
ModelDestroyedAssertion.new(model).run(self)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def assert_not_model_destroyed(model)
|
37
|
+
with_backtrace(caller) do
|
38
|
+
ModelNotDestroyedAssertion.new(model).run(self)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def assert_field_saved(model, field, expected_value)
|
43
|
+
with_backtrace(caller) do
|
44
|
+
FieldSavedAssertion.new(model, field, expected_value).run(self)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def assert_not_field_saved(model, field, expected_value)
|
49
|
+
with_backtrace(caller) do
|
50
|
+
FieldNotSavedAssertion.new(model, field, expected_value).run(self)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def assert_model_saved(model)
|
55
|
+
with_backtrace(caller) do
|
56
|
+
ModelSavedAssertion.new(model).run(self)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def assert_not_model_saved(model)
|
61
|
+
with_backtrace(caller) do
|
62
|
+
ModelNotSavedAssertion.new(model).run(self)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class AssociationSavedAssertionBase
|
67
|
+
def initialize(model, association, expected_value)
|
68
|
+
reflection = model.record.association(association).reflection
|
69
|
+
if reflection.macro != :belongs_to
|
70
|
+
raise ArgumentError, "association must be a belongs to"
|
71
|
+
end
|
72
|
+
@expected_value = expected_value || NULL_MODEL
|
73
|
+
# use `record.class` instead of `record_class` here, the configured
|
74
|
+
# `record_class` won't match the actual `record` class because `record` is
|
75
|
+
# a fake record
|
76
|
+
expected_foreign_type = @expected_value.record.class.to_s
|
77
|
+
expected_foreign_key = @expected_value.id
|
78
|
+
@assertions = [
|
79
|
+
build_assertion(model, reflection.foreign_type, expected_foreign_type),
|
80
|
+
build_assertion(model, reflection.foreign_key, expected_foreign_key)
|
81
|
+
].compact
|
82
|
+
end
|
83
|
+
|
84
|
+
def run(context)
|
85
|
+
@assertions.each{ |a| a.run(context) }
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def build_assertion(model, field, expected_value)
|
91
|
+
return unless field
|
92
|
+
self.field_assertion_class.new(model, field, expected_value)
|
93
|
+
end
|
94
|
+
|
95
|
+
NullModel = Struct.new(:id, :record)
|
96
|
+
NullRecord = Struct.new(:class)
|
97
|
+
NullRecordClass = Struct.new(:name)
|
98
|
+
NULL_MODEL = NullModel.new(nil, NullRecord.new(NullRecordClass.new))
|
99
|
+
end
|
100
|
+
|
101
|
+
class AssociationSavedAssertion < AssociationSavedAssertionBase
|
102
|
+
def field_assertion_class; FieldSavedAssertion; end
|
103
|
+
end
|
104
|
+
|
105
|
+
class AssociationNotSavedAssertion < AssociationSavedAssertionBase
|
106
|
+
def field_assertion_class; FieldNotSavedAssertion; end
|
107
|
+
end
|
108
|
+
|
109
|
+
class FieldSavedAssertionBase
|
110
|
+
def initialize(model, field, expected_value)
|
111
|
+
if !model.record.kind_of?(MR::FakeRecord)
|
112
|
+
raise ArgumentError, "model must be using a fake record"
|
113
|
+
end
|
114
|
+
@expected_value = expected_value
|
115
|
+
@field = field.to_s
|
116
|
+
@saved = model.record.saved_attributes.key?(@field)
|
117
|
+
@saved_as = model.record.saved_attributes[@field]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class FieldSavedAssertion < FieldSavedAssertionBase
|
122
|
+
def run(context)
|
123
|
+
if @saved
|
124
|
+
context.assert_equal @expected_value, @saved_as, saved_as_desc
|
125
|
+
else
|
126
|
+
context.assert_true @saved, saved_desc
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def saved_desc
|
133
|
+
"Expected #{@field.inspect} field was saved."
|
134
|
+
end
|
135
|
+
|
136
|
+
def saved_as_desc
|
137
|
+
"Expected #{@field.inspect} field was saved as #{@expected_value.inspect}."
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class FieldNotSavedAssertion < FieldSavedAssertionBase
|
142
|
+
def run(context)
|
143
|
+
if @saved
|
144
|
+
context.assert_not_equal @expected_value, @saved_as, saved_as_desc
|
145
|
+
else
|
146
|
+
context.assert_false @saved, saved_desc
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def saved_desc
|
153
|
+
"Expected #{@field.inspect} field was not saved."
|
154
|
+
end
|
155
|
+
|
156
|
+
def saved_as_desc
|
157
|
+
"Expected #{@field.inspect} field was not saved as #{@expected_value.inspect}."
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class ModelDestroyedAssertionBase
|
162
|
+
def initialize(model)
|
163
|
+
@model = model
|
164
|
+
@destroyed = @model.destroyed?
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class ModelDestroyedAssertion < ModelDestroyedAssertionBase
|
169
|
+
def run(context)
|
170
|
+
context.assert_true(@destroyed){ destroyed_desc }
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
def destroyed_desc
|
176
|
+
"Expected #{@model.inspect} was destroyed."
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class ModelNotDestroyedAssertion < ModelDestroyedAssertionBase
|
181
|
+
def run(context)
|
182
|
+
context.assert_false(@destroyed){ destroyed_desc }
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
def destroyed_desc
|
188
|
+
"Expected #{@model.inspect} was not destroyed."
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class ModelSavedAssertionBase
|
193
|
+
def initialize(model)
|
194
|
+
@model = model
|
195
|
+
fake_record = model.record
|
196
|
+
if !fake_record.kind_of?(MR::FakeRecord)
|
197
|
+
raise ArgumentError, "model must be using a fake record"
|
198
|
+
end
|
199
|
+
@saved = fake_record.save_called
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class ModelSavedAssertion < ModelSavedAssertionBase
|
204
|
+
def run(context)
|
205
|
+
context.assert_true(@saved){ saved_desc }
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
def saved_desc
|
211
|
+
"Expected #{@model.inspect} was saved."
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
class ModelNotSavedAssertion < ModelSavedAssertionBase
|
216
|
+
def run(context)
|
217
|
+
context.assert_false(@saved){ saved_desc }
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
221
|
+
|
222
|
+
def saved_desc
|
223
|
+
"Expected #{@model.inspect} was not saved."
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|