sam-dm-core 0.9.6
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.
- data/.autotest +26 -0
- data/CONTRIBUTING +51 -0
- data/FAQ +92 -0
- data/History.txt +145 -0
- data/MIT-LICENSE +22 -0
- data/Manifest.txt +125 -0
- data/QUICKLINKS +12 -0
- data/README.txt +143 -0
- data/Rakefile +30 -0
- data/SPECS +63 -0
- data/TODO +1 -0
- data/lib/dm-core.rb +224 -0
- data/lib/dm-core/adapters.rb +4 -0
- data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
- data/lib/dm-core/adapters/data_objects_adapter.rb +707 -0
- data/lib/dm-core/adapters/mysql_adapter.rb +136 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +188 -0
- data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
- data/lib/dm-core/associations.rb +199 -0
- data/lib/dm-core/associations/many_to_many.rb +147 -0
- data/lib/dm-core/associations/many_to_one.rb +107 -0
- data/lib/dm-core/associations/one_to_many.rb +309 -0
- data/lib/dm-core/associations/one_to_one.rb +61 -0
- data/lib/dm-core/associations/relationship.rb +218 -0
- data/lib/dm-core/associations/relationship_chain.rb +81 -0
- data/lib/dm-core/auto_migrations.rb +113 -0
- data/lib/dm-core/collection.rb +638 -0
- data/lib/dm-core/dependency_queue.rb +31 -0
- data/lib/dm-core/hook.rb +11 -0
- data/lib/dm-core/identity_map.rb +45 -0
- data/lib/dm-core/is.rb +16 -0
- data/lib/dm-core/logger.rb +232 -0
- data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
- data/lib/dm-core/migrator.rb +29 -0
- data/lib/dm-core/model.rb +471 -0
- data/lib/dm-core/naming_conventions.rb +84 -0
- data/lib/dm-core/property.rb +673 -0
- data/lib/dm-core/property_set.rb +162 -0
- data/lib/dm-core/query.rb +625 -0
- data/lib/dm-core/repository.rb +159 -0
- data/lib/dm-core/resource.rb +637 -0
- data/lib/dm-core/scope.rb +58 -0
- data/lib/dm-core/support.rb +7 -0
- data/lib/dm-core/support/array.rb +13 -0
- data/lib/dm-core/support/assertions.rb +8 -0
- data/lib/dm-core/support/errors.rb +23 -0
- data/lib/dm-core/support/kernel.rb +7 -0
- data/lib/dm-core/support/symbol.rb +41 -0
- data/lib/dm-core/transaction.rb +267 -0
- data/lib/dm-core/type.rb +160 -0
- data/lib/dm-core/type_map.rb +80 -0
- data/lib/dm-core/types.rb +19 -0
- data/lib/dm-core/types/boolean.rb +7 -0
- data/lib/dm-core/types/discriminator.rb +34 -0
- data/lib/dm-core/types/object.rb +24 -0
- data/lib/dm-core/types/paranoid_boolean.rb +34 -0
- data/lib/dm-core/types/paranoid_datetime.rb +33 -0
- data/lib/dm-core/types/serial.rb +9 -0
- data/lib/dm-core/types/text.rb +10 -0
- data/lib/dm-core/version.rb +3 -0
- data/script/all +5 -0
- data/script/performance.rb +203 -0
- data/script/profile.rb +87 -0
- data/spec/integration/association_spec.rb +1371 -0
- data/spec/integration/association_through_spec.rb +203 -0
- data/spec/integration/associations/many_to_many_spec.rb +449 -0
- data/spec/integration/associations/many_to_one_spec.rb +163 -0
- data/spec/integration/associations/one_to_many_spec.rb +151 -0
- data/spec/integration/auto_migrations_spec.rb +398 -0
- data/spec/integration/collection_spec.rb +1069 -0
- data/spec/integration/data_objects_adapter_spec.rb +32 -0
- data/spec/integration/dependency_queue_spec.rb +58 -0
- data/spec/integration/model_spec.rb +127 -0
- data/spec/integration/mysql_adapter_spec.rb +85 -0
- data/spec/integration/postgres_adapter_spec.rb +731 -0
- data/spec/integration/property_spec.rb +233 -0
- data/spec/integration/query_spec.rb +506 -0
- data/spec/integration/repository_spec.rb +57 -0
- data/spec/integration/resource_spec.rb +475 -0
- data/spec/integration/sqlite3_adapter_spec.rb +352 -0
- data/spec/integration/sti_spec.rb +208 -0
- data/spec/integration/strategic_eager_loading_spec.rb +138 -0
- data/spec/integration/transaction_spec.rb +75 -0
- data/spec/integration/type_spec.rb +271 -0
- data/spec/lib/logging_helper.rb +18 -0
- data/spec/lib/mock_adapter.rb +27 -0
- data/spec/lib/model_loader.rb +91 -0
- data/spec/lib/publicize_methods.rb +28 -0
- data/spec/models/vehicles.rb +34 -0
- data/spec/models/zoo.rb +47 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +86 -0
- data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
- data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
- data/spec/unit/adapters/data_objects_adapter_spec.rb +628 -0
- data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
- data/spec/unit/associations/many_to_many_spec.rb +17 -0
- data/spec/unit/associations/many_to_one_spec.rb +152 -0
- data/spec/unit/associations/one_to_many_spec.rb +393 -0
- data/spec/unit/associations/one_to_one_spec.rb +7 -0
- data/spec/unit/associations/relationship_spec.rb +71 -0
- data/spec/unit/associations_spec.rb +242 -0
- data/spec/unit/auto_migrations_spec.rb +111 -0
- data/spec/unit/collection_spec.rb +182 -0
- data/spec/unit/data_mapper_spec.rb +35 -0
- data/spec/unit/identity_map_spec.rb +126 -0
- data/spec/unit/is_spec.rb +80 -0
- data/spec/unit/migrator_spec.rb +33 -0
- data/spec/unit/model_spec.rb +339 -0
- data/spec/unit/naming_conventions_spec.rb +36 -0
- data/spec/unit/property_set_spec.rb +83 -0
- data/spec/unit/property_spec.rb +753 -0
- data/spec/unit/query_spec.rb +530 -0
- data/spec/unit/repository_spec.rb +93 -0
- data/spec/unit/resource_spec.rb +626 -0
- data/spec/unit/scope_spec.rb +142 -0
- data/spec/unit/transaction_spec.rb +493 -0
- data/spec/unit/type_map_spec.rb +114 -0
- data/spec/unit/type_spec.rb +119 -0
- data/tasks/ci.rb +68 -0
- data/tasks/dm.rb +63 -0
- data/tasks/doc.rb +20 -0
- data/tasks/gemspec.rb +23 -0
- data/tasks/hoe.rb +46 -0
- data/tasks/install.rb +20 -0
- metadata +216 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "one_to_many")
|
2
|
+
module DataMapper
|
3
|
+
module Associations
|
4
|
+
module ManyToMany
|
5
|
+
extend Assertions
|
6
|
+
|
7
|
+
# Setup many to many relationship between two models
|
8
|
+
# -
|
9
|
+
# @api private
|
10
|
+
def self.setup(name, model, options = {})
|
11
|
+
assert_kind_of 'name', name, Symbol
|
12
|
+
assert_kind_of 'model', model, Model
|
13
|
+
assert_kind_of 'options', options, Hash
|
14
|
+
|
15
|
+
repository_name = model.repository.name
|
16
|
+
|
17
|
+
model.class_eval <<-EOS, __FILE__, __LINE__
|
18
|
+
def #{name}(query = {})
|
19
|
+
#{name}_association.all(query)
|
20
|
+
end
|
21
|
+
|
22
|
+
def #{name}=(children)
|
23
|
+
#{name}_association.replace(children)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def #{name}_association
|
29
|
+
@#{name}_association ||= begin
|
30
|
+
unless relationship = model.relationships(#{repository_name.inspect})[#{name.inspect}]
|
31
|
+
raise ArgumentError, "Relationship #{name.inspect} does not exist in \#{model}"
|
32
|
+
end
|
33
|
+
association = Proxy.new(relationship, self)
|
34
|
+
parent_associations << association
|
35
|
+
association
|
36
|
+
end
|
37
|
+
end
|
38
|
+
EOS
|
39
|
+
|
40
|
+
opts = options.dup
|
41
|
+
opts.delete(:through)
|
42
|
+
opts[:child_model] ||= opts.delete(:class_name) || Extlib::Inflection.classify(name)
|
43
|
+
opts[:parent_model] = model
|
44
|
+
opts[:repository_name] = repository_name
|
45
|
+
opts[:remote_relationship_name] ||= opts.delete(:remote_name) || Extlib::Inflection.tableize(opts[:child_model])
|
46
|
+
opts[:parent_key] = opts[:parent_key]
|
47
|
+
opts[:child_key] = opts[:child_key]
|
48
|
+
opts[:mutable] = true
|
49
|
+
|
50
|
+
names = [ opts[:child_model], opts[:parent_model].name ].sort
|
51
|
+
model_name = names.join
|
52
|
+
storage_name = Extlib::Inflection.tableize(Extlib::Inflection.pluralize(names[0]) + names[1])
|
53
|
+
|
54
|
+
opts[:near_relationship_name] = Extlib::Inflection.tableize(model_name).to_sym
|
55
|
+
|
56
|
+
model.has(model.n, opts[:near_relationship_name])
|
57
|
+
|
58
|
+
relationship = model.relationships(repository_name)[name] = RelationshipChain.new(opts)
|
59
|
+
|
60
|
+
unless Object.const_defined?(model_name)
|
61
|
+
model = DataMapper::Model.new(storage_name)
|
62
|
+
|
63
|
+
model.class_eval <<-EOS, __FILE__, __LINE__
|
64
|
+
def self.name; #{model_name.inspect} end
|
65
|
+
def self.default_repository_name; #{repository_name.inspect} end
|
66
|
+
def self.many_to_many; true end
|
67
|
+
EOS
|
68
|
+
|
69
|
+
names.each do |n|
|
70
|
+
model.belongs_to(Extlib::Inflection.underscore(n).to_sym)
|
71
|
+
end
|
72
|
+
|
73
|
+
Object.const_set(model_name, model)
|
74
|
+
end
|
75
|
+
|
76
|
+
relationship
|
77
|
+
end
|
78
|
+
|
79
|
+
class Proxy < DataMapper::Associations::OneToMany::Proxy
|
80
|
+
def delete(resource)
|
81
|
+
through = near_association.get(*(@parent.key + resource.key))
|
82
|
+
near_association.delete(through)
|
83
|
+
orphan_resource(super)
|
84
|
+
end
|
85
|
+
|
86
|
+
def clear
|
87
|
+
near_association.clear
|
88
|
+
super
|
89
|
+
end
|
90
|
+
|
91
|
+
def destroy
|
92
|
+
near_association.destroy
|
93
|
+
super
|
94
|
+
end
|
95
|
+
|
96
|
+
def save
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def new_child(attributes)
|
102
|
+
remote_relationship.parent_model.new(attributes)
|
103
|
+
end
|
104
|
+
|
105
|
+
def relate_resource(resource)
|
106
|
+
assert_mutable
|
107
|
+
add_default_association_values(resource)
|
108
|
+
@orphans.delete(resource)
|
109
|
+
|
110
|
+
# TODO: fix this so it does not automatically save on append, if possible
|
111
|
+
resource.save if resource.new_record?
|
112
|
+
through_resource = @relationship.child_model.new
|
113
|
+
@relationship.child_key.zip(@relationship.parent_key) do |child_key,parent_key|
|
114
|
+
through_resource.send("#{child_key.name}=", parent_key.get(@parent))
|
115
|
+
end
|
116
|
+
remote_relationship.child_key.zip(remote_relationship.parent_key) do |child_key,parent_key|
|
117
|
+
through_resource.send("#{child_key.name}=", parent_key.get(resource))
|
118
|
+
end
|
119
|
+
near_association << through_resource
|
120
|
+
|
121
|
+
resource
|
122
|
+
end
|
123
|
+
|
124
|
+
def orphan_resource(resource)
|
125
|
+
assert_mutable
|
126
|
+
@orphans << resource
|
127
|
+
resource
|
128
|
+
end
|
129
|
+
|
130
|
+
def assert_mutable
|
131
|
+
end
|
132
|
+
|
133
|
+
def remote_relationship
|
134
|
+
@remote_relationship ||= @relationship.send(:remote_relationship)
|
135
|
+
end
|
136
|
+
|
137
|
+
def near_association
|
138
|
+
@near_association ||= @parent.send(near_relationship_name)
|
139
|
+
end
|
140
|
+
|
141
|
+
def near_relationship_name
|
142
|
+
@near_relationship_name ||= @relationship.send(:instance_variable_get, :@near_relationship_name)
|
143
|
+
end
|
144
|
+
end # class Proxy
|
145
|
+
end # module ManyToMany
|
146
|
+
end # module Associations
|
147
|
+
end # module DataMapper
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Associations
|
3
|
+
module ManyToOne
|
4
|
+
extend Assertions
|
5
|
+
|
6
|
+
# Setup many to one relationship between two models
|
7
|
+
# -
|
8
|
+
# @api private
|
9
|
+
def self.setup(name, model, options = {})
|
10
|
+
assert_kind_of 'name', name, Symbol
|
11
|
+
assert_kind_of 'model', model, Model
|
12
|
+
assert_kind_of 'options', options, Hash
|
13
|
+
|
14
|
+
repository_name = model.repository.name
|
15
|
+
|
16
|
+
model.class_eval <<-EOS, __FILE__, __LINE__
|
17
|
+
def #{name}
|
18
|
+
#{name}_association.nil? ? nil : #{name}_association
|
19
|
+
end
|
20
|
+
|
21
|
+
def #{name}=(parent)
|
22
|
+
#{name}_association.replace(parent)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def #{name}_association
|
28
|
+
@#{name}_association ||= begin
|
29
|
+
unless relationship = model.relationships(#{repository_name.inspect})[:#{name}]
|
30
|
+
raise ArgumentError, "Relationship #{name.inspect} does not exist in \#{model}"
|
31
|
+
end
|
32
|
+
association = Proxy.new(relationship, self)
|
33
|
+
child_associations << association
|
34
|
+
association
|
35
|
+
end
|
36
|
+
end
|
37
|
+
EOS
|
38
|
+
|
39
|
+
model.relationships(repository_name)[name] = Relationship.new(
|
40
|
+
name,
|
41
|
+
repository_name,
|
42
|
+
model,
|
43
|
+
options.fetch(:class_name, Extlib::Inflection.classify(name)),
|
44
|
+
options
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
class Proxy
|
49
|
+
include Assertions
|
50
|
+
|
51
|
+
instance_methods.each { |m| undef_method m unless %w[ __id__ __send__ class kind_of? respond_to? assert_kind_of should should_not instance_variable_set instance_variable_get ].include?(m) }
|
52
|
+
|
53
|
+
def replace(parent)
|
54
|
+
@parent = parent
|
55
|
+
@relationship.attach_parent(@child, @parent)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def save
|
60
|
+
return false if @parent.nil?
|
61
|
+
return true unless parent.new_record?
|
62
|
+
|
63
|
+
@relationship.with_repository(parent) do
|
64
|
+
result = parent.save
|
65
|
+
@relationship.child_key.set(@child, @relationship.parent_key.get(parent)) if result
|
66
|
+
result
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def reload
|
71
|
+
@parent = nil
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
def kind_of?(klass)
|
76
|
+
super || parent.kind_of?(klass)
|
77
|
+
end
|
78
|
+
|
79
|
+
def respond_to?(method, include_private = false)
|
80
|
+
super || parent.respond_to?(method, include_private)
|
81
|
+
end
|
82
|
+
|
83
|
+
def instance_variable_get(variable)
|
84
|
+
super || parent.instance_variable_get(variable)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def initialize(relationship, child)
|
90
|
+
assert_kind_of 'relationship', relationship, Relationship
|
91
|
+
assert_kind_of 'child', child, Resource
|
92
|
+
|
93
|
+
@relationship = relationship
|
94
|
+
@child = child
|
95
|
+
end
|
96
|
+
|
97
|
+
def parent
|
98
|
+
@parent ||= @relationship.get_parent(@child)
|
99
|
+
end
|
100
|
+
|
101
|
+
def method_missing(method, *args, &block)
|
102
|
+
parent.__send__(method, *args, &block)
|
103
|
+
end
|
104
|
+
end # class Proxy
|
105
|
+
end # module ManyToOne
|
106
|
+
end # module Associations
|
107
|
+
end # module DataMapper
|
@@ -0,0 +1,309 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Associations
|
3
|
+
module OneToMany
|
4
|
+
extend Assertions
|
5
|
+
|
6
|
+
# Setup one to many relationship between two models
|
7
|
+
# -
|
8
|
+
# @api private
|
9
|
+
def self.setup(name, model, options = {})
|
10
|
+
assert_kind_of 'name', name, Symbol
|
11
|
+
assert_kind_of 'model', model, Model
|
12
|
+
assert_kind_of 'options', options, Hash
|
13
|
+
|
14
|
+
repository_name = model.repository.name
|
15
|
+
|
16
|
+
model.class_eval <<-EOS, __FILE__, __LINE__
|
17
|
+
def #{name}(query = {})
|
18
|
+
#{name}_association.all(query)
|
19
|
+
end
|
20
|
+
|
21
|
+
def #{name}=(children)
|
22
|
+
#{name}_association.replace(children)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def #{name}_association
|
28
|
+
@#{name}_association ||= begin
|
29
|
+
unless relationship = model.relationships(#{repository_name.inspect})[#{name.inspect}]
|
30
|
+
raise ArgumentError, "Relationship #{name.inspect} does not exist in \#{model}"
|
31
|
+
end
|
32
|
+
association = Proxy.new(relationship, self)
|
33
|
+
parent_associations << association
|
34
|
+
association
|
35
|
+
end
|
36
|
+
end
|
37
|
+
EOS
|
38
|
+
|
39
|
+
model.relationships(repository_name)[name] = if options.has_key?(:through)
|
40
|
+
opts = options.dup
|
41
|
+
|
42
|
+
if opts.key?(:class_name) && !opts.key?(:child_key)
|
43
|
+
warn(<<-EOS.margin)
|
44
|
+
You have specified #{model.base_model.name}.has(#{name.inspect}) with :class_name => #{opts[:class_name].inspect}. You probably also want to specify the :child_key option.
|
45
|
+
EOS
|
46
|
+
end
|
47
|
+
|
48
|
+
opts[:child_model] ||= opts.delete(:class_name) || Extlib::Inflection.classify(name)
|
49
|
+
opts[:parent_model] = model
|
50
|
+
opts[:repository_name] = repository_name
|
51
|
+
opts[:near_relationship_name] = opts.delete(:through)
|
52
|
+
opts[:remote_relationship_name] ||= opts.delete(:remote_name) || name
|
53
|
+
opts[:parent_key] = opts[:parent_key]
|
54
|
+
opts[:child_key] = opts[:child_key]
|
55
|
+
|
56
|
+
RelationshipChain.new( opts )
|
57
|
+
else
|
58
|
+
Relationship.new(
|
59
|
+
name,
|
60
|
+
repository_name,
|
61
|
+
options.fetch(:class_name, Extlib::Inflection.classify(name)),
|
62
|
+
model,
|
63
|
+
options
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# TODO: look at making this inherit from Collection. The API is
|
69
|
+
# almost identical, and it would make more sense for the
|
70
|
+
# relationship.get_children method to return a Proxy than a
|
71
|
+
# Collection that is wrapped in a Proxy.
|
72
|
+
class Proxy
|
73
|
+
include Assertions
|
74
|
+
|
75
|
+
instance_methods.each { |m| undef_method m unless %w[ __id__ __send__ class kind_of? respond_to? assert_kind_of should should_not instance_variable_set instance_variable_get ].include?(m) }
|
76
|
+
|
77
|
+
# FIXME: remove when RelationshipChain#get_children can return a Collection
|
78
|
+
def all(query = {})
|
79
|
+
query.empty? ? self : @relationship.get_children(@parent, query)
|
80
|
+
end
|
81
|
+
|
82
|
+
# FIXME: remove when RelationshipChain#get_children can return a Collection
|
83
|
+
def first(*args)
|
84
|
+
if args.last.respond_to?(:merge)
|
85
|
+
query = args.pop
|
86
|
+
@relationship.get_children(@parent, query, :first, *args)
|
87
|
+
else
|
88
|
+
super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def <<(resource)
|
93
|
+
assert_mutable
|
94
|
+
return self if !resource.new_record? && self.include?(resource)
|
95
|
+
super
|
96
|
+
relate_resource(resource)
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
def push(*resources)
|
101
|
+
assert_mutable
|
102
|
+
resources.reject { |resource| !resource.new_record? && self.include?(resource) }
|
103
|
+
super
|
104
|
+
resources.each { |resource| relate_resource(resource) }
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
def unshift(*resources)
|
109
|
+
assert_mutable
|
110
|
+
resources.reject { |resource| !resource.new_record? && self.include?(resource) }
|
111
|
+
super
|
112
|
+
resources.each { |resource| relate_resource(resource) }
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
def replace(other)
|
117
|
+
assert_mutable
|
118
|
+
each { |resource| orphan_resource(resource) }
|
119
|
+
other = other.map { |resource| resource.kind_of?(Hash) ? new_child(resource) : resource }
|
120
|
+
super
|
121
|
+
other.each { |resource| relate_resource(resource) }
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
def pop
|
126
|
+
assert_mutable
|
127
|
+
orphan_resource(super)
|
128
|
+
end
|
129
|
+
|
130
|
+
def shift
|
131
|
+
assert_mutable
|
132
|
+
orphan_resource(super)
|
133
|
+
end
|
134
|
+
|
135
|
+
def delete(resource, &block)
|
136
|
+
assert_mutable
|
137
|
+
orphan_resource(super)
|
138
|
+
end
|
139
|
+
|
140
|
+
def delete_at(index)
|
141
|
+
assert_mutable
|
142
|
+
orphan_resource(super)
|
143
|
+
end
|
144
|
+
|
145
|
+
def clear
|
146
|
+
assert_mutable
|
147
|
+
each { |resource| orphan_resource(resource) }
|
148
|
+
super
|
149
|
+
self
|
150
|
+
end
|
151
|
+
|
152
|
+
def build(attributes = {})
|
153
|
+
assert_mutable
|
154
|
+
attributes = default_attributes.merge(attributes)
|
155
|
+
resource = children.respond_to?(:build) ? super(attributes) : new_child(attributes)
|
156
|
+
resource
|
157
|
+
end
|
158
|
+
|
159
|
+
def create(attributes = {})
|
160
|
+
assert_mutable
|
161
|
+
raise UnsavedParentError, 'You cannot create until the parent is saved' if @parent.new_record?
|
162
|
+
attributes = default_attributes.merge(attributes)
|
163
|
+
resource = children.respond_to?(:create) ? super(attributes) : @relationship.child_model.create(attributes)
|
164
|
+
self << resource
|
165
|
+
resource
|
166
|
+
end
|
167
|
+
|
168
|
+
def update(attributes = {})
|
169
|
+
assert_mutable
|
170
|
+
raise UnsavedParentError, 'You cannot mass-update until the parent is saved' if @parent.new_record?
|
171
|
+
super
|
172
|
+
end
|
173
|
+
|
174
|
+
def update!(attributes = {})
|
175
|
+
assert_mutable
|
176
|
+
raise UnsavedParentError, 'You cannot mass-update without validations until the parent is saved' if @parent.new_record?
|
177
|
+
super
|
178
|
+
end
|
179
|
+
|
180
|
+
def destroy
|
181
|
+
assert_mutable
|
182
|
+
raise UnsavedParentError, 'You cannot mass-delete until the parent is saved' if @parent.new_record?
|
183
|
+
super
|
184
|
+
end
|
185
|
+
|
186
|
+
def destroy!
|
187
|
+
assert_mutable
|
188
|
+
raise UnsavedParentError, 'You cannot mass-delete without validations until the parent is saved' if @parent.new_record?
|
189
|
+
super
|
190
|
+
end
|
191
|
+
|
192
|
+
def reload
|
193
|
+
@children = nil
|
194
|
+
self
|
195
|
+
end
|
196
|
+
|
197
|
+
def save
|
198
|
+
return true if children.frozen?
|
199
|
+
|
200
|
+
# save every resource in the collection
|
201
|
+
each { |resource| save_resource(resource) }
|
202
|
+
|
203
|
+
# save orphan resources
|
204
|
+
@orphans.each do |resource|
|
205
|
+
begin
|
206
|
+
save_resource(resource, nil)
|
207
|
+
rescue
|
208
|
+
children << resource unless children.frozen? || children.include?(resource)
|
209
|
+
raise
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# FIXME: remove when RelationshipChain#get_children can return a Collection
|
214
|
+
# place the children into a Collection if not already
|
215
|
+
if children.kind_of?(Array) && !children.frozen?
|
216
|
+
@children = @relationship.get_children(@parent).replace(children)
|
217
|
+
end
|
218
|
+
|
219
|
+
true
|
220
|
+
end
|
221
|
+
|
222
|
+
def kind_of?(klass)
|
223
|
+
super || children.kind_of?(klass)
|
224
|
+
end
|
225
|
+
|
226
|
+
def respond_to?(method, include_private = false)
|
227
|
+
super || children.respond_to?(method, include_private)
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
|
232
|
+
def initialize(relationship, parent)
|
233
|
+
assert_kind_of 'relationship', relationship, Relationship
|
234
|
+
assert_kind_of 'parent', parent, Resource
|
235
|
+
|
236
|
+
@relationship = relationship
|
237
|
+
@parent = parent
|
238
|
+
@orphans = []
|
239
|
+
end
|
240
|
+
|
241
|
+
def children
|
242
|
+
@children ||= @relationship.get_children(@parent)
|
243
|
+
end
|
244
|
+
|
245
|
+
def assert_mutable
|
246
|
+
raise ImmutableAssociationError, 'You can not modify this association' if children.frozen?
|
247
|
+
end
|
248
|
+
|
249
|
+
def default_attributes
|
250
|
+
default_attributes = {}
|
251
|
+
|
252
|
+
@relationship.query.each do |attribute, value|
|
253
|
+
next if Query::OPTIONS.include?(attribute) || attribute.kind_of?(Query::Operator)
|
254
|
+
default_attributes[attribute] = value
|
255
|
+
end
|
256
|
+
|
257
|
+
@relationship.child_key.zip(@relationship.parent_key.get(@parent)) do |property,value|
|
258
|
+
default_attributes[property.name] = value
|
259
|
+
end
|
260
|
+
|
261
|
+
default_attributes
|
262
|
+
end
|
263
|
+
|
264
|
+
def add_default_association_values(resource)
|
265
|
+
default_attributes.each do |attribute, value|
|
266
|
+
next if !resource.respond_to?("#{attribute}=") || resource.attribute_loaded?(attribute)
|
267
|
+
resource.send("#{attribute}=", value)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def new_child(attributes)
|
272
|
+
@relationship.child_model.new(default_attributes.merge(attributes))
|
273
|
+
end
|
274
|
+
|
275
|
+
def relate_resource(resource)
|
276
|
+
assert_mutable
|
277
|
+
add_default_association_values(resource)
|
278
|
+
@orphans.delete(resource)
|
279
|
+
resource
|
280
|
+
end
|
281
|
+
|
282
|
+
def orphan_resource(resource)
|
283
|
+
assert_mutable
|
284
|
+
@orphans << resource
|
285
|
+
resource
|
286
|
+
end
|
287
|
+
|
288
|
+
def save_resource(resource, parent = @parent)
|
289
|
+
@relationship.with_repository(resource) do |r|
|
290
|
+
if parent.nil? && resource.model.respond_to?(:many_to_many)
|
291
|
+
resource.destroy
|
292
|
+
else
|
293
|
+
@relationship.attach_parent(resource, parent)
|
294
|
+
resource.save
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def method_missing(method, *args, &block)
|
300
|
+
results = children.__send__(method, *args, &block) if children.respond_to?(method)
|
301
|
+
|
302
|
+
return self if LazyArray::RETURN_SELF.include?(method) && results.kind_of?(Array)
|
303
|
+
|
304
|
+
results
|
305
|
+
end
|
306
|
+
end # class Proxy
|
307
|
+
end # module OneToMany
|
308
|
+
end # module Associations
|
309
|
+
end # module DataMapper
|