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.
@@ -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
- To request an emission estimate asynchronously, simply pass an URL as the <tt>:callback</tt> option to <tt>#emission</tt>:
60
+ == API keys
59
61
 
60
- > RentalCar.new.emission :callback => http://example.com/my/callback/handler
62
+ You should get an API key from http://keys.brighterplanet.com and set it globally:
61
63
 
62
- A good way to test this is to set up a {PostBin}[http://postbin.org]
64
+ Carbon.key = '12903019230128310293'
63
65
 
64
- == Documentation
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
- Read the {carbon gem RDoc}[http://rdoc.info/projects/brighterplanet/carbon] for more.
72
+ The answer: <tt>#to_characteristic</tt> and, failing that, <tt>#to_param</tt>.
67
73
 
68
- == Exceptions
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.0
1
+ 0.2.1
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{carbon}
8
- s.version = "0.2.0"
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"]
@@ -51,6 +51,8 @@ module Carbon
51
51
  end
52
52
  class RateLimited < RuntimeError # :nodoc:
53
53
  end
54
+ class TriedToUseAsyncResponseAsNumber < RuntimeError # :nodoc:
55
+ end
54
56
 
55
57
  # The api key obtained from http://keys.brighterplanet.com
56
58
  mattr_accessor :key
@@ -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>#to_param</tt> 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.)
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</b> on the emitter. In this example, it's up to you to make sure my_car.manufacturer.to_param returns an epa_code.
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
- def take_options(options = {})
18
- options.each do |k, v|
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
- case other
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
- else
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
- send "#{parent.mode}_body"
9
+ params.to_query
10
+ end
11
+ def params
12
+ send "#{parent.mode}_params"
10
13
  end
11
- def async_body # :nodoc:
12
- params = params
13
- params[:emitter] = parent.emitter.class.carbon_base.emitter_common_name
14
- params[:callback] = parent.callback
15
- params[:callback_content_type] = parent.callback_content_type
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 => params.to_query
20
- }.to_query
22
+ :MessageBody => hash.to_query
23
+ }
21
24
  end
22
- def realtime_body # :nodoc:
23
- params.to_query
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 params
29
- params = parent.emitter.class.carbon_base.translation_table.inject({}) do |memo, translation|
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 = current_value.to_param
39
- if current_value.present?
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
- params[:timeframe] = parent.timeframe if parent.timeframe
50
- params[:key] = parent.key if parent.key
51
- params
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"
@@ -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 =~ /mixer\[upc\]=123/
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.timeframe.should == t
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.timeframe.should == t
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 = 'http://www.postbin.org/1dj0146'
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
- c.emission :timeframe => Timeframe.new(:year => 2009), :callback => 'http://www.postbin.org/1dj0146'
224
+ d.emission.to_f
154
225
  }.should_not raise_error
155
226
  end
156
227
  end
@@ -10,17 +10,20 @@ require 'active_support/json/encoding'
10
10
  require 'carbon'
11
11
 
12
12
  require 'fakeweb'
13
- {
14
- 'automobiles' => {
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
- 'factories' => {
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
- }.each do |k, v|
25
- FakeWeb.register_uri :post, /http:\/\/carbon.brighterplanet.com\/#{k}/, :body => v.to_json
26
- end
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: 23
4
+ hash: 21
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 0
10
- version: 0.2.0
9
+ - 1
10
+ version: 0.2.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Derek Kastner