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,103 @@
1
+ require 'mr/factory'
2
+ require 'mr/factory/config'
3
+ require 'mr/factory/model_stack'
4
+ require 'mr/factory/record_factory'
5
+ require 'mr/fake_record'
6
+
7
+ module MR; end
8
+ module MR::Factory
9
+
10
+ class ModelFactory
11
+
12
+ attr_reader :model_class, :record_class, :config
13
+
14
+ def initialize(model_class, record_class, &block)
15
+ @model_class = model_class
16
+ @record_class = record_class
17
+ @config = Config.new(@record_class)
18
+ self.instance_eval(&block) if block
19
+
20
+ @record_factory = MR::Factory::RecordFactory.new(record_class)
21
+ end
22
+
23
+ def model(args = nil)
24
+ @model_class.new(@record_factory.record).tap do |model|
25
+ self.config.apply_args(model, args || {})
26
+ end
27
+ end
28
+ alias :instance :model
29
+
30
+ def saved_model(args = nil)
31
+ model = self.model(args).tap(&:save)
32
+ model.record.reset_save_called if model.record.kind_of?(MR::FakeRecord)
33
+ model
34
+ end
35
+ alias :saved_instance :saved_model
36
+
37
+ def stack(args = nil)
38
+ MR::Factory::ModelStack.new(self.model(args), self.config)
39
+ end
40
+ alias :instance_stack :stack
41
+
42
+ def stack_model(args = nil)
43
+ self.stack(args).model
44
+ end
45
+
46
+ def saved_stack(args = nil)
47
+ self.stack(args).tap(&:create)
48
+ end
49
+
50
+ def saved_stack_model(args = nil)
51
+ self.saved_stack(args).model
52
+ end
53
+
54
+ def saved_dependencies_stack(args = nil)
55
+ self.stack(args).tap(&:create_deps)
56
+ end
57
+ alias :saved_deps_stack :saved_dependencies_stack
58
+
59
+ def saved_dependencies_stack_model(args = nil)
60
+ self.saved_deps_stack(args).model
61
+ end
62
+ alias :saved_deps_stack_model :saved_dependencies_stack_model
63
+
64
+ private
65
+
66
+ def default_association(association_name, factory, options = nil)
67
+ self.config.add_association_factory(association_name, factory, options)
68
+ rescue NoAssociationError => exception
69
+ raise exception.class, exception.message, caller
70
+ end
71
+
72
+ def default_args(&block)
73
+ self.config.set_default_args(&block)
74
+ end
75
+
76
+ class Config
77
+ include MR::Factory::WithAssociationsConfig
78
+
79
+ def build_associated_model(association_name, record_class)
80
+ self.factory_for(association_name, record_class).model
81
+ end
82
+
83
+ def build_associated_record(association_name, record_class)
84
+ self.build_associated_model(association_name, record_class).record
85
+ end
86
+
87
+ def ar_association_for(model, name)
88
+ if (reflection = model.record_class.reflect_on_association(name))
89
+ model.record.association(reflection.name)
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def build_factory_for_record_class(record_class)
96
+ MR::Factory::ModelFactory.new(record_class.model_class, record_class)
97
+ end
98
+ end
99
+
100
+ end
101
+
102
+
103
+ end
@@ -0,0 +1,28 @@
1
+ require 'mr/factory/record_factory'
2
+ require 'mr/factory/record_stack'
3
+
4
+ module MR; end
5
+ module MR::Factory
6
+
7
+ class ModelStack
8
+
9
+ attr_reader :model
10
+
11
+ def initialize(model, factory_config)
12
+ @model = model
13
+ @record_stack = MR::Factory::RecordStack.for_record(model.record, factory_config)
14
+ end
15
+
16
+ def factory_config; @record_stack.factory_config; end
17
+
18
+ def create; @record_stack.create; end
19
+ def destroy; @record_stack.destroy; end
20
+
21
+ def create_dependencies; @record_stack.create_dependencies; end
22
+ alias :create_deps :create_dependencies
23
+
24
+ def destroy_dependencies; @record_stack.destroy_dependencies; end
25
+ alias :destroy_deps :destroy_dependencies
26
+ end
27
+
28
+ end
@@ -0,0 +1,104 @@
1
+ require 'mr/factory'
2
+ require 'mr/factory/config'
3
+ require 'mr/read_model'
4
+
5
+ module MR; end
6
+ module MR::Factory
7
+
8
+ class ReadModelFactory
9
+
10
+ attr_reader :read_model_class, :config
11
+
12
+ def initialize(read_model_class, &block)
13
+ unless read_model_class < MR::ReadModelStruct
14
+ raise ArgumentError, "takes a read model or read model struct"
15
+ end
16
+ @read_model_class = read_model_class
17
+ @config = Config.new(read_model_class)
18
+ self.instance_eval(&block) if block
19
+ end
20
+
21
+ def read_model_data(args = nil)
22
+ Data.new.tap do |read_model_data|
23
+ self.config.apply_args(read_model_data, args || {})
24
+ end
25
+ end
26
+
27
+ def read_model(args = nil)
28
+ @read_model_class.new(self.read_model_data(args))
29
+ end
30
+ alias :instance :read_model
31
+
32
+ private
33
+
34
+ def default_args(&block)
35
+ self.config.set_default_args(&block)
36
+ end
37
+
38
+ class Config
39
+ include MR::Factory::Config
40
+
41
+ def read_model_class
42
+ self.object_class
43
+ end
44
+
45
+ private
46
+
47
+ def apply_default_args(data)
48
+ field_defaults.each do |field_name, field_type|
49
+ data[field_name] = MR::Factory.send(field_type)
50
+ end
51
+ json_struct_list_defaults.each do |field_name, factory|
52
+ data[field_name] = Factory.integer(3).times.map{ factory.read_model_data }
53
+ end
54
+ json_struct_obj_defaults.each do |field_name, factory|
55
+ data[field_name] = factory.read_model_data
56
+ end
57
+ super(data)
58
+ end
59
+
60
+ def field_defaults
61
+ @field_defaults ||= self.read_model_class.fields.inject({}) do |h, field|
62
+ h.merge!(field.name.to_s => field.type)
63
+ end
64
+ end
65
+
66
+ def json_struct_list_defaults
67
+ @json_struct_list_defaults ||= build_defaults_for_json_struct_fields(
68
+ self.read_model_class.json_struct_lists
69
+ )
70
+ end
71
+
72
+ def json_struct_obj_defaults
73
+ @json_struct_obj_defaults ||= build_defaults_for_json_struct_fields(
74
+ self.read_model_class.json_struct_objs
75
+ )
76
+ end
77
+
78
+ def build_defaults_for_json_struct_fields(fields)
79
+ fields.inject({}) do |h, field|
80
+ factory = MR::Factory::ReadModelFactory.new(field.struct_class)
81
+ h.merge!(field.name.to_s => factory)
82
+ end
83
+ end
84
+ end
85
+
86
+ class Data
87
+ def initialize; @hash = {}; end
88
+
89
+ def [](key); @hash[key]; end
90
+ def []=(key, value); @hash[key] = value; end
91
+
92
+ def method_missing(method, *args, &block)
93
+ method_string = method.to_s
94
+ if method_string =~ /=\z/ && args.size == 1
95
+ @hash[method_string.gsub('=', '')] = args.first
96
+ else
97
+ super
98
+ end
99
+ end
100
+ end
101
+
102
+ end
103
+
104
+ end
@@ -0,0 +1,130 @@
1
+ require 'mr/factory'
2
+ require 'mr/factory/config'
3
+ require 'mr/factory/record_stack'
4
+ require 'mr/fake_record'
5
+
6
+ module MR; end
7
+ module MR::Factory
8
+
9
+ class RecordFactory
10
+
11
+ attr_reader :record_class, :config
12
+
13
+ def initialize(record_class, &block)
14
+ @record_class = record_class
15
+ @config = Config.new(record_class)
16
+ self.instance_eval(&block) if block
17
+ end
18
+
19
+ def record(args = nil)
20
+ @record_class.new.tap do |record|
21
+ self.config.apply_args(record, args || {})
22
+ end
23
+ end
24
+ alias :instance :record
25
+
26
+ def saved_record(args = nil)
27
+ record = self.record(args).tap(&:save!)
28
+ record.reset_save_called if record.kind_of?(MR::FakeRecord)
29
+ record
30
+ end
31
+ alias :saved_instance :saved_record
32
+
33
+ def stack(args = nil)
34
+ MR::Factory::RecordStack.for_record(self.record(args), self.config)
35
+ end
36
+ alias :instance_stack :stack
37
+
38
+ def stack_record(args = nil)
39
+ self.stack(args).record
40
+ end
41
+
42
+ def saved_stack(args = nil)
43
+ self.stack(args).tap(&:create)
44
+ end
45
+
46
+ def saved_stack_record(args = nil)
47
+ self.saved_stack(args).record
48
+ end
49
+
50
+ def saved_dependencies_stack(args = nil)
51
+ self.stack(args).tap(&:create_deps)
52
+ end
53
+ alias :saved_deps_stack :saved_dependencies_stack
54
+
55
+ def saved_dependencies_stack_record(args = nil)
56
+ self.saved_deps_stack(args).record
57
+ end
58
+ alias :saved_deps_stack_record :saved_dependencies_stack_record
59
+
60
+ private
61
+
62
+ def default_association(association_name, factory, options = nil)
63
+ self.config.add_association_factory(association_name, factory, options)
64
+ rescue NoAssociationError => exception
65
+ raise exception.class, exception.message, caller
66
+ end
67
+
68
+ def default_args(&block)
69
+ self.config.set_default_args(&block)
70
+ end
71
+
72
+ class Config
73
+ include MR::Factory::WithAssociationsConfig
74
+
75
+ def build_associated_record(association_name, record_class)
76
+ self.factory_for(association_name, record_class).record
77
+ end
78
+
79
+ def ar_association_for(record, name)
80
+ if (reflection = record.class.reflect_on_association(name))
81
+ record.association(reflection.name)
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def apply_default_args(record)
88
+ column_defaults.each do |column_name, column_type|
89
+ record.send("#{column_name}=", MR::Factory.send(column_type))
90
+ end
91
+ super(record)
92
+ end
93
+
94
+ def build_factory_for_record_class(record_class)
95
+ MR::Factory::RecordFactory.new(record_class)
96
+ end
97
+
98
+ def column_defaults
99
+ @column_defaults ||= begin
100
+ association_column_names = association_column_names(self.record_class)
101
+ self.record_class.columns.inject({}) do |args, column|
102
+ column_type = column.type || column.sql_type
103
+ if should_default_column?(column, association_column_names, column_type)
104
+ args[column.name.to_s] = column_type
105
+ end
106
+ args
107
+ end
108
+ end
109
+ end
110
+
111
+ def should_default_column?(column, association_column_names, column_type)
112
+ !column.primary &&
113
+ !association_column_names.include?(column.name) &&
114
+ !column_type.nil? &&
115
+ MR::Factory.respond_to?(column_type)
116
+ end
117
+
118
+ def association_column_names(record_class)
119
+ names = []
120
+ record_class.reflect_on_all_associations(:belongs_to).each do |reflection|
121
+ names << reflection.foreign_type if reflection.options[:polymorphic]
122
+ names << reflection.foreign_key
123
+ end
124
+ names
125
+ end
126
+ end
127
+
128
+ end
129
+
130
+ end
@@ -0,0 +1,219 @@
1
+ require 'mr/factory'
2
+ require 'mr/fake_record'
3
+
4
+ module MR; end
5
+ module MR::Factory
6
+
7
+ class RecordStack
8
+
9
+ def self.for_record(record, factory_config)
10
+ self.new(RecordData.new(record), factory_config)
11
+ end
12
+
13
+ attr_reader :record_data, :factory_config, :record_lookup
14
+ attr_reader :record_stacks
15
+
16
+ def initialize(record_data, factory_config, record_lookup = nil)
17
+ @record_data = record_data
18
+ @factory_config = factory_config
19
+ @record_lookup = record_lookup || build_lookup(@record_data)
20
+
21
+ @record_stacks = @record_data.association_datas.map do |association_data|
22
+ next unless association_data.required?(factory_config)
23
+
24
+ associated_record_data = association_data.get_record_data(
25
+ @factory_config,
26
+ @record_lookup
27
+ )
28
+ associated_factory_config = @factory_config.factory_config_for(
29
+ association_data.name,
30
+ associated_record_data.record_class
31
+ )
32
+ @record_lookup[associated_record_data.record_class] ||= associated_record_data
33
+ @record_data.set_association(association_data.name, associated_record_data)
34
+ MR::Factory::RecordStack.new(
35
+ associated_record_data,
36
+ associated_factory_config,
37
+ @record_lookup
38
+ )
39
+ end.compact
40
+ end
41
+
42
+ def record
43
+ self.record_data.record
44
+ end
45
+
46
+ def create
47
+ self.create_dependencies
48
+ self.record_data.create_record
49
+ true
50
+ end
51
+
52
+ def destroy
53
+ self.record_data.destroy_record
54
+ self.destroy_dependencies
55
+ true
56
+ end
57
+
58
+ def create_dependencies
59
+ self.record_stacks.each(&:create)
60
+ self.record_data.refresh_record_associations
61
+ true
62
+ end
63
+ alias :create_deps :create_dependencies
64
+
65
+ def destroy_dependencies
66
+ self.record_stacks.each(&:destroy)
67
+ true
68
+ end
69
+ alias :destroy_deps :destroy_dependencies
70
+
71
+ private
72
+
73
+ def build_lookup(record_data)
74
+ load_preset_associations_into_lookup({}, record_data)
75
+ end
76
+
77
+ def load_preset_associations_into_lookup(lookup, record_data)
78
+ record_data.association_datas.select(&:preset?).each do |association_data|
79
+ preset_record_data = association_data.preset_record_data
80
+ lookup[preset_record_data.record_class] ||= preset_record_data
81
+ load_preset_associations_into_lookup(lookup, preset_record_data)
82
+ end
83
+ lookup
84
+ end
85
+
86
+ class RecordData
87
+
88
+ attr_reader :record, :association_datas
89
+
90
+ def initialize(record)
91
+ @record = record
92
+ @association_datas = build_association_datas(@record)
93
+ end
94
+
95
+ def record_class
96
+ @record.class
97
+ end
98
+
99
+ def set_association(name, record_data)
100
+ @record.send("#{name}=", record_data.record)
101
+ end
102
+
103
+ def create_record
104
+ if @record.new_record?
105
+ @record.save!
106
+ @record.reset_save_called if @record.kind_of?(MR::FakeRecord)
107
+ end
108
+ end
109
+
110
+ def destroy_record
111
+ @record.destroy unless @record.destroyed?
112
+ end
113
+
114
+ # ensures records have their associations foreign type/key attributes set
115
+ # by re-setting the association to its current value
116
+ def refresh_record_associations
117
+ @association_datas.each do |association_data|
118
+ associated_record = @record.send(association_data.name)
119
+ @record.send("#{association_data.name}=", associated_record)
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ def build_association_datas(record)
126
+ record.class.reflect_on_all_associations(:belongs_to).map do |reflection|
127
+ AssociationData.new(record.association(reflection.name))
128
+ end.compact.sort
129
+ end
130
+
131
+ end
132
+
133
+ class AssociationData
134
+
135
+ attr_reader :association, :preset_record_data
136
+
137
+ def initialize(association)
138
+ @association = association
139
+
140
+ preset_record = association.owner.send("#{self.name}")
141
+ @preset_record_data = RecordData.new(preset_record) if preset_record
142
+ end
143
+
144
+ def name
145
+ self.association.reflection.name
146
+ end
147
+
148
+ def record_class
149
+ self.association.klass
150
+ end
151
+
152
+ def preset?
153
+ !self.preset_record_data.nil?
154
+ end
155
+
156
+ def required?(factory_config)
157
+ self.preset? ||
158
+ factory_config.force_in_stack_association?(self.name) ||
159
+ columns_required?(self.association)
160
+ end
161
+
162
+ def get_record_data(factory_config, lookup)
163
+ if self.preset?
164
+ self.preset_record_data
165
+ elsif (lookup_record_data = get_lookup_record_data(factory_config, lookup))
166
+ lookup_record_data
167
+ else
168
+ RecordData.new(build_record(factory_config))
169
+ end
170
+ end
171
+
172
+ def <=>(other)
173
+ if other.kind_of?(self.class)
174
+ self.name.to_s <=> other.name.to_s
175
+ else
176
+ super
177
+ end
178
+ end
179
+
180
+ private
181
+
182
+ # get the intersection of the record classes and use a random match to
183
+ # get a record data from the lookup; if their are no matching record
184
+ # classes the intersection returns an empty array which will return a
185
+ # `nil` value from the lookup
186
+ def get_lookup_record_data(factory_config, lookup)
187
+ record_classes = lookup_record_classes(factory_config) & lookup.keys
188
+ lookup[record_classes.sample]
189
+ end
190
+
191
+ def lookup_record_classes(factory_config)
192
+ if self.record_class
193
+ [self.record_class]
194
+ else
195
+ factory_config.record_classes_for(self.name)
196
+ end
197
+ end
198
+
199
+ def build_record(factory_config)
200
+ factory_config.build_associated_record(self.name, self.record_class)
201
+ rescue NoRecordClassError
202
+ raise NoRecordClassError.for_association(self.association)
203
+ end
204
+
205
+ def columns_required?(association)
206
+ column_required?(association.owner, association.reflection.foreign_key) ||
207
+ column_required?(association.owner, association.reflection.foreign_type)
208
+ end
209
+
210
+ def column_required?(record, column_name)
211
+ column = record.column_for_attribute(column_name)
212
+ !(column.nil? || column.null)
213
+ end
214
+
215
+ end
216
+
217
+ end
218
+
219
+ end