virtualbox 0.4.1 → 0.4.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/Readme.md +9 -9
- data/VERSION +1 -1
- data/docs/GettingStarted.md +11 -11
- data/docs/WhatsNew.md +1 -1
- data/lib/virtualbox.rb +10 -1
- data/lib/virtualbox/abstract_model.rb +47 -29
- data/lib/virtualbox/abstract_model/attributable.rb +16 -16
- data/lib/virtualbox/abstract_model/dirty.rb +10 -10
- data/lib/virtualbox/abstract_model/relatable.rb +22 -22
- data/lib/virtualbox/abstract_model/validatable.rb +4 -4
- data/lib/virtualbox/attached_device.rb +23 -23
- data/lib/virtualbox/command.rb +9 -9
- data/lib/virtualbox/dvd.rb +7 -7
- data/lib/virtualbox/ext/subclass_listing.rb +1 -1
- data/lib/virtualbox/extra_data.rb +17 -17
- data/lib/virtualbox/forwarded_port.rb +23 -23
- data/lib/virtualbox/hard_drive.rb +27 -27
- data/lib/virtualbox/image.rb +25 -18
- data/lib/virtualbox/nic.rb +22 -22
- data/lib/virtualbox/proxies/collection.rb +4 -4
- data/lib/virtualbox/shared_folder.rb +25 -25
- data/lib/virtualbox/storage_controller.rb +16 -16
- data/lib/virtualbox/vm.rb +95 -42
- data/test/virtualbox/abstract_model/attributable_test.rb +28 -28
- data/test/virtualbox/abstract_model/dirty_test.rb +13 -13
- data/test/virtualbox/abstract_model/relatable_test.rb +36 -36
- data/test/virtualbox/abstract_model/validatable_test.rb +22 -22
- data/test/virtualbox/abstract_model_test.rb +46 -36
- data/test/virtualbox/attached_device_test.rb +47 -47
- data/test/virtualbox/command_test.rb +12 -12
- data/test/virtualbox/dvd_test.rb +15 -19
- data/test/virtualbox/ext/subclass_listing_test.rb +2 -2
- data/test/virtualbox/extra_data_test.rb +37 -37
- data/test/virtualbox/forwarded_port_test.rb +31 -31
- data/test/virtualbox/hard_drive_test.rb +40 -48
- data/test/virtualbox/image_test.rb +36 -33
- data/test/virtualbox/nic_test.rb +22 -22
- data/test/virtualbox/proxies/collection_test.rb +6 -6
- data/test/virtualbox/shared_folder_test.rb +36 -36
- data/test/virtualbox/storage_controller_test.rb +14 -14
- data/test/virtualbox/vm_test.rb +121 -70
- data/test/virtualbox_test.rb +19 -0
- data/virtualbox.gemspec +5 -3
- metadata +4 -2
data/Readme.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# VirtualBox Ruby Gem
|
2
2
|
|
3
|
-
The VirtualBox ruby gem is a library which allows anyone to control VirtualBox
|
3
|
+
The VirtualBox ruby gem is a library which allows anyone to control VirtualBox
|
4
4
|
from ruby code! Create, destroy, start, stop, suspend, and resume virtual machines.
|
5
5
|
Also list virtual machines, list hard drives, network devices, etc.
|
6
6
|
|
@@ -19,9 +19,9 @@ the gem, you must set the path to your `VBoxManage` binary:
|
|
19
19
|
## Basic Usage
|
20
20
|
|
21
21
|
The virtualbox gem is modeled after ActiveRecord. If you've used ActiveRecord, you'll
|
22
|
-
feel very comfortable using the virtualbox gem.
|
22
|
+
feel very comfortable using the virtualbox gem.
|
23
23
|
|
24
|
-
There is a [quick getting started guide](http://mitchellh.github.com/virtualbox/file.GettingStarted.html) to
|
24
|
+
There is a [quick getting started guide](http://mitchellh.github.com/virtualbox/file.GettingStarted.html) to
|
25
25
|
get you acquainted with the conventions of the virtualbox gem.
|
26
26
|
|
27
27
|
Complete documentation can be found at [http://mitchellh.github.com/virtualbox](http://mitchellh.github.com/virtualbox).
|
@@ -29,29 +29,29 @@ Complete documentation can be found at [http://mitchellh.github.com/virtualbox](
|
|
29
29
|
Below are some examples:
|
30
30
|
|
31
31
|
require 'virtualbox'
|
32
|
-
|
32
|
+
|
33
33
|
vm = VirtualBox::VM.find("my-vm")
|
34
|
-
|
34
|
+
|
35
35
|
# Let's first print out some basic info about the VM
|
36
36
|
puts "Memory: #{vm.memory}"
|
37
|
-
|
37
|
+
|
38
38
|
vm.storage_controllers.each do |sc|
|
39
39
|
sc.attached_devices.each do |device|
|
40
40
|
puts "Attached Device: #{device.uuid}"
|
41
41
|
end
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
# Let's modify the memory and name...
|
45
45
|
vm.memory = 360
|
46
46
|
vm.name = "my-renamed-vm"
|
47
|
-
|
47
|
+
|
48
48
|
# Save it!
|
49
49
|
vm.save
|
50
50
|
|
51
51
|
Or here is an example of creating a hard drive:
|
52
52
|
|
53
53
|
require 'virtualbox'
|
54
|
-
|
54
|
+
|
55
55
|
hd = VirtualBox::HardDrive.new
|
56
56
|
hd.location = "foo.vdi"
|
57
57
|
hd.size = 2000 # megabytes
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.2
|
data/docs/GettingStarted.md
CHANGED
@@ -43,7 +43,7 @@ sort, enumerate, etc.
|
|
43
43
|
|
44
44
|
drives = VirtualBox::HardDrive.all
|
45
45
|
puts "You have #{drives.length} hard drives!"
|
46
|
-
|
46
|
+
|
47
47
|
drives.each do |drive|
|
48
48
|
puts "Drive: #{drive.uuid}"
|
49
49
|
end
|
@@ -54,7 +54,7 @@ of that model exist.
|
|
54
54
|
### Find
|
55
55
|
|
56
56
|
This example uses {VirtualBox::VM}, which will probably be the most common
|
57
|
-
model you search for.
|
57
|
+
model you search for.
|
58
58
|
|
59
59
|
vm = VirtualBox::VM.find("MyVM")
|
60
60
|
puts "This VM has #{vm.memory} MB of RAM allocated to it."
|
@@ -76,7 +76,7 @@ dynamically, they don't show up as methods in the documentation. Because of this
|
|
76
76
|
attributes are listed for every model in their overviews. For examples, see the
|
77
77
|
overviews of {VirtualBox::VM}, {VirtualBox::HardDrive}, etc.
|
78
78
|
|
79
|
-
In addition to an attribute list, many models also have _relationships_.
|
79
|
+
In addition to an attribute list, many models also have _relationships_.
|
80
80
|
Relationships are, for our purposes, similar enough to attributes that they
|
81
81
|
can be treated the same. Relationship accessing methods are also dynamically
|
82
82
|
generated, so they are listed within the overviews of the models as well (if they
|
@@ -88,7 +88,7 @@ way, and can therefore be accessed through each other.
|
|
88
88
|
Reading attributes is simple. Let's use a {VirtualBox::VM} as an example:
|
89
89
|
|
90
90
|
vm = VirtualBox::VM.find("FooVM")
|
91
|
-
|
91
|
+
|
92
92
|
# Accessing attributes:
|
93
93
|
vm.memory
|
94
94
|
vm.name
|
@@ -101,7 +101,7 @@ Relationships are read the exact same way as attributes. Again using a
|
|
101
101
|
{VirtualBox::VM} as an example:
|
102
102
|
|
103
103
|
vm = VirtualBox::VM.find("FooVM")
|
104
|
-
|
104
|
+
|
105
105
|
# storage_controllers is a relationship containing an array of all the
|
106
106
|
# storage controllers on this VM
|
107
107
|
vm.storage_controllers.each do |sc|
|
@@ -118,7 +118,7 @@ virtualbox models such as {VirtualBox::StorageController}.
|
|
118
118
|
In addition to simply reading attributes and relationships, most can be modified
|
119
119
|
as well. I say "most" because some attributes are `readonly` and some relationships
|
120
120
|
simply don't support being directly modified (though their objects may, I'll get to
|
121
|
-
this in a moment). By looking at the attribute list it is easy to spot a readonly
|
121
|
+
this in a moment). By looking at the attribute list it is easy to spot a readonly
|
122
122
|
attribute, which will have the `:readonly` option set to `true`. Below is an example
|
123
123
|
of what you might see in the overview of some model:
|
124
124
|
|
@@ -153,7 +153,7 @@ you can set it just like an attribute. Below, we use {VirtualBox::AttachedDevice
|
|
153
153
|
an example:
|
154
154
|
|
155
155
|
ad = VirtualBox::AttachedDevice.new
|
156
|
-
|
156
|
+
|
157
157
|
# Attached devices have an image relationship
|
158
158
|
ad.image = VirtualBox::DVD.empty_drive
|
159
159
|
|
@@ -170,7 +170,7 @@ relationship itself. The example below uses {VirtualBox::VM}.
|
|
170
170
|
|
171
171
|
Saving models is _really_ easy: you simply call `save`. That's all! Well, there are
|
172
172
|
some subtleties, but that's the basic idea. `save` will typically **also save relationships**
|
173
|
-
so if you modify a relationship object or relationship itself, calling `save` on the
|
173
|
+
so if you modify a relationship object or relationship itself, calling `save` on the
|
174
174
|
parent object will typically save the relationships as well. `save` always returns
|
175
175
|
`true` or `false` depending on whether the operation was a success or not. If you'd like
|
176
176
|
instead to know why a `save` failed, you can call the method with a `true` parameter
|
@@ -180,10 +180,10 @@ if there is a failure. The message on this object contains the reason.
|
|
180
180
|
Below is an example of saving a simple {VirtualBox::VM} object:
|
181
181
|
|
182
182
|
vm = VirtualBox::VM.find("FooVM")
|
183
|
-
|
183
|
+
|
184
184
|
# Double the memory
|
185
185
|
vm.memory = vm.memory.to_i * 2
|
186
|
-
|
186
|
+
|
187
187
|
# This will return true/false depending on success
|
188
188
|
vm.save
|
189
189
|
|
@@ -191,6 +191,6 @@ Below is an example where an exception will be raised if an error occurs:
|
|
191
191
|
|
192
192
|
vm = VirtualBox::VM.find("FooVM")
|
193
193
|
vm.memory = "INVALID"
|
194
|
-
|
194
|
+
|
195
195
|
# This will raise an exception, since the memory is invalid
|
196
196
|
vm.save(true)
|
data/docs/WhatsNew.md
CHANGED
data/lib/virtualbox.rb
CHANGED
@@ -12,4 +12,13 @@ require 'virtualbox/hard_drive'
|
|
12
12
|
require 'virtualbox/nic'
|
13
13
|
require 'virtualbox/shared_folder'
|
14
14
|
require 'virtualbox/storage_controller'
|
15
|
-
require 'virtualbox/vm'
|
15
|
+
require 'virtualbox/vm'
|
16
|
+
|
17
|
+
module VirtualBox
|
18
|
+
class <<self
|
19
|
+
# Returns installed VirtualBox version like '3.1.2r56127'.
|
20
|
+
def version
|
21
|
+
Command.vboxmanage("-v").chomp.strip
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -13,7 +13,7 @@ module VirtualBox
|
|
13
13
|
include Dirty
|
14
14
|
include Relatable
|
15
15
|
include Validatable
|
16
|
-
|
16
|
+
|
17
17
|
# Returns a boolean denoting if the record is new or existing. This
|
18
18
|
# method is provided for subclasses to use to differentiate between
|
19
19
|
# creating a new object or saving an existing one. An example of this
|
@@ -23,53 +23,53 @@ module VirtualBox
|
|
23
23
|
new_record! if @new_record.nil?
|
24
24
|
@new_record
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
# Explicitly resets the model to a new record. If you're using this
|
28
28
|
# method outside of virtualbox library core, you should really be
|
29
29
|
# asking yourself "why?"
|
30
30
|
def new_record!
|
31
31
|
@new_record = true
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
# Explicitly sets the model to not be a new record. If you're using
|
35
35
|
# this method outside of virtualbox library core, you should really
|
36
36
|
# be asking yourself "why?"
|
37
37
|
def existing_record!
|
38
38
|
@new_record = false
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
# Returns the errors for a model.
|
42
42
|
def errors
|
43
43
|
error_hash = super
|
44
|
-
|
44
|
+
|
45
45
|
self.class.relationships.each do |name, options|
|
46
46
|
next unless options && options[:klass].respond_to?(:errors_for_relationship)
|
47
47
|
relationship_errors = options[:klass].errors_for_relationship(self, relationship_data[name])
|
48
|
-
|
48
|
+
|
49
49
|
error_hash.merge!({ :foos => relationship_errors }) if relationship_errors.length > 0
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
error_hash
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
# Validates the model and relationships.
|
56
56
|
def validate(*args)
|
57
57
|
# First clear all previous errors
|
58
58
|
clear_errors
|
59
|
-
|
59
|
+
|
60
60
|
# Then do the validations
|
61
61
|
failed = false
|
62
62
|
self.class.relationships.each do |name, options|
|
63
63
|
next unless options && options[:klass].respond_to?(:validate_relationship)
|
64
64
|
failed = true if !options[:klass].validate_relationship(self, relationship_data[name], *args)
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
return !failed
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
# Saves the model attributes and relationships.
|
71
71
|
#
|
72
|
-
# The method can be passed any arbitrary arguments, which are
|
72
|
+
# The method can be passed any arbitrary arguments, which are
|
73
73
|
# implementation specific (see {VM#save}, which does this).
|
74
74
|
def save(*args)
|
75
75
|
# Go through changed attributes and call save_attribute for
|
@@ -79,31 +79,31 @@ module VirtualBox
|
|
79
79
|
end
|
80
80
|
|
81
81
|
save_relationships(*args)
|
82
|
-
|
82
|
+
|
83
83
|
# No longer a new record
|
84
84
|
@new_record = false
|
85
85
|
end
|
86
|
-
|
86
|
+
|
87
87
|
# Saves a single attribute of the model. This method on the abstract
|
88
88
|
# model does nothing on its own, and is expected to be overridden
|
89
|
-
# by any subclasses.
|
89
|
+
# by any subclasses.
|
90
90
|
#
|
91
91
|
# This method clears the dirty status of the attribute.
|
92
92
|
def save_attribute(key, value, *args)
|
93
93
|
clear_dirty!(key)
|
94
94
|
end
|
95
|
-
|
95
|
+
|
96
96
|
# Sets the initial attributes from a hash. This method is meant to be used
|
97
97
|
# once to initially setup the attributes. It is **not a mass-assignment**
|
98
98
|
# method for updating attributes.
|
99
99
|
#
|
100
|
-
# This method does **not** affect dirtiness, but also does not clear it.
|
101
|
-
# This means that if you call populate_attributes, the same attributes
|
102
|
-
# that were dirty before the call will be dirty after the call (but no
|
103
|
-
# more and no less). This distinction is important because most subclasses
|
104
|
-
# of AbstractModel only save changed attributes, and ignore unchanged
|
105
|
-
# attributes. Attempting to change attributes through this method will
|
106
|
-
# cause them to not be saved, which is surely unexpected behaviour for
|
100
|
+
# This method does **not** affect dirtiness, but also does not clear it.
|
101
|
+
# This means that if you call populate_attributes, the same attributes
|
102
|
+
# that were dirty before the call will be dirty after the call (but no
|
103
|
+
# more and no less). This distinction is important because most subclasses
|
104
|
+
# of AbstractModel only save changed attributes, and ignore unchanged
|
105
|
+
# attributes. Attempting to change attributes through this method will
|
106
|
+
# cause them to not be saved, which is surely unexpected behaviour for
|
107
107
|
# most users.
|
108
108
|
#
|
109
109
|
# Calling this method will also cause the model to assume that it is not
|
@@ -111,21 +111,21 @@ module VirtualBox
|
|
111
111
|
def populate_attributes(attribs)
|
112
112
|
# No longer a new record
|
113
113
|
@new_record = false
|
114
|
-
|
114
|
+
|
115
115
|
ignore_dirty do
|
116
116
|
super
|
117
|
-
|
117
|
+
|
118
118
|
populate_relationships(attribs)
|
119
119
|
end
|
120
|
-
end
|
121
|
-
|
120
|
+
end
|
121
|
+
|
122
122
|
# Overwrites {Attributable#write_attribute} to set the dirty state of
|
123
123
|
# the written attribute. See {Dirty#set_dirty!} as well.
|
124
124
|
def write_attribute(name, value)
|
125
125
|
set_dirty!(name, read_attribute(name), value)
|
126
126
|
super
|
127
127
|
end
|
128
|
-
|
128
|
+
|
129
129
|
# Overwrites {Relatable#set_relationship} to set the dirty state of the
|
130
130
|
# relationship. See {Dirty#set_dirty!} as well.
|
131
131
|
def set_relationship(key, value)
|
@@ -133,7 +133,7 @@ module VirtualBox
|
|
133
133
|
new_value = super
|
134
134
|
set_dirty!(key, existing, new_value)
|
135
135
|
end
|
136
|
-
|
136
|
+
|
137
137
|
# Destroys the model. The exact behaviour of this method is expected to be
|
138
138
|
# defined on the subclasses. This method on AbstractModel simply
|
139
139
|
# propagates the destroy to the dependent relationships. For more information
|
@@ -144,5 +144,23 @@ module VirtualBox
|
|
144
144
|
destroy_relationship(name, *args) if options[:dependent] == :destroy
|
145
145
|
end
|
146
146
|
end
|
147
|
+
|
148
|
+
# Creates a human-readable format for this model. This method overrides the
|
149
|
+
# default `#<class>` syntax since this doesn't work well for AbstractModels.
|
150
|
+
# Instead, it abbreviates it, instead showing all the attributes and their
|
151
|
+
# values, and `...` for relationships.
|
152
|
+
def inspect
|
153
|
+
values = []
|
154
|
+
|
155
|
+
self.class.attributes.each do |name, options|
|
156
|
+
values.push("#{name.inspect}=#{read_attribute(name).inspect}")
|
157
|
+
end
|
158
|
+
|
159
|
+
self.class.relationships.each do |name, options|
|
160
|
+
values.push("#{name.inspect}=...")
|
161
|
+
end
|
162
|
+
|
163
|
+
"#<#{self.class} #{values.join(", ")}>".strip
|
164
|
+
end
|
147
165
|
end
|
148
166
|
end
|
@@ -9,7 +9,7 @@ module VirtualBox
|
|
9
9
|
# Make sure to also see the {ClassMethods}.
|
10
10
|
#
|
11
11
|
# ## Defining a Basic Attribute
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# attribute :name
|
14
14
|
#
|
15
15
|
# The example above would put the "name" attribute on the class. This
|
@@ -27,7 +27,7 @@ module VirtualBox
|
|
27
27
|
#
|
28
28
|
# The example above allows age to be read, but not written to via the
|
29
29
|
# `age=` method. The attribute is still able to written using
|
30
|
-
# {#write_attribute} but this is generally only for
|
30
|
+
# {#write_attribute} but this is generally only for
|
31
31
|
# inter-class use, and not for users of it.
|
32
32
|
#
|
33
33
|
# ## Defining Default Values
|
@@ -39,15 +39,15 @@ module VirtualBox
|
|
39
39
|
#
|
40
40
|
# ## Populating Multiple Attributes
|
41
41
|
#
|
42
|
-
# Attributes can be mass populated using {#populate_attributes}. Below
|
42
|
+
# Attributes can be mass populated using {#populate_attributes}. Below
|
43
43
|
# is an example of the use.
|
44
44
|
#
|
45
45
|
# class Person
|
46
46
|
# include Attributable
|
47
|
-
#
|
47
|
+
#
|
48
48
|
# attribute :name
|
49
49
|
# attribute :age, :readonly => true
|
50
|
-
#
|
50
|
+
#
|
51
51
|
# def initialize
|
52
52
|
# populate_attributes({
|
53
53
|
# :name => "Steven",
|
@@ -63,7 +63,7 @@ module VirtualBox
|
|
63
63
|
# ## Custom Populate Keys
|
64
64
|
#
|
65
65
|
# Sometimes the attribute names don't match the keys of the hash that will be
|
66
|
-
# used to populate it. For this purpose, you can define a custom
|
66
|
+
# used to populate it. For this purpose, you can define a custom
|
67
67
|
# `populate_key`. Example:
|
68
68
|
#
|
69
69
|
# attribute :path, :populate_key => :location
|
@@ -77,11 +77,11 @@ module VirtualBox
|
|
77
77
|
def self.included(base)
|
78
78
|
base.extend ClassMethods
|
79
79
|
end
|
80
|
-
|
80
|
+
|
81
81
|
# Defines the class methods for the {Attributable} module. For
|
82
82
|
# detailed overview documentation, see {Attributable}.
|
83
83
|
module ClassMethods
|
84
|
-
# Defines an attribute on the model.
|
84
|
+
# Defines an attribute on the model.
|
85
85
|
#
|
86
86
|
# @param [Symbol] name The name of the attribute, which will also be
|
87
87
|
# used to set the accessor methods.
|
@@ -94,10 +94,10 @@ module VirtualBox
|
|
94
94
|
def attribute(name, options = {})
|
95
95
|
name = name.to_sym
|
96
96
|
attributes[name] = options
|
97
|
-
|
97
|
+
|
98
98
|
# Create the method for reading this attribute
|
99
99
|
define_method(name) { read_attribute(name) }
|
100
|
-
|
100
|
+
|
101
101
|
# Create the writer method for it unless the attribute is readonly,
|
102
102
|
# then remove the method if it exists
|
103
103
|
if !options[:readonly]
|
@@ -113,13 +113,13 @@ module VirtualBox
|
|
113
113
|
def attributes
|
114
114
|
@attributes ||= {}
|
115
115
|
end
|
116
|
-
|
116
|
+
|
117
117
|
# Used to propagate attributes to subclasses. This method makes sure that
|
118
118
|
# subclasses of a class with {Attributable} included will inherit the
|
119
119
|
# attributes as well, which would be the expected behaviour.
|
120
120
|
def inherited(subclass)
|
121
121
|
super rescue NoMethodError
|
122
|
-
|
122
|
+
|
123
123
|
attributes.each do |name, option|
|
124
124
|
subclass.attribute(name, option)
|
125
125
|
end
|
@@ -128,7 +128,7 @@ module VirtualBox
|
|
128
128
|
|
129
129
|
# Does the initial population of the various attributes. It will
|
130
130
|
# ignore attributes which are not defined or have no value in the
|
131
|
-
# hash.
|
131
|
+
# hash.
|
132
132
|
#
|
133
133
|
# Population uses the attributes `populate_key` if present to
|
134
134
|
# determine which value to take. Example:
|
@@ -152,7 +152,7 @@ module VirtualBox
|
|
152
152
|
|
153
153
|
# Writes an attribute. This method ignores the `readonly` option
|
154
154
|
# on attribute definitions. This method is mostly meant for
|
155
|
-
# internal use on setting attributes (including readonly
|
155
|
+
# internal use on setting attributes (including readonly
|
156
156
|
# attributes), whereas users of a class which includes this
|
157
157
|
# module should use the accessor methods, such as `name=`.
|
158
158
|
def write_attribute(name, value)
|
@@ -161,14 +161,14 @@ module VirtualBox
|
|
161
161
|
|
162
162
|
# Reads an attribute. This method will return `nil` if the
|
163
163
|
# attribute doesn't exist. If the attribute does exist but
|
164
|
-
# doesn't have a value set, it'll use the `default` value
|
164
|
+
# doesn't have a value set, it'll use the `default` value
|
165
165
|
# if specified.
|
166
166
|
def read_attribute(name)
|
167
167
|
if has_attribute?(name)
|
168
168
|
attributes[name] || self.class.attributes[name][:default]
|
169
169
|
end
|
170
170
|
end
|
171
|
-
|
171
|
+
|
172
172
|
# Returns a hash of all attributes and their options.
|
173
173
|
def attributes
|
174
174
|
@attribute_values ||= {}
|