carbon 0.2.0 → 0.2.1
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.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
|