virtualbox 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/.gitignore +3 -3
  2. data/Gemfile +8 -8
  3. data/Rakefile +2 -0
  4. data/Readme.md +11 -2
  5. data/VERSION +1 -1
  6. data/docs/WhatsNew.md +40 -24
  7. data/lib/virtualbox.rb +9 -0
  8. data/lib/virtualbox/abstract_model.rb +80 -10
  9. data/lib/virtualbox/abstract_model/attributable.rb +61 -1
  10. data/lib/virtualbox/abstract_model/relatable.rb +88 -6
  11. data/lib/virtualbox/attached_device.rb +18 -10
  12. data/lib/virtualbox/command.rb +13 -0
  13. data/lib/virtualbox/dvd.rb +35 -0
  14. data/lib/virtualbox/exceptions.rb +1 -0
  15. data/lib/virtualbox/extra_data.rb +10 -21
  16. data/lib/virtualbox/global.rb +126 -0
  17. data/lib/virtualbox/hard_drive.rb +33 -9
  18. data/lib/virtualbox/image.rb +2 -3
  19. data/lib/virtualbox/media.rb +19 -0
  20. data/lib/virtualbox/nic.rb +28 -67
  21. data/lib/virtualbox/shared_folder.rb +7 -12
  22. data/lib/virtualbox/storage_controller.rb +8 -36
  23. data/lib/virtualbox/system_property.rb +55 -0
  24. data/lib/virtualbox/usb.rb +72 -0
  25. data/lib/virtualbox/vm.rb +126 -25
  26. data/test/test_helper.rb +118 -12
  27. data/test/virtualbox/abstract_model/attributable_test.rb +55 -5
  28. data/test/virtualbox/abstract_model/relatable_test.rb +66 -4
  29. data/test/virtualbox/abstract_model_test.rb +140 -8
  30. data/test/virtualbox/attached_device_test.rb +10 -7
  31. data/test/virtualbox/command_test.rb +13 -0
  32. data/test/virtualbox/dvd_test.rb +50 -28
  33. data/test/virtualbox/extra_data_test.rb +11 -51
  34. data/test/virtualbox/global_test.rb +78 -0
  35. data/test/virtualbox/hard_drive_test.rb +34 -57
  36. data/test/virtualbox/image_test.rb +0 -5
  37. data/test/virtualbox/nic_test.rb +11 -64
  38. data/test/virtualbox/shared_folder_test.rb +5 -5
  39. data/test/virtualbox/storage_controller_test.rb +15 -30
  40. data/test/virtualbox/system_property_test.rb +71 -0
  41. data/test/virtualbox/usb_test.rb +35 -0
  42. data/test/virtualbox/vm_test.rb +62 -121
  43. data/virtualbox.gemspec +15 -2
  44. metadata +23 -4
data/.gitignore CHANGED
@@ -1,6 +1,6 @@
1
- bin/*
2
- vendor/gems/*
3
1
  doc/*
4
2
  .yardoc/*
5
3
  pkg/*
6
- test/coverage/*
4
+ test/coverage/*
5
+ .bundle/*
6
+ Gemfile.lock
data/Gemfile CHANGED
@@ -1,12 +1,12 @@
1
- # Gems required for testing only. To install run
2
- # gem bundle test
3
- only :test do
1
+ source :gemcutter
2
+
3
+ # External Dependencies
4
+ gem "nokogiri", "1.4.1"
5
+
6
+ # Gems required for testing only.
7
+ group :test do
4
8
  gem "contest", ">= 0.1.2"
5
9
  gem "mocha"
6
10
  gem "ruby-debug", ">= 0.10.3" if RUBY_VERSION < '1.9'
7
11
  gem "ruby-debug19", ">= 0.11.6" if RUBY_VERSION >= '1.9'
8
- end
9
-
10
- # Makes sure that our code doesn't request gems outside
11
- # of our dependency list.
12
- disable_system_gems
12
+ end
data/Rakefile CHANGED
@@ -8,6 +8,8 @@ begin
8
8
  gemspec.homepage = "http://github.com/mitchellh/virtualbox"
9
9
  gemspec.authors = ["Mitchell Hashimoto"]
10
10
  gemspec.executables = []
11
+
12
+ gemspec.add_dependency('nokogiri', '>= 1.4.1')
11
13
  end
12
14
  Jeweler::GemcutterTasks.new
13
15
  rescue LoadError
data/Readme.md CHANGED
@@ -69,10 +69,19 @@ Please use the [issue tracker](https://github.com/mitchellh/virtualbox/issues).
69
69
  ## Contributing
70
70
 
71
71
  If you'd like to contribute to VirtualBox, the first step to developing is to
72
- clone this repo, get [wycat's bundler](http://github.com/wycats/bundler) if you
72
+ clone this repo, get [bundler](http://github.com/carlhuda/bundler) if you
73
73
  don't have it already, and do the following:
74
74
 
75
- gem bundle test
75
+ bundle install
76
+ bundle lock
76
77
  rake
77
78
 
78
79
  This will run the test suite, which should come back all green! Then you're good to go!
80
+
81
+ ## Special Thanks
82
+
83
+ These folks went above and beyond with contributions to the virtualbox gem, and
84
+ for that, I have to say "thanks!"
85
+
86
+ * [Kieran Pilkington](http://github.com/KieranP)
87
+ * [Aleksey Palazhchenko](http://github.com/AlekSi)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.3
1
+ 0.5.0
data/docs/WhatsNew.md CHANGED
@@ -1,34 +1,50 @@
1
- # What's New in 0.4.x?
1
+ # What's New in 0.5.x?
2
2
 
3
- ## "Extra Data" on VMs / Global
3
+ ## HUGE Speed Boost! Very few system calls!
4
4
 
5
- Extra data is persistent key-value storage which is available as a way to store any information
6
- wanted. VirtualBox uses it for storing statistics and settings. You can use it for anything!
7
- Setting extra data on virtual machines is now as easy as a ruby hash:
5
+ Most of the data retrieved by the virtualbox library now comes via XML parsing, rather
6
+ than making calls to `VBoxManage`. This results in a drastic speedup. The few relationships or
7
+ attributes which require a system call are typically _lazy loaded_ (covered below), so they
8
+ don't incur a performance penalty unless they're used.
8
9
 
9
- vm = VirtualBox::VM.find("FooVM")
10
- vm.extra_data["i_was_here"] = "yes!"
11
- vm.save
10
+ The one caveat is that you now need to set the path to the global VirtualBox configuration
11
+ XML. The virtualbox library will do its best to guess this path based on the operating
12
+ system, but this is hardly foolproof. To set the virtualbox config path, it is a simple
13
+ one-liner:
12
14
 
13
- Read more about extra data {VirtualBox::ExtraData here}.
15
+ # Remember, this won't be necessary MOST of the time
16
+ VirtualBox::Global.vboxconfig = "~/path/to/VirtualBox.xml"
14
17
 
15
- ## Port Forwarding
18
+ ## Lazy Loading of Attributes and Relationships
16
19
 
17
- If a VM is using NAT for its network, the host machine can't access any outward facing
18
- services of the guest (for example: a web host, ftp server, etc.). Port forwarding is
19
- one way to facilitate this need. Port forwarding is straight forward to setup:
20
+ Although still not widely used (will be in future patch releases), some attributes and
21
+ relationships are now _lazy loaded_. This means that since they're probably expensive
22
+ to load (many system calls, heavy parsing, etc.) they aren't loaded initially. Instead,
23
+ they are only loaded if they're used. This means that you don't incur the penalty cost
24
+ of loading them unless you use it! Fantastic!
20
25
 
21
- vm = VirtualBox::VM.find("FooVM")
22
- port = VirtualBox::ForwardedPort.new
23
- port.name = "http"
24
- port.guestport = 80
25
- port.hostport = 8080
26
- vm.forwarded_ports << port
27
- vm.save
26
+ There is no real "example code" for this feature since to the casual user, it happens
27
+ transparently in the background and generally "just works." If you're _really_ curious,
28
+ then feel free to check out any class which derives from {VirtualBox::AbstractModel}
29
+ and any attribute or relationship with the `:lazy => true` option is lazy loaded!
28
30
 
29
- Read more about port forwarding {VirtualBox::ForwardedPort here}.
31
+ ## System Properties
30
32
 
31
- ## More Ruby Versions Supported!
33
+ A small but meaningful update is the ability to view the system properties for the
34
+ host system which VirtualBox is running. This is done via the {VirtualBox::SystemProperty}
35
+ class, which is simply a `Hash`. System properties are immutable properties defined
36
+ by the host system, which typically are limits imposed upon VirtualBox, such as
37
+ maximum RAM size or default path to machine files. Retrieving the system properties
38
+ is quite easy:
32
39
 
33
- Previously, virtualbox only supported 1.8.7. It now supports 1.8.6 and 1.9.x thanks
34
- to AlekSi.
40
+ properties = VirtualBox::SystemProperty.all
41
+ properties.each do |key, value|
42
+ puts "#{key} = #{value}"
43
+ end
44
+
45
+ ## USB Device Relationship on VMs
46
+
47
+ Previously, {VirtualBox::VM VM} object would only be able to tell you if there
48
+ were USB devices enabled or not. Now, `usbs` is a full-fledged relationship
49
+ on VM. This relationship is access just like any other. For more information
50
+ view the {VirtualBox::USB USB} class.
data/lib/virtualbox.rb CHANGED
@@ -1,4 +1,9 @@
1
1
  $:.unshift(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ # External Dependencies
4
+ require 'nokogiri'
5
+
6
+ # Internal Dependencies
2
7
  require 'virtualbox/exceptions'
3
8
  require 'virtualbox/command'
4
9
  require 'virtualbox/abstract_model'
@@ -10,9 +15,13 @@ require 'virtualbox/extra_data'
10
15
  require 'virtualbox/forwarded_port'
11
16
  require 'virtualbox/hard_drive'
12
17
  require 'virtualbox/nic'
18
+ require 'virtualbox/usb'
13
19
  require 'virtualbox/shared_folder'
14
20
  require 'virtualbox/storage_controller'
15
21
  require 'virtualbox/vm'
22
+ require 'virtualbox/media'
23
+ require 'virtualbox/global'
24
+ require 'virtualbox/system_property'
16
25
 
17
26
  module VirtualBox
18
27
  class <<self
@@ -14,6 +14,30 @@ module VirtualBox
14
14
  include Relatable
15
15
  include Validatable
16
16
 
17
+ class <<self
18
+ # Returns whether or not the class should be reloaded.
19
+ #
20
+ # @return [Boolean]
21
+ def reload?
22
+ !!@_reload
23
+ end
24
+
25
+ def reload!
26
+ @_reload = true
27
+ end
28
+
29
+ def reloaded!
30
+ @_reload = false
31
+ end
32
+ end
33
+
34
+ # Signals to the class that it should be reloaded. This simply toggles
35
+ # a boolean value to true. It is up to the subclass to implement functionality
36
+ # around it. See {reload?}
37
+ def reload!
38
+ self.class.reload!
39
+ end
40
+
17
41
  # Returns a boolean denoting if the record is new or existing. This
18
42
  # method is provided for subclasses to use to differentiate between
19
43
  # creating a new object or saving an existing one. An example of this
@@ -78,7 +102,12 @@ module VirtualBox
78
102
  save_attribute(key, values[1], *args)
79
103
  end
80
104
 
81
- save_relationships(*args)
105
+ # Go through and only save the loaded relationships, since
106
+ # only those would be modified.
107
+ self.class.relationships.each do |name, options|
108
+ next if lazy_relationship?(name) && !loaded_relationship?(name)
109
+ save_relationship(name, *args)
110
+ end
82
111
 
83
112
  # No longer a new record
84
113
  @new_record = false
@@ -93,6 +122,20 @@ module VirtualBox
93
122
  clear_dirty!(key)
94
123
  end
95
124
 
125
+ # Overriding {Attributable#lazy_attribute?} to always return false for
126
+ # new records, since new records shouldn't load lazy data.
127
+ def lazy_attribute?(*args)
128
+ return false if new_record?
129
+ super
130
+ end
131
+
132
+ # Overriding {Relatable#lazy_relationship?} to always return false for
133
+ # new records, since new records shouldn't load lazy data.
134
+ def lazy_relationship?(*args)
135
+ return false if new_record?
136
+ super
137
+ end
138
+
96
139
  # Sets the initial attributes from a hash. This method is meant to be used
97
140
  # once to initially setup the attributes. It is **not a mass-assignment**
98
141
  # method for updating attributes.
@@ -108,21 +151,39 @@ module VirtualBox
108
151
  #
109
152
  # Calling this method will also cause the model to assume that it is not
110
153
  # a new record (see {#new_record?}).
111
- def populate_attributes(attribs)
112
- # No longer a new record
113
- @new_record = false
114
-
154
+ def populate_attributes(attribs, opts={})
115
155
  ignore_dirty do
116
- super
156
+ super(attribs)
117
157
 
118
- populate_relationships(attribs)
158
+ populate_relationships(attribs) unless opts[:ignore_relationships]
119
159
  end
160
+
161
+ # No longer a new record
162
+ existing_record!
163
+ end
164
+
165
+ # Loads and populates the relationships with the given data. This method
166
+ # is meant to be used once to initially setup the relatoinships.
167
+ #
168
+ # This methods does **not** affect dirtiness, but also does not clear it.
169
+ #
170
+ # Calling this method will also cuase the model to assume that it is not
171
+ # a new record (see {#new_record?})
172
+ def populate_relationships(data)
173
+ existing_record!
174
+ ignore_dirty { super }
175
+ end
176
+
177
+ # Populates a single relationship with the given data.
178
+ def populate_relationship(name, data)
179
+ existing_record!
180
+ ignore_dirty { super }
120
181
  end
121
182
 
122
183
  # Overwrites {Attributable#write_attribute} to set the dirty state of
123
184
  # the written attribute. See {Dirty#set_dirty!} as well.
124
185
  def write_attribute(name, value)
125
- set_dirty!(name, read_attribute(name), value)
186
+ set_dirty!(name, read_attribute(name), value) unless lazy_attribute?(name) && !loaded_attribute?(name)
126
187
  super
127
188
  end
128
189
 
@@ -148,12 +209,21 @@ module VirtualBox
148
209
  # Creates a human-readable format for this model. This method overrides the
149
210
  # default `#<class>` syntax since this doesn't work well for AbstractModels.
150
211
  # Instead, it abbreviates it, instead showing all the attributes and their
151
- # values, and `...` for relationships.
212
+ # values, and `...` for relationships. For attributes which are themselves
213
+ # AbstractModels, it shows the class name to avoid extremely verbose inspections
214
+ # and infinite loops.
152
215
  def inspect
153
216
  values = []
154
217
 
155
218
  self.class.attributes.each do |name, options|
156
- values.push("#{name.inspect}=#{read_attribute(name).inspect}")
219
+ value = read_attribute(name)
220
+ value = if value.is_a?(AbstractModel)
221
+ "#<#{value.class.name}>"
222
+ else
223
+ value.inspect
224
+ end
225
+
226
+ values.push("#{name.inspect}=#{value}")
157
227
  end
158
228
 
159
229
  self.class.relationships.each do |name, options|
@@ -73,6 +73,50 @@ module VirtualBox
73
73
  # puts path # => "Home"
74
74
  # end
75
75
  #
76
+ # ## Lazy Loading Attributes
77
+ #
78
+ # While most attributes are fairly trivial to calculate and populate, sometimes
79
+ # attributes may have an expensive cost to populate, and are generally not worth
80
+ # populating unless a user of the class requests that attribute. This is known as
81
+ # _lazy loading_ the attributes. This is possibly by specifying the `:lazy` option
82
+ # on the attribute. In this case, the first time (and _only_ the first time) the
83
+ # attribute is requested, `load_attribute` will be called with the name of the
84
+ # attribute as the parameter. This method is then expected to call `write_attribute`
85
+ # on that attribute to give it a value.
86
+ #
87
+ # class ExpensiveAttributeModel
88
+ # include VirtualBox::AbstractModel::Attributable
89
+ # attribute :expensive_attribute, :lazy => true
90
+ #
91
+ # def load_attribute(name)
92
+ # if name == :expensive_attribute
93
+ # write_attribute(name, perform_expensive_calculation)
94
+ # end
95
+ # end
96
+ # end
97
+ #
98
+ # Using the above definition, we could use the class like so:
99
+ #
100
+ # # Initializing is fast, since no attribute population is done
101
+ # model = ExpensiveAttributeModel.new
102
+ #
103
+ # # But this is slow, since it has to calculate.
104
+ # puts model.expensive_attribute
105
+ #
106
+ # # But ONLY THE FIRST TIME. This time is FAST!
107
+ # puts model.expensive_attribute
108
+ #
109
+ # In addition to calling `load_attribute` on initial read, `write_attribute`
110
+ # when performed on a lazy loaded attribute will mark it as "loaded" so there
111
+ # will be no load called on the first request. Example, using the above class
112
+ # once again:
113
+ #
114
+ # model = ExpensiveAttributeModel.new
115
+ # model.write_attribute(:expensive_attribute, 42)
116
+ #
117
+ # # This is FAST, since "load_attribute" is not called
118
+ # puts model.expensive_attribute # => 42
119
+ #
76
120
  module Attributable
77
121
  def self.included(base)
78
122
  base.extend ClassMethods
@@ -146,7 +190,7 @@ module VirtualBox
146
190
  def populate_attributes(attribs)
147
191
  self.class.attributes.each do |key, options|
148
192
  value_key = options[:populate_key] || key
149
- write_attribute(key, attribs[value_key])
193
+ write_attribute(key, attribs[value_key]) if attribs.has_key?(value_key)
150
194
  end
151
195
  end
152
196
 
@@ -165,6 +209,11 @@ module VirtualBox
165
209
  # if specified.
166
210
  def read_attribute(name)
167
211
  if has_attribute?(name)
212
+ if lazy_attribute?(name) && !loaded_attribute?(name)
213
+ # Load the lazy attribute
214
+ load_attribute(name.to_sym)
215
+ end
216
+
168
217
  attributes[name] || self.class.attributes[name][:default]
169
218
  end
170
219
  end
@@ -179,6 +228,17 @@ module VirtualBox
179
228
  self.class.attributes.has_key?(name.to_sym)
180
229
  end
181
230
 
231
+ # Returns boolean value denoting if an attribute is "lazy loaded"
232
+ def lazy_attribute?(name)
233
+ has_attribute?(name) && self.class.attributes[name.to_sym][:lazy]
234
+ end
235
+
236
+ # Returns boolean value denoting if an attribute has been loaded
237
+ # yet.
238
+ def loaded_attribute?(name)
239
+ attributes.has_key?(name)
240
+ end
241
+
182
242
  # Returns a boolean value denoting if an attribute is readonly.
183
243
  # This method also returns false for **nonexistent attributes**
184
244
  # so it should be used in conjunction with {#has_attribute?} if
@@ -78,6 +78,46 @@ module VirtualBox
78
78
  #
79
79
  # This is not a feature built-in to Relatable but figured it should be
80
80
  # mentioned here.
81
+ #
82
+ # # Lazy Relationships
83
+ #
84
+ # Often, relationships are pretty heavy things to load. Data may have to be
85
+ # retrieved, classes instantiated, etc. If a class has many relationships, or
86
+ # many relationships within many relationships, the time and memory required
87
+ # for relationships really begins to add up. To address this issue, _lazy relationships_
88
+ # are available. Lazy relationships defer loading their content until the
89
+ # last possible moment, or rather, when a user requests the data. By specifing
90
+ # the `:lazy => true` option to relationships, relationships will not be loaded
91
+ # immediately. Instead, when they're first requested, `load_relationship` will
92
+ # be called on the model, with the name of the relationship given as a
93
+ # parameter. It is up to this method to call {#populate_relationship} at some
94
+ # point with the data to setup the relationship. An example follows:
95
+ #
96
+ # class SomeModel
97
+ # include VirtualBox::AbstractModel::Relatable
98
+ #
99
+ # relationship :foos, Foo, :lazy => true
100
+ #
101
+ # def load_relationship(name)
102
+ # if name == :foos
103
+ # populate_relationship(name, get_data_for_a_long_time)
104
+ # end
105
+ # end
106
+ # end
107
+ #
108
+ # Using the above class, we can use it like so:
109
+ #
110
+ # model = SomeModel.new
111
+ #
112
+ # # This initial load takes awhile as it loads...
113
+ # model.foos
114
+ #
115
+ # # Instant! (Just a hash lookup. No load necessary)
116
+ # model.foos
117
+ #
118
+ # One catch: If a model attempts to {#destroy_relationship destroy} a lazy
119
+ # relationship, it will first load the relationship, since destroy typically
120
+ # depends on some data of the relationship.
81
121
  module Relatable
82
122
  def self.included(base)
83
123
  base.extend ClassMethods
@@ -99,7 +139,7 @@ module VirtualBox
99
139
  relationships << [name, { :klass => klass }.merge(options)]
100
140
 
101
141
  # Define the method to read the relationship
102
- define_method(name) { relationship_data[name] }
142
+ define_method(name) { read_relationship(name) }
103
143
 
104
144
  # Define the method to set the relationship
105
145
  define_method("#{name}=") { |*args| set_relationship(name, *args) }
@@ -138,9 +178,19 @@ module VirtualBox
138
178
  end
139
179
  end
140
180
 
181
+ # Reads a relationship. This is equivalent to {Attributable#read_attribute},
182
+ # but for relationships.
183
+ def read_relationship(name)
184
+ if lazy_relationship?(name) && !loaded_relationship?(name)
185
+ load_relationship(name)
186
+ end
187
+
188
+ relationship_data[name.to_sym]
189
+ end
190
+
141
191
  # Saves the model, calls save_relationship on all relations. It is up to
142
192
  # the relation to determine whether anything changed, etc. Simply
143
- # calls `save_relationship` on each relationshp class passing in the
193
+ # calls `save_relationship` on each relationship class passing in the
144
194
  # following parameters:
145
195
  #
146
196
  # * **caller** - The class which is calling save
@@ -150,20 +200,34 @@ module VirtualBox
150
200
  # end and they'll be pushed through to the `save_relationship` method.
151
201
  def save_relationships(*args)
152
202
  self.class.relationships.each do |name, options|
153
- next unless options[:klass].respond_to?(:save_relationship)
154
- options[:klass].save_relationship(self, relationship_data[name], *args)
203
+ save_relationship(name, *args)
155
204
  end
156
205
  end
157
206
 
207
+ # Saves a single relationship. It is up to the relationship class to
208
+ # determine whether anything changed and how saving is implemented. Simply
209
+ # calls `save_relationship` on the relationship class.
210
+ def save_relationship(name, *args)
211
+ options = self.class.relationships_hash[name]
212
+ return unless options[:klass].respond_to?(:save_relationship)
213
+ options[:klass].save_relationship(self, relationship_data[name], *args)
214
+ end
215
+
158
216
  # The equivalent to {Attributable#populate_attributes}, but with
159
217
  # relationships.
160
218
  def populate_relationships(data)
161
219
  self.class.relationships.each do |name, options|
162
- next unless options[:klass].respond_to?(:populate_relationship)
163
- relationship_data[name] = options[:klass].populate_relationship(self, data)
220
+ populate_relationship(name, data) unless lazy_relationship?(name)
164
221
  end
165
222
  end
166
223
 
224
+ # Populate a single relationship.
225
+ def populate_relationship(name, data)
226
+ options = self.class.relationships_hash[name]
227
+ return unless options[:klass].respond_to?(:populate_relationship)
228
+ relationship_data[name] = options[:klass].populate_relationship(self, data)
229
+ end
230
+
167
231
  # Calls `destroy_relationship` on each of the relationships. Any
168
232
  # arbitrary args may be added and they will be forarded to the
169
233
  # relationship's `destroy_relationship` method.
@@ -181,6 +245,11 @@ module VirtualBox
181
245
  def destroy_relationship(name, *args)
182
246
  options = self.class.relationships_hash[name]
183
247
  return unless options && options[:klass].respond_to?(:destroy_relationship)
248
+
249
+ # Read relationship, which forces lazy relationships to load, which is
250
+ # probably necessary for destroying
251
+ read_relationship(name)
252
+
184
253
  options[:klass].destroy_relationship(self, relationship_data[name], *args)
185
254
  end
186
255
 
@@ -199,6 +268,19 @@ module VirtualBox
199
268
  self.class.has_relationship?(key.to_sym)
200
269
  end
201
270
 
271
+ # Returns boolean denoting if a relationship is to be lazy loaded.
272
+ #
273
+ # @return [Boolean]
274
+ def lazy_relationship?(key)
275
+ options = self.class.relationships_hash[key.to_sym]
276
+ !options.nil? && options[:lazy]
277
+ end
278
+
279
+ # Returns boolean denoting if a relationship has been loaded.
280
+ def loaded_relationship?(key)
281
+ relationship_data.has_key?(key)
282
+ end
283
+
202
284
  # Sets a relationship to the given value. This is not guaranteed to
203
285
  # do anything, since "set_relationship" will be called on the class
204
286
  # that the relationship is associated with and its expected to return