virtualbox 0.4.3 → 0.5.0

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