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.
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,53 @@
1
+ require 'mr/query'
2
+
3
+ module MR
4
+
5
+ class FakeQuery
6
+
7
+ attr_reader :results, :count
8
+ alias :results! :results
9
+ alias :count! :count
10
+
11
+ def initialize(results)
12
+ @results = results || []
13
+ @count = @results.size
14
+ end
15
+
16
+ def first
17
+ @results.first
18
+ end
19
+ alias :first! :first
20
+
21
+ def paged(page_num = nil, page_size = nil)
22
+ FakePagedQuery.new(self, page_num, page_size)
23
+ end
24
+
25
+ end
26
+
27
+ class FakePagedQuery < FakeQuery
28
+
29
+ attr_reader :page_num, :page_size, :page_offset, :total_count
30
+ alias :total_count! :total_count
31
+
32
+ def initialize(query, page_num, page_size)
33
+ @page_num = MR::PagedQuery::PageNumber.new(page_num)
34
+ @page_size = MR::PagedQuery::PageSize.new(page_size)
35
+ @page_offset = MR::PagedQuery::PageOffset.new(@page_num, @page_size)
36
+ @unpaged_results = query.results.dup
37
+ @total_count = @unpaged_results.size
38
+ @has_next_page = (@page_offset + @page_size) < @total_count
39
+
40
+ super(@unpaged_results.dup[@page_offset, @page_size])
41
+ end
42
+
43
+ def has_next_page?
44
+ @has_next_page
45
+ end
46
+
47
+ def is_last_page?
48
+ !self.has_next_page?
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,58 @@
1
+ require 'much-plugin'
2
+ require 'mr/fake_record/associations'
3
+ require 'mr/fake_record/attributes'
4
+ require 'mr/fake_record/persistence'
5
+ require 'mr/record'
6
+
7
+ module MR
8
+
9
+ module FakeRecord
10
+ include MuchPlugin
11
+ extend MR::FakeRecord::Persistence::TransactionMethods
12
+
13
+ plugin_included do
14
+ include MR::Record
15
+ include MR::FakeRecord::Associations
16
+ include MR::FakeRecord::Attributes
17
+ include MR::FakeRecord::Persistence
18
+ extend ClassMethods
19
+ include InstanceMethods
20
+ end
21
+
22
+ module ClassMethods
23
+
24
+ def model_class(value = nil)
25
+ @model_class = value if value
26
+ @model_class
27
+ end
28
+
29
+ end
30
+
31
+ module InstanceMethods
32
+
33
+ def initialize(attrs = nil)
34
+ self.attributes = attrs || {}
35
+ end
36
+
37
+ def ==(other)
38
+ other.kind_of?(self.class) ? self.id == other.id : super
39
+ end
40
+ alias :eql? :==
41
+
42
+ def hash
43
+ self.id.hash
44
+ end
45
+
46
+ def inspect
47
+ object_hex = (self.object_id << 1).to_s(16)
48
+ attributes_inspect = self.class.attributes.map do |attribute|
49
+ "@#{attribute.name}=#{attribute.read(self).inspect}"
50
+ end.sort.join(" ")
51
+ "#<#{self.class}:0x#{object_hex} #{attributes_inspect}>"
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,257 @@
1
+ require 'much-plugin'
2
+ require 'active_support/core_ext'
3
+ require 'mr/fake_record/attributes'
4
+
5
+ module MR; end
6
+ module MR::FakeRecord
7
+
8
+ module Associations
9
+ include MuchPlugin
10
+
11
+ plugin_included do
12
+ include MR::FakeRecord::Attributes
13
+ extend ClassMethods
14
+ include InstanceMethods
15
+ end
16
+
17
+ module ClassMethods
18
+
19
+ def reflections
20
+ @reflections ||= MR::FakeRecord::ReflectionSet.new
21
+ end
22
+
23
+ def belongs_to(name, options = nil)
24
+ self.reflections.add_belongs_to(self, name, options)
25
+ end
26
+
27
+ def has_one(name, options = nil)
28
+ self.reflections.add_has_one(self, name, options)
29
+ end
30
+
31
+ def has_many(name, options = nil)
32
+ self.reflections.add_has_many(self, name, options)
33
+ end
34
+
35
+ # ActiveRecord methods
36
+
37
+ def reflect_on_association(name)
38
+ self.reflections.find(name)
39
+ end
40
+
41
+ def reflect_on_all_associations(type = nil)
42
+ self.reflections.all(type)
43
+ end
44
+
45
+ end
46
+
47
+ module InstanceMethods
48
+
49
+ # ActiveRecord methods
50
+
51
+ def association(name)
52
+ @association_cache ||= {}
53
+ @association_cache[name.to_sym] ||= begin
54
+ reflection = self.class.reflections.find(name)
55
+ reflection.association_class.new(self, reflection)
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+
63
+ class ReflectionSet
64
+ def initialize
65
+ @belongs_to = {}
66
+ @has_one = {}
67
+ @has_many = {}
68
+ end
69
+
70
+ def belongs_to
71
+ @belongs_to.values.sort
72
+ end
73
+
74
+ def has_one
75
+ @has_one.values.sort
76
+ end
77
+
78
+ def has_many
79
+ @has_many.values.sort
80
+ end
81
+
82
+ def find(name)
83
+ @belongs_to[name.to_s] || @has_one[name.to_s] || @has_many[name.to_s]
84
+ end
85
+
86
+ def all(type = nil)
87
+ case type
88
+ when :belongs_to, :has_one, :has_many
89
+ self.send(type)
90
+ else
91
+ self.belongs_to + self.has_one + self.has_many
92
+ end
93
+ end
94
+
95
+ def add_belongs_to(fake_record_class, name, options = nil)
96
+ options ||= {}
97
+ options[:foreign_key] ||= "#{name}_id"
98
+ if options[:polymorphic]
99
+ options[:foreign_type] ||= "#{name}_type"
100
+ end
101
+ reflection = Reflection.new(:belongs_to, name, options)
102
+ reflection.define_accessor_on(fake_record_class)
103
+ @belongs_to[name.to_s] = reflection
104
+ end
105
+
106
+ def add_has_one(fake_record_class, name, options = nil)
107
+ reflection = Reflection.new(:has_one, name, options)
108
+ reflection.define_accessor_on(fake_record_class)
109
+ @has_one[name.to_s] = reflection
110
+ end
111
+
112
+ def add_has_many(fake_record_class, name, options = nil)
113
+ reflection = Reflection.new(:has_many, name, options)
114
+ reflection.define_accessor_on(fake_record_class)
115
+ @has_many[name.to_s] = reflection
116
+ end
117
+ end
118
+
119
+ class Reflection
120
+ attr_reader :reader_method_name, :writer_method_name
121
+
122
+ # ActiveRecord methods
123
+ attr_reader :name, :macro, :options
124
+ attr_reader :foreign_key, :foreign_type
125
+ attr_reader :association_class
126
+
127
+ BELONGS_TO_ASSOC_PROC = proc do |r|
128
+ r.options[:polymorphic] ? PolymorphicBelongsToAssociation : BelongsToAssociation
129
+ end
130
+ ASSOCIATION_CLASS = {
131
+ :belongs_to => BELONGS_TO_ASSOC_PROC,
132
+ :has_one => proc{ HasOneAssociation },
133
+ :has_many => proc{ HasManyAssociation }
134
+ }.freeze
135
+
136
+ def initialize(macro, name, options = nil)
137
+ @macro = macro.to_sym
138
+ @name = name
139
+ @options = options || {}
140
+
141
+ @class_name = @options[:class_name]
142
+ @foreign_key = @options[:foreign_key]
143
+ @foreign_type = @options[:foreign_type]
144
+
145
+ @reader_method_name = name.to_s
146
+ @writer_method_name = "#{@reader_method_name}="
147
+ @association_class = ASSOCIATION_CLASS[@macro].call(self)
148
+ end
149
+
150
+ # ActiveRecord method
151
+ def klass
152
+ @klass ||= (@class_name.to_s.constantize if @class_name)
153
+ end
154
+
155
+ def define_accessor_on(fake_record_class)
156
+ reflection = self
157
+ fake_record_class.class_eval do
158
+
159
+ define_method(reflection.reader_method_name) do
160
+ self.association(reflection.name).read
161
+ end
162
+ define_method(reflection.writer_method_name) do |value|
163
+ self.association(reflection.name).write(value)
164
+ end
165
+
166
+ end
167
+ end
168
+
169
+ def <=>(other)
170
+ if other.kind_of?(self.class)
171
+ [ self.macro, self.options[:polymorphic], self.name ].map(&:to_s) <=>
172
+ [ other.macro, other.options[:polymorphic], other.name ].map(&:to_s)
173
+ else
174
+ super
175
+ end
176
+ end
177
+ end
178
+
179
+ class Association
180
+ # ActiveRecord method
181
+ attr_reader :owner, :reflection
182
+
183
+ def initialize(owner, reflection)
184
+ @owner = owner
185
+ @reflection = reflection
186
+ @ivar_name = "@#{@reflection.name}"
187
+ end
188
+
189
+ def read; raise NotImplementedError; end
190
+ def write(value); raise NotImplementedError; end
191
+
192
+ # ActiveRecord method
193
+ def klass
194
+ self.reflection.klass
195
+ end
196
+
197
+ def <=>(other)
198
+ other.kind_of?(Association) ? self.reflection <=> other.reflection : super
199
+ end
200
+ end
201
+
202
+ class OneToOneAssociation < Association
203
+ def read
204
+ @owner.instance_variable_get(@ivar_name)
205
+ end
206
+
207
+ def write(value)
208
+ @owner.instance_variable_set(@ivar_name, value)
209
+ write_attributes(value || NULL_RECORD)
210
+ value
211
+ end
212
+
213
+ private
214
+
215
+ def write_attributes(associated_fake_record); end
216
+
217
+ NullRecord = Struct.new(:id, :class)
218
+ NullClass = Struct.new(:name)
219
+ NULL_RECORD = NullRecord.new(nil, NullClass.new)
220
+ end
221
+
222
+ class OneToManyAssociation < Association
223
+ def read
224
+ @owner.instance_variable_get(@ivar_name) ||
225
+ @owner.instance_variable_set(@ivar_name, [])
226
+ end
227
+
228
+ def write(value)
229
+ @owner.instance_variable_set(@ivar_name, [*value].compact)
230
+ end
231
+ end
232
+
233
+ class BelongsToAssociation < OneToOneAssociation
234
+ def write_attributes(associated_fake_record)
235
+ super
236
+ associated_id = associated_fake_record.id
237
+ @owner.send("#{self.reflection.foreign_key}=", associated_id)
238
+ end
239
+ end
240
+
241
+ class PolymorphicBelongsToAssociation < BelongsToAssociation
242
+ def write_attributes(associated_fake_record)
243
+ super
244
+ associated_type = associated_fake_record.class.name
245
+ @owner.send("#{self.reflection.foreign_type}=", associated_type)
246
+ end
247
+
248
+ def klass
249
+ class_name = @owner.send(self.reflection.foreign_type)
250
+ class_name.constantize if class_name
251
+ end
252
+ end
253
+
254
+ HasOneAssociation = OneToOneAssociation
255
+ HasManyAssociation = OneToManyAssociation
256
+
257
+ end
@@ -0,0 +1,168 @@
1
+ require 'much-plugin'
2
+
3
+ module MR; end
4
+ module MR::FakeRecord
5
+
6
+ module Attributes
7
+ include MuchPlugin
8
+
9
+ plugin_included do
10
+ extend ClassMethods
11
+ include InstanceMethods
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ def attributes
17
+ @attributes ||= MR::FakeRecord::AttributeSet.new
18
+ end
19
+
20
+ def attribute(name, type, options = nil)
21
+ self.attributes.add(self, name, type, options)
22
+ end
23
+
24
+ # ActiveRecord methods
25
+
26
+ def columns
27
+ self.attributes.to_a
28
+ end
29
+
30
+ end
31
+
32
+ module InstanceMethods
33
+
34
+ attr_writer :saved_attributes
35
+
36
+ def saved_attributes
37
+ @saved_attributes ||= {}
38
+ end
39
+
40
+ # ActiveRecord methods
41
+
42
+ def attributes
43
+ self.class.attributes.read_all(self)
44
+ end
45
+
46
+ def attributes=(new_attributes)
47
+ return unless new_attributes.is_a?(Hash)
48
+ self.class.attributes.batch_write(new_attributes, self)
49
+ end
50
+
51
+ def column_for_attribute(name)
52
+ self.class.attributes.find(name)
53
+ rescue NoAttributeError
54
+ return nil
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+ class AttributeSet
62
+ include Enumerable
63
+
64
+ def initialize
65
+ @attributes = {}
66
+ end
67
+
68
+ def find(name)
69
+ @attributes[name.to_s] || raise(NoAttributeError.new(name))
70
+ end
71
+
72
+ def each(&block)
73
+ @attributes.values.each(&block)
74
+ end
75
+
76
+ def read_all(record)
77
+ @attributes.values.inject({}) do |h, attribute|
78
+ h.merge(attribute.name => attribute.read(record))
79
+ end
80
+ end
81
+
82
+ def batch_write(new_attributes, record)
83
+ new_attributes.each{ |name, value| find(name).write(value, record) }
84
+ end
85
+
86
+ def to_a
87
+ @attributes.values.sort
88
+ end
89
+
90
+ def add(record_class, name, type, options = nil)
91
+ attribute = Attribute.new(name, type, options)
92
+ attribute.define_on(record_class)
93
+ @attributes[name.to_s] = attribute
94
+ end
95
+
96
+ end
97
+
98
+ class Attribute
99
+ attr_reader :name, :type
100
+ attr_reader :reader_method_name, :writer_method_name
101
+ attr_reader :was_method_name, :changed_method_name
102
+
103
+ # ActiveRecord methods
104
+ attr_reader :primary, :null
105
+
106
+ def initialize(name, type, options = nil)
107
+ options ||= {}
108
+ @name = name.to_s
109
+ @type = type.to_sym
110
+ @primary = (@type == :primary_key)
111
+ @null = options.key?(:null) ? !!options[:null] : true
112
+
113
+ @reader_method_name = @name
114
+ @writer_method_name = "#{@reader_method_name}="
115
+ @was_method_name = "#{@reader_method_name}_was"
116
+ @changed_method_name = "#{@reader_method_name}_changed?"
117
+ end
118
+
119
+ def read(record)
120
+ record.send(@reader_method_name)
121
+ end
122
+
123
+ def write(value, record)
124
+ record.send(@writer_method_name, value)
125
+ end
126
+
127
+ def was(record)
128
+ record.saved_attributes[@name]
129
+ end
130
+
131
+ def changed?(record)
132
+ read(record) != was(record)
133
+ end
134
+
135
+ def ==(other)
136
+ self.name == other.name &&
137
+ self.type == other.type &&
138
+ self.null == other.null
139
+ end
140
+
141
+ def <=>(other)
142
+ self.name <=> other.name
143
+ end
144
+
145
+ def define_on(record_class)
146
+ attribute = self
147
+ record_class.class_eval do
148
+
149
+ attr_accessor attribute.reader_method_name
150
+ define_method(attribute.was_method_name) do
151
+ attribute.was(self)
152
+ end
153
+ define_method(attribute.changed_method_name) do
154
+ attribute.changed?(self)
155
+ end
156
+
157
+ end
158
+ end
159
+
160
+ end
161
+
162
+ class NoAttributeError < RuntimeError
163
+ def initialize(attr_name)
164
+ super "the '#{attr_name}' attribute doesn't exist"
165
+ end
166
+ end
167
+
168
+ end