mr 0.35.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/mr.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'much-plugin'
|
2
|
+
|
3
|
+
require 'mr/after_commit/fake_record'
|
4
|
+
require 'mr/after_commit/record'
|
5
|
+
require 'mr/after_commit/record_procs_methods'
|
6
|
+
require 'mr/model'
|
7
|
+
|
8
|
+
module MR
|
9
|
+
|
10
|
+
module AfterCommit
|
11
|
+
include MuchPlugin
|
12
|
+
|
13
|
+
# demeter these constants so they are available from the `MR::AfterCommit`
|
14
|
+
# namespace
|
15
|
+
VALID_CALLBACK_TYPES = RecordProcsMethods::VALID_CALLBACK_TYPES
|
16
|
+
DEFAULT_CALLBACK_TYPE = RecordProcsMethods::DEFAULT_CALLBACK_TYPE
|
17
|
+
|
18
|
+
plugin_included do
|
19
|
+
include MR::Model
|
20
|
+
include InstanceMethods
|
21
|
+
end
|
22
|
+
|
23
|
+
module InstanceMethods
|
24
|
+
|
25
|
+
def after_commit_procs(*args)
|
26
|
+
self.record.after_commit_procs(*args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def after_commit(on = nil, &block)
|
30
|
+
self.record.add_after_commit_proc(on, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def prepend_after_commit(on = nil, &block)
|
34
|
+
self.record.prepend_after_commit_proc(on, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def clear_after_commit_procs(*args)
|
38
|
+
self.record.clear_after_commit_procs(*args)
|
39
|
+
end
|
40
|
+
|
41
|
+
def called_after_commit_procs(*args)
|
42
|
+
self.record.called_after_commit_procs(*args)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'much-plugin'
|
2
|
+
|
3
|
+
require 'mr/after_commit/record_procs_methods'
|
4
|
+
require 'mr/fake_record'
|
5
|
+
|
6
|
+
module MR; end
|
7
|
+
module MR::AfterCommit
|
8
|
+
|
9
|
+
module FakeRecord
|
10
|
+
include MuchPlugin
|
11
|
+
|
12
|
+
plugin_included do
|
13
|
+
include MR::FakeRecord
|
14
|
+
include RecordProcsMethods
|
15
|
+
include InstanceMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
module InstanceMethods
|
19
|
+
|
20
|
+
def save!
|
21
|
+
is_new = self.new_record?
|
22
|
+
super.tap do
|
23
|
+
callback_type = is_new ? :create : :update
|
24
|
+
mr_after_commit_call_procs(callback_type)
|
25
|
+
mr_after_commit_call_procs(:save)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def destroy
|
30
|
+
super.tap do
|
31
|
+
mr_after_commit_call_procs(:destroy)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'much-plugin'
|
2
|
+
|
3
|
+
require 'mr/after_commit/record_procs_methods'
|
4
|
+
require 'mr/record'
|
5
|
+
|
6
|
+
module MR; end
|
7
|
+
module MR::AfterCommit
|
8
|
+
|
9
|
+
module Record
|
10
|
+
include MuchPlugin
|
11
|
+
|
12
|
+
plugin_included do
|
13
|
+
include MR::Record
|
14
|
+
include RecordProcsMethods
|
15
|
+
include InstanceMethods
|
16
|
+
|
17
|
+
after_commit :mr_after_commit_call_procs_for_create, :on => :create
|
18
|
+
after_commit :mr_after_commit_call_procs_for_update, :on => :update
|
19
|
+
after_commit :mr_after_commit_call_procs_for_destroy, :on => :destroy
|
20
|
+
end
|
21
|
+
|
22
|
+
module InstanceMethods
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# ActiveRecord runs `after_save` after `after_create`, so run the save
|
27
|
+
# procs after the create procs
|
28
|
+
def mr_after_commit_call_procs_for_create
|
29
|
+
mr_after_commit_call_procs(:create)
|
30
|
+
mr_after_commit_call_procs(:save)
|
31
|
+
end
|
32
|
+
|
33
|
+
# ActiveRecord runs `after_save` after `after_update`, so run the save
|
34
|
+
# procs after the update procs
|
35
|
+
def mr_after_commit_call_procs_for_update
|
36
|
+
mr_after_commit_call_procs(:update)
|
37
|
+
mr_after_commit_call_procs(:save)
|
38
|
+
end
|
39
|
+
|
40
|
+
def mr_after_commit_call_procs_for_destroy
|
41
|
+
mr_after_commit_call_procs(:destroy)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module MR; end
|
2
|
+
module MR::AfterCommit
|
3
|
+
|
4
|
+
module RecordProcsMethods
|
5
|
+
|
6
|
+
VALID_CALLBACK_TYPES = [
|
7
|
+
:create,
|
8
|
+
:update,
|
9
|
+
:save,
|
10
|
+
:destroy
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
DEFAULT_CALLBACK_TYPE = :save.freeze
|
14
|
+
|
15
|
+
# these methods are used by the `Record` and `FakeRecord` mixins
|
16
|
+
|
17
|
+
def after_commit_procs(*keys)
|
18
|
+
if keys.empty?
|
19
|
+
mr_after_commit_procs_hash.values.flatten
|
20
|
+
else
|
21
|
+
keys.map{ |k| mr_after_commit_procs_hash[k.to_sym] }.flatten
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_after_commit_proc(callback_type = nil, &block)
|
26
|
+
callback_type ||= DEFAULT_CALLBACK_TYPE
|
27
|
+
mr_after_commit_procs_hash[callback_type.to_sym] << block
|
28
|
+
end
|
29
|
+
|
30
|
+
def prepend_after_commit_proc(callback_type = nil, &block)
|
31
|
+
callback_type ||= DEFAULT_CALLBACK_TYPE
|
32
|
+
mr_after_commit_procs_hash[callback_type.to_sym].unshift(block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def clear_after_commit_procs(*keys)
|
36
|
+
if keys.empty?
|
37
|
+
mr_after_commit_procs_hash.clear
|
38
|
+
else
|
39
|
+
keys.map{ |k| mr_after_commit_procs_hash.delete(k.to_sym) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def called_after_commit_procs(*keys)
|
44
|
+
if keys.empty?
|
45
|
+
mr_after_commit_called_procs_hash.values.flatten
|
46
|
+
else
|
47
|
+
keys.map{ |k| mr_after_commit_called_procs_hash[k.to_sym] }.flatten
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def mr_after_commit_call_procs(callback_type)
|
54
|
+
procs = self.after_commit_procs(callback_type)
|
55
|
+
procs.each(&:call)
|
56
|
+
mr_after_commit_called_procs_hash[callback_type] += procs
|
57
|
+
self.clear_after_commit_procs(callback_type)
|
58
|
+
end
|
59
|
+
|
60
|
+
def mr_after_commit_procs_hash
|
61
|
+
@mr_after_commit_procs_hash ||= MR::AfterCommit::CallbackProcsHash.new
|
62
|
+
end
|
63
|
+
|
64
|
+
def mr_after_commit_called_procs_hash
|
65
|
+
@mr_after_commit_called_procs_hash ||= MR::AfterCommit::CallbackProcsHash.new
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
module CallbackProcsHash
|
71
|
+
def self.new
|
72
|
+
Hash.new do |h, k|
|
73
|
+
if !VALID_CALLBACK_TYPES.include?(k)
|
74
|
+
raise ArgumentError, "#{k.inspect} is not a valid callback " \
|
75
|
+
"type, use: #{VALID_CALLBACK_TYPES.join(', ')}"
|
76
|
+
end
|
77
|
+
h[k] = []
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
data/lib/mr/factory.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'assert/factory'
|
2
|
+
require 'thread'
|
3
|
+
require 'mr/factory/model_factory'
|
4
|
+
require 'mr/factory/read_model_factory'
|
5
|
+
require 'mr/factory/record_factory'
|
6
|
+
require 'mr/type_converter'
|
7
|
+
|
8
|
+
module MR
|
9
|
+
|
10
|
+
module Factory
|
11
|
+
extend Assert::Factory
|
12
|
+
extend self
|
13
|
+
|
14
|
+
def new(object_class, *args, &block)
|
15
|
+
if object_class < MR::Model
|
16
|
+
ModelFactory.new(object_class, *args, &block)
|
17
|
+
elsif object_class < MR::ReadModelStruct
|
18
|
+
ReadModelFactory.new(object_class, *args, &block)
|
19
|
+
elsif object_class < MR::Record
|
20
|
+
RecordFactory.new(object_class, *args, &block)
|
21
|
+
else
|
22
|
+
raise ArgumentError, "takes a MR::Model, MR::Record, or MR::ReadModel"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def primary_key(identifier = nil)
|
27
|
+
identifier ||= 'MR::Factory'
|
28
|
+
@primary_keys ||= {}
|
29
|
+
@primary_keys[identifier.to_s] ||= PrimaryKeyProvider.new
|
30
|
+
self.type_cast(@primary_keys[identifier.to_s].next, :primary_key)
|
31
|
+
end
|
32
|
+
|
33
|
+
def decimal(max = nil)
|
34
|
+
self.type_cast(Assert::Factory::Random.float(max), :decimal)
|
35
|
+
end
|
36
|
+
|
37
|
+
def timestamp
|
38
|
+
self.datetime
|
39
|
+
end
|
40
|
+
|
41
|
+
def type_converter
|
42
|
+
@type_converter ||= MR::TypeConverter.new
|
43
|
+
end
|
44
|
+
|
45
|
+
class PrimaryKeyProvider
|
46
|
+
attr_reader :mutex, :current
|
47
|
+
def initialize
|
48
|
+
@current = 0
|
49
|
+
@mutex = Mutex.new
|
50
|
+
end
|
51
|
+
def next
|
52
|
+
@mutex.synchronize{ @current += 1 }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# used by factories and stacks to say they can't determine a record class for
|
57
|
+
# an association (usually a polymorphic association doesn't have its foreign
|
58
|
+
# type attribute set)
|
59
|
+
class NoRecordClassError < RuntimeError
|
60
|
+
def self.for_association(ar_association)
|
61
|
+
owner_record_class = ar_association.owner.class
|
62
|
+
association_name = ar_association.reflection.name
|
63
|
+
message = "can't build '#{association_name}' association on " \
|
64
|
+
"#{owner_record_class}"
|
65
|
+
if ar_association.reflection.options[:polymorphic]
|
66
|
+
foreign_type_attribute = ar_association.reflection.foreign_type
|
67
|
+
message += " -- try manually setting it, building it via a stack " \
|
68
|
+
"if you've configured default associations, or setting " \
|
69
|
+
"its '#{foreign_type_attribute}' attribute"
|
70
|
+
else
|
71
|
+
message += " -- try manually setting it or building it via a stack " \
|
72
|
+
"if you've configured default associations"
|
73
|
+
end
|
74
|
+
self.new(message)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
NoAssociationError = Class.new(ArgumentError)
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
require 'much-plugin'
|
2
|
+
|
3
|
+
module MR; end
|
4
|
+
module MR::Factory
|
5
|
+
|
6
|
+
module Config
|
7
|
+
include MuchPlugin
|
8
|
+
|
9
|
+
plugin_included do
|
10
|
+
include InstanceMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
module InstanceMethods
|
14
|
+
|
15
|
+
attr_reader :object_class
|
16
|
+
|
17
|
+
def initialize(object_class)
|
18
|
+
@object_class = object_class
|
19
|
+
end
|
20
|
+
|
21
|
+
# make the methods lazy-eval'd so factories can be built without paying
|
22
|
+
# the performance of them building out their config, this makes it less
|
23
|
+
# costly to require real and fake factories together in a test suite
|
24
|
+
# (i.e. we don't want to split our real and fake factories into separate
|
25
|
+
# files)
|
26
|
+
|
27
|
+
def apply_args(object, args)
|
28
|
+
apply_default_args(object)
|
29
|
+
apply_args_from_hash(object, args)
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_default_args(&block)
|
34
|
+
@default_args_proc = block
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def object_builder_class; ObjectBuilder; end
|
40
|
+
|
41
|
+
def apply_default_args(object)
|
42
|
+
apply_args_from_proc(object, &@default_args_proc) if @default_args_proc
|
43
|
+
end
|
44
|
+
|
45
|
+
def apply_args_from_hash(object, args_hash)
|
46
|
+
object_builder_class.apply_hash(object, self, args_hash)
|
47
|
+
end
|
48
|
+
|
49
|
+
def apply_args_from_proc(object, &args_proc)
|
50
|
+
object_builder_class.apply_proc(object, self, &args_proc)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
class ObjectBuilder
|
56
|
+
|
57
|
+
def self.apply_hash(object, factory_config, hash)
|
58
|
+
self.new(object, factory_config).tap do |builder|
|
59
|
+
hash.each{ |k, v| builder.set(k, v) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.apply_proc(object, factory_config, &proc)
|
64
|
+
self.new(object, factory_config).tap do |builder|
|
65
|
+
builder.instance_eval(&proc)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize(object, factory_config)
|
70
|
+
@__mr_ob_object = object
|
71
|
+
@__mr_ob_factory_config = factory_config
|
72
|
+
end
|
73
|
+
|
74
|
+
def set(name, value)
|
75
|
+
__mr_ob_set_attribute(@__mr_ob_object, name, value)
|
76
|
+
rescue ArgumentError => exception
|
77
|
+
exception.set_backtrace(caller)
|
78
|
+
raise exception
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def __mr_ob_set_attribute(object, name, value)
|
84
|
+
object.send("#{name}=", value)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
module WithAssociationsConfig
|
91
|
+
include MuchPlugin
|
92
|
+
|
93
|
+
plugin_included do
|
94
|
+
include MR::Factory::Config
|
95
|
+
include InstanceMethods
|
96
|
+
end
|
97
|
+
|
98
|
+
module InstanceMethods
|
99
|
+
|
100
|
+
def record_class
|
101
|
+
self.object_class
|
102
|
+
end
|
103
|
+
|
104
|
+
def force_in_stack_association_names
|
105
|
+
@force_in_stack_association_names ||= []
|
106
|
+
end
|
107
|
+
|
108
|
+
def force_in_stack_association?(association_name)
|
109
|
+
self.force_in_stack_association_names.include?(association_name)
|
110
|
+
end
|
111
|
+
|
112
|
+
def default_factories
|
113
|
+
@default_factories ||= Hash.new do |h, record_class|
|
114
|
+
# raise a no record class error if passed a `nil` record class, this
|
115
|
+
# can happen when a polymorphic association is passed to `factory_for`
|
116
|
+
# and its foreign type attribute isn't set
|
117
|
+
raise NoRecordClassError if record_class.nil?
|
118
|
+
h[record_class] = build_factory_for_record_class(record_class)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def association_factories
|
123
|
+
@association_factories ||= {}
|
124
|
+
end
|
125
|
+
|
126
|
+
def factories_for(association_name, record_class)
|
127
|
+
key = AssociationFactoriesKey.new(association_name, record_class)
|
128
|
+
self.association_factories[key] || [self.default_factories[record_class]]
|
129
|
+
end
|
130
|
+
|
131
|
+
def factory_for(association_name, record_class)
|
132
|
+
self.factories_for(association_name, record_class).sample
|
133
|
+
end
|
134
|
+
|
135
|
+
def factory_config_for(association_name, record_class)
|
136
|
+
self.factory_for(association_name, record_class).config
|
137
|
+
end
|
138
|
+
|
139
|
+
def record_classes_for(association_name)
|
140
|
+
self.factories_for(association_name, nil).map(&:record_class)
|
141
|
+
rescue NoRecordClassError
|
142
|
+
[]
|
143
|
+
end
|
144
|
+
|
145
|
+
def build_associated_record(association_name, record_class)
|
146
|
+
raise NotImplementedError
|
147
|
+
end
|
148
|
+
|
149
|
+
def add_association_factory(association_name, factory, options = nil)
|
150
|
+
reflection = self.record_class.reflect_on_association(association_name)
|
151
|
+
if reflection.nil?
|
152
|
+
raise NoAssociationError, "there is no #{association_name.inspect} " \
|
153
|
+
"association for #{self.record_class.inspect}"
|
154
|
+
end
|
155
|
+
|
156
|
+
options ||= {}
|
157
|
+
if options[:force_in_stack]
|
158
|
+
(self.force_in_stack_association_names << association_name).uniq!
|
159
|
+
end
|
160
|
+
|
161
|
+
add_default_factory(factory.record_class, factory)
|
162
|
+
|
163
|
+
# for polymorphic associations add a lookup with no associated record
|
164
|
+
# class, this will allow a polymorphic association without their foreign
|
165
|
+
# type attribute set to use the configured factories for the association
|
166
|
+
if reflection.options[:polymorphic]
|
167
|
+
key = AssociationFactoriesKey.new(association_name)
|
168
|
+
add_association_factory_by_key(key, factory)
|
169
|
+
end
|
170
|
+
|
171
|
+
key = AssociationFactoriesKey.new(association_name, factory.record_class)
|
172
|
+
add_association_factory_by_key(key, factory)
|
173
|
+
end
|
174
|
+
|
175
|
+
def ar_association_for(object, name)
|
176
|
+
raise NotImplementedError
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def object_builder_class; WithAssociationsConfig::ObjectBuilder; end
|
182
|
+
|
183
|
+
def build_factory_for_record_class(record_class)
|
184
|
+
raise NotImplementedError
|
185
|
+
end
|
186
|
+
|
187
|
+
def add_association_factory_by_key(key, factory)
|
188
|
+
self.association_factories[key] = [] if !self.association_factories.key?(key)
|
189
|
+
self.association_factories[key] << factory
|
190
|
+
end
|
191
|
+
|
192
|
+
def add_default_factory(record_class, factory)
|
193
|
+
if !self.default_factories.key?(record_class)
|
194
|
+
self.default_factories[record_class] = factory
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
class ObjectBuilder < MR::Factory::Config::ObjectBuilder
|
201
|
+
|
202
|
+
def set(name, value)
|
203
|
+
if value.kind_of?(Hash) && (association = __mr_ob_get_association(name))
|
204
|
+
__mr_ob_set_association_from_hash(association, name, value)
|
205
|
+
else
|
206
|
+
super(name, value)
|
207
|
+
end
|
208
|
+
rescue ArgumentError, NoRecordClassError => exception
|
209
|
+
exception.set_backtrace(caller)
|
210
|
+
raise exception
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
def __mr_ob_get_association(name)
|
216
|
+
@__mr_ob_factory_config.ar_association_for(@__mr_ob_object, name)
|
217
|
+
end
|
218
|
+
|
219
|
+
def __mr_ob_set_association_from_hash(association, name, value)
|
220
|
+
factory = begin
|
221
|
+
@__mr_ob_factory_config.factory_for(name, association.klass)
|
222
|
+
rescue NoRecordClassError
|
223
|
+
raise NoRecordClassError.for_association(association)
|
224
|
+
end
|
225
|
+
associated_object = @__mr_ob_object.send(name) || factory.instance
|
226
|
+
self.class.apply_hash(associated_object, factory.config, value)
|
227
|
+
__mr_ob_set_attribute(@__mr_ob_object, name, associated_object)
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
|
232
|
+
module AssociationFactoriesKey
|
233
|
+
def self.new(association_name, associated_record_class = nil)
|
234
|
+
[association_name, associated_record_class].compact.join("-")
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|