cistern 0.0.3
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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/cistern.gemspec +19 -0
- data/lib/cistern.rb +22 -0
- data/lib/cistern/attributes.rb +203 -0
- data/lib/cistern/collection.rb +122 -0
- data/lib/cistern/hash.rb +7 -0
- data/lib/cistern/mock.rb +15 -0
- data/lib/cistern/model.rb +61 -0
- data/lib/cistern/service.rb +148 -0
- data/lib/cistern/version.rb +3 -0
- data/lib/cistern/wait_for.rb +15 -0
- metadata +76 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Josh Lane
|
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,29 @@
|
|
1
|
+
# Cistern
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'cistern'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install cistern
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/cistern.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/cistern/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Josh Lane"]
|
6
|
+
gem.email = ["me@joshualane.com"]
|
7
|
+
gem.description = %q{API client framework extracted from Fog}
|
8
|
+
gem.summary = %q{API client framework}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "cistern"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Cistern::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency "formatador"
|
19
|
+
end
|
data/lib/cistern.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'cistern/version'
|
2
|
+
require 'formatador'
|
3
|
+
|
4
|
+
module Cistern
|
5
|
+
Error = Class.new(StandardError)
|
6
|
+
|
7
|
+
require 'cistern/hash'
|
8
|
+
require 'cistern/mock'
|
9
|
+
require 'cistern/wait_for'
|
10
|
+
require 'cistern/attributes'
|
11
|
+
require 'cistern/collection'
|
12
|
+
require 'cistern/model'
|
13
|
+
require 'cistern/service'
|
14
|
+
|
15
|
+
def self.timeout=(timeout)
|
16
|
+
@timeout= timeout
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.timeout
|
20
|
+
@timeout || 0
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
module Cistern
|
2
|
+
module Attributes
|
3
|
+
module ClassMethods
|
4
|
+
|
5
|
+
def _load(marshalled)
|
6
|
+
new(Marshal.load(marshalled))
|
7
|
+
end
|
8
|
+
|
9
|
+
def aliases
|
10
|
+
@aliases ||= {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def attributes
|
14
|
+
@attributes ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
def attribute(name, options = {})
|
18
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
19
|
+
def #{name}
|
20
|
+
attributes[:#{name}]
|
21
|
+
end
|
22
|
+
EOS
|
23
|
+
case options[:type]
|
24
|
+
when :boolean
|
25
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
26
|
+
def #{name}=(new_#{name})
|
27
|
+
attributes[:#{name}] = case new_#{name}
|
28
|
+
when true,'true'
|
29
|
+
true
|
30
|
+
when false,'false'
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
EOS
|
35
|
+
when :float
|
36
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
37
|
+
def #{name}=(new_#{name})
|
38
|
+
attributes[:#{name}] = new_#{name} && new_#{name}.to_f
|
39
|
+
end
|
40
|
+
EOS
|
41
|
+
when :integer
|
42
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
43
|
+
def #{name}=(new_#{name})
|
44
|
+
attributes[:#{name}] = new_#{name} && new_#{name}.to_i
|
45
|
+
end
|
46
|
+
EOS
|
47
|
+
when :string
|
48
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
49
|
+
def #{name}=(new_#{name})
|
50
|
+
attributes[:#{name}] = new_#{name} && new_#{name}.to_s
|
51
|
+
end
|
52
|
+
EOS
|
53
|
+
when :time
|
54
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
55
|
+
def #{name}=(new_#{name})
|
56
|
+
attributes[:#{name}] = if new_#{name}.nil? || new_#{name} == "" || new_#{name}.is_a?(Time)
|
57
|
+
new_#{name}
|
58
|
+
else
|
59
|
+
Time.parse(new_#{name})
|
60
|
+
end
|
61
|
+
end
|
62
|
+
EOS
|
63
|
+
when :array
|
64
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
65
|
+
def #{name}=(new_#{name})
|
66
|
+
attributes[:#{name}] = [*new_#{name}]
|
67
|
+
end
|
68
|
+
EOS
|
69
|
+
else
|
70
|
+
if squash = options[:squash]
|
71
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
72
|
+
def #{name}=(new_data)
|
73
|
+
if new_data.is_a?(::Hash)
|
74
|
+
if new_data.has_key?(:'#{squash}')
|
75
|
+
attributes[:#{name}] = new_data[:'#{squash}']
|
76
|
+
elsif new_data.has_key?("#{squash}")
|
77
|
+
attributes[:#{name}] = new_data["#{squash}"]
|
78
|
+
else
|
79
|
+
attributes[:#{name}] = [ new_data ]
|
80
|
+
end
|
81
|
+
else
|
82
|
+
attributes[:#{name}] = new_data
|
83
|
+
end
|
84
|
+
end
|
85
|
+
EOS
|
86
|
+
else
|
87
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
88
|
+
def #{name}=(new_#{name})
|
89
|
+
attributes[:#{name}] = new_#{name}
|
90
|
+
end
|
91
|
+
EOS
|
92
|
+
end
|
93
|
+
end
|
94
|
+
@attributes ||= []
|
95
|
+
@attributes |= [name]
|
96
|
+
for new_alias in [*options[:aliases]]
|
97
|
+
aliases[new_alias] = name
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def identity(name, options = {})
|
102
|
+
@identity = name
|
103
|
+
self.attribute(name, options)
|
104
|
+
end
|
105
|
+
|
106
|
+
def ignore_attributes(*args)
|
107
|
+
@ignored_attributes = args
|
108
|
+
end
|
109
|
+
|
110
|
+
def ignored_attributes
|
111
|
+
@ignored_attributes ||= []
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
module InstanceMethods
|
117
|
+
|
118
|
+
def _dump(level)
|
119
|
+
Marshal.dump(attributes)
|
120
|
+
end
|
121
|
+
|
122
|
+
def attributes
|
123
|
+
@attributes ||= {}
|
124
|
+
end
|
125
|
+
|
126
|
+
def dup
|
127
|
+
copy = super
|
128
|
+
copy.dup_attributes!
|
129
|
+
copy
|
130
|
+
end
|
131
|
+
|
132
|
+
def identity
|
133
|
+
send(self.class.instance_variable_get('@identity'))
|
134
|
+
end
|
135
|
+
|
136
|
+
def identity=(new_identity)
|
137
|
+
send("#{self.class.instance_variable_get('@identity')}=", new_identity)
|
138
|
+
end
|
139
|
+
|
140
|
+
def merge_attributes(new_attributes = {})
|
141
|
+
for key, value in new_attributes
|
142
|
+
unless self.class.ignored_attributes.include?(key)
|
143
|
+
if aliased_key = self.class.aliases[key]
|
144
|
+
send("#{aliased_key}=", value)
|
145
|
+
elsif self.respond_to?("#{key}=",true)
|
146
|
+
send("#{key}=", value)
|
147
|
+
else
|
148
|
+
attributes[key] = value
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
self
|
153
|
+
end
|
154
|
+
|
155
|
+
def new_record?
|
156
|
+
!identity
|
157
|
+
end
|
158
|
+
|
159
|
+
# check that the attributes specified in args exist and is not nil
|
160
|
+
def requires(*args)
|
161
|
+
missing = missing_attributes(args)
|
162
|
+
if missing.length == 1
|
163
|
+
raise(ArgumentError, "#{missing.first} is required for this operation")
|
164
|
+
elsif missing.any?
|
165
|
+
raise(ArgumentError, "#{missing[0...-1].join(", ")} and #{missing[-1]} are required for this operation")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def requires_one(*args)
|
170
|
+
missing = missing_attributes(args)
|
171
|
+
if missing.length == args.length
|
172
|
+
raise(ArgumentError, "#{missing[0...-1].join(", ")} or #{missing[-1]} are required for this operation")
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
protected
|
177
|
+
|
178
|
+
def missing_attributes(args)
|
179
|
+
missing = []
|
180
|
+
for arg in [:connection] | args
|
181
|
+
unless send("#{arg}") || attributes.has_key?(arg)
|
182
|
+
missing << arg
|
183
|
+
end
|
184
|
+
end
|
185
|
+
missing
|
186
|
+
end
|
187
|
+
|
188
|
+
def dup_attributes!
|
189
|
+
@attributes = @attributes.dup
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
|
194
|
+
def remap_attributes(attributes, mapping)
|
195
|
+
for key, value in mapping
|
196
|
+
if attributes.key?(key)
|
197
|
+
attributes[value] = attributes.delete(key)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
class Cistern::Collection < Array
|
2
|
+
extend Cistern::Attributes::ClassMethods
|
3
|
+
include Cistern::Attributes::InstanceMethods
|
4
|
+
|
5
|
+
Array.public_instance_methods(false).each do |method|
|
6
|
+
unless [:reject, :select, :slice].include?(method.to_sym)
|
7
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
8
|
+
def #{method}(*args)
|
9
|
+
unless @loaded
|
10
|
+
lazy_load
|
11
|
+
end
|
12
|
+
super
|
13
|
+
end
|
14
|
+
EOS
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
%w[reject select slice].each do |method|
|
19
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
20
|
+
def #{method}(*args)
|
21
|
+
unless @loaded
|
22
|
+
lazy_load
|
23
|
+
end
|
24
|
+
data = super
|
25
|
+
self.clone.clear.concat(data)
|
26
|
+
end
|
27
|
+
EOS
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.model(new_model=nil)
|
31
|
+
if new_model == nil
|
32
|
+
@model
|
33
|
+
else
|
34
|
+
@model = new_model
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(attributes = {})
|
39
|
+
@loaded = false
|
40
|
+
merge_attributes(attributes)
|
41
|
+
end
|
42
|
+
|
43
|
+
def create(attributes={})
|
44
|
+
model = self.new(attributes)
|
45
|
+
model.save
|
46
|
+
end
|
47
|
+
|
48
|
+
def get(identity)
|
49
|
+
raise NotImplementedError
|
50
|
+
end
|
51
|
+
|
52
|
+
def clear
|
53
|
+
@loaded = true
|
54
|
+
super
|
55
|
+
end
|
56
|
+
|
57
|
+
def model
|
58
|
+
self.class.instance_variable_get('@model')
|
59
|
+
end
|
60
|
+
|
61
|
+
attr_accessor :connection
|
62
|
+
|
63
|
+
def new(attributes = {})
|
64
|
+
unless attributes.is_a?(::Hash)
|
65
|
+
raise(ArgumentError.new("Initialization parameters must be an attributes hash, got #{attributes.class} #{attributes.inspect}"))
|
66
|
+
end
|
67
|
+
model.new(
|
68
|
+
attributes.merge(
|
69
|
+
:collection => self,
|
70
|
+
:connection => connection
|
71
|
+
)
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def load(objects)
|
76
|
+
clear
|
77
|
+
for object in objects
|
78
|
+
self << new(object)
|
79
|
+
end
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
def inspect
|
84
|
+
Thread.current[:formatador] ||= Formatador.new
|
85
|
+
data = "#{Thread.current[:formatador].indentation}<#{self.class.name}\n"
|
86
|
+
Thread.current[:formatador].indent do
|
87
|
+
unless self.class.attributes.empty?
|
88
|
+
data << "#{Thread.current[:formatador].indentation}"
|
89
|
+
data << self.class.attributes.map {|attribute| "#{attribute}=#{send(attribute).inspect}"}.join(",\n#{Thread.current[:formatador].indentation}")
|
90
|
+
data << "\n"
|
91
|
+
end
|
92
|
+
data << "#{Thread.current[:formatador].indentation}["
|
93
|
+
unless self.empty?
|
94
|
+
data << "\n"
|
95
|
+
Thread.current[:formatador].indent do
|
96
|
+
data << self.map {|member| member.inspect}.join(",\n")
|
97
|
+
data << "\n"
|
98
|
+
end
|
99
|
+
data << Thread.current[:formatador].indentation
|
100
|
+
end
|
101
|
+
data << "]\n"
|
102
|
+
end
|
103
|
+
data << "#{Thread.current[:formatador].indentation}>"
|
104
|
+
data
|
105
|
+
end
|
106
|
+
|
107
|
+
def reload
|
108
|
+
clear
|
109
|
+
lazy_load
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
def table(attributes = nil)
|
114
|
+
Formatador.display_table(self.map {|instance| instance.attributes}, attributes)
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def lazy_load
|
120
|
+
self.all
|
121
|
+
end
|
122
|
+
end
|
data/lib/cistern/hash.rb
ADDED
data/lib/cistern/mock.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module Cistern
|
2
|
+
class Mock
|
3
|
+
def self.not_implemented
|
4
|
+
raise NotImplementedError
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.random_hex(length)
|
8
|
+
rand(('f' * length).to_i(16)).to_s(16).rjust(length, '0')
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.random_numbers(length)
|
12
|
+
rand(('9' * length).to_i).to_s
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Cistern::Model
|
2
|
+
extend Cistern::Attributes::ClassMethods
|
3
|
+
include Cistern::Attributes::InstanceMethods
|
4
|
+
|
5
|
+
attr_accessor :collection, :connection
|
6
|
+
|
7
|
+
def initialize(attributes={})
|
8
|
+
merge_attributes(attributes)
|
9
|
+
end
|
10
|
+
|
11
|
+
def inspect
|
12
|
+
Thread.current[:formatador] ||= Formatador.new
|
13
|
+
data = "#{Thread.current[:formatador].indentation}<#{self.class.name}"
|
14
|
+
Thread.current[:formatador].indent do
|
15
|
+
unless self.class.attributes.empty?
|
16
|
+
data << "\n#{Thread.current[:formatador].indentation}"
|
17
|
+
data << self.class.attributes.map {|attribute| "#{attribute}=#{send(attribute).inspect}"}.join(",\n#{Thread.current[:formatador].indentation}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
data << "\n#{Thread.current[:formatador].indentation}>"
|
21
|
+
data
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def save
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
def reload
|
30
|
+
requires :identity
|
31
|
+
|
32
|
+
if data = collection.get(identity)
|
33
|
+
new_attributes = data.attributes
|
34
|
+
merge_attributes(new_attributes)
|
35
|
+
self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def ==(comparison_object)
|
40
|
+
comparison_object.equal?(self) ||
|
41
|
+
(comparison_object.is_a?(self.class) &&
|
42
|
+
comparison_object.identity == self.identity &&
|
43
|
+
!comparison_object.new_record?)
|
44
|
+
end
|
45
|
+
|
46
|
+
def wait_for(timeout=Cistern.timeout, interval=1, &block)
|
47
|
+
reload
|
48
|
+
retries = 3
|
49
|
+
Cistern.wait_for(timeout, interval) do
|
50
|
+
if reload
|
51
|
+
retries = 3
|
52
|
+
elsif retries > 0
|
53
|
+
retries -= 1
|
54
|
+
sleep(1)
|
55
|
+
elsif retries == 0
|
56
|
+
raise Cistern::Error.new("Reload failed, #{self.class} #{self.identity} went away.") # FIXME: pretty much assumes you are calling #ready?
|
57
|
+
end
|
58
|
+
instance_eval(&block)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
class Cistern::Service
|
2
|
+
def self.mock!; @mocking= true; end
|
3
|
+
def self.mocking?; @mocking; end
|
4
|
+
def self.unmock!; @mocking= false; end
|
5
|
+
module Collections
|
6
|
+
|
7
|
+
def collections
|
8
|
+
service.collections
|
9
|
+
end
|
10
|
+
|
11
|
+
def mocked_requests
|
12
|
+
service.mocked_requests
|
13
|
+
end
|
14
|
+
|
15
|
+
def requests
|
16
|
+
service.requests
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
class << self
|
21
|
+
def inherited(klass)
|
22
|
+
klass.class_eval <<-EOS, __FILE__, __LINE__
|
23
|
+
module Collections
|
24
|
+
include Cistern::Service::Collections
|
25
|
+
|
26
|
+
def service
|
27
|
+
#{klass.name}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
def self.service
|
31
|
+
#{klass.name}
|
32
|
+
end
|
33
|
+
EOS
|
34
|
+
end
|
35
|
+
def model_path(model_path)
|
36
|
+
@model_path = model_path
|
37
|
+
end
|
38
|
+
|
39
|
+
def request_path(request_path)
|
40
|
+
@request_path = request_path
|
41
|
+
end
|
42
|
+
|
43
|
+
def collections
|
44
|
+
@collections ||= []
|
45
|
+
end
|
46
|
+
|
47
|
+
def models
|
48
|
+
@models ||= []
|
49
|
+
end
|
50
|
+
|
51
|
+
def recognized_arguments
|
52
|
+
@recognized_arguments ||= []
|
53
|
+
end
|
54
|
+
|
55
|
+
def required_arguments
|
56
|
+
@required_arguments ||= []
|
57
|
+
end
|
58
|
+
|
59
|
+
def requests
|
60
|
+
@requests ||= []
|
61
|
+
end
|
62
|
+
|
63
|
+
def requires(*args)
|
64
|
+
self.required_arguments.concat(args)
|
65
|
+
end
|
66
|
+
|
67
|
+
def recognizes(*args)
|
68
|
+
self.recognized_arguments.concat(args)
|
69
|
+
end
|
70
|
+
|
71
|
+
def model(model_name)
|
72
|
+
models << model_name
|
73
|
+
end
|
74
|
+
|
75
|
+
def mocked_requests
|
76
|
+
@mocked_requests ||= []
|
77
|
+
end
|
78
|
+
|
79
|
+
def request(request_name)
|
80
|
+
requests << request_name
|
81
|
+
end
|
82
|
+
|
83
|
+
def collection(collection_name)
|
84
|
+
collections << collection_name
|
85
|
+
end
|
86
|
+
|
87
|
+
def validate_options(options={})
|
88
|
+
required_options = Cistern::Hash.slice(options, *required_arguments)
|
89
|
+
missing_required_options = required_arguments - required_options.keys
|
90
|
+
unless missing_required_options.empty?
|
91
|
+
raise "Missing required options: #{missing_required_options.inspect}"
|
92
|
+
end
|
93
|
+
recognized_options = Cistern::Hash.slice(options, *(required_arguments + recognized_arguments))
|
94
|
+
unrecognized_options = options.keys - (required_arguments + recognized_arguments)
|
95
|
+
unless unrecognized_options.empty?
|
96
|
+
raise "Unrecognized options: #{unrecognized_options.inspect}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def setup_requirements
|
101
|
+
@required ||= false
|
102
|
+
unless @required
|
103
|
+
models.each do |model|
|
104
|
+
require File.join(@model_path, model.to_s)
|
105
|
+
end
|
106
|
+
requests.each do |request|
|
107
|
+
require File.join(@request_path, request.to_s)
|
108
|
+
if service::Mock.method_defined?(request)
|
109
|
+
mocked_requests << request
|
110
|
+
else
|
111
|
+
service::Mock.module_eval <<-EOS, __FILE__, __LINE__
|
112
|
+
def #{request}(*args)
|
113
|
+
Cistern::Mock.not_implemented
|
114
|
+
end
|
115
|
+
EOS
|
116
|
+
end
|
117
|
+
end
|
118
|
+
collections.each do |collection|
|
119
|
+
require File.join(@model_path, collection.to_s)
|
120
|
+
class_name = collection.to_s.split("_").map(&:capitalize).join
|
121
|
+
self.const_get(:Collections).module_eval <<-EOS, __FILE__, __LINE__
|
122
|
+
def #{collection}(attributes={})
|
123
|
+
#{service}::#{class_name}.new({connection: self}.merge(attributes))
|
124
|
+
end
|
125
|
+
EOS
|
126
|
+
end
|
127
|
+
@required = true
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def new(options={})
|
132
|
+
validate_options(options)
|
133
|
+
setup_requirements
|
134
|
+
|
135
|
+
if self.mocking?
|
136
|
+
self.const_get(:Mock).send(:include, self.const_get(:Collections))
|
137
|
+
self.const_get(:Mock).new(options)
|
138
|
+
else
|
139
|
+
self.const_get(:Real).send(:include, self.const_get(:Collections))
|
140
|
+
self.const_get(:Real).new(options)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def reset!
|
145
|
+
self.const_get(:Mock).reset!
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Cistern
|
2
|
+
def self.wait_for(timeout=Cistern.timeout, interval=1, &block)
|
3
|
+
duration = 0
|
4
|
+
start = Time.now
|
5
|
+
until yield || duration > timeout
|
6
|
+
sleep(interval.to_f)
|
7
|
+
duration = Time.now - start
|
8
|
+
end
|
9
|
+
if duration > timeout
|
10
|
+
false
|
11
|
+
else
|
12
|
+
{ :duration => duration }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cistern
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josh Lane
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: formatador
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: API client framework extracted from Fog
|
31
|
+
email:
|
32
|
+
- me@joshualane.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- Gemfile
|
39
|
+
- LICENSE
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- cistern.gemspec
|
43
|
+
- lib/cistern.rb
|
44
|
+
- lib/cistern/attributes.rb
|
45
|
+
- lib/cistern/collection.rb
|
46
|
+
- lib/cistern/hash.rb
|
47
|
+
- lib/cistern/mock.rb
|
48
|
+
- lib/cistern/model.rb
|
49
|
+
- lib/cistern/service.rb
|
50
|
+
- lib/cistern/version.rb
|
51
|
+
- lib/cistern/wait_for.rb
|
52
|
+
homepage: ''
|
53
|
+
licenses: []
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
requirements: []
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.8.24
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: API client framework
|
76
|
+
test_files: []
|