carbon 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +24 -16
- data/VERSION +1 -1
- data/carbon.gemspec +1 -1
- data/lib/carbon.rb +2 -0
- data/lib/carbon/base.rb +2 -2
- data/lib/carbon/emission_estimate.rb +13 -8
- data/lib/carbon/emission_estimate/request.rb +24 -17
- data/spec/lib/carbon_spec.rb +76 -5
- data/spec/spec_helper.rb +11 -8
- metadata +3 -3
data/README.rdoc
CHANGED
@@ -4,6 +4,10 @@ Carbon is a Ruby API wrapper for the {Brighter Planet emission estimate web serv
|
|
4
4
|
|
5
5
|
By querying the web service, it can estimate the carbon emissions of many real-life objects, such as cars and houses, based on particular attributes that they may have.
|
6
6
|
|
7
|
+
== Full documentation
|
8
|
+
|
9
|
+
Give the quick start a try! Once you've exhausted that, read the {carbon gem RDoc}[http://rdoc.info/projects/brighterplanet/carbon] for more.
|
10
|
+
|
7
11
|
== Quick start
|
8
12
|
|
9
13
|
<b>You'll need a Brighter Planet API key. See the "API keys" section below for details.</b>
|
@@ -52,20 +56,32 @@ When you want to calculate emissions, simply call <tt>RentalCar#emission</tt>.
|
|
52
56
|
=> "kilograms"
|
53
57
|
> my_emission.methodology
|
54
58
|
=> "http://carbon.brighterplanet.com/automobiles.html?[...]"
|
55
|
-
|
56
|
-
=== Asynchronous queries
|
57
59
|
|
58
|
-
|
60
|
+
== API keys
|
59
61
|
|
60
|
-
|
62
|
+
You should get an API key from http://keys.brighterplanet.com and set it globally:
|
61
63
|
|
62
|
-
|
64
|
+
Carbon.key = '12903019230128310293'
|
63
65
|
|
64
|
-
|
66
|
+
Now all of your queries will use that key.
|
67
|
+
|
68
|
+
== A note on <tt>#to_param</tt>
|
69
|
+
|
70
|
+
Once you understand how to map attributes using the DSL, you might still be confused about what the gem actually calls on your objects.
|
65
71
|
|
66
|
-
|
72
|
+
The answer: <tt>#to_characteristic</tt> and, failing that, <tt>#to_param</tt>.
|
67
73
|
|
68
|
-
|
74
|
+
Let's say <tt>RentalCar#make</tt> returns a <tt>Make</tt> object. You should define either <tt>Make#to_characteristic</tt> or <tt>Make#to_param</tt>. The gem <b>doesn't know</b> that you want it to call <tt>Make#name</tt> or <tt>Make#epa_code</tt> or anything else.
|
75
|
+
|
76
|
+
== A note on asynchronous queries
|
77
|
+
|
78
|
+
To request an emission estimate asynchronously, simply pass an URL as the <tt>:callback</tt> option to <tt>#emission</tt>:
|
79
|
+
|
80
|
+
> RentalCar.new.emission :callback => http://example.com/my/callback/handler
|
81
|
+
|
82
|
+
A good way to test this is to set up a {PostBin}[http://postbin.org].
|
83
|
+
|
84
|
+
== A note on exceptions
|
69
85
|
|
70
86
|
Since this gem connects to a web service, you need to be ready for network problems and latency. For example:
|
71
87
|
|
@@ -89,14 +105,6 @@ Since this gem connects to a web service, you need to be ready for network probl
|
|
89
105
|
# Please contact us at staff@brighterplanet.com if you see too many of these.
|
90
106
|
end
|
91
107
|
|
92
|
-
== API keys
|
93
|
-
|
94
|
-
You should get an API key from http://keys.brighterplanet.com and set it globally:
|
95
|
-
|
96
|
-
Carbon.key = '12903019230128310293'
|
97
|
-
|
98
|
-
Now all of your queries will use that key.
|
99
|
-
|
100
108
|
== Copyright
|
101
109
|
|
102
110
|
Copyright (c) 2010 Brighter Planet.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.1
|
data/carbon.gemspec
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{carbon}
|
8
|
-
s.version = "0.2.
|
8
|
+
s.version = "0.2.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Derek Kastner", "Seamus Abshere", "Andy Rossmeissl"]
|
data/lib/carbon.rb
CHANGED
data/lib/carbon/base.rb
CHANGED
@@ -23,7 +23,7 @@ module Carbon
|
|
23
23
|
#
|
24
24
|
# Two general rules:
|
25
25
|
# * Take note of what Brighter Planet expects to receive. If you send <tt>fuel_economy</tt> and we're expecting <tt>fuel_efficiency</tt>, we won't understand! (Hint: use the <tt>:as</tt> option.)
|
26
|
-
# * Make sure <tt>#to_param</tt> is set up. The gem always calls <tt>#
|
26
|
+
# * Make sure <tt>#to_characteristic</tt> or <tt>#to_param</tt> is set up. The gem always calls one of these (it will try <tt>#to_characteristic</tt> first) before sending, so that's your change to key things by "EPA code" (fictional) or whatever else you want. (Hint: use the <tt>:key</tt> option.)
|
27
27
|
#
|
28
28
|
# There are two optional parameters:
|
29
29
|
# * <tt>:as</tt> - if Brighter Planet expects <tt>fuel_efficiency</tt>, and you say <tt>mpg</tt>, you can write <tt>provide :mpg, :as => :fuel_efficiency</tt>. This will result in a query like <tt>?fuel_efficiency=XYZ</tt>.
|
@@ -37,7 +37,7 @@ module Carbon
|
|
37
37
|
# provide :manufacturer, :as => :make, :key => :epa_code # make[epa_code]=my_car.manufacturer.to_param
|
38
38
|
# end
|
39
39
|
#
|
40
|
-
# Note that no matter what you send to us, the gem always calls <b>to_param</
|
40
|
+
# Note that no matter what you send to us, the gem always calls <b><tt>#to_characteristic</tt></b> (or, failing that, <tt>#to_param</tt>) on the emitter. In this example, it's up to you to make sure my_car.manufacturer.to_param returns an epa_code.
|
41
41
|
def provide(attr_name, options = {})
|
42
42
|
options = options.symbolize_keys
|
43
43
|
characteristic = if options.has_key? :as
|
@@ -2,7 +2,9 @@ require 'carbon/emission_estimate/response'
|
|
2
2
|
require 'carbon/emission_estimate/request'
|
3
3
|
|
4
4
|
module Carbon
|
5
|
-
# Let's start off by saying that <tt>EmissionEstimate</tt> objects quack like numbers.
|
5
|
+
# Let's start off by saying that realtime <tt>EmissionEstimate</tt> objects quack like numbers.
|
6
|
+
#
|
7
|
+
# If you ask for a callback, on the other hand, you can't use them as numbers.
|
6
8
|
#
|
7
9
|
# So, you can just say <tt>my_car.emission</tt> and you'll get something like <tt>4308.29</tt>.
|
8
10
|
#
|
@@ -14,16 +16,17 @@ module Carbon
|
|
14
16
|
@emitter = emitter
|
15
17
|
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
+
VALID_OPTIONS = [:callback_content_type, :key, :callback, :timeframe]
|
20
|
+
def take_options(options)
|
21
|
+
return if options.blank?
|
22
|
+
options.slice(*VALID_OPTIONS).each do |k, v|
|
19
23
|
instance_variable_set "@#{k}", v
|
20
24
|
end
|
21
25
|
end
|
22
26
|
|
23
|
-
# I can be compared directly to a number.
|
27
|
+
# I can be compared directly to a number, unless I'm an async request.
|
24
28
|
def ==(other)
|
25
|
-
|
26
|
-
when Numeric
|
29
|
+
if other.is_a? ::Numeric and mode == :realtime
|
27
30
|
other == response.number
|
28
31
|
else
|
29
32
|
super
|
@@ -39,8 +42,11 @@ module Carbon
|
|
39
42
|
def method_missing(method_id, *args, &blk)
|
40
43
|
if !block_given? and args.empty? and response.data.has_key? method_id.to_s
|
41
44
|
response.data[method_id.to_s]
|
42
|
-
|
45
|
+
elsif ::Float.method_defined? method_id
|
46
|
+
raise TriedToUseAsyncResponseAsNumber if mode == :async
|
43
47
|
response.number.send method_id, *args, &blk
|
48
|
+
else
|
49
|
+
super
|
44
50
|
end
|
45
51
|
end
|
46
52
|
|
@@ -50,7 +56,6 @@ module Carbon
|
|
50
56
|
attr_accessor :callback
|
51
57
|
attr_accessor :timeframe
|
52
58
|
|
53
|
-
attr_reader :data
|
54
59
|
attr_reader :emitter
|
55
60
|
def request
|
56
61
|
@request ||= Request.new self
|
@@ -6,27 +6,30 @@ module Carbon
|
|
6
6
|
@parent = parent
|
7
7
|
end
|
8
8
|
def body
|
9
|
-
|
9
|
+
params.to_query
|
10
|
+
end
|
11
|
+
def params
|
12
|
+
send "#{parent.mode}_params"
|
10
13
|
end
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
def async_params # :nodoc:
|
15
|
+
hash = _params
|
16
|
+
hash[:emitter] = parent.emitter.class.carbon_base.emitter_common_name
|
17
|
+
hash[:callback] = parent.callback
|
18
|
+
hash[:callback_content_type] = parent.callback_content_type
|
16
19
|
{
|
17
20
|
:Action => 'SendMessage',
|
18
21
|
:Version => '2009-02-01',
|
19
|
-
:MessageBody =>
|
20
|
-
}
|
22
|
+
:MessageBody => hash.to_query
|
23
|
+
}
|
21
24
|
end
|
22
|
-
def
|
23
|
-
|
25
|
+
def realtime_params # :nodoc:
|
26
|
+
_params
|
24
27
|
end
|
25
28
|
# Used internally, but you can look if you want.
|
26
29
|
#
|
27
30
|
# Returns the params hash that will be send to the emission estimate server.
|
28
|
-
def
|
29
|
-
|
31
|
+
def _params
|
32
|
+
hash = parent.emitter.class.carbon_base.translation_table.inject({}) do |memo, translation|
|
30
33
|
characteristic, key = translation
|
31
34
|
if characteristic.is_a? ::Array
|
32
35
|
current_value = parent.emitter.send characteristic[0]
|
@@ -35,8 +38,12 @@ module Carbon
|
|
35
38
|
current_value = parent.emitter.send characteristic
|
36
39
|
as = characteristic
|
37
40
|
end
|
38
|
-
current_value =
|
39
|
-
|
41
|
+
current_value = begin
|
42
|
+
current_value.to_characteristic
|
43
|
+
rescue NoMethodError
|
44
|
+
current_value.to_param
|
45
|
+
end
|
46
|
+
if current_value.is_a?(FalseClass) or current_value.present?
|
40
47
|
if key
|
41
48
|
memo[as] ||= {}
|
42
49
|
memo[as][key] = current_value
|
@@ -46,9 +53,9 @@ module Carbon
|
|
46
53
|
end
|
47
54
|
memo
|
48
55
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
56
|
+
hash[:timeframe] = parent.timeframe if parent.timeframe
|
57
|
+
hash[:key] = parent.key if parent.key
|
58
|
+
hash
|
52
59
|
end
|
53
60
|
def realtime_url # :nodoc:
|
54
61
|
"#{::Carbon::REALTIME_URL}/#{parent.emitter.class.carbon_base.emitter_common_name.pluralize}.json"
|
data/spec/lib/carbon_spec.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
CALLBACK_URL = 'http://www.postbin.org/1dj0146'
|
4
|
+
|
3
5
|
class RentalCar
|
4
6
|
include Carbon
|
5
7
|
attr_accessor :model, :model_year, :fuel_economy
|
@@ -26,6 +28,9 @@ class DonutFactory
|
|
26
28
|
class Mixer
|
27
29
|
attr_accessor :upc
|
28
30
|
def to_param
|
31
|
+
raise "Use #to_characteristic instead please"
|
32
|
+
end
|
33
|
+
def to_characteristic
|
29
34
|
upc
|
30
35
|
end
|
31
36
|
end
|
@@ -88,6 +93,15 @@ describe Carbon do
|
|
88
93
|
c.emission(:timeframe => Timeframe.new(:year => 2009)).should == 134.599
|
89
94
|
c.emission.response.raw_request.object_id.should_not == first_raw_request.object_id
|
90
95
|
end
|
96
|
+
|
97
|
+
it "should recalculate if the callback changes" do
|
98
|
+
c = RentalCar.new
|
99
|
+
c.model = 'Acura'
|
100
|
+
c.emission.should == 134.599
|
101
|
+
first_raw_request = c.emission.response.raw_request
|
102
|
+
c.emission.callback = CALLBACK_URL
|
103
|
+
c.emission.response.raw_request.object_id.should_not == first_raw_request.object_id
|
104
|
+
end
|
91
105
|
end
|
92
106
|
|
93
107
|
describe 'synchronous (realtime) requests' do
|
@@ -100,7 +114,7 @@ describe Carbon do
|
|
100
114
|
it 'send complex params' do
|
101
115
|
d = DonutFactory.new
|
102
116
|
d.mixer.upc = 123
|
103
|
-
d.emission.request.body.should
|
117
|
+
d.emission.request.body.should include({:mixer => { :upc => 123 }}.to_query)
|
104
118
|
end
|
105
119
|
|
106
120
|
it 'should not send attributes that are blank' do
|
@@ -109,6 +123,12 @@ describe Carbon do
|
|
109
123
|
d.emission.request.body.should_not =~ /oven_count/
|
110
124
|
d.emission.request.body.should_not =~ /timeframe/
|
111
125
|
end
|
126
|
+
|
127
|
+
it 'should send attributes that are false' do
|
128
|
+
d = DonutFactory.new
|
129
|
+
d.mixer.upc = false
|
130
|
+
d.emission.request.body.should include({:mixer => { :upc => 'false' }}.to_query)
|
131
|
+
end
|
112
132
|
|
113
133
|
it 'should send the key' do
|
114
134
|
d = DonutFactory.new
|
@@ -127,30 +147,81 @@ describe Carbon do
|
|
127
147
|
c = RentalCar.new
|
128
148
|
t = Timeframe.new(:year => 2009)
|
129
149
|
c.emission.timeframe = t
|
130
|
-
c.emission.
|
150
|
+
c.emission.request.body.should include(t.to_query(:timeframe))
|
131
151
|
end
|
132
152
|
|
133
153
|
it 'should accept timeframes inline' do
|
134
154
|
c = RentalCar.new
|
135
155
|
t = Timeframe.new(:year => 2009)
|
136
156
|
c.emission(:timeframe => t)
|
137
|
-
c.emission.
|
157
|
+
c.emission.request.body.should include(t.to_query(:timeframe))
|
138
158
|
end
|
139
159
|
|
140
160
|
it 'should not generate post bodies with lots of empty params' do
|
141
161
|
c = RentalCar.new
|
142
162
|
c.emission :timeframe => Timeframe.new(:year => 2009)
|
143
163
|
c.emission.request.body.should_not include('&&')
|
164
|
+
c.emission.request.body.should_not =~ /=[^a-z0-9]/i
|
144
165
|
end
|
145
166
|
end
|
146
167
|
|
147
168
|
describe 'asynchronous (queued) requests' do
|
148
169
|
it 'should post a message to SQS' do
|
149
170
|
c = RentalCar.new
|
150
|
-
c.emission.callback =
|
171
|
+
c.emission.callback = CALLBACK_URL
|
151
172
|
c.emission.request.url.should =~ /queue.amazonaws.com/
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'should have nil data in its response' do
|
176
|
+
c = RentalCar.new
|
177
|
+
c.emission.callback = CALLBACK_URL
|
178
|
+
c.emission.emission_value.should be_nil
|
179
|
+
c.emission.emission_units.should be_nil
|
180
|
+
c.emission.methodology.should be_nil
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should not compare itself to numbers" do
|
184
|
+
c = RentalCar.new
|
185
|
+
c.emission.callback = CALLBACK_URL
|
186
|
+
c.emission.should_not == 0.0
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'should not allow itself to be treated as a number' do
|
190
|
+
c = RentalCar.new
|
191
|
+
c.emission.callback = CALLBACK_URL
|
192
|
+
lambda {
|
193
|
+
c.emission + 5
|
194
|
+
}.should raise_error(::Carbon::TriedToUseAsyncResponseAsNumber)
|
195
|
+
lambda {
|
196
|
+
c.emission.to_f
|
197
|
+
}.should raise_error(::Carbon::TriedToUseAsyncResponseAsNumber)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
describe 'internally' do
|
202
|
+
it "should ignore invalid options passed to #emission" do
|
203
|
+
c = RentalCar.new
|
204
|
+
t = Timeframe.new(:year => 2009)
|
205
|
+
c.emission :timeframe => t, :method_missing => 'helo there', :response => 'foo'
|
206
|
+
c.emission.instance_variable_get(:@timeframe).object_id.should == t.object_id
|
207
|
+
c.emission.instance_variable_get(:@method_missing).should be_nil
|
208
|
+
c.emission.instance_variable_get(:@response).should be_nil
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should raise an error on EmissionEstimate if method isn't found" do
|
212
|
+
c = RentalCar.new
|
213
|
+
lambda {
|
214
|
+
c.emission.foobar
|
215
|
+
}.should raise_error(NoMethodError, /EmissionEstimate/)
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should use #to_characteristic instead of #to_param if it's available" do
|
219
|
+
d = DonutFactory.new
|
220
|
+
lambda {
|
221
|
+
d.mixer.to_param
|
222
|
+
}.should raise_error(RuntimeError, /instead please/)
|
152
223
|
lambda {
|
153
|
-
|
224
|
+
d.emission.to_f
|
154
225
|
}.should_not raise_error
|
155
226
|
end
|
156
227
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -10,17 +10,20 @@ require 'active_support/json/encoding'
|
|
10
10
|
require 'carbon'
|
11
11
|
|
12
12
|
require 'fakeweb'
|
13
|
-
|
14
|
-
|
13
|
+
[
|
14
|
+
[ /http:\/\/carbon.brighterplanet.com\/automobiles/, {
|
15
15
|
'emission' => '134.599',
|
16
16
|
'emission_units' => 'kilograms',
|
17
17
|
'methodology' => 'http://carbon.brighterplanet.com/something'
|
18
|
-
},
|
19
|
-
|
18
|
+
}.to_json],
|
19
|
+
[ /http:\/\/carbon.brighterplanet.com\/factories/, {
|
20
20
|
'emission' => 1000.0,
|
21
21
|
'emission_units' => 'kilograms',
|
22
22
|
'methodology' => 'http://carbon.brighterplanet.com/something'
|
23
|
-
}
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
}.to_json],
|
24
|
+
[ /https:\/\/queue.amazonaws.com/,
|
25
|
+
'Nothing to see here.']
|
26
|
+
].each do |url_matcher, response|
|
27
|
+
FakeWeb.register_uri :post, url_matcher, :body => response
|
28
|
+
end
|
29
|
+
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: carbon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 1
|
10
|
+
version: 0.2.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Derek Kastner
|