cistern 0.0.3 → 0.1.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/.gitignore +1 -0
- data/Gemfile +13 -0
- data/Guardfile +6 -0
- data/README.md +7 -1
- data/Rakefile +4 -1
- data/cistern.gemspec +1 -3
- data/lib/cistern/attributes.rb +137 -167
- data/lib/cistern/collection.rb +13 -47
- data/lib/cistern/formatter.rb +5 -0
- data/lib/cistern/formatters/awesome_print.rb +31 -0
- data/lib/cistern/formatters/default.rb +5 -0
- data/lib/cistern/formatters/formatador.rb +44 -0
- data/lib/cistern/model.rb +10 -12
- data/lib/cistern/version.rb +1 -1
- data/lib/cistern.rb +4 -1
- data/spec/collection_spec.rb +35 -0
- data/spec/model_spec.rb +108 -0
- data/spec/spec_helper.rb +6 -0
- metadata +16 -21
data/.gitignore
CHANGED
data/Gemfile
CHANGED
|
@@ -2,3 +2,16 @@ source 'https://rubygems.org'
|
|
|
2
2
|
|
|
3
3
|
# Specify your gem's dependencies in cistern.gemspec
|
|
4
4
|
gemspec
|
|
5
|
+
|
|
6
|
+
group :test do
|
|
7
|
+
gem "rspec", "~> 2.0"
|
|
8
|
+
gem "guard-rspec"
|
|
9
|
+
gem "rake"
|
|
10
|
+
gem 'rb-fsevent', '~> 0.9.1'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
group :formatters do
|
|
14
|
+
gem 'formatador'
|
|
15
|
+
gem 'awesome_print'
|
|
16
|
+
end
|
|
17
|
+
|
data/Guardfile
ADDED
data/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Cistern
|
|
2
2
|
|
|
3
|
+
[](http://travis-ci.org/lanej/cistern)
|
|
4
|
+
|
|
3
5
|
TODO: Write a gem description
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
@@ -20,10 +22,14 @@ Or install it yourself as:
|
|
|
20
22
|
|
|
21
23
|
TODO: Write usage instructions here
|
|
22
24
|
|
|
25
|
+
## Releasing
|
|
26
|
+
|
|
27
|
+
$ gem bump -trv (major|minor|patch)
|
|
28
|
+
|
|
23
29
|
## Contributing
|
|
24
30
|
|
|
25
31
|
1. Fork it
|
|
26
32
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
27
33
|
3. Commit your changes (`git commit -am 'Added some feature'`)
|
|
28
34
|
4. Push to the branch (`git push origin my-new-feature`)
|
|
29
|
-
|
|
35
|
+
. Create new Pull Request
|
data/Rakefile
CHANGED
data/cistern.gemspec
CHANGED
|
@@ -6,7 +6,7 @@ Gem::Specification.new do |gem|
|
|
|
6
6
|
gem.email = ["me@joshualane.com"]
|
|
7
7
|
gem.description = %q{API client framework extracted from Fog}
|
|
8
8
|
gem.summary = %q{API client framework}
|
|
9
|
-
gem.homepage = ""
|
|
9
|
+
gem.homepage = "http://joshualane.com/cistern"
|
|
10
10
|
|
|
11
11
|
gem.files = `git ls-files`.split($\)
|
|
12
12
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
@@ -14,6 +14,4 @@ Gem::Specification.new do |gem|
|
|
|
14
14
|
gem.name = "cistern"
|
|
15
15
|
gem.require_paths = ["lib"]
|
|
16
16
|
gem.version = Cistern::VERSION
|
|
17
|
-
|
|
18
|
-
gem.add_dependency "formatador"
|
|
19
17
|
end
|
data/lib/cistern/attributes.rb
CHANGED
|
@@ -1,203 +1,173 @@
|
|
|
1
|
-
module Cistern
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
module Cistern::Attributes
|
|
2
|
+
def self.parsers
|
|
3
|
+
@parsers ||= {
|
|
4
|
+
:string => lambda{|v,opts| v.to_s},
|
|
5
|
+
:time => lambda{|v,opts| v.is_a?(Time) ? v : v && Time.parse(v.to_s)},
|
|
6
|
+
:integer => lambda{|v,opts| v && v.to_i},
|
|
7
|
+
:float => lambda{|v,opts| v && v.to_f},
|
|
8
|
+
:array => lambda{|v,opts| [*v]},
|
|
9
|
+
:boolean => Proc.new do |v, opts|
|
|
10
|
+
{
|
|
11
|
+
true => true,
|
|
12
|
+
"true" => true,
|
|
13
|
+
"1" => true,
|
|
14
|
+
1 => true,
|
|
15
|
+
false => false,
|
|
16
|
+
"false" => false,
|
|
17
|
+
"0" => false,
|
|
18
|
+
0 => false,
|
|
19
|
+
}[v]
|
|
20
|
+
end,
|
|
21
|
+
}
|
|
22
|
+
end
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
24
|
+
def self.transforms
|
|
25
|
+
@transforms ||= {
|
|
26
|
+
:squash => Proc.new do |k, v, options|
|
|
27
|
+
squash = options[:squash]
|
|
28
|
+
if v.is_a?(::Hash)
|
|
29
|
+
if v.key?(squash.to_s.to_sym)
|
|
30
|
+
v[squash.to_s.to_sym]
|
|
31
|
+
elsif v.has_key?(squash.to_s)
|
|
32
|
+
v[squash.to_s]
|
|
86
33
|
else
|
|
87
|
-
|
|
88
|
-
def #{name}=(new_#{name})
|
|
89
|
-
attributes[:#{name}] = new_#{name}
|
|
90
|
-
end
|
|
91
|
-
EOS
|
|
34
|
+
v
|
|
92
35
|
end
|
|
36
|
+
else v
|
|
93
37
|
end
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
end
|
|
99
|
-
end
|
|
38
|
+
end,
|
|
39
|
+
:none => lambda{|k, v, opts| v},
|
|
40
|
+
}
|
|
41
|
+
end
|
|
100
42
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
end
|
|
43
|
+
def self.default_parser
|
|
44
|
+
@default_parser ||= lambda{|v, opts| v}
|
|
45
|
+
end
|
|
105
46
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
47
|
+
module ClassMethods
|
|
48
|
+
def _load(marshalled)
|
|
49
|
+
new(Marshal.load(marshalled))
|
|
50
|
+
end
|
|
109
51
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
52
|
+
def aliases
|
|
53
|
+
@aliases ||= {}
|
|
54
|
+
end
|
|
113
55
|
|
|
56
|
+
def attributes
|
|
57
|
+
@attributes ||= []
|
|
114
58
|
end
|
|
115
59
|
|
|
116
|
-
|
|
60
|
+
def attribute(name, options = {})
|
|
61
|
+
parser = Cistern::Attributes.parsers[options[:type]] ||
|
|
62
|
+
options[:parser] ||
|
|
63
|
+
Cistern::Attributes.default_parser
|
|
64
|
+
transform = Cistern::Attributes.transforms[options[:squash] ? :squash : :none] ||
|
|
65
|
+
Cistern::Attributes.default_transform
|
|
117
66
|
|
|
118
|
-
|
|
119
|
-
|
|
67
|
+
self.send(:define_method, name) do
|
|
68
|
+
attributes[name.to_s.to_sym]
|
|
120
69
|
end
|
|
121
70
|
|
|
122
|
-
|
|
123
|
-
|
|
71
|
+
self.send(:define_method, "#{name}=") do |value|
|
|
72
|
+
transformed = transform.call(name, value, options)
|
|
73
|
+
attributes[name.to_s.to_sym]= parser.call(transformed, options)
|
|
124
74
|
end
|
|
125
75
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
copy.dup_attributes!
|
|
129
|
-
copy
|
|
130
|
-
end
|
|
76
|
+
@attributes ||= []
|
|
77
|
+
@attributes |= [name]
|
|
131
78
|
|
|
132
|
-
|
|
133
|
-
|
|
79
|
+
for new_alias in [*options[:aliases]]
|
|
80
|
+
aliases[new_alias] = name
|
|
134
81
|
end
|
|
82
|
+
end
|
|
135
83
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
84
|
+
def identity(name, options = {})
|
|
85
|
+
@identity = name
|
|
86
|
+
self.attribute(name, options)
|
|
87
|
+
end
|
|
139
88
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
89
|
+
def ignore_attributes(*args)
|
|
90
|
+
@ignored_attributes = args
|
|
91
|
+
end
|
|
154
92
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
93
|
+
def ignored_attributes
|
|
94
|
+
@ignored_attributes ||= []
|
|
95
|
+
end
|
|
96
|
+
end
|
|
158
97
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
98
|
+
module InstanceMethods
|
|
99
|
+
def _dump(level)
|
|
100
|
+
Marshal.dump(attributes)
|
|
101
|
+
end
|
|
168
102
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
raise(ArgumentError, "#{missing[0...-1].join(", ")} or #{missing[-1]} are required for this operation")
|
|
173
|
-
end
|
|
174
|
-
end
|
|
103
|
+
def attributes
|
|
104
|
+
@attributes ||= {}
|
|
105
|
+
end
|
|
175
106
|
|
|
176
|
-
|
|
107
|
+
def attributes=(attributes)
|
|
108
|
+
@attributes = attributes
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def dup
|
|
112
|
+
copy = super
|
|
113
|
+
copy.attributes= copy.attributes.dup
|
|
114
|
+
copy
|
|
115
|
+
end
|
|
177
116
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
117
|
+
def identity
|
|
118
|
+
send(self.class.instance_variable_get('@identity'))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def identity=(new_identity)
|
|
122
|
+
send("#{self.class.instance_variable_get('@identity')}=", new_identity)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def merge_attributes(new_attributes = {})
|
|
126
|
+
for key, value in new_attributes
|
|
127
|
+
unless self.class.ignored_attributes.include?(key)
|
|
128
|
+
if aliased_key = self.class.aliases[key]
|
|
129
|
+
send("#{aliased_key}=", value)
|
|
130
|
+
elsif self.respond_to?("#{key}=", true)
|
|
131
|
+
send("#{key}=", value)
|
|
132
|
+
else
|
|
133
|
+
attributes[key] = value
|
|
183
134
|
end
|
|
184
135
|
end
|
|
185
|
-
missing
|
|
186
136
|
end
|
|
137
|
+
self
|
|
138
|
+
end
|
|
187
139
|
|
|
188
|
-
|
|
189
|
-
|
|
140
|
+
def new_record?
|
|
141
|
+
!identity
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# check that the attributes specified in args exist and is not nil
|
|
145
|
+
def requires(*args)
|
|
146
|
+
missing = missing_attributes(args)
|
|
147
|
+
if missing.length == 1
|
|
148
|
+
raise(ArgumentError, "#{missing.first} is required for this operation")
|
|
149
|
+
elsif missing.any?
|
|
150
|
+
raise(ArgumentError, "#{missing[0...-1].join(", ")} and #{missing[-1]} are required for this operation")
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def requires_one(*args)
|
|
155
|
+
missing = missing_attributes(args)
|
|
156
|
+
if missing.length == args.length
|
|
157
|
+
raise(ArgumentError, "#{missing[0...-1].join(", ")} or #{missing[-1]} are required for this operation")
|
|
190
158
|
end
|
|
159
|
+
end
|
|
191
160
|
|
|
192
|
-
|
|
161
|
+
protected
|
|
193
162
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
163
|
+
def missing_attributes(args)
|
|
164
|
+
missing = []
|
|
165
|
+
for arg in [:connection] | args
|
|
166
|
+
unless send("#{arg}") || attributes.has_key?(arg)
|
|
167
|
+
missing << arg
|
|
199
168
|
end
|
|
200
169
|
end
|
|
170
|
+
missing
|
|
201
171
|
end
|
|
202
172
|
end
|
|
203
173
|
end
|
data/lib/cistern/collection.rb
CHANGED
|
@@ -2,29 +2,23 @@ class Cistern::Collection < Array
|
|
|
2
2
|
extend Cistern::Attributes::ClassMethods
|
|
3
3
|
include Cistern::Attributes::InstanceMethods
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
super
|
|
13
|
-
end
|
|
14
|
-
EOS
|
|
5
|
+
%w[reject select slice].each do |method|
|
|
6
|
+
define_method(method) do |*args, &block|
|
|
7
|
+
unless @loaded
|
|
8
|
+
lazy_load
|
|
9
|
+
end
|
|
10
|
+
data = super(*args, &block)
|
|
11
|
+
self.clone.clear.concat(data)
|
|
15
12
|
end
|
|
16
13
|
end
|
|
17
14
|
|
|
18
|
-
%w[
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
lazy_load
|
|
23
|
-
end
|
|
24
|
-
data = super
|
|
25
|
-
self.clone.clear.concat(data)
|
|
15
|
+
%w[first last].each do |method|
|
|
16
|
+
define_method(method) do
|
|
17
|
+
unless @loaded
|
|
18
|
+
lazy_load
|
|
26
19
|
end
|
|
27
|
-
|
|
20
|
+
super()
|
|
21
|
+
end
|
|
28
22
|
end
|
|
29
23
|
|
|
30
24
|
def self.model(new_model=nil)
|
|
@@ -80,40 +74,12 @@ class Cistern::Collection < Array
|
|
|
80
74
|
self
|
|
81
75
|
end
|
|
82
76
|
|
|
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
77
|
def reload
|
|
108
78
|
clear
|
|
109
79
|
lazy_load
|
|
110
80
|
self
|
|
111
81
|
end
|
|
112
82
|
|
|
113
|
-
def table(attributes = nil)
|
|
114
|
-
Formatador.display_table(self.map {|instance| instance.attributes}, attributes)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
83
|
private
|
|
118
84
|
|
|
119
85
|
def lazy_load
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'awesome_print'
|
|
2
|
+
|
|
3
|
+
module Cistern::Formatter::AwesomePrint
|
|
4
|
+
def self.call(model)
|
|
5
|
+
model.ai
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module AwesomePrint::Cistern
|
|
10
|
+
def self.included(base)
|
|
11
|
+
base.send :alias_method, :cast_without_cistern, :cast
|
|
12
|
+
base.send :alias_method, :cast, :cast_with_cistern
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def cast_with_cistern(object, type)
|
|
16
|
+
cast = cast_without_cistern(object, type)
|
|
17
|
+
if object.is_a?(Cistern::Model)
|
|
18
|
+
cast = :cistern_model
|
|
19
|
+
end
|
|
20
|
+
cast
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Format Cistern::Model
|
|
24
|
+
#------------------------------------------------------------------------------
|
|
25
|
+
def awesome_cistern_model(object)
|
|
26
|
+
data = object.attributes.keys.inject({}){|r,k| r.merge(k => object.send(k))}
|
|
27
|
+
"#{object} " << awesome_hash(data)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
AwesomePrint::Formatter.send(:include, AwesomePrint::Cistern)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'formatador'
|
|
2
|
+
|
|
3
|
+
module Cistern::Formatter::Formatador
|
|
4
|
+
def self.call(model)
|
|
5
|
+
Thread.current[:formatador] ||= Formatador.new
|
|
6
|
+
data = "#{Thread.current[:formatador].indentation}<#{model.class.name}"
|
|
7
|
+
Thread.current[:formatador].indent do
|
|
8
|
+
unless model.class.attributes.empty?
|
|
9
|
+
data << "\n#{Thread.current[:formatador].indentation}"
|
|
10
|
+
data << model.class.attributes.map {|attribute| "#{attribute}=#{model.send(attribute).inspect}"}.join(",\n#{Thread.current[:formatador].indentation}")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
data << "\n#{Thread.current[:formatador].indentation}>"
|
|
14
|
+
data
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def inspect
|
|
18
|
+
Thread.current[:formatador] ||= Formatador.new
|
|
19
|
+
data = "#{Thread.current[:formatador].indentation}<#{self.class.name}\n"
|
|
20
|
+
Thread.current[:formatador].indent do
|
|
21
|
+
unless self.class.attributes.empty?
|
|
22
|
+
data << "#{Thread.current[:formatador].indentation}"
|
|
23
|
+
data << self.class.attributes.map {|attribute| "#{attribute}=#{send(attribute).inspect}"}.join(",\n#{Thread.current[:formatador].indentation}")
|
|
24
|
+
data << "\n"
|
|
25
|
+
end
|
|
26
|
+
data << "#{Thread.current[:formatador].indentation}["
|
|
27
|
+
unless self.empty?
|
|
28
|
+
data << "\n"
|
|
29
|
+
Thread.current[:formatador].indent do
|
|
30
|
+
data << self.map {|member| member.inspect}.join(",\n")
|
|
31
|
+
data << "\n"
|
|
32
|
+
end
|
|
33
|
+
data << Thread.current[:formatador].indentation
|
|
34
|
+
end
|
|
35
|
+
data << "]\n"
|
|
36
|
+
end
|
|
37
|
+
data << "#{Thread.current[:formatador].indentation}>"
|
|
38
|
+
data
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def table(attributes = nil)
|
|
42
|
+
Formatador.display_table(self.map {|instance| instance.attributes}, attributes)
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/cistern/model.rb
CHANGED
|
@@ -4,23 +4,21 @@ class Cistern::Model
|
|
|
4
4
|
|
|
5
5
|
attr_accessor :collection, :connection
|
|
6
6
|
|
|
7
|
-
def
|
|
8
|
-
|
|
7
|
+
def self.formatter
|
|
8
|
+
@formatter ||= Cistern::Formatter::Default
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.formatter=(formatter)
|
|
12
|
+
@formatter = formatter
|
|
9
13
|
end
|
|
10
14
|
|
|
11
15
|
def inspect
|
|
12
|
-
|
|
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
|
|
16
|
+
self.class.formatter.call(self)
|
|
22
17
|
end
|
|
23
18
|
|
|
19
|
+
def initialize(attributes={})
|
|
20
|
+
merge_attributes(attributes)
|
|
21
|
+
end
|
|
24
22
|
|
|
25
23
|
def save
|
|
26
24
|
raise NotImplementedError
|
data/lib/cistern/version.rb
CHANGED
data/lib/cistern.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require 'cistern/version'
|
|
2
|
-
require '
|
|
2
|
+
require 'time'
|
|
3
3
|
|
|
4
4
|
module Cistern
|
|
5
5
|
Error = Class.new(StandardError)
|
|
@@ -12,6 +12,9 @@ module Cistern
|
|
|
12
12
|
require 'cistern/model'
|
|
13
13
|
require 'cistern/service'
|
|
14
14
|
|
|
15
|
+
autoload :Formatter, 'cistern/formatter'
|
|
16
|
+
|
|
17
|
+
|
|
15
18
|
def self.timeout=(timeout)
|
|
16
19
|
@timeout= timeout
|
|
17
20
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe "Cistern::Collection" do
|
|
4
|
+
class SampleCollectionModel < Cistern::Model
|
|
5
|
+
identity :id
|
|
6
|
+
attribute :name
|
|
7
|
+
end
|
|
8
|
+
class SampleCollection < Cistern::Collection
|
|
9
|
+
model SampleCollectionModel
|
|
10
|
+
|
|
11
|
+
def all
|
|
12
|
+
self.load([{id: 1}, {id: 3, name: "tom"}, {id: 2}])
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "should give first" do
|
|
17
|
+
SampleCollection.new.first.should == SampleCollectionModel.new(id: 1)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "should give last" do
|
|
21
|
+
SampleCollection.new.last.should == SampleCollectionModel.new(id: 2)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "should reject" do
|
|
25
|
+
SampleCollection.new.reject{|m| m.id == 2}.should == [SampleCollectionModel.new(id: 1), SampleCollectionModel.new(id: 3)]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "should select" do
|
|
29
|
+
SampleCollection.new.select{|m| m.id == 2}.should == [SampleCollectionModel.new(id: 2)]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "should slice" do
|
|
33
|
+
SampleCollection.new.slice(0,2).should == [SampleCollectionModel.new(id: 1), SampleCollectionModel.new(id: 3, name: "tom")]
|
|
34
|
+
end
|
|
35
|
+
end
|
data/spec/model_spec.rb
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe "Cistern::Model" do
|
|
4
|
+
|
|
5
|
+
it "should duplicate a model" do
|
|
6
|
+
class DupSpec < Cistern::Model
|
|
7
|
+
identity :id
|
|
8
|
+
attribute :name
|
|
9
|
+
attribute :properties
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
model = DupSpec.new(id: 1, name: "string", properties: {value: "something", else: "what"})
|
|
13
|
+
duplicate = model.dup
|
|
14
|
+
|
|
15
|
+
duplicate.should == model
|
|
16
|
+
duplicate.should_not eql model
|
|
17
|
+
|
|
18
|
+
model.name= "anotherstring"
|
|
19
|
+
duplicate.name.should == "string"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context "attribute parsing" do
|
|
23
|
+
class TypeSpec < Cistern::Model
|
|
24
|
+
identity :id
|
|
25
|
+
attribute :name, type: :string
|
|
26
|
+
attribute :created_at, type: :time
|
|
27
|
+
attribute :flag, type: :boolean
|
|
28
|
+
attribute :list, type: :array
|
|
29
|
+
attribute :number, type: :integer
|
|
30
|
+
attribute :floater, type: :float
|
|
31
|
+
attribute :butternut, type: :integer, aliases: "squash", squash: "id"
|
|
32
|
+
attribute :custom, parser: lambda{|v, opts| "X!#{v}"}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "should parse string" do
|
|
36
|
+
TypeSpec.new(name: 1).name.should == "1"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "should parse time" do
|
|
40
|
+
time = Time.now
|
|
41
|
+
created_at = TypeSpec.new(created_at: time.to_s).created_at
|
|
42
|
+
created_at.should be_a(Time)
|
|
43
|
+
created_at.to_i.should == time.to_i
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "should parse boolean" do
|
|
47
|
+
TypeSpec.new(flag: "false").flag.should be_false
|
|
48
|
+
TypeSpec.new(flag: "true").flag.should be_true
|
|
49
|
+
TypeSpec.new(flag: false).flag.should be_false
|
|
50
|
+
TypeSpec.new(flag: true).flag.should be_true
|
|
51
|
+
TypeSpec.new(flag: "0").flag.should be_false
|
|
52
|
+
TypeSpec.new(flag: "1").flag.should be_true
|
|
53
|
+
TypeSpec.new(flag: 0).flag.should be_false
|
|
54
|
+
TypeSpec.new(flag: 1).flag.should be_true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "should parse an array" do
|
|
58
|
+
TypeSpec.new(list: []).list.should == []
|
|
59
|
+
TypeSpec.new(list: "item").list.should == ["item"]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "should parse a float" do
|
|
63
|
+
TypeSpec.new(floater: "0.01").floater.should == 0.01
|
|
64
|
+
TypeSpec.new(floater: 0.01).floater.should == 0.01
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "should use custom parser" do
|
|
68
|
+
TypeSpec.new(custom: "15").custom.should == "X!15"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "should squash and cast" do
|
|
72
|
+
TypeSpec.new({"squash" => {"id" => "12"}}).butternut.should == 12
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context "inspection engine" do
|
|
77
|
+
class InspectorSpec < Cistern::Model
|
|
78
|
+
identity :id
|
|
79
|
+
attribute :name
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
after(:all) do
|
|
83
|
+
InspectorSpec.formatter= Cistern::Formatter::Default
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "should default to default formatter" do
|
|
87
|
+
InspectorSpec.formatter.should == Cistern::Formatter::Default
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "should use default" do
|
|
91
|
+
InspectorSpec.new(id: 1, name: "name").inspect.should match /#<InspectorSpec:0x[0-9a-f]+ attributes={id:1,name:\"name\"}/
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "should use awesome_print" do
|
|
95
|
+
defined?(AwesomePrint).should be_false # don't require if not used
|
|
96
|
+
InspectorSpec.formatter= Cistern::Formatter::AwesomePrint
|
|
97
|
+
|
|
98
|
+
InspectorSpec.new(id: 1, name: "name").inspect.match /(?x-mi:\#<InspectorSpec:0x[0-9a-f]+>\ {\n\ \ \ \ \ \ :id\x1B\[0;37m\ =>\ \x1B\[0m\x1B\[1;34m1\x1B\[0m,\n\ \ \ \ :name\x1B\[0;37m\ =>\ \x1B\[0m\x1B\[0;33m"name"\x1B\[0m\n})/
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "should use formatador" do
|
|
102
|
+
defined?(Formatador).should be_false # don't require if not used
|
|
103
|
+
InspectorSpec.formatter= Cistern::Formatter::Formatador
|
|
104
|
+
|
|
105
|
+
InspectorSpec.new(id: 1, name: "name").inspect.should == " <InspectorSpec\n id=1,\n name=\"name\"\n >"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cistern
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,24 +9,8 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2012-
|
|
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'
|
|
12
|
+
date: 2012-11-30 00:00:00.000000000 Z
|
|
13
|
+
dependencies: []
|
|
30
14
|
description: API client framework extracted from Fog
|
|
31
15
|
email:
|
|
32
16
|
- me@joshualane.com
|
|
@@ -36,6 +20,7 @@ extra_rdoc_files: []
|
|
|
36
20
|
files:
|
|
37
21
|
- .gitignore
|
|
38
22
|
- Gemfile
|
|
23
|
+
- Guardfile
|
|
39
24
|
- LICENSE
|
|
40
25
|
- README.md
|
|
41
26
|
- Rakefile
|
|
@@ -43,13 +28,20 @@ files:
|
|
|
43
28
|
- lib/cistern.rb
|
|
44
29
|
- lib/cistern/attributes.rb
|
|
45
30
|
- lib/cistern/collection.rb
|
|
31
|
+
- lib/cistern/formatter.rb
|
|
32
|
+
- lib/cistern/formatters/awesome_print.rb
|
|
33
|
+
- lib/cistern/formatters/default.rb
|
|
34
|
+
- lib/cistern/formatters/formatador.rb
|
|
46
35
|
- lib/cistern/hash.rb
|
|
47
36
|
- lib/cistern/mock.rb
|
|
48
37
|
- lib/cistern/model.rb
|
|
49
38
|
- lib/cistern/service.rb
|
|
50
39
|
- lib/cistern/version.rb
|
|
51
40
|
- lib/cistern/wait_for.rb
|
|
52
|
-
|
|
41
|
+
- spec/collection_spec.rb
|
|
42
|
+
- spec/model_spec.rb
|
|
43
|
+
- spec/spec_helper.rb
|
|
44
|
+
homepage: http://joshualane.com/cistern
|
|
53
45
|
licenses: []
|
|
54
46
|
post_install_message:
|
|
55
47
|
rdoc_options: []
|
|
@@ -73,4 +65,7 @@ rubygems_version: 1.8.24
|
|
|
73
65
|
signing_key:
|
|
74
66
|
specification_version: 3
|
|
75
67
|
summary: API client framework
|
|
76
|
-
test_files:
|
|
68
|
+
test_files:
|
|
69
|
+
- spec/collection_spec.rb
|
|
70
|
+
- spec/model_spec.rb
|
|
71
|
+
- spec/spec_helper.rb
|