dm-core 0.9.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.
- data/CHANGELOG +144 -0
- data/FAQ +74 -0
- data/MIT-LICENSE +22 -0
- data/QUICKLINKS +12 -0
- data/README +143 -0
- data/lib/dm-core.rb +213 -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 +701 -0
- data/lib/dm-core/adapters/mysql_adapter.rb +132 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +179 -0
- data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
- data/lib/dm-core/associations.rb +172 -0
- data/lib/dm-core/associations/many_to_many.rb +138 -0
- data/lib/dm-core/associations/many_to_one.rb +101 -0
- data/lib/dm-core/associations/one_to_many.rb +275 -0
- data/lib/dm-core/associations/one_to_one.rb +61 -0
- data/lib/dm-core/associations/relationship.rb +116 -0
- data/lib/dm-core/associations/relationship_chain.rb +74 -0
- data/lib/dm-core/auto_migrations.rb +64 -0
- data/lib/dm-core/collection.rb +604 -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 +233 -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 +399 -0
- data/lib/dm-core/naming_conventions.rb +52 -0
- data/lib/dm-core/property.rb +611 -0
- data/lib/dm-core/property_set.rb +158 -0
- data/lib/dm-core/query.rb +590 -0
- data/lib/dm-core/repository.rb +159 -0
- data/lib/dm-core/resource.rb +618 -0
- data/lib/dm-core/scope.rb +35 -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 +32 -0
- data/lib/dm-core/types/object.rb +20 -0
- data/lib/dm-core/types/paranoid_boolean.rb +23 -0
- data/lib/dm-core/types/paranoid_datetime.rb +22 -0
- data/lib/dm-core/types/serial.rb +9 -0
- data/lib/dm-core/types/text.rb +10 -0
- data/spec/integration/association_spec.rb +1215 -0
- data/spec/integration/association_through_spec.rb +150 -0
- data/spec/integration/associations/many_to_many_spec.rb +171 -0
- data/spec/integration/associations/many_to_one_spec.rb +123 -0
- data/spec/integration/associations/one_to_many_spec.rb +66 -0
- data/spec/integration/auto_migrations_spec.rb +398 -0
- data/spec/integration/collection_spec.rb +1015 -0
- data/spec/integration/data_objects_adapter_spec.rb +32 -0
- data/spec/integration/model_spec.rb +68 -0
- data/spec/integration/mysql_adapter_spec.rb +85 -0
- data/spec/integration/postgres_adapter_spec.rb +732 -0
- data/spec/integration/property_spec.rb +224 -0
- data/spec/integration/query_spec.rb +376 -0
- data/spec/integration/repository_spec.rb +57 -0
- data/spec/integration/resource_spec.rb +324 -0
- data/spec/integration/sqlite3_adapter_spec.rb +352 -0
- data/spec/integration/sti_spec.rb +185 -0
- data/spec/integration/transaction_spec.rb +75 -0
- data/spec/integration/type_spec.rb +149 -0
- data/spec/lib/mock_adapter.rb +27 -0
- data/spec/spec_helper.rb +112 -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 +627 -0
- data/spec/unit/adapters/postgres_adapter_spec.rb +125 -0
- data/spec/unit/associations/many_to_many_spec.rb +14 -0
- data/spec/unit/associations/many_to_one_spec.rb +138 -0
- data/spec/unit/associations/one_to_many_spec.rb +385 -0
- data/spec/unit/associations/one_to_one_spec.rb +7 -0
- data/spec/unit/associations/relationship_spec.rb +67 -0
- data/spec/unit/associations_spec.rb +205 -0
- data/spec/unit/auto_migrations_spec.rb +110 -0
- data/spec/unit/collection_spec.rb +174 -0
- data/spec/unit/data_mapper_spec.rb +21 -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 +28 -0
- data/spec/unit/property_set_spec.rb +96 -0
- data/spec/unit/property_spec.rb +447 -0
- data/spec/unit/query_spec.rb +485 -0
- data/spec/unit/repository_spec.rb +93 -0
- data/spec/unit/resource_spec.rb +557 -0
- data/spec/unit/scope_spec.rb +131 -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
- metadata +187 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# TODO: move to dm-more/dm-migrations
|
|
2
|
+
|
|
3
|
+
module DataMapper
|
|
4
|
+
class TypeMap
|
|
5
|
+
|
|
6
|
+
attr_accessor :parent, :chains
|
|
7
|
+
|
|
8
|
+
def initialize(parent = nil, &blk)
|
|
9
|
+
@parent, @chains = parent, {}
|
|
10
|
+
|
|
11
|
+
blk.call(self) unless blk.nil?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def map(type)
|
|
15
|
+
@chains[type] ||= TypeChain.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def lookup(type)
|
|
19
|
+
if type_mapped?(type)
|
|
20
|
+
lookup_from_map(type)
|
|
21
|
+
else
|
|
22
|
+
lookup_by_type(type)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def lookup_from_map(type)
|
|
27
|
+
lookup_from_parent(type).merge(map(type).translate)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def lookup_from_parent(type)
|
|
31
|
+
if !@parent.nil? && @parent.type_mapped?(type)
|
|
32
|
+
@parent[type]
|
|
33
|
+
else
|
|
34
|
+
{}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @raise <DataMapper::TypeMap::Error> if the type is not a default primitive or has a type map entry.
|
|
39
|
+
def lookup_by_type(type)
|
|
40
|
+
raise DataMapper::TypeMap::Error.new(type) unless type.respond_to?(:primitive) && !type.primitive.nil?
|
|
41
|
+
|
|
42
|
+
lookup(type.primitive).merge(Type::PROPERTY_OPTIONS.inject({}) {|h, k| h[k] = type.send(k); h})
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
alias [] lookup
|
|
46
|
+
|
|
47
|
+
def type_mapped?(type)
|
|
48
|
+
@chains.has_key?(type) || (@parent.nil? ? false : @parent.type_mapped?(type))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class TypeChain
|
|
52
|
+
attr_accessor :primitive, :attributes
|
|
53
|
+
|
|
54
|
+
def initialize
|
|
55
|
+
@attributes = {}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def to(primitive)
|
|
59
|
+
@primitive = primitive
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def with(attributes)
|
|
64
|
+
raise "method 'with' expects a hash" unless attributes.kind_of?(Hash)
|
|
65
|
+
@attributes.merge!(attributes)
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def translate
|
|
70
|
+
@attributes.merge((@primitive.nil? ? {} : {:primitive => @primitive}))
|
|
71
|
+
end
|
|
72
|
+
end # class TypeChain
|
|
73
|
+
|
|
74
|
+
class Error < StandardError
|
|
75
|
+
def initialize(type)
|
|
76
|
+
super("Type #{type} must have a default primitive or type map entry")
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end # class TypeMap
|
|
80
|
+
end # module DataMapper
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
dir = Pathname(__FILE__).dirname.expand_path / 'types'
|
|
2
|
+
|
|
3
|
+
require dir / 'boolean'
|
|
4
|
+
require dir / 'discriminator'
|
|
5
|
+
require dir / 'text'
|
|
6
|
+
require dir / 'paranoid_datetime'
|
|
7
|
+
require dir / 'paranoid_boolean'
|
|
8
|
+
require dir / 'object'
|
|
9
|
+
require dir / 'serial'
|
|
10
|
+
|
|
11
|
+
unless defined?(DM)
|
|
12
|
+
DM = DataMapper::Types
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module DataMapper
|
|
16
|
+
module Resource
|
|
17
|
+
include Types
|
|
18
|
+
end # module Resource
|
|
19
|
+
end # module DataMapper
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Types
|
|
3
|
+
class Discriminator < DataMapper::Type
|
|
4
|
+
primitive Class
|
|
5
|
+
track :set
|
|
6
|
+
default lambda { |r,p| p.model }
|
|
7
|
+
nullable false
|
|
8
|
+
|
|
9
|
+
def self.bind(property)
|
|
10
|
+
model = property.model
|
|
11
|
+
|
|
12
|
+
model.class_eval <<-EOS, __FILE__, __LINE__
|
|
13
|
+
def self.child_classes
|
|
14
|
+
@child_classes ||= []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
after_class_method :inherited, :add_scope_for_discriminator
|
|
18
|
+
|
|
19
|
+
def self.add_scope_for_discriminator(target)
|
|
20
|
+
target.send(:scope_stack) << DataMapper::Query.new(target.repository, target, :#{property.name} => target.child_classes << target)
|
|
21
|
+
propagate_child_classes(target)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.propagate_child_classes(target)
|
|
25
|
+
child_classes << target
|
|
26
|
+
superclass.send(:propagate_child_classes,target) if superclass.respond_to?(:propagate_child_classes)
|
|
27
|
+
end
|
|
28
|
+
EOS
|
|
29
|
+
end
|
|
30
|
+
end # class Discriminator
|
|
31
|
+
end # module Types
|
|
32
|
+
end # module DataMapper
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "base64"
|
|
2
|
+
|
|
3
|
+
module DataMapper
|
|
4
|
+
module Types
|
|
5
|
+
class Object < DataMapper::Type
|
|
6
|
+
primitive String
|
|
7
|
+
size 65535
|
|
8
|
+
lazy true
|
|
9
|
+
track :hash
|
|
10
|
+
|
|
11
|
+
def self.dump(value, property)
|
|
12
|
+
Base64.encode64(Marshal.dump(value))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.load(value, property)
|
|
16
|
+
value.nil? ? nil : Marshal.load(Base64.decode64(value))
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Types
|
|
3
|
+
class ParanoidBoolean < DataMapper::Type(Boolean)
|
|
4
|
+
primitive TrueClass
|
|
5
|
+
default false
|
|
6
|
+
|
|
7
|
+
def self.bind(property)
|
|
8
|
+
model = property.model
|
|
9
|
+
repository = property.repository
|
|
10
|
+
|
|
11
|
+
model.class_eval <<-EOS
|
|
12
|
+
def destroy
|
|
13
|
+
attribute_set(#{property.name.inspect}, true)
|
|
14
|
+
save
|
|
15
|
+
end
|
|
16
|
+
EOS
|
|
17
|
+
|
|
18
|
+
model.send(:scope_stack) << DataMapper::Query.new(repository, model, property.name => nil)
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end # class ParanoidBoolean
|
|
22
|
+
end # module Types
|
|
23
|
+
end # module DataMapper
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Types
|
|
3
|
+
class ParanoidDateTime < DataMapper::Type(DateTime)
|
|
4
|
+
primitive DateTime
|
|
5
|
+
|
|
6
|
+
def self.bind(property)
|
|
7
|
+
model = property.model
|
|
8
|
+
repository = property.repository
|
|
9
|
+
|
|
10
|
+
model.class_eval <<-EOS
|
|
11
|
+
def destroy
|
|
12
|
+
attribute_set(#{property.name.inspect}, DateTime.now)
|
|
13
|
+
save
|
|
14
|
+
end
|
|
15
|
+
EOS
|
|
16
|
+
|
|
17
|
+
model.send(:scope_stack) << DataMapper::Query.new(repository, model, property.name => nil)
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
end # class ParanoidDateTime
|
|
21
|
+
end # module Types
|
|
22
|
+
end # module DataMapper
|
|
@@ -0,0 +1,1215 @@
|
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
|
2
|
+
|
|
3
|
+
if ADAPTER
|
|
4
|
+
repository(ADAPTER) do
|
|
5
|
+
class Engine
|
|
6
|
+
include DataMapper::Resource
|
|
7
|
+
|
|
8
|
+
def self.default_repository_name
|
|
9
|
+
ADAPTER
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
property :id, Serial
|
|
13
|
+
property :name, String
|
|
14
|
+
|
|
15
|
+
has n, :yards
|
|
16
|
+
has n, :fussy_yards, :class_name => 'Yard', :rating.gte => 3, :type => 'particular'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Yard
|
|
20
|
+
include DataMapper::Resource
|
|
21
|
+
|
|
22
|
+
def self.default_repository_name
|
|
23
|
+
ADAPTER
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
property :id, Serial
|
|
27
|
+
property :name, String
|
|
28
|
+
property :rating, Integer
|
|
29
|
+
property :type, String
|
|
30
|
+
|
|
31
|
+
belongs_to :engine
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class Pie
|
|
35
|
+
include DataMapper::Resource
|
|
36
|
+
|
|
37
|
+
def self.default_repository_name
|
|
38
|
+
ADAPTER
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
property :id, Serial
|
|
42
|
+
property :name, String
|
|
43
|
+
|
|
44
|
+
belongs_to :sky
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class Sky
|
|
48
|
+
include DataMapper::Resource
|
|
49
|
+
|
|
50
|
+
def self.default_repository_name
|
|
51
|
+
ADAPTER
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
property :id, Serial
|
|
55
|
+
property :name, String
|
|
56
|
+
|
|
57
|
+
has 1, :pie
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class Host
|
|
61
|
+
include DataMapper::Resource
|
|
62
|
+
|
|
63
|
+
def self.default_repository_name
|
|
64
|
+
ADAPTER
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
property :id, Serial
|
|
68
|
+
property :name, String
|
|
69
|
+
|
|
70
|
+
has n, :slices, :order => [:id.desc]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class Slice
|
|
74
|
+
include DataMapper::Resource
|
|
75
|
+
|
|
76
|
+
def self.default_repository_name
|
|
77
|
+
ADAPTER
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
property :id, Serial
|
|
81
|
+
property :name, String
|
|
82
|
+
|
|
83
|
+
belongs_to :host
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
class Node
|
|
87
|
+
include DataMapper::Resource
|
|
88
|
+
|
|
89
|
+
def self.default_repository_name
|
|
90
|
+
ADAPTER
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
property :id, Serial
|
|
94
|
+
property :name, String
|
|
95
|
+
|
|
96
|
+
has n, :children, :class_name => 'Node', :child_key => [ :parent_id ]
|
|
97
|
+
belongs_to :parent, :class_name => 'Node', :child_key => [ :parent_id ]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
module Models
|
|
101
|
+
class Project
|
|
102
|
+
include DataMapper::Resource
|
|
103
|
+
|
|
104
|
+
def self.default_repository_name
|
|
105
|
+
ADAPTER
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
property :title, String, :length => 255, :key => true
|
|
109
|
+
property :summary, DataMapper::Types::Text
|
|
110
|
+
|
|
111
|
+
has n, :tasks, :class_name => 'Models::Task'
|
|
112
|
+
has 1, :goal, :class_name => 'Models::Goal'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
class Goal
|
|
116
|
+
include DataMapper::Resource
|
|
117
|
+
|
|
118
|
+
def self.default_repository_name
|
|
119
|
+
ADAPTER
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
property :title, String, :length => 255, :key => true
|
|
123
|
+
property :summary, DataMapper::Types::Text
|
|
124
|
+
|
|
125
|
+
belongs_to :project, :class_name => "Models::Project"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
class Task
|
|
129
|
+
include DataMapper::Resource
|
|
130
|
+
|
|
131
|
+
def self.default_repository_name
|
|
132
|
+
ADAPTER
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
property :title, String, :length => 255, :key => true
|
|
136
|
+
property :description, DataMapper::Types::Text
|
|
137
|
+
|
|
138
|
+
belongs_to :project, :class_name => 'Models::Project'
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
class Galaxy
|
|
143
|
+
include DataMapper::Resource
|
|
144
|
+
|
|
145
|
+
def self.default_repository_name
|
|
146
|
+
ADAPTER
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
property :name, String, :key => true, :length => 255
|
|
150
|
+
property :size, Float, :key => true, :precision => 15, :scale => 6
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
class Star
|
|
154
|
+
include DataMapper::Resource
|
|
155
|
+
|
|
156
|
+
def self.default_repository_name
|
|
157
|
+
ADAPTER
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
belongs_to :galaxy
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
describe DataMapper::Associations do
|
|
165
|
+
describe 'namespaced associations' do
|
|
166
|
+
before do
|
|
167
|
+
Models::Project.auto_migrate!(ADAPTER)
|
|
168
|
+
Models::Task.auto_migrate!(ADAPTER)
|
|
169
|
+
Models::Goal.auto_migrate!(ADAPTER)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it 'should allow namespaced classes in parent and child for many <=> one' do
|
|
173
|
+
m = Models::Project.new(:title => 'p1', :summary => 'sum1')
|
|
174
|
+
m.tasks << Models::Task.new(:title => 't1', :description => 'desc 1')
|
|
175
|
+
m.save
|
|
176
|
+
|
|
177
|
+
t = Models::Task.first(:title => 't1')
|
|
178
|
+
|
|
179
|
+
t.project.should_not be_nil
|
|
180
|
+
t.project.title.should == 'p1'
|
|
181
|
+
t.project.tasks.size.should == 1
|
|
182
|
+
|
|
183
|
+
p = Models::Project.first(:title => 'p1')
|
|
184
|
+
|
|
185
|
+
p.tasks.size.should == 1
|
|
186
|
+
p.tasks[0].title.should == 't1'
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it 'should allow namespaced classes in parent and child for one <=> one' do
|
|
190
|
+
g = Models::Goal.new(:title => "g2", :description => "desc 2")
|
|
191
|
+
p = Models::Project.create!(:title => "p2", :summary => "sum 2", :goal => g)
|
|
192
|
+
|
|
193
|
+
pp = Models::Project.first(:title => 'p2')
|
|
194
|
+
pp.goal.title.should == "g2"
|
|
195
|
+
|
|
196
|
+
g = Models::Goal.first(:title => "g2")
|
|
197
|
+
|
|
198
|
+
g.project.should_not be_nil
|
|
199
|
+
g.project.title.should == 'p2'
|
|
200
|
+
|
|
201
|
+
g.project.goal.should_not be_nil
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
describe 'many to one associations' do
|
|
206
|
+
before do
|
|
207
|
+
Engine.auto_migrate!(ADAPTER)
|
|
208
|
+
Yard.auto_migrate!(ADAPTER)
|
|
209
|
+
|
|
210
|
+
engine1 = Engine.create!(:name => 'engine1')
|
|
211
|
+
engine2 = Engine.create!(:name => 'engine2')
|
|
212
|
+
yard1 = Yard.create!(:name => 'yard1', :engine => engine1)
|
|
213
|
+
yard2 = Yard.create!(:name => 'yard2')
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
it '#belongs_to' do
|
|
217
|
+
yard = Yard.new
|
|
218
|
+
yard.should respond_to(:engine)
|
|
219
|
+
yard.should respond_to(:engine=)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it 'should load without the parent'
|
|
223
|
+
|
|
224
|
+
it 'should allow substituting the parent' do
|
|
225
|
+
yard1 = Yard.first(:name => 'yard1')
|
|
226
|
+
engine2 = Engine.first(:name => 'engine2')
|
|
227
|
+
|
|
228
|
+
yard1.engine = engine2
|
|
229
|
+
yard1.save
|
|
230
|
+
Yard.first(:name => 'yard1').engine.should == engine2
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
it '#belongs_to with namespaced models' do
|
|
234
|
+
repository(ADAPTER) do
|
|
235
|
+
module FlightlessBirds
|
|
236
|
+
class Ostrich
|
|
237
|
+
include DataMapper::Resource
|
|
238
|
+
property :id, Serial
|
|
239
|
+
property :name, String
|
|
240
|
+
belongs_to :sky # there's something sad about this :'(
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
FlightlessBirds::Ostrich.properties.slice(:sky_id).should_not be_empty
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it 'should load the associated instance' do
|
|
249
|
+
engine1 = Engine.first(:name => 'engine1')
|
|
250
|
+
Yard.first(:name => 'yard1').engine.should == engine1
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
it 'should save the association key in the child' do
|
|
254
|
+
engine2 = Engine.first(:name => 'engine2')
|
|
255
|
+
|
|
256
|
+
Yard.create!(:name => 'yard3', :engine => engine2)
|
|
257
|
+
Yard.first(:name => 'yard3').engine.should == engine2
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it 'should set the association key immediately' do
|
|
261
|
+
engine = Engine.first(:name => 'engine1')
|
|
262
|
+
Yard.new(:engine => engine).engine_id.should == engine.id
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
it 'should save the parent upon saving of child' do
|
|
266
|
+
e = Engine.new(:name => 'engine10')
|
|
267
|
+
y = Yard.create!(:name => 'yard10', :engine => e)
|
|
268
|
+
|
|
269
|
+
y.engine.name.should == 'engine10'
|
|
270
|
+
Engine.first(:name => 'engine10').should_not be_nil
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
it 'should convert NULL parent ids into nils' do
|
|
274
|
+
Yard.first(:name => 'yard2').engine.should be_nil
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
it 'should save nil parents as NULL ids' do
|
|
278
|
+
y1 = Yard.create!(:id => 20, :name => 'yard20')
|
|
279
|
+
y2 = Yard.create!(:id => 30, :name => 'yard30', :engine => nil)
|
|
280
|
+
|
|
281
|
+
y1.id.should == 20
|
|
282
|
+
y1.engine.should be_nil
|
|
283
|
+
y2.id.should == 30
|
|
284
|
+
y2.engine.should be_nil
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
it 'should respect length on foreign keys' do
|
|
288
|
+
property = Star.relationships[:galaxy].child_key[:galaxy_name]
|
|
289
|
+
property.length.should == 255
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
it 'should respect precision and scale on foreign keys' do
|
|
293
|
+
property = Star.relationships[:galaxy].child_key[:galaxy_size]
|
|
294
|
+
property.precision.should == 15
|
|
295
|
+
property.scale.should == 6
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
it 'should be reloaded when calling Resource#reload' do
|
|
299
|
+
e = Engine.new(:name => 'engine40')
|
|
300
|
+
y = Yard.create!(:name => 'yard40', :engine => e)
|
|
301
|
+
|
|
302
|
+
y.send(:engine_association).should_receive(:reload).once
|
|
303
|
+
|
|
304
|
+
lambda { y.reload }.should_not raise_error
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
describe 'one to one associations' do
|
|
309
|
+
before do
|
|
310
|
+
Sky.auto_migrate!(ADAPTER)
|
|
311
|
+
Pie.auto_migrate!(ADAPTER)
|
|
312
|
+
|
|
313
|
+
pie1 = Pie.create!(:name => 'pie1')
|
|
314
|
+
pie2 = Pie.create!(:name => 'pie2')
|
|
315
|
+
sky1 = Sky.create!(:name => 'sky1', :pie => pie1)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
it '#has 1' do
|
|
319
|
+
s = Sky.new
|
|
320
|
+
s.should respond_to(:pie)
|
|
321
|
+
s.should respond_to(:pie=)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
it 'should allow substituting the child' do
|
|
325
|
+
sky1 = Sky.first(:name => 'sky1')
|
|
326
|
+
pie1 = Pie.first(:name => 'pie1')
|
|
327
|
+
pie2 = Pie.first(:name => 'pie2')
|
|
328
|
+
|
|
329
|
+
sky1.pie.should == pie1
|
|
330
|
+
pie2.sky.should be_nil
|
|
331
|
+
|
|
332
|
+
sky1.pie = pie2
|
|
333
|
+
sky1.save
|
|
334
|
+
|
|
335
|
+
pie2.sky.should == sky1
|
|
336
|
+
pie1.reload.sky.should be_nil
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
it 'should load the associated instance' do
|
|
340
|
+
sky1 = Sky.first(:name => 'sky1')
|
|
341
|
+
pie1 = Pie.first(:name => 'pie1')
|
|
342
|
+
|
|
343
|
+
sky1.pie.should == pie1
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
it 'should save the association key in the child' do
|
|
347
|
+
pie2 = Pie.first(:name => 'pie2')
|
|
348
|
+
|
|
349
|
+
sky2 = Sky.create!(:id => 2, :name => 'sky2', :pie => pie2)
|
|
350
|
+
pie2.sky.should == sky2
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
it 'should save the children upon saving of parent' do
|
|
354
|
+
p = Pie.new(:id => 10, :name => 'pie10')
|
|
355
|
+
s = Sky.create!(:id => 10, :name => 'sky10', :pie => p)
|
|
356
|
+
|
|
357
|
+
p.sky.should == s
|
|
358
|
+
|
|
359
|
+
Pie.first(:name => 'pie10').should_not be_nil
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
it 'should save nil parents as NULL ids' do
|
|
363
|
+
p1 = Pie.create!(:id => 20, :name => 'pie20')
|
|
364
|
+
p2 = Pie.create!(:id => 30, :name => 'pie30', :sky => nil)
|
|
365
|
+
|
|
366
|
+
p1.id.should == 20
|
|
367
|
+
p1.sky.should be_nil
|
|
368
|
+
p2.id.should == 30
|
|
369
|
+
p2.sky.should be_nil
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
it 'should be reloaded when calling Resource#reload' do
|
|
373
|
+
pie = Pie.first(:name => 'pie1')
|
|
374
|
+
pie.send(:sky_association).should_receive(:reload).once
|
|
375
|
+
lambda { pie.reload }.should_not raise_error
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
describe 'one to many associations' do
|
|
380
|
+
before do
|
|
381
|
+
Host.auto_migrate!(ADAPTER)
|
|
382
|
+
Slice.auto_migrate!(ADAPTER)
|
|
383
|
+
Engine.auto_migrate!(ADAPTER)
|
|
384
|
+
Yard.auto_migrate!(ADAPTER)
|
|
385
|
+
|
|
386
|
+
host1 = Host.create!(:name => 'host1')
|
|
387
|
+
host2 = Host.create!(:name => 'host2')
|
|
388
|
+
slice1 = Slice.create!(:name => 'slice1', :host => host1)
|
|
389
|
+
slice2 = Slice.create!(:name => 'slice2', :host => host1)
|
|
390
|
+
slice3 = Slice.create!(:name => 'slice3')
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
it '#has n' do
|
|
394
|
+
h = Host.new
|
|
395
|
+
h.should respond_to(:slices)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
it 'should allow removal of a child through a loaded association' do
|
|
399
|
+
host1 = Host.first(:name => 'host1')
|
|
400
|
+
slice2 = host1.slices.first
|
|
401
|
+
|
|
402
|
+
host1.slices.size.should == 2
|
|
403
|
+
host1.slices.delete(slice2)
|
|
404
|
+
host1.slices.size.should == 1
|
|
405
|
+
|
|
406
|
+
slice2 = Slice.first(:name => 'slice2')
|
|
407
|
+
slice2.host.should_not be_nil
|
|
408
|
+
|
|
409
|
+
host1.save
|
|
410
|
+
|
|
411
|
+
slice2.reload.host.should be_nil
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
it 'should use the IdentityMap correctly' do
|
|
415
|
+
repository(ADAPTER) do
|
|
416
|
+
host1 = Host.first(:name => 'host1')
|
|
417
|
+
|
|
418
|
+
slice = host1.slices.first
|
|
419
|
+
slice2 = host1.slices(:order => [:id]).last # should be the same as 1
|
|
420
|
+
slice3 = Slice.get(2) # should be the same as 1
|
|
421
|
+
|
|
422
|
+
slice.object_id.should == slice2.object_id
|
|
423
|
+
slice.object_id.should == slice3.object_id
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
it '#<< should add exactly the parameters' do
|
|
428
|
+
engine = Engine.new(:name => 'my engine')
|
|
429
|
+
4.times do |i|
|
|
430
|
+
engine.yards << Yard.new(:name => "yard nr #{i}")
|
|
431
|
+
end
|
|
432
|
+
engine.save
|
|
433
|
+
engine.yards.size.should == 4
|
|
434
|
+
4.times do |i|
|
|
435
|
+
engine.yards.any? do |yard|
|
|
436
|
+
yard.name == "yard nr #{i}"
|
|
437
|
+
end.should == true
|
|
438
|
+
end
|
|
439
|
+
engine = Engine.get!(engine.id)
|
|
440
|
+
engine.yards.size.should == 4
|
|
441
|
+
4.times do |i|
|
|
442
|
+
engine.yards.any? do |yard|
|
|
443
|
+
yard.name == "yard nr #{i}"
|
|
444
|
+
end.should == true
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
it '#<< should add default values for relationships that have conditions' do
|
|
449
|
+
# it should add default values
|
|
450
|
+
engine = Engine.new(:name => 'my engine')
|
|
451
|
+
engine.fussy_yards << Yard.new(:name => 'yard 1', :rating => 4 )
|
|
452
|
+
engine.save
|
|
453
|
+
Yard.first(:name => 'yard 1').type.should == 'particular'
|
|
454
|
+
# it should not add default values if the condition's property already has a value
|
|
455
|
+
engine.fussy_yards << Yard.new(:name => 'yard 2', :rating => 4, :type => 'not particular')
|
|
456
|
+
engine.save
|
|
457
|
+
Yard.first(:name => 'yard 2').type.should == 'not particular'
|
|
458
|
+
# it should ignore non :eql conditions
|
|
459
|
+
engine.fussy_yards << Yard.new(:name => 'yard 3')
|
|
460
|
+
engine.save
|
|
461
|
+
Yard.first(:name => 'yard 3').rating.should == nil
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
it 'should load the associated instances, in the correct order' do
|
|
465
|
+
host1 = Host.first(:name => 'host1')
|
|
466
|
+
|
|
467
|
+
host1.slices.should_not be_nil
|
|
468
|
+
host1.slices.size.should == 2
|
|
469
|
+
host1.slices.first.name.should == 'slice2' # ordered by [:id.desc]
|
|
470
|
+
host1.slices.last.name.should == 'slice1'
|
|
471
|
+
|
|
472
|
+
slice3 = Slice.first(:name => 'slice3')
|
|
473
|
+
|
|
474
|
+
slice3.host.should be_nil
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
it 'should add and save the associated instance' do
|
|
478
|
+
host1 = Host.first(:name => 'host1')
|
|
479
|
+
host1.slices << Slice.new(:id => 4, :name => 'slice4')
|
|
480
|
+
host1.save
|
|
481
|
+
|
|
482
|
+
Slice.first(:name => 'slice4').host.should == host1
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
it 'should not save the associated instance if the parent is not saved' do
|
|
486
|
+
h = Host.new(:id => 10, :name => 'host10')
|
|
487
|
+
h.slices << Slice.new(:id => 10, :name => 'slice10')
|
|
488
|
+
|
|
489
|
+
Slice.first(:name => 'slice10').should be_nil
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
it 'should save the associated instance upon saving of parent' do
|
|
493
|
+
h = Host.new(:id => 10, :name => 'host10')
|
|
494
|
+
h.slices << Slice.new(:id => 10, :name => 'slice10')
|
|
495
|
+
h.save
|
|
496
|
+
|
|
497
|
+
s = Slice.first(:name => 'slice10')
|
|
498
|
+
|
|
499
|
+
s.should_not be_nil
|
|
500
|
+
s.host.should == h
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
it 'should save the associated instances upon saving of parent when mass-assigned' do
|
|
504
|
+
h = Host.create!(:id => 10, :name => 'host10', :slices => [ Slice.new(:id => 10, :name => 'slice10') ])
|
|
505
|
+
|
|
506
|
+
s = Slice.first(:name => 'slice10')
|
|
507
|
+
|
|
508
|
+
s.should_not be_nil
|
|
509
|
+
s.host.should == h
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
it 'should have finder-functionality' do
|
|
513
|
+
h = Host.first(:name => 'host1')
|
|
514
|
+
|
|
515
|
+
h.slices.should have(2).entries
|
|
516
|
+
|
|
517
|
+
s = h.slices.all(:name => 'slice2')
|
|
518
|
+
|
|
519
|
+
s.should have(1).entries
|
|
520
|
+
s.first.id.should == 2
|
|
521
|
+
|
|
522
|
+
h.slices.first(:name => 'slice2').should == s.first
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
it 'should be reloaded when calling Resource#reload' do
|
|
526
|
+
host = Host.first(:name => 'host1')
|
|
527
|
+
host.send(:slices_association).should_receive(:reload).once
|
|
528
|
+
lambda { host.reload }.should_not raise_error
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
describe 'many-to-one and one-to-many associations combined' do
|
|
533
|
+
before do
|
|
534
|
+
Node.auto_migrate!(ADAPTER)
|
|
535
|
+
|
|
536
|
+
Node.create!(:name => 'r1')
|
|
537
|
+
Node.create!(:name => 'r2')
|
|
538
|
+
Node.create!(:name => 'r1c1', :parent_id => 1)
|
|
539
|
+
Node.create!(:name => 'r1c2', :parent_id => 1)
|
|
540
|
+
Node.create!(:name => 'r1c3', :parent_id => 1)
|
|
541
|
+
Node.create!(:name => 'r1c1c1', :parent_id => 3)
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
it 'should properly set #parent' do
|
|
545
|
+
r1 = Node.get 1
|
|
546
|
+
r1.parent.should be_nil
|
|
547
|
+
|
|
548
|
+
n3 = Node.get 3
|
|
549
|
+
n3.parent.should == r1
|
|
550
|
+
|
|
551
|
+
n6 = Node.get 6
|
|
552
|
+
n6.parent.should == n3
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
it 'should properly set #children' do
|
|
556
|
+
r1 = Node.get(1)
|
|
557
|
+
off = r1.children
|
|
558
|
+
off.size.should == 3
|
|
559
|
+
off.include?(Node.get(3)).should be_true
|
|
560
|
+
off.include?(Node.get(4)).should be_true
|
|
561
|
+
off.include?(Node.get(5)).should be_true
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
it 'should allow to create root nodes' do
|
|
565
|
+
r = Node.create!(:name => 'newroot')
|
|
566
|
+
r.parent.should be_nil
|
|
567
|
+
r.children.size.should == 0
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
it 'should properly delete nodes' do
|
|
571
|
+
r1 = Node.get 1
|
|
572
|
+
|
|
573
|
+
r1.children.size.should == 3
|
|
574
|
+
r1.children.delete(Node.get(4))
|
|
575
|
+
r1.save
|
|
576
|
+
Node.get(4).parent.should be_nil
|
|
577
|
+
r1.children.size.should == 2
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
describe 'through-associations' do
|
|
582
|
+
before :all do
|
|
583
|
+
repository(ADAPTER) do
|
|
584
|
+
module Sweets
|
|
585
|
+
class Shop
|
|
586
|
+
include DataMapper::Resource
|
|
587
|
+
def self.default_repository_name
|
|
588
|
+
ADAPTER
|
|
589
|
+
end
|
|
590
|
+
property :id, Serial
|
|
591
|
+
property :name, String
|
|
592
|
+
has n, :cakes, :class_name => 'Sweets::Cake' # has n
|
|
593
|
+
has n, :recipes, :through => :cakes, :class_name => 'Sweets::Recipe' # has n => has 1
|
|
594
|
+
has n, :ingredients, :through => :cakes, :class_name => 'Sweets::Ingredient' # has n => has 1 => has n
|
|
595
|
+
has n, :creators, :through => :cakes, :class_name => 'Sweets::Creator' # has n => has 1 => has 1
|
|
596
|
+
has n, :slices, :through => :cakes, :class_name => 'Sweets::Slice' # has n => has n
|
|
597
|
+
has n, :bites, :through => :cakes, :class_name => 'Sweets::Bite' # has n => has n => has n
|
|
598
|
+
has n, :shapes, :through => :cakes, :class_name => 'Sweets::Shape' # has n => has n => has 1
|
|
599
|
+
has n, :customers, :through => :cakes, :class_name => 'Sweets::Customer' # has n => belongs_to (pending)
|
|
600
|
+
has 1, :shop_owner, :class_name => 'Sweets::ShopOwner' # has 1
|
|
601
|
+
has 1, :wife, :through => :shop_owner, :class_name => 'Sweets::Wife' # has 1 => has 1
|
|
602
|
+
has 1, :ring, :through => :shop_owner, :class_name => 'Sweets::Ring' # has 1 => has 1 => has 1
|
|
603
|
+
has n, :coats, :through => :shop_owner, :class_name => 'Sweets::Coat' # has 1 => has 1 => has n
|
|
604
|
+
has n, :children, :through => :shop_owner, :class_name => 'Sweets::Child' # has 1 => has n
|
|
605
|
+
has n, :toys, :through => :shop_owner, :class_name => 'Sweets::Toy' # has 1 => has n => has n
|
|
606
|
+
has n, :boogers, :through => :shop_owner, :class_name => 'Sweets::Booger' # has 1 => has n => has 1
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
class ShopOwner
|
|
610
|
+
include DataMapper::Resource
|
|
611
|
+
def self.default_repository_name
|
|
612
|
+
ADAPTER
|
|
613
|
+
end
|
|
614
|
+
property :id, Serial
|
|
615
|
+
property :name, String
|
|
616
|
+
belongs_to :shop, :class_name => 'Sweets::Shop'
|
|
617
|
+
has 1, :wife, :class_name => 'Sweets::Wife'
|
|
618
|
+
has n, :children, :class_name => 'Sweets::Child'
|
|
619
|
+
has n, :toys, :through => :children, :class_name => 'Sweets::Toy'
|
|
620
|
+
has n, :boogers, :through => :children, :class_name => 'Sweets::Booger'
|
|
621
|
+
has n, :coats, :through => :wife, :class_name => 'Sweets::Coat'
|
|
622
|
+
has 1, :ring, :through => :wife, :class_name => 'Sweets::Ring'
|
|
623
|
+
has n, :schools, :through => :children, :class_name => 'Sweets::School'
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
class Wife
|
|
627
|
+
include DataMapper::Resource
|
|
628
|
+
def self.default_repository_name
|
|
629
|
+
ADAPTER
|
|
630
|
+
end
|
|
631
|
+
property :id, Serial
|
|
632
|
+
property :name, String
|
|
633
|
+
belongs_to :shop_owner, :class_name => 'Sweets::ShopOwner'
|
|
634
|
+
has 1, :ring, :class_name => 'Sweets::Ring'
|
|
635
|
+
has n, :coats, :class_name => 'Sweets::Coat'
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
class Coat
|
|
639
|
+
include DataMapper::Resource
|
|
640
|
+
def self.default_repository_name
|
|
641
|
+
ADAPTER
|
|
642
|
+
end
|
|
643
|
+
property :id, Serial
|
|
644
|
+
property :name, String
|
|
645
|
+
belongs_to :wife, :class_name => 'Sweets::Wife'
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
class Ring
|
|
649
|
+
include DataMapper::Resource
|
|
650
|
+
def self.default_repository_name
|
|
651
|
+
ADAPTER
|
|
652
|
+
end
|
|
653
|
+
property :id, Serial
|
|
654
|
+
property :name, String
|
|
655
|
+
belongs_to :wife, :class_name => 'Sweets::Wife'
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
class Child
|
|
659
|
+
include DataMapper::Resource
|
|
660
|
+
def self.default_repository_name
|
|
661
|
+
ADAPTER
|
|
662
|
+
end
|
|
663
|
+
property :id, Serial
|
|
664
|
+
property :name, String
|
|
665
|
+
belongs_to :shop_owner, :class_name => 'Sweets::ShopOwner'
|
|
666
|
+
has n, :toys, :class_name => 'Sweets::Toy'
|
|
667
|
+
has 1, :booger, :class_name => 'Sweets::Booger'
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
class Booger
|
|
671
|
+
include DataMapper::Resource
|
|
672
|
+
def self.default_repository_name
|
|
673
|
+
ADAPTER
|
|
674
|
+
end
|
|
675
|
+
property :id, Serial
|
|
676
|
+
property :name, String
|
|
677
|
+
belongs_to :child, :class_name => 'Sweets::Child'
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
class Toy
|
|
681
|
+
include DataMapper::Resource
|
|
682
|
+
def self.default_repository_name
|
|
683
|
+
ADAPTER
|
|
684
|
+
end
|
|
685
|
+
property :id, Serial
|
|
686
|
+
property :name, String
|
|
687
|
+
belongs_to :child, :class_name => 'Sweets::Child'
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
class Cake
|
|
691
|
+
include DataMapper::Resource
|
|
692
|
+
def self.default_repository_name
|
|
693
|
+
ADAPTER
|
|
694
|
+
end
|
|
695
|
+
property :id, Serial
|
|
696
|
+
property :name, String
|
|
697
|
+
belongs_to :shop, :class_name => 'Sweets::Shop'
|
|
698
|
+
belongs_to :customer, :class_name => 'Sweets::Customer'
|
|
699
|
+
has n, :slices, :class_name => 'Sweets::Slice'
|
|
700
|
+
has n, :bites, :through => :slices, :class_name => 'Sweets::Bite'
|
|
701
|
+
has 1, :recipe, :class_name => 'Sweets::Recipe'
|
|
702
|
+
has n, :ingredients, :through => :recipe, :class_name => 'Sweets::Ingredient'
|
|
703
|
+
has 1, :creator, :through => :recipe, :class_name => 'Sweets::Creator'
|
|
704
|
+
has n, :shapes, :through => :slices, :class_name => 'Sweets::Shape'
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
class Recipe
|
|
708
|
+
include DataMapper::Resource
|
|
709
|
+
def self.default_repository_name
|
|
710
|
+
ADAPTER
|
|
711
|
+
end
|
|
712
|
+
property :id, Serial
|
|
713
|
+
property :name, String
|
|
714
|
+
belongs_to :cake, :class_name => 'Sweets::Cake'
|
|
715
|
+
has n, :ingredients, :class_name => 'Sweets::Ingredient'
|
|
716
|
+
has 1, :creator, :class_name => 'Sweets::Creator'
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
class Customer
|
|
720
|
+
include DataMapper::Resource
|
|
721
|
+
def self.default_repository_name
|
|
722
|
+
ADAPTER
|
|
723
|
+
end
|
|
724
|
+
property :id, Serial
|
|
725
|
+
property :name, String
|
|
726
|
+
has n, :cakes, :class_name => 'Sweets::Cake'
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
class Creator
|
|
730
|
+
include DataMapper::Resource
|
|
731
|
+
def self.default_repository_name
|
|
732
|
+
ADAPTER
|
|
733
|
+
end
|
|
734
|
+
property :id, Serial
|
|
735
|
+
property :name, String
|
|
736
|
+
belongs_to :recipe, :class_name => 'Sweets::Recipe'
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
class Ingredient
|
|
740
|
+
include DataMapper::Resource
|
|
741
|
+
def self.default_repository_name
|
|
742
|
+
ADAPTER
|
|
743
|
+
end
|
|
744
|
+
property :id, Serial
|
|
745
|
+
property :name, String
|
|
746
|
+
belongs_to :recipe, :class_name => 'Sweets::Recipe'
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
class Slice
|
|
750
|
+
include DataMapper::Resource
|
|
751
|
+
def self.default_repository_name
|
|
752
|
+
ADAPTER
|
|
753
|
+
end
|
|
754
|
+
property :id, Serial
|
|
755
|
+
property :size, Integer
|
|
756
|
+
belongs_to :cake, :class_name => 'Sweets::Cake'
|
|
757
|
+
has n, :bites, :class_name => 'Sweets::Bite'
|
|
758
|
+
has 1, :shape, :class_name => 'Sweets::Shape'
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
class Shape
|
|
762
|
+
include DataMapper::Resource
|
|
763
|
+
def self.default_repository_name
|
|
764
|
+
ADAPTER
|
|
765
|
+
end
|
|
766
|
+
property :id, Serial
|
|
767
|
+
property :name, String
|
|
768
|
+
belongs_to :slice, :class_name => 'Sweets::Slice'
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
class Bite
|
|
772
|
+
include DataMapper::Resource
|
|
773
|
+
def self.default_repository_name
|
|
774
|
+
ADAPTER
|
|
775
|
+
end
|
|
776
|
+
property :id, Serial
|
|
777
|
+
property :name, String
|
|
778
|
+
belongs_to :slice, :class_name => 'Sweets::Slice'
|
|
779
|
+
end
|
|
780
|
+
|
|
781
|
+
DataMapper::Resource.descendants.each do |descendant|
|
|
782
|
+
descendant.auto_migrate!(ADAPTER) if descendant.name =~ /^Sweets::/
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
betsys = Shop.new(:name => "Betsy's")
|
|
786
|
+
betsys.save
|
|
787
|
+
|
|
788
|
+
#
|
|
789
|
+
# has n
|
|
790
|
+
#
|
|
791
|
+
|
|
792
|
+
german_chocolate = Cake.new(:name => 'German Chocolate')
|
|
793
|
+
betsys.cakes << german_chocolate
|
|
794
|
+
german_chocolate.save
|
|
795
|
+
short_cake = Cake.new(:name => 'Short Cake')
|
|
796
|
+
betsys.cakes << short_cake
|
|
797
|
+
short_cake.save
|
|
798
|
+
|
|
799
|
+
# has n => belongs_to
|
|
800
|
+
|
|
801
|
+
old_customer = Customer.new(:name => 'John Johnsen')
|
|
802
|
+
old_customer.cakes << german_chocolate
|
|
803
|
+
old_customer.cakes << short_cake
|
|
804
|
+
german_chocolate.save
|
|
805
|
+
short_cake.save
|
|
806
|
+
old_customer.save
|
|
807
|
+
|
|
808
|
+
# has n => has 1
|
|
809
|
+
|
|
810
|
+
schwarzwald = Recipe.new(:name => 'Schwarzwald Cake')
|
|
811
|
+
schwarzwald.save
|
|
812
|
+
german_chocolate.recipe = schwarzwald
|
|
813
|
+
german_chocolate.save
|
|
814
|
+
shortys_special = Recipe.new(:name => "Shorty's Special")
|
|
815
|
+
shortys_special.save
|
|
816
|
+
short_cake.recipe = shortys_special
|
|
817
|
+
short_cake.save
|
|
818
|
+
|
|
819
|
+
# has n => has 1 => has 1
|
|
820
|
+
|
|
821
|
+
runar = Creator.new(:name => 'Runar')
|
|
822
|
+
schwarzwald.creator = runar
|
|
823
|
+
runar.save
|
|
824
|
+
berit = Creator.new(:name => 'Berit')
|
|
825
|
+
shortys_special.creator = berit
|
|
826
|
+
berit.save
|
|
827
|
+
|
|
828
|
+
# has n => has 1 => has n
|
|
829
|
+
|
|
830
|
+
4.times do |i| schwarzwald.ingredients << Ingredient.new(:name => "Secret ingredient nr #{i}") end
|
|
831
|
+
6.times do |i| shortys_special.ingredients << Ingredient.new(:name => "Well known ingredient nr #{i}") end
|
|
832
|
+
|
|
833
|
+
# has n => has n
|
|
834
|
+
|
|
835
|
+
10.times do |i| german_chocolate.slices << Slice.new(:size => i) end
|
|
836
|
+
5.times do |i| short_cake.slices << Slice.new(:size => i) end
|
|
837
|
+
german_chocolate.slices.size.should == 10
|
|
838
|
+
# has n => has n => has 1
|
|
839
|
+
|
|
840
|
+
german_chocolate.slices.each do |slice|
|
|
841
|
+
shape = Shape.new(:name => 'square')
|
|
842
|
+
slice.shape = shape
|
|
843
|
+
shape.save
|
|
844
|
+
end
|
|
845
|
+
short_cake.slices.each do |slice|
|
|
846
|
+
shape = Shape.new(:name => 'round')
|
|
847
|
+
slice.shape = shape
|
|
848
|
+
shape.save
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
# has n => has n => has n
|
|
852
|
+
german_chocolate.slices.each do |slice|
|
|
853
|
+
6.times do |i|
|
|
854
|
+
slice.bites << Bite.new(:name => "Big bite nr #{i}")
|
|
855
|
+
end
|
|
856
|
+
end
|
|
857
|
+
short_cake.slices.each do |slice|
|
|
858
|
+
3.times do |i|
|
|
859
|
+
slice.bites << Bite.new(:name => "Small bite nr #{i}")
|
|
860
|
+
end
|
|
861
|
+
end
|
|
862
|
+
|
|
863
|
+
#
|
|
864
|
+
# has 1
|
|
865
|
+
#
|
|
866
|
+
|
|
867
|
+
betsy = ShopOwner.new(:name => 'Betsy')
|
|
868
|
+
betsys.shop_owner = betsy
|
|
869
|
+
betsys.save
|
|
870
|
+
|
|
871
|
+
# has 1 => has 1
|
|
872
|
+
|
|
873
|
+
barry = Wife.new(:name => 'Barry')
|
|
874
|
+
betsy.wife = barry
|
|
875
|
+
barry.save
|
|
876
|
+
|
|
877
|
+
# has 1 => has 1 => has 1
|
|
878
|
+
|
|
879
|
+
golden = Ring.new(:name => 'golden')
|
|
880
|
+
barry.ring = golden
|
|
881
|
+
golden.save
|
|
882
|
+
|
|
883
|
+
# has 1 => has 1 => has n
|
|
884
|
+
|
|
885
|
+
3.times do |i|
|
|
886
|
+
barry.coats << Coat.new(:name => "Fancy coat nr #{i}")
|
|
887
|
+
end
|
|
888
|
+
barry.save
|
|
889
|
+
|
|
890
|
+
# has 1 => has n
|
|
891
|
+
|
|
892
|
+
5.times do |i|
|
|
893
|
+
betsy.children << Child.new(:name => "Snotling nr #{i}")
|
|
894
|
+
end
|
|
895
|
+
betsy.save
|
|
896
|
+
|
|
897
|
+
# has 1 => has n => has n
|
|
898
|
+
|
|
899
|
+
betsy.children.each do |child|
|
|
900
|
+
4.times do |i|
|
|
901
|
+
child.toys << Toy.new(:name => "Cheap toy nr #{i}")
|
|
902
|
+
end
|
|
903
|
+
child.save
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
# has 1 => has n => has 1
|
|
907
|
+
|
|
908
|
+
betsy.children.each do |child|
|
|
909
|
+
booger = Booger.new(:name => 'Nasty booger')
|
|
910
|
+
child.booger = booger
|
|
911
|
+
child.save
|
|
912
|
+
end
|
|
913
|
+
end
|
|
914
|
+
end
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
#
|
|
918
|
+
# has n
|
|
919
|
+
#
|
|
920
|
+
|
|
921
|
+
it 'should return the right children for has n => has n relationships' do
|
|
922
|
+
Sweets::Shop.first.slices.size.should == 15
|
|
923
|
+
10.times do |i|
|
|
924
|
+
Sweets::Shop.first.slices.select do |slice|
|
|
925
|
+
slice.cake == Sweets::Cake.first(:name => 'German Chocolate') && slice.size == i
|
|
926
|
+
end
|
|
927
|
+
end
|
|
928
|
+
end
|
|
929
|
+
|
|
930
|
+
it 'should return the right children for has n => has n => has 1' do
|
|
931
|
+
Sweets::Shop.first.shapes.size.should == 15
|
|
932
|
+
Sweets::Shop.first.shapes.select do |shape|
|
|
933
|
+
shape.name == 'square'
|
|
934
|
+
end.size.should == 10
|
|
935
|
+
Sweets::Shop.first.shapes.select do |shape|
|
|
936
|
+
shape.name == 'round'
|
|
937
|
+
end.size.should == 5
|
|
938
|
+
end
|
|
939
|
+
|
|
940
|
+
it 'should return the right children for has n => has n => has n' do
|
|
941
|
+
Sweets::Shop.first.bites.size.should == 75
|
|
942
|
+
Sweets::Shop.first.bites.select do |bite|
|
|
943
|
+
bite.slice.cake == Sweets::Cake.first(:name => 'German Chocolate')
|
|
944
|
+
end.size.should == 60
|
|
945
|
+
Sweets::Shop.first.bites.select do |bite|
|
|
946
|
+
bite.slice.cake == Sweets::Cake.first(:name => 'Short Cake')
|
|
947
|
+
end.size.should == 15
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
it 'should return the right children for has n => belongs_to relationships' do
|
|
951
|
+
Sweets::Customer.first.cakes.size.should == 2
|
|
952
|
+
customers = Sweets::Shop.first.customers.select do |customer|
|
|
953
|
+
customer.name == 'John Johnsen'
|
|
954
|
+
end
|
|
955
|
+
customers.size.should == 1
|
|
956
|
+
# another example can be found here: http://pastie.textmate.org/private/tt1hf1syfsytyxdgo4qxawfl
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
it 'should return the right children for has n => has 1 relationships' do
|
|
960
|
+
Sweets::Shop.first.recipes.size.should == 2
|
|
961
|
+
Sweets::Shop.first.recipes.select do |recipe|
|
|
962
|
+
recipe.name == 'Schwarzwald Cake'
|
|
963
|
+
end.size.should == 1
|
|
964
|
+
Sweets::Shop.first.recipes.select do |recipe|
|
|
965
|
+
recipe.name == "Shorty's Special"
|
|
966
|
+
end.size.should == 1
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
it 'should return the right children for has n => has 1 => has 1 relationships' do
|
|
970
|
+
Sweets::Shop.first.creators.size.should == 2
|
|
971
|
+
Sweets::Shop.first.creators.any? do |creator|
|
|
972
|
+
creator.name == 'Runar'
|
|
973
|
+
end.should == true
|
|
974
|
+
Sweets::Shop.first.creators.any? do |creator|
|
|
975
|
+
creator.name == 'Berit'
|
|
976
|
+
end.should == true
|
|
977
|
+
end
|
|
978
|
+
|
|
979
|
+
it 'should return the right children for has n => has 1 => has n relationships' do
|
|
980
|
+
Sweets::Shop.first.ingredients.size.should == 10
|
|
981
|
+
4.times do |i|
|
|
982
|
+
Sweets::Shop.first.ingredients.any? do |ingredient|
|
|
983
|
+
ingredient.name == "Secret ingredient nr #{i}" && ingredient.recipe.cake == Sweets::Cake.first(:name => 'German Chocolate')
|
|
984
|
+
end.should == true
|
|
985
|
+
end
|
|
986
|
+
6.times do |i|
|
|
987
|
+
Sweets::Shop.first.ingredients.any? do |ingredient|
|
|
988
|
+
ingredient.name == "Well known ingredient nr #{i}" && ingredient.recipe.cake == Sweets::Cake.first(:name => 'Short Cake')
|
|
989
|
+
end.should == true
|
|
990
|
+
end
|
|
991
|
+
end
|
|
992
|
+
|
|
993
|
+
#
|
|
994
|
+
# has 1
|
|
995
|
+
#
|
|
996
|
+
|
|
997
|
+
it 'should return the right children for has 1 => has 1 relationships' do
|
|
998
|
+
Sweets::Shop.first.wife.should == Sweets::Wife.first
|
|
999
|
+
end
|
|
1000
|
+
|
|
1001
|
+
it 'should return the right children for has 1 => has 1 => has 1 relationships' do
|
|
1002
|
+
Sweets::Shop.first.ring.should == Sweets::Ring.first
|
|
1003
|
+
end
|
|
1004
|
+
|
|
1005
|
+
it 'should return the right children for has 1 => has 1 => has n relationships' do
|
|
1006
|
+
Sweets::Shop.first.coats.size.should == 3
|
|
1007
|
+
3.times do |i|
|
|
1008
|
+
Sweets::Shop.first.coats.any? do |coat|
|
|
1009
|
+
coat.name == "Fancy coat nr #{i}"
|
|
1010
|
+
end.should == true
|
|
1011
|
+
end
|
|
1012
|
+
end
|
|
1013
|
+
|
|
1014
|
+
it 'should return the right children for has 1 => has n relationships' do
|
|
1015
|
+
Sweets::Shop.first.children.size.should == 5
|
|
1016
|
+
5.times do |i|
|
|
1017
|
+
Sweets::Shop.first.children.any? do |child|
|
|
1018
|
+
child.name == "Snotling nr #{i}"
|
|
1019
|
+
end.should == true
|
|
1020
|
+
end
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
it 'should return the right children for has 1 => has n => has 1 relationships' do
|
|
1024
|
+
Sweets::Shop.first.boogers.size.should == 5
|
|
1025
|
+
Sweets::Shop.first.boogers.inject(Set.new) do |sum, booger|
|
|
1026
|
+
sum << booger.child_id
|
|
1027
|
+
end.size.should == 5
|
|
1028
|
+
end
|
|
1029
|
+
|
|
1030
|
+
it 'should return the right children for has 1 => has n => has n relationships' do
|
|
1031
|
+
Sweets::Shop.first.toys.size.should == 20
|
|
1032
|
+
5.times do |child_nr|
|
|
1033
|
+
4.times do |toy_nr|
|
|
1034
|
+
Sweets::Shop.first.toys.any? do |toy|
|
|
1035
|
+
toy.name == "Cheap toy nr #{toy_nr}" && toy.child = Sweets::Child.first(:name => "Snotling nr #{child_nr}")
|
|
1036
|
+
end.should == true
|
|
1037
|
+
end
|
|
1038
|
+
end
|
|
1039
|
+
end
|
|
1040
|
+
|
|
1041
|
+
#
|
|
1042
|
+
# misc
|
|
1043
|
+
#
|
|
1044
|
+
|
|
1045
|
+
it 'should raise exception if you try to change it' do
|
|
1046
|
+
lambda do
|
|
1047
|
+
Sweets::Shop.first.wife = Sweets::Wife.new(:name => 'Larry')
|
|
1048
|
+
end.should raise_error(DataMapper::Associations::ImmutableAssociationError)
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
it 'should be reloaded when calling Resource#reload' do
|
|
1052
|
+
betsys = Sweets::Shop.first(:name => "Betsy's")
|
|
1053
|
+
betsys.send(:customers_association).should_receive(:reload).once
|
|
1054
|
+
lambda { betsys.reload }.should_not raise_error
|
|
1055
|
+
end
|
|
1056
|
+
|
|
1057
|
+
end
|
|
1058
|
+
|
|
1059
|
+
if false # Many to many not yet implemented
|
|
1060
|
+
describe "many to many associations" do
|
|
1061
|
+
before(:all) do
|
|
1062
|
+
class RightItem
|
|
1063
|
+
include DataMapper::Resource
|
|
1064
|
+
|
|
1065
|
+
def self.default_repository_name
|
|
1066
|
+
ADAPTER
|
|
1067
|
+
end
|
|
1068
|
+
|
|
1069
|
+
property :id, Serial
|
|
1070
|
+
property :name, String
|
|
1071
|
+
|
|
1072
|
+
has n..n, :left_items
|
|
1073
|
+
end
|
|
1074
|
+
|
|
1075
|
+
class LeftItem
|
|
1076
|
+
include DataMapper::Resource
|
|
1077
|
+
|
|
1078
|
+
def self.default_repository_name
|
|
1079
|
+
ADAPTER
|
|
1080
|
+
end
|
|
1081
|
+
|
|
1082
|
+
property :id, Serial
|
|
1083
|
+
property :name, String
|
|
1084
|
+
|
|
1085
|
+
has n..n, :right_items
|
|
1086
|
+
end
|
|
1087
|
+
|
|
1088
|
+
RightItem.auto_migrate!
|
|
1089
|
+
LeftItem.auto_migrate!
|
|
1090
|
+
end
|
|
1091
|
+
|
|
1092
|
+
def create_item_pair(number)
|
|
1093
|
+
@ri = RightItem.new(:name => "ri#{number}")
|
|
1094
|
+
@li = LeftItem.new(:name => "li#{number}")
|
|
1095
|
+
end
|
|
1096
|
+
|
|
1097
|
+
it "should add to the assocaiton from the left" do
|
|
1098
|
+
pending "Waiting on Many To Many to be implemented"
|
|
1099
|
+
create_item_pair "0000"
|
|
1100
|
+
@ri.save; @li.save
|
|
1101
|
+
@ri.should_not be_new_record
|
|
1102
|
+
@li.should_not be_new_record
|
|
1103
|
+
|
|
1104
|
+
@li.right_items << @ri
|
|
1105
|
+
@li.right_items.should include(@ri)
|
|
1106
|
+
@li.reload
|
|
1107
|
+
@ri.reload
|
|
1108
|
+
@li.right_items.should include(@ri)
|
|
1109
|
+
end
|
|
1110
|
+
|
|
1111
|
+
it "should add to the association from the right" do
|
|
1112
|
+
create_item_pair "0010"
|
|
1113
|
+
@ri.save; @li.save
|
|
1114
|
+
@ri.should_not be_new_record
|
|
1115
|
+
@li.should_not be_new_record
|
|
1116
|
+
|
|
1117
|
+
@ri.left_items << @li
|
|
1118
|
+
@ri.left_items.should include(@li)
|
|
1119
|
+
@li.reload
|
|
1120
|
+
@ri.reload
|
|
1121
|
+
@ri.left_items.should include(@li)
|
|
1122
|
+
end
|
|
1123
|
+
|
|
1124
|
+
it "should load the assocaited collection from the either side" do
|
|
1125
|
+
pending "Waiting on Many To Many to be implemented"
|
|
1126
|
+
create_item_pair "0020"
|
|
1127
|
+
@ri.save; @li.save
|
|
1128
|
+
@ri.left_items << @li
|
|
1129
|
+
@ri.reload; @li.reload
|
|
1130
|
+
|
|
1131
|
+
@ri.left_items.should include(@li)
|
|
1132
|
+
@li.right_items.should include(@ri)
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
it "should load the assocatied collection from the right" do
|
|
1136
|
+
pending "Waiting on Many To Many to be implemented"
|
|
1137
|
+
create_item_pair "0030"
|
|
1138
|
+
@ri.save; @li.save
|
|
1139
|
+
@li.right_items << @li
|
|
1140
|
+
@ri.reload; @li.reload
|
|
1141
|
+
|
|
1142
|
+
@ri.left_items.should include(@li)
|
|
1143
|
+
@li.right_items.should include(@ri)
|
|
1144
|
+
|
|
1145
|
+
end
|
|
1146
|
+
|
|
1147
|
+
it "should save the left side of the association if new record" do
|
|
1148
|
+
pending "Waiting on Many To Many to be implemented"
|
|
1149
|
+
create_item_pair "0040"
|
|
1150
|
+
@ri.save
|
|
1151
|
+
@li.should be_new_record
|
|
1152
|
+
@ri.left_items << @li
|
|
1153
|
+
@li.should_not be_new_record
|
|
1154
|
+
end
|
|
1155
|
+
|
|
1156
|
+
it "should save the right side of the assocaition if new record" do
|
|
1157
|
+
pending "Waiting on Many To Many to be implemented"
|
|
1158
|
+
create_item_pair "0050"
|
|
1159
|
+
@li.save
|
|
1160
|
+
@ri.should be_new_record
|
|
1161
|
+
@li.right_items << @ri
|
|
1162
|
+
@ri.should_not be_new_record
|
|
1163
|
+
end
|
|
1164
|
+
|
|
1165
|
+
it "should save both side of the assocaition if new record" do
|
|
1166
|
+
pending "Waiting on Many To Many to be implemented"
|
|
1167
|
+
create_item_pair "0060"
|
|
1168
|
+
@li.should be_new_record
|
|
1169
|
+
@ri.should be_new_record
|
|
1170
|
+
@ri.left_items << @li
|
|
1171
|
+
@ri.should_not be_new_record
|
|
1172
|
+
@li.should_not be_new_record
|
|
1173
|
+
end
|
|
1174
|
+
|
|
1175
|
+
it "should remove an item from the left collection without destroying the item" do
|
|
1176
|
+
pending "Waiting on Many To Many to be implemented"
|
|
1177
|
+
create_item_pair "0070"
|
|
1178
|
+
@li.save; @ri.save
|
|
1179
|
+
@ri.left_items << @li
|
|
1180
|
+
@ri.reload; @li.reload
|
|
1181
|
+
@ri.left_items.should include(@li)
|
|
1182
|
+
@ri.left_items.delete(@li)
|
|
1183
|
+
@ri.left_items.should_not include(@li)
|
|
1184
|
+
@li.reload
|
|
1185
|
+
LeftItem.get(@li.id).should_not be_nil
|
|
1186
|
+
end
|
|
1187
|
+
|
|
1188
|
+
it "should remove an item from the right collection without destroying the item" do
|
|
1189
|
+
pending "Waiting on Many To Many to be implemented"
|
|
1190
|
+
create_item_pair "0080"
|
|
1191
|
+
@li.save; @ri.save
|
|
1192
|
+
@li.right_items << @ri
|
|
1193
|
+
@li.reload; @ri.reload
|
|
1194
|
+
@li.right_items.should include(@ri)
|
|
1195
|
+
@li.right_items.delete(@ri)
|
|
1196
|
+
@li.right_items.should_not include(@ri)
|
|
1197
|
+
@ri.reload
|
|
1198
|
+
RightItem.get(@ri.id).should_not be_nil
|
|
1199
|
+
end
|
|
1200
|
+
|
|
1201
|
+
it "should remove the item from the collection when an item is deleted" do
|
|
1202
|
+
pending "Waiting on Many To Many to be implemented"
|
|
1203
|
+
create_item_pair "0090"
|
|
1204
|
+
@li.save; @ri.save
|
|
1205
|
+
@ri.left_items << @li
|
|
1206
|
+
@ri.reload; @li.reload
|
|
1207
|
+
@ri.left_items.should include(@li)
|
|
1208
|
+
@li.destroy
|
|
1209
|
+
@ri.reload
|
|
1210
|
+
@ri.left_items.should_not include(@li)
|
|
1211
|
+
end
|
|
1212
|
+
end
|
|
1213
|
+
end
|
|
1214
|
+
end
|
|
1215
|
+
end
|