her 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Her
2
2
 
3
- Her is an ORM (Object Relational Mapper) that maps REST resources to Ruby objects. It is designed to build applications that are powered by a RESTful API and no database.
3
+ Her is an ORM (Object Relational Mapper) that maps REST resources to Ruby objects. It is designed to build applications that are powered by a RESTful API instead of a database.
4
4
 
5
5
  [![Build Status](https://secure.travis-ci.org/remiprev/her.png)](http://travis-ci.org/remiprev/her)
6
6
 
@@ -56,7 +56,7 @@ User.find(1)
56
56
 
57
57
  ## Middleware
58
58
 
59
- Since Her relies on [Faraday](https://github.com/technoweenie/faraday) to send HTTP requests, you can add additional middleware to handle requests and responses.
59
+ Since Her relies on [Faraday](https://github.com/technoweenie/faraday) to send HTTP requests, you can add additional middleware to handle requests and responses. Using a block in the `setup` call, you have access to Faraday’s `builder` object and are able to customize the middleware stack used on each request and response.
60
60
 
61
61
  ### Authentication
62
62
 
@@ -64,14 +64,19 @@ Her doesn’t support any kind of authentication. However, it’s very easy to i
64
64
 
65
65
  ```ruby
66
66
  class MyAuthentication < Faraday::Middleware
67
+ def initialize(app, options={})
68
+ @options = options
69
+ end
70
+
67
71
  def call(env)
68
- env[:request_headers]["X-API-Token"] = "bb2b2dd75413d32c1ac421d39e95b978d1819ff611f68fc2fdd5c8b9c7331192"
72
+ env[:request_headers]["X-API-Token"] = @options[:token] if @options.include?(:token)
69
73
  @app.call(env)
70
74
  end
71
75
  end
72
76
 
73
77
  Her::API.setup :base_uri => "https://api.example.com" do |builder|
74
- builder.use MyAuthentication
78
+ # This token could be stored in the client session
79
+ builder.use MyAuthentication, :token => "bb2b2dd75413d32c1ac421d39e95b978d1819ff611f68fc2fdd5c8b9c7331192"
75
80
  end
76
81
  ```
77
82
 
@@ -91,14 +96,14 @@ By default, Her handles JSON data. It expects the resource/collection data to be
91
96
  [{ "id" : 1, "name" : "Tobias Fünke" }]
92
97
  ```
93
98
 
94
- However, you can define your own parsing method, using a response middleware. The middleware is expected to set `env[:body]` to a hash with three keys: `data`, `errors` and `metadata`. The following code enables parsing JSON data and treating the result as first-level properties. Using the builder block, we then replace the default parser.
99
+ However, you can define your own parsing method, using a response middleware. The middleware is expected to set `env[:body]` to a hash with three keys: `data`, `errors` and `metadata`. The following code enables parsing JSON data and treating the result as first-level properties. Using the builder block, we then replace the default parser with our custom parser.
95
100
 
96
101
  ```ruby
97
102
  class MyCustomParser < Faraday::Response::Middleware
98
103
  def on_complete(env)
99
104
  json = MultiJson.load(env[:body], :symbolize_keys => true)
100
105
  env[:body] = {
101
- :data => json[:data],
106
+ :data => json[:result],
102
107
  :errors => json[:errors],
103
108
  :metadata => json[:metadata]
104
109
  }
@@ -106,10 +111,10 @@ class MyCustomParser < Faraday::Response::Middleware
106
111
  end
107
112
 
108
113
  Her::API.setup :base_uri => "https://api.example.com" do |builder|
109
- builder.delete Her::Middleware::DefaultParseJSON
110
- builder.use MyCustomParser
114
+ # We use the `swap` method to replace Her’s default parser middleware
115
+ builder.swap Her::Middleware::DefaultParseJSON, MyCustomParser
111
116
  end
112
- # User.find(1) will now expect "https://api.example.com/users/1" to return something like '{ "data" => { "id": 1, "name": "Tobias Fünke" }, "errors" => [] }'
117
+ # User.find(1) will now expect "https://api.example.com/users/1" to return something like '{ "result" => { "id": 1, "name": "Tobias Fünke" }, "errors" => [] }'
113
118
  ```
114
119
 
115
120
  ### OAuth
@@ -136,7 +141,8 @@ TWITTER_CREDENTIALS = {
136
141
  }
137
142
 
138
143
  Her::API.setup :base_uri => "https://api.twitter.com/1/" do |builder|
139
- builder.use FaradayMiddleware::OAuth, TWITTER_CREDENTIALS
144
+ # We need to insert the middleware at the beginning of the stack (hence the `insert 0`)
145
+ builder.insert 0, FaradayMiddleware::OAuth, TWITTER_CREDENTIALS
140
146
  end
141
147
 
142
148
  class Tweet
@@ -161,12 +167,26 @@ In your Ruby code:
161
167
 
162
168
  ```ruby
163
169
  class MyCache
164
- def write(key, value); end
165
- def read(key); end
166
- def fetch(key, &block); end
170
+ def initialize
171
+ @cache = {}
172
+ end
173
+
174
+ def write(key, value)
175
+ @cache[key] = value
176
+ end
177
+
178
+ def read(key)
179
+ @cache[key]
180
+ end
181
+
182
+ def fetch(key, &block)
183
+ return value = read(key) if value.nil?
184
+ write key, yield
185
+ end
167
186
  end
168
187
 
169
188
  # A cache system must respond to `#write`, `#read` and `#fetch`.
189
+ # We should be probably using something like Memcached here, not a global object
170
190
  $cache = MyCache.new
171
191
 
172
192
  Her::API.setup :base_uri => "https://api.example.com" do |builder|
@@ -178,12 +198,15 @@ class User
178
198
  end
179
199
 
180
200
  @user = User.find(1)
181
- @user = User.find(1) # This request will be fetched from the cache
201
+ # GET /users/1
202
+
203
+ @user = User.find(1)
204
+ # This request will be fetched from the cache
182
205
  ```
183
206
 
184
207
  ## Relationships
185
208
 
186
- You can define `has_many`, `has_one` and `belongs_to` relationships in your models. The relationship data is handled in two different ways. When parsing a resource from JSON data, if there’s a relationship data included, it will be used to create new Ruby objects.
209
+ You can define `has_many`, `has_one` and `belongs_to` relationships in your models. The relationship data is handled in two different ways. If there’s relationship data when parsing a resource, it will be used to create new Ruby objects.
187
210
 
188
211
  If no relationship data was included when parsing a resource, calling a method with the same name as the relationship will fetch the data (providing there’s an HTTP request available for it in the API).
189
212
 
@@ -210,7 +233,7 @@ class Organization
210
233
  end
211
234
  ```
212
235
 
213
- If there’s relationship data in the resource, no extra HTTP request is made when calling the `#comments` method and an array of resources are returned:
236
+ If there’s relationship data in the resource, no extra HTTP request is made when calling the `#comments` method and an array of resources is returned:
214
237
 
215
238
  ```ruby
216
239
  @user = User.find(1) # { :data => { :id => 1, :name => "George Michael Bluth", :comments => [{ :id => 1, :text => "Foo" }, { :id => 2, :text => "Bar" }], :role => { :id => 1, :name => "Admin" }, :organization => { :id => 2, :name => "Bluth Company" } }}
@@ -226,21 +249,21 @@ If there’s no relationship data in the resource, an extra HTTP request (to `GE
226
249
  @user.comments # => [#<Comment id=1>, #<Comment id=2>] fetched from /users/1/comments
227
250
  ```
228
251
 
229
- For `has_one` relationship, an extra HTTP request (to `GET /users/1/role`) is made when calling the `#role` method:
252
+ For `has_one` relationships, an extra HTTP request (to `GET /users/1/role`) is made when calling the `#role` method:
230
253
 
231
254
  ```ruby
232
255
  @user = User.find(1) # { :data => { :id => 1, :name => "George Michael Bluth" }}
233
256
  @user.role # => #<Role id=1> fetched from /users/1/role
234
257
  ```
235
258
 
236
- For `belongs_to` relationship, an extra HTTP request (to `GET /organizations/2`) is made when calling the `#organization` method:
259
+ For `belongs_to` relationships, an extra HTTP request (to `GET /organizations/2`) is made when calling the `#organization` method:
237
260
 
238
261
  ```ruby
239
262
  @user = User.find(1) # { :data => { :id => 1, :name => "George Michael Bluth", :organization_id => 2 }}
240
263
  @user.organization # => #<Organization id=2> fetched from /organizations/2
241
264
  ```
242
265
 
243
- However, subsequent calls to `#comments` or `#role` will not trigger the extra HTTP request.
266
+ However, subsequent calls to `#comments`, `#role` and `#organization` will not trigger extra HTTP requests as the data has already been fetched.
244
267
 
245
268
  ## Hooks
246
269
 
@@ -260,8 +283,6 @@ end
260
283
  # POST /users&fullname=Tobias+Fünke&internal_id=42
261
284
  ```
262
285
 
263
- In the future, adding hooks to all models will be possible, as well as defining and triggering your own hooks (eg. for your custom requests).
264
-
265
286
  ## Custom requests
266
287
 
267
288
  You can easily define custom requests for your models using `custom_get`, `custom_post`, etc.
@@ -343,12 +364,12 @@ end
343
364
  # GET /hello_users/1
344
365
  ```
345
366
 
346
- You can include custom variables in your paths:
367
+ You can also include custom variables in your paths:
347
368
 
348
369
  ```ruby
349
370
  class User
350
371
  include Her::Model
351
- collection_path "/organizations/:organization_id/users/:id"
372
+ collection_path "/organizations/:organization_id/users"
352
373
  end
353
374
 
354
375
  @user = User.find(1, :_organization_id => 2)
@@ -356,6 +377,10 @@ end
356
377
 
357
378
  @user = User.all(:_organization_id => 2)
358
379
  # GET /organizations/2/users
380
+
381
+ @user = User.new(:fullname => "Tobias Fünke", :organization_id => 2)
382
+ @user.save
383
+ # POST /organizations/2/users
359
384
  ```
360
385
 
361
386
  ## Multiple APIs
@@ -394,7 +419,7 @@ Category.all
394
419
  ## Things to be done
395
420
 
396
421
  * Better error handling
397
- * Better documentation
422
+ * Better API documentation (using YARD)
398
423
 
399
424
  ## Contributors
400
425
 
@@ -403,6 +428,7 @@ Feel free to contribute and submit issues/pull requests [on GitHub](https://gith
403
428
  * [@jfcixmedia](https://github.com/jfcixmedia)
404
429
  * [@EtienneLem](https://github.com/EtienneLem)
405
430
  * [@rafaelss](https://github.com/rafaelss)
431
+ * [@tysontate](https://github.com/tysontate)
406
432
 
407
433
  Take a look at the `spec` folder before you do, and make sure `bundle exec rake spec` passes after your modifications :)
408
434
 
@@ -1,5 +1,5 @@
1
1
  # Create custom parser
2
- class TwitterSearchParser < Faraday::Response::Middleware
2
+ class TwitterParser < Faraday::Response::Middleware
3
3
  METADATA_KEYS = [:completed_in, :max_id, :max_id_str, :next_page, :page, :query, :refresh_url, :results_per_page, :since_id, :since_id_str]
4
4
 
5
5
  def on_complete(env)
@@ -21,7 +21,10 @@ TWITTER_CREDENTIALS = {
21
21
  }
22
22
 
23
23
  # Initialize API
24
- Her::API.setup :base_uri => "https://api.twitter.com/1/", :parse_middleware => TwitterSearchParser, :add_middleware => [FaradayMiddleware::OAuth => TWITTER_CREDENTIALS]
24
+ Her::API.setup :base_uri => "https://api.twitter.com/1/" do |builder|
25
+ builder.insert 0, FaradayMiddleware::OAuth, TWITTER_CREDENTIALS
26
+ builder.swap Her::Middleware::DefaultParseJSON, TwitterParser
27
+ end
25
28
 
26
29
  # Define classes
27
30
  class Tweet
@@ -31,12 +34,16 @@ class Tweet
31
34
  get "/statuses/home_timeline.json"
32
35
  end
33
36
 
37
+ def self.mentions
38
+ get "/statuses/mentions.json"
39
+ end
40
+
34
41
  def username
35
42
  user[:screen_name]
36
43
  end
37
44
  end
38
45
 
39
46
  get "/" do
40
- @tweets = Tweet.timeline
47
+ @tweets = Tweet.mentions
41
48
  haml :index
42
49
  end
@@ -3,6 +3,7 @@ source :rubygems
3
3
  gem "sinatra"
4
4
  gem "haml"
5
5
  gem "thin", :require => false
6
+ gem "faraday_middleware"
6
7
 
7
8
  gem "her", :path => File.join(File.dirname(__FILE__),"../..")
8
9
 
@@ -12,8 +12,32 @@ class TwitterSearchParser < Faraday::Response::Middleware
12
12
  end
13
13
  end
14
14
 
15
+ class MyCache
16
+ def initialize
17
+ @cache = {}
18
+ end
19
+
20
+ def write(key, value)
21
+ @cache[key] = value
22
+ end
23
+
24
+ def read(key)
25
+ @cache[key]
26
+ end
27
+
28
+ def fetch(key, &block)
29
+ return value = read(key) if value.nil?
30
+ write key, yield
31
+ end
32
+ end
33
+
34
+ $cache = MyCache.new
35
+
15
36
  # Initialize API
16
- Her::API.setup :base_uri => "http://search.twitter.com", :parse_middleware => TwitterSearchParser
37
+ Her::API.setup :base_uri => "http://search.twitter.com" do |builder|
38
+ builder.swap Her::Middleware::FirstLevelParseJSON, TwitterSearchParser
39
+ builder.use FaradayMiddleware::Caching, $cache
40
+ end
17
41
 
18
42
  # Define classes
19
43
  class Tweet
@@ -3,14 +3,12 @@ module Her
3
3
  # so it knows where to make those requests. In Rails, this is usually done in `config/initializers/her.rb`:
4
4
  class API
5
5
  # @private
6
- attr_reader :base_uri, :middleware
6
+ attr_reader :base_uri, :middleware, :connection
7
7
 
8
8
  # Setup a default API connection. Accepted arguments and options are the same as {API#setup}.
9
- def self.setup(attrs={}) # {{{
9
+ def self.setup(attrs={}, &block) # {{{
10
10
  @@default_api = new
11
- connection = @@default_api.setup(attrs)
12
- yield connection.builder if block_given?
13
- connection
11
+ @@default_api.setup(attrs, &block)
14
12
  end # }}}
15
13
 
16
14
  # Setup the API connection.
@@ -60,19 +58,12 @@ module Her
60
58
 
61
59
  @middleware.flatten!
62
60
  middleware = @middleware
63
- @connection = Faraday.new(:url => @base_uri) do |builder|
64
- middleware.each do |item|
65
- klass = item.is_a?(Hash) ? item.keys.first : item
66
- args = item.is_a?(Hash) ? item.values.first : nil
67
- if args
68
- builder.use klass, args
69
- else
70
- builder.use klass
71
- end
61
+ @connection = Faraday.new(:url => @base_uri) do |connection|
62
+ middleware.each do |klass|
63
+ connection.use klass
72
64
  end
65
+ yield connection.builder if block_given?
73
66
  end
74
- yield @connection.builder if block_given?
75
- @connection
76
67
  end # }}}
77
68
 
78
69
  # Define a custom parsing procedure. The procedure is passed the response object and is
@@ -35,8 +35,9 @@ module Her
35
35
  extend Her::Model::Paths
36
36
 
37
37
  # Define default settings
38
- collection_path "/#{self.to_s.downcase.pluralize}"
39
- resource_path "/#{self.to_s.downcase.pluralize}/:id"
38
+ base_path = self.name.split("::").last.downcase.pluralize
39
+ collection_path "/#{base_path}"
40
+ resource_path "/#{base_path}/:id"
40
41
  uses_api Her::API.default_api
41
42
  end
42
43
  end
@@ -1,3 +1,3 @@
1
1
  module Her
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.2"
3
3
  end
@@ -16,20 +16,52 @@ describe Her::API do
16
16
  @api.setup :base_uri => "https://api.example.com"
17
17
  @api.base_uri.should == "https://api.example.com"
18
18
  end # }}}
19
+
20
+ it "sets custom middleware with #use" do # {{{
21
+ class Foo; end;
22
+ class Bar; end;
23
+
24
+ @api = Her::API.new
25
+ @api.setup :base_uri => "https://api.example.com" do |builder|
26
+ builder.use Foo
27
+ builder.use Bar
28
+ end
29
+ @api.connection.builder.handlers.should == [Her::Middleware::FirstLevelParseJSON, Faraday::Request::UrlEncoded, Faraday::Adapter::NetHttp, Foo, Bar]
30
+ end # }}}
31
+
32
+ it "sets custom middleware with #insert" do # {{{
33
+ class Foo; end;
34
+ class Bar; end;
35
+
36
+ @api = Her::API.new
37
+ @api.setup :base_uri => "https://api.example.com" do |builder|
38
+ builder.use Foo
39
+ builder.insert 0, Bar
40
+ end
41
+ @api.connection.builder.handlers.should == [Bar, Her::Middleware::FirstLevelParseJSON, Faraday::Request::UrlEncoded, Faraday::Adapter::NetHttp, Foo]
42
+ end # }}}
43
+
44
+ it "delete some default middleware" do # {{{
45
+ @api = Her::API.new
46
+ @api.setup :base_uri => "https://api.example.com" do |builder|
47
+ builder.delete Faraday::Request::UrlEncoded
48
+ end
49
+ @api.connection.builder.handlers.should == [Her::Middleware::FirstLevelParseJSON, Faraday::Adapter::NetHttp]
50
+ end # }}}
19
51
  end
20
52
 
21
53
  describe "#request" do
22
54
  it "makes HTTP requests" do # {{{
23
55
  FakeWeb.register_uri(:get, "https://api.example.com/foo", :body => "Foo, it is.")
24
56
 
25
- class Foo < Faraday::Response::Middleware
57
+ class SimpleParser < Faraday::Response::Middleware
26
58
  def on_complete(env)
27
59
  env[:body] = { :data => env[:body] }
28
60
  end
29
61
  end
30
62
 
31
63
  @api = Her::API.new
32
- @api.setup :base_uri => "https://api.example.com", :parse_middleware => Foo
64
+ @api.setup :base_uri => "https://api.example.com", :parse_middleware => SimpleParser
33
65
  parsed_data = @api.request(:_method => :get, :_path => "/foo")
34
66
  parsed_data[:data] == "Foo, it is."
35
67
  end # }}}
@@ -63,8 +95,7 @@ describe Her::API do
63
95
 
64
96
  @api = Her::API.new
65
97
  @api.setup :base_uri => "https://api.example.com" do |connection|
66
- connection.delete Her::Middleware::DefaultParseJSON
67
- connection.use CustomParser
98
+ connection.swap Her::Middleware::DefaultParseJSON, CustomParser
68
99
  end
69
100
  parsed_data = @api.request(:_method => :get, :_path => "users/1")
70
101
  parsed_data[:data].should == { :id => 1, :name => "George Michael Bluth" }
@@ -3,38 +3,53 @@ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
3
3
 
4
4
  describe Her::Model::Paths do
5
5
  context "building request paths" do
6
- before do # {{{
7
- spawn_model :User
8
- end # }}}
9
-
10
- describe "#build_request_path" do
11
- it "builds paths with defaults" do # {{{
12
- User.build_request_path(id: "foo").should == "/users/foo"
13
- User.build_request_path.should == "/users"
14
- end # }}}
15
-
16
- it "builds paths with custom collection path" do # {{{
17
- User.collection_path "/utilisateurs"
18
- User.build_request_path(id: "foo").should == "/utilisateurs/foo"
19
- User.build_request_path.should == "/utilisateurs"
6
+ context "simple model" do
7
+ before do # {{{
8
+ spawn_model :User
20
9
  end # }}}
21
10
 
22
- it "builds paths with custom collection path with multiple variables" do # {{{
23
- User.collection_path "/organizations/:organization_id/utilisateurs"
24
- User.build_request_path(:id => "foo", :_organization_id => "acme").should == "/organizations/acme/utilisateurs/foo"
25
- User.build_request_path(:_organization_id => "acme").should == "/organizations/acme/utilisateurs"
26
- end # }}}
11
+ describe "#build_request_path" do
12
+ it "builds paths with defaults" do # {{{
13
+ User.build_request_path(id: "foo").should == "/users/foo"
14
+ User.build_request_path.should == "/users"
15
+ end # }}}
16
+
17
+ it "builds paths with custom collection path" do # {{{
18
+ User.collection_path "/utilisateurs"
19
+ User.build_request_path(id: "foo").should == "/utilisateurs/foo"
20
+ User.build_request_path.should == "/utilisateurs"
21
+ end # }}}
22
+
23
+ it "builds paths with custom collection path with multiple variables" do # {{{
24
+ User.collection_path "/organizations/:organization_id/utilisateurs"
25
+ User.build_request_path(:id => "foo", :_organization_id => "acme").should == "/organizations/acme/utilisateurs/foo"
26
+ User.build_request_path(:_organization_id => "acme").should == "/organizations/acme/utilisateurs"
27
+ end # }}}
28
+
29
+ it "builds paths with custom item path" do # {{{
30
+ User.resource_path "/utilisateurs/:id"
31
+ User.build_request_path(id: "foo").should == "/utilisateurs/foo"
32
+ User.build_request_path.should == "/users"
33
+ end # }}}
34
+
35
+ it "raises exceptions when building a path without required custom variables" do # {{{
36
+ User.collection_path "/organizations/:organization_id/utilisateurs"
37
+ expect { User.build_request_path(:id => "foo") }.should raise_error(Her::Errors::PathError)
38
+ end # }}}
39
+ end
40
+ end
27
41
 
28
- it "builds paths with custom item path" do # {{{
29
- User.resource_path "/utilisateurs/:id"
30
- User.build_request_path(id: "foo").should == "/utilisateurs/foo"
31
- User.build_request_path.should == "/users"
42
+ context "nested model" do
43
+ before do # {{{
44
+ spawn_submodel :Base, :User
32
45
  end # }}}
33
46
 
34
- it "raises exceptions when building a path without required custom variables" do # {{{
35
- User.collection_path "/organizations/:organization_id/utilisateurs"
36
- expect { User.build_request_path(:id => "foo") }.should raise_error(Her::Errors::PathError)
37
- end # }}}
47
+ describe "#build_request_path" do
48
+ it "builds paths with defaults" do # {{{
49
+ Base::User.build_request_path(id: "foo").should == "/users/foo"
50
+ Base::User.build_request_path.should == "/users"
51
+ end # }}}
52
+ end
38
53
  end
39
54
  end
40
55
 
@@ -19,8 +19,13 @@ class Array
19
19
  def to_json; MultiJson.dump(self); end
20
20
  end
21
21
 
22
- def spawn_model(klass, attrs={}, &block)
22
+ def spawn_model(klass, &block)
23
23
  Object.instance_eval { remove_const klass } if Object.const_defined?(klass)
24
24
  Object.const_set(klass, Class.new).send(:include, Her::Model)
25
25
  Object.const_get(klass).class_eval(&block) if block_given?
26
26
  end
27
+
28
+ def spawn_submodel(mod, klass)
29
+ Object.instance_eval { remove_const mod } if Object.const_defined?(mod)
30
+ Object.const_set(mod, Module.new).const_set(klass, Class.new).send(:include, Her::Model)
31
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: her
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-30 00:00:00.000000000Z
12
+ date: 2012-05-01 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -281,7 +281,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
281
281
  version: '0'
282
282
  segments:
283
283
  - 0
284
- hash: 453966821948784652
284
+ hash: -2369072493348034064
285
285
  required_rubygems_version: !ruby/object:Gem::Requirement
286
286
  none: false
287
287
  requirements:
@@ -290,7 +290,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
290
290
  version: '0'
291
291
  segments:
292
292
  - 0
293
- hash: 453966821948784652
293
+ hash: -2369072493348034064
294
294
  requirements: []
295
295
  rubyforge_project:
296
296
  rubygems_version: 1.8.18