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
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
|