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 +50 -24
- data/examples/twitter-oauth/app.rb +10 -3
- data/examples/twitter-search/Gemfile +1 -0
- data/examples/twitter-search/app.rb +25 -1
- data/lib/her/api.rb +7 -16
- data/lib/her/model.rb +3 -2
- data/lib/her/version.rb +1 -1
- data/spec/api_spec.rb +35 -4
- data/spec/model/paths_spec.rb +42 -27
- data/spec/spec_helper.rb +6 -1
- metadata +4 -4
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
|
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"] =
|
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
|
-
|
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[:
|
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
|
-
|
110
|
-
builder.
|
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 '{ "
|
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
|
-
|
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
|
165
|
-
|
166
|
-
|
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
|
-
|
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.
|
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
|
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`
|
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`
|
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`
|
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
|
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
|
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/"
|
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.
|
47
|
+
@tweets = Tweet.mentions
|
41
48
|
haml :index
|
42
49
|
end
|
@@ -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"
|
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
|
data/lib/her/api.rb
CHANGED
@@ -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
|
-
|
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 |
|
64
|
-
middleware.each do |
|
65
|
-
|
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
|
data/lib/her/model.rb
CHANGED
@@ -35,8 +35,9 @@ module Her
|
|
35
35
|
extend Her::Model::Paths
|
36
36
|
|
37
37
|
# Define default settings
|
38
|
-
|
39
|
-
|
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
|
data/lib/her/version.rb
CHANGED
data/spec/api_spec.rb
CHANGED
@@ -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
|
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 =>
|
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.
|
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" }
|
data/spec/model/paths_spec.rb
CHANGED
@@ -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
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
|
data/spec/spec_helper.rb
CHANGED
@@ -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,
|
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.
|
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-
|
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:
|
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:
|
293
|
+
hash: -2369072493348034064
|
294
294
|
requirements: []
|
295
295
|
rubyforge_project:
|
296
296
|
rubygems_version: 1.8.18
|