dorb 0.0.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +198 -0
- data/Rakefile +4 -0
- data/dorb.gemspec +30 -0
- data/lib/dorb.rb +18 -0
- data/lib/dorb/config.rb +36 -0
- data/lib/dorb/core_ext/hash.rb +24 -0
- data/lib/dorb/droplet.rb +10 -0
- data/lib/dorb/image.rb +10 -0
- data/lib/dorb/region.rb +10 -0
- data/lib/dorb/resource.rb +126 -0
- data/lib/dorb/size.rb +16 -0
- data/lib/dorb/ssh_key.rb +10 -0
- data/lib/dorb/version.rb +3 -0
- data/spec/config_spec.rb +34 -0
- data/spec/droplet_spec.rb +7 -0
- data/spec/fixtures/cassettes/DORB_Droplet/all.yml +57 -0
- data/spec/fixtures/cassettes/DORB_Droplet/find.yml +111 -0
- data/spec/fixtures/cassettes/DORB_Droplet/find_missing.yml +57 -0
- data/spec/fixtures/cassettes/DORB_Image/all.yml +75 -0
- data/spec/fixtures/cassettes/DORB_Image/find.yml +129 -0
- data/spec/fixtures/cassettes/DORB_Image/find_missing.yml +57 -0
- data/spec/fixtures/cassettes/DORB_Region/all.yml +58 -0
- data/spec/fixtures/cassettes/DORB_Region/find.yml +112 -0
- data/spec/fixtures/cassettes/DORB_Region/find_missing.yml +57 -0
- data/spec/fixtures/cassettes/DORB_SSHKey/all.yml +57 -0
- data/spec/fixtures/cassettes/DORB_SSHKey/find.yml +113 -0
- data/spec/fixtures/cassettes/DORB_SSHKey/find_missing.yml +57 -0
- data/spec/fixtures/cassettes/DORB_Size/all.yml +597 -0
- data/spec/fixtures/cassettes/DORB_Size/find.yml +651 -0
- data/spec/fixtures/cassettes/DORB_Size/find_missing.yml +57 -0
- data/spec/image_spec.rb +7 -0
- data/spec/region_spec.rb +7 -0
- data/spec/size_spec.rb +19 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/ssh_key_spec.rb +7 -0
- data/spec/support/shared_examples_for_resource.rb +40 -0
- data/spec/support/vcr.rb +8 -0
- metadata +209 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 60ba017e4aaa0670f9debe6271fd0a4934b88a76
|
4
|
+
data.tar.gz: 1032b5218896c4007081a48f0af4ac0a6977c1c7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 06f866a53ccacdba53dcf965c56acc7b32f725be954eb8b0bcd736ef1daa18b9310306c6fcff8efb1284e4b046dd6f5c331c5f03a631e1299dc31342e09fb238
|
7
|
+
data.tar.gz: 209117c4efc5527a2f1a1777953b47c456840cad55c7389f5bc4fe7e12f28d3f990dda08712e67f8efc5a474c150df6a2e755f748c36596808a5dfdda48386dc
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- "1.8.7"
|
4
|
+
- "1.9.2"
|
5
|
+
- "1.9.3"
|
6
|
+
- "2.0.0"
|
7
|
+
- jruby-18mode # JRuby in 1.8 mode
|
8
|
+
- jruby-19mode # JRuby in 1.9 mode
|
9
|
+
- rbx-18mode
|
10
|
+
- rbx-19mode
|
11
|
+
env:
|
12
|
+
- DIGITAL_OCEAN_CLIENT_ID=123456789 DIGITAL_OCEAN_API_KEY=fedcba9876543210
|
13
|
+
before_install:
|
14
|
+
- gem install bundler
|
15
|
+
# uncomment this line if your project needs to run something other than `rake`:
|
16
|
+
# script: bundle exec rspec spec
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Darrin Wortlehock
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/exempla/dorb.png)](https://travis-ci.org/exempla/dorb)
|
2
|
+
[![Code Climate](https://codeclimate.com/github/exempla/dorb.png)](https://codeclimate.com/github/exempla/dorb)
|
3
|
+
|
4
|
+
# DORB - Digital Ocean Ruby Bindings
|
5
|
+
|
6
|
+
Interact with the [Digital Ocean](http://www.digitalocean.com) [API](http://www.digitalocean.com/api) in an idiomatic ruby way.
|
7
|
+
|
8
|
+
DORB exposes the Digital Ocean API as Ruby objects, has a comprehensive test suite and supports the entire API.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'dorb'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install dorb
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
Require the library in your code as
|
27
|
+
|
28
|
+
require 'dorb'
|
29
|
+
|
30
|
+
## Configuration
|
31
|
+
|
32
|
+
Set your client key and API key
|
33
|
+
|
34
|
+
# This should be in an initializer or similar
|
35
|
+
DORB::Config.setup \
|
36
|
+
:client_key => 'YOUR_CLIENT_KEY_HERE',
|
37
|
+
:api_key => 'YOUR_API_KEY_HERE'
|
38
|
+
|
39
|
+
Any method that calls the Digital Ocean API will raise DORB::ConfigurationError if either Client Key or API Key are not configured
|
40
|
+
|
41
|
+
## Getting Started
|
42
|
+
|
43
|
+
### Droplets
|
44
|
+
|
45
|
+
##### Show all active droplets
|
46
|
+
This method returns all active droplets that are currently running in your account.
|
47
|
+
|
48
|
+
DORB::Droplet.all
|
49
|
+
|
50
|
+
##### Show droplet
|
51
|
+
This method returns a droplet for a specific droplet id.
|
52
|
+
|
53
|
+
DORB::Droplet.find id
|
54
|
+
|
55
|
+
DORB::Droplet.find_by_ip ip_address
|
56
|
+
|
57
|
+
DORB::Droplet.find_all_by_name name
|
58
|
+
|
59
|
+
##### New droplet
|
60
|
+
This method allows you to create a new droplet.
|
61
|
+
|
62
|
+
DORB::Droplet.new :name => name, :size => size, :image => image, :region => region, :ssh_keys => [keys]
|
63
|
+
|
64
|
+
##### Reboot droplet
|
65
|
+
This method allows you to reboot a droplet. This is the preferred method to use if a server is not responding
|
66
|
+
|
67
|
+
droplet.reboot
|
68
|
+
|
69
|
+
##### Power cycle droplet
|
70
|
+
This method will turn off the droplet and then turn it back on
|
71
|
+
|
72
|
+
droplet.power_cycle
|
73
|
+
|
74
|
+
##### Shut down droplet
|
75
|
+
This method allows you to shutdown a running droplet. The droplet will remain in your account
|
76
|
+
|
77
|
+
droplet.shutdown
|
78
|
+
|
79
|
+
##### Power off droplet
|
80
|
+
This method allows you to power off a running droplet. The droplet will remain in your account
|
81
|
+
|
82
|
+
droplet.power_off
|
83
|
+
|
84
|
+
##### Power on droplet
|
85
|
+
This method allows you to power on a powered off droplet
|
86
|
+
|
87
|
+
droplet.power_on
|
88
|
+
|
89
|
+
##### Reset root password
|
90
|
+
This method will reset the root password for a droplet. Please be aware that this will reboot the droplet to allow resetting the password.
|
91
|
+
|
92
|
+
droplet.password_reset
|
93
|
+
|
94
|
+
##### Resize droplet
|
95
|
+
This method allows you to resize a specific droplet to a different size. This will affect the number of processors and memory allocated to the droplet.
|
96
|
+
|
97
|
+
droplet.resize size
|
98
|
+
|
99
|
+
##### Take a snapshot
|
100
|
+
This method allows you to take a snapshot of the running droplet, which can later be restored or used to create a new droplet from the same image. Please be aware this may cause a reboot.
|
101
|
+
|
102
|
+
droplet.snapshot
|
103
|
+
|
104
|
+
##### Restore droplet
|
105
|
+
This method allows you to restore a droplet with a previous image or snapshot. This will be a mirror copy of the image or snapshot to your droplet. Be sure you have backed up any necessary information prior to restore.
|
106
|
+
|
107
|
+
droplet.restore image
|
108
|
+
|
109
|
+
##### Rebuild droplet
|
110
|
+
This method allows you to reinstall a droplet with a default image. This is useful if you want to start again but retain the same IP address for your droplet.
|
111
|
+
|
112
|
+
droplet.rebuild image
|
113
|
+
|
114
|
+
##### Enable automatic backups
|
115
|
+
This method enables automatic backups which run in the background daily to backup your droplet's data.
|
116
|
+
|
117
|
+
droplet.enable_backups
|
118
|
+
|
119
|
+
##### Disable automatic backups
|
120
|
+
This method disables automatic backups from running to backup your droplet's data.
|
121
|
+
|
122
|
+
droplet.disable_backups
|
123
|
+
|
124
|
+
##### Destroy droplet
|
125
|
+
This method destroys one of your droplets - this is irreversible.
|
126
|
+
|
127
|
+
droplet.destroy
|
128
|
+
|
129
|
+
### Images
|
130
|
+
|
131
|
+
##### All images
|
132
|
+
This method returns all the available images that can be accessed by your client ID. You will have access to all public images by default, and any snapshots or backups that you have created in your own account.
|
133
|
+
|
134
|
+
DORB::Image.all
|
135
|
+
|
136
|
+
##### Find image
|
137
|
+
This method returns an image instance
|
138
|
+
|
139
|
+
DORB::Image.find id
|
140
|
+
|
141
|
+
DORB::Image.find_all_by_name name
|
142
|
+
|
143
|
+
##### Destroy image
|
144
|
+
This method allows you to destroy an image. There is no way to restore a deleted image so be careful and ensure your data is properly backed up.
|
145
|
+
|
146
|
+
image.destroy
|
147
|
+
|
148
|
+
### SSH Keys
|
149
|
+
|
150
|
+
##### All SSH keys
|
151
|
+
This method lists all the available public SSH keys in your account that can be added to a droplet.
|
152
|
+
|
153
|
+
DORB::SSHKey.all
|
154
|
+
|
155
|
+
##### Find SSH key
|
156
|
+
This method returns an SSHKey instance
|
157
|
+
|
158
|
+
DORB::SSHKey.find id
|
159
|
+
|
160
|
+
DORB::SSHKey.find_all_by_name name
|
161
|
+
|
162
|
+
##### Add SSH key
|
163
|
+
This method allows you to add a new public SSH key to your account.
|
164
|
+
|
165
|
+
DORB::SSHKey.add name, ssh_key_pub
|
166
|
+
|
167
|
+
##### Edit SSH key
|
168
|
+
This method allows you to modify an existing public SSH key in your account.
|
169
|
+
|
170
|
+
ssh_key.edit ssh_key_pub
|
171
|
+
|
172
|
+
##### Destroy SSH Key
|
173
|
+
This method will delete the SSH key from your account.
|
174
|
+
|
175
|
+
ssh_key.destroy
|
176
|
+
|
177
|
+
### Sizes
|
178
|
+
|
179
|
+
##### All Sizes
|
180
|
+
This method returns all the available sizes that can be used to create a droplet.
|
181
|
+
|
182
|
+
DORB::Size.all
|
183
|
+
|
184
|
+
### Regions
|
185
|
+
|
186
|
+
##### All Regions
|
187
|
+
This method will return all the available regions within the Digital Ocean cloud.
|
188
|
+
|
189
|
+
DORB::Region.all
|
190
|
+
|
191
|
+
|
192
|
+
## Contributing
|
193
|
+
|
194
|
+
1. Fork it
|
195
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
196
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
197
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
198
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/dorb.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'dorb/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "dorb"
|
8
|
+
spec.version = DORB::VERSION
|
9
|
+
spec.authors = ["Darrin Wortlehock"]
|
10
|
+
spec.email = ["darrin@exempla.co.uk"]
|
11
|
+
spec.description = %q{Interact with the Digital Ocean API in an idiomatic ruby way.}
|
12
|
+
spec.summary = %q{DORB exposes the Digital Ocean API as Ruby objects, has 100% test coverage and supports the entire API.}
|
13
|
+
spec.homepage = "https://github.com/exempla/dorb"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "rest-client"
|
22
|
+
spec.add_dependency "json"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rspec"
|
27
|
+
spec.add_development_dependency "vcr"
|
28
|
+
spec.add_development_dependency "webmock"
|
29
|
+
|
30
|
+
end
|
data/lib/dorb.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
|
3
|
+
require 'dorb/core_ext/hash'
|
4
|
+
require 'dorb/version'
|
5
|
+
require 'dorb/config'
|
6
|
+
require 'dorb/resource'
|
7
|
+
require 'dorb/region'
|
8
|
+
require 'dorb/size'
|
9
|
+
require 'dorb/image'
|
10
|
+
require 'dorb/ssh_key'
|
11
|
+
require 'dorb/droplet'
|
12
|
+
|
13
|
+
module DORB
|
14
|
+
API_ENDPOINT = 'https://api.digitalocean.com'
|
15
|
+
APIError = Class.new StandardError
|
16
|
+
ConfigurationError = Class.new StandardError
|
17
|
+
InvalidStateError = Class.new StandardError
|
18
|
+
end
|
data/lib/dorb/config.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module DORB
|
2
|
+
module Config
|
3
|
+
attr_writer :client_id, :api_key
|
4
|
+
|
5
|
+
def client_id
|
6
|
+
if @client_id
|
7
|
+
@client_id
|
8
|
+
else
|
9
|
+
raise DORB::ConfigurationError.new \
|
10
|
+
"Cannot complete request. Please set client_id with DORB::Config.setup first!"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def api_key
|
15
|
+
if @api_key
|
16
|
+
@api_key
|
17
|
+
else
|
18
|
+
raise DORB::ConfigurationError.new \
|
19
|
+
"Cannot complete request. Please set api_key with DORB::Config.setup first!"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def setup opts={}
|
24
|
+
opts.map do |k,v|
|
25
|
+
send("#{k}=", v)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# def method_missing(meth, *args, &blk)
|
30
|
+
# const = meth.to_s.upcase
|
31
|
+
# DORB.const_set(const, args.first) unless DORB.const_defined? const
|
32
|
+
# end
|
33
|
+
|
34
|
+
extend self
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
def stringify_keys
|
4
|
+
dup.stringify_keys!
|
5
|
+
end
|
6
|
+
|
7
|
+
def stringify_keys!
|
8
|
+
keys.each do |key|
|
9
|
+
self[key.to_s] = delete(key)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def symbolize_keys
|
14
|
+
dup.symbolize_keys!
|
15
|
+
end
|
16
|
+
|
17
|
+
def symbolize_keys!
|
18
|
+
keys.each do |key|
|
19
|
+
self[(key.to_sym rescue key) || key] = delete(key)
|
20
|
+
end
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/dorb/droplet.rb
ADDED
data/lib/dorb/image.rb
ADDED
data/lib/dorb/region.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rest_client'
|
3
|
+
|
4
|
+
module DORB
|
5
|
+
module Resource
|
6
|
+
|
7
|
+
def self.included klass
|
8
|
+
klass.extend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
attr_accessor :collection_resource_name
|
14
|
+
attr_accessor :singular_resource_name
|
15
|
+
attr_accessor :attribute_names
|
16
|
+
attr_accessor :extended_attributes
|
17
|
+
alias :extended_attributes? :extended_attributes
|
18
|
+
|
19
|
+
def define_attribute names
|
20
|
+
[names].flatten.each do |name|
|
21
|
+
self.attribute_names = [self.attribute_names].flatten.compact << name
|
22
|
+
attr_accessor(name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def all(options={})
|
27
|
+
all_attributes = index(options)
|
28
|
+
all_attributes.map do |attributes|
|
29
|
+
if self.extended_attributes?
|
30
|
+
find(attributes['id'])
|
31
|
+
else
|
32
|
+
new(attributes)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def find(id, options={})
|
38
|
+
api_response = show(id, options)
|
39
|
+
new(api_response)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def index(options={})
|
45
|
+
call_api
|
46
|
+
end
|
47
|
+
|
48
|
+
def show(id, options={})
|
49
|
+
call_api(id)
|
50
|
+
end
|
51
|
+
|
52
|
+
def create(params={})
|
53
|
+
raise NotImplementedError
|
54
|
+
end
|
55
|
+
|
56
|
+
def update(params={})
|
57
|
+
raise NotImplementedError
|
58
|
+
end
|
59
|
+
|
60
|
+
def delete(params={})
|
61
|
+
raise NotImplementedError
|
62
|
+
end
|
63
|
+
|
64
|
+
def call_api(action=nil, extra_params={})
|
65
|
+
url = url_for_action(action)
|
66
|
+
params = extra_params.merge(credentials)
|
67
|
+
response = RestClient.get url, :params => params do |response, request, result, &block|
|
68
|
+
raise APIError.new(api_call_error_message(request,response)) unless response.code < 400
|
69
|
+
response.return!(request, result, &block)
|
70
|
+
end
|
71
|
+
parse_api_response(response)
|
72
|
+
end
|
73
|
+
|
74
|
+
def api_call_error_message(request, response)
|
75
|
+
"API Call to #{request.method} #{request.url} failed. Response was #{response.description}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def url_for_action(action=nil)
|
79
|
+
uri = URI.parse(DORB::API_ENDPOINT)
|
80
|
+
uri.path << "/#{self.collection_resource_name}"
|
81
|
+
uri.path << "/#{action}" unless action.nil?
|
82
|
+
uri.path.gsub!(/\/\/+/, '/')
|
83
|
+
uri.to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
def credentials
|
87
|
+
{ :client_id => DORB::Config.client_id, :api_key => DORB::Config.api_key }
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_api_response(response)
|
91
|
+
parsed_response = JSON.parse(response.to_str)
|
92
|
+
unless parsed_response['status'] == 'OK'
|
93
|
+
message = "API call failed."
|
94
|
+
parsed_response.each do |key, value|
|
95
|
+
message << " #{key.gsub('_',' ').capitalize} was '#{value}'."
|
96
|
+
end
|
97
|
+
raise APIError.new(message)
|
98
|
+
end
|
99
|
+
parsed_response[self.collection_resource_name] || parsed_response[self.singular_resource_name]
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
attr_accessor :id, :name
|
105
|
+
|
106
|
+
def initialize( attributes={} )
|
107
|
+
required, optional = split_required_attributes(attributes, [:id, :name])
|
108
|
+
self.id = required[:id]
|
109
|
+
self.name = required[:name]
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def split_required_attributes(attributes, required)
|
115
|
+
optional_attributes = attributes.symbolize_keys
|
116
|
+
required_attributes = {}
|
117
|
+
required.each do |key|
|
118
|
+
required_attributes[key] = optional_attributes.delete(key) do |missing_key|
|
119
|
+
raise ArgumentError.new("Attribute #{missing_key.inspect} is required")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
[required_attributes, optional_attributes]
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|