her 0.6.5 → 0.6.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZGIzMGM1MTY2YmRmZjZjNmJhNzhhOGQ0YjcxZmMxZDM3MTJlNWVlZg==
4
+ NWQ2MzM5ODU1NDgyMmFlODFmNzJiZWRkY2ViMGNhYmY0MDc2YTIyZQ==
5
5
  data.tar.gz: !binary |-
6
- NTM0Y2JjYWZiNjAxOTJhMTUyZjc0OTYwZjU1YTRkNGViOWM4MTcwZA==
6
+ YTUzYTM2MjY4ZGM2Njc3NDA2ZGRlMDUyYzc4MmNmOGFkODhjNmVlZA==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- MTExZDExMjk5ODU5MmQ1Nzg4YmIwNWEyYmJmZDRiMTFiMjdlNTkwNjViNzQw
10
- YjcxN2MyM2Q2ODlmZmNkODk0OTQyOGUwY2ViMWUwMTViMWI2OTMxYWVmZjNk
11
- NzQ0MzhiY2JlZGE2YWY4NzIyMTg1YWVhYjgwNTNhYjkzYzVjNjY=
9
+ ZmMyZTY3NTg1ODFjNDBkODYzYWFlZDg4YTZhNTM5Mzk2NzViYzZhZmI3NTQw
10
+ OTI5ZDQyOWVjZDg5MzRmNGVhMDYzOTcyYTlkM2MwNWI1NzYzYTIyNDU2NGE1
11
+ YTg3MDY3OWU5NTQxODFlYjhiY2Y0MGYwYTVkMjdlZTg3NzUzZmM=
12
12
  data.tar.gz: !binary |-
13
- YjJiN2Y5NGY5NWRjMTcyOTRiNDgwOGVjMjZmYTYxY2NmNjQ3NDhiNDUwMDg2
14
- MWFhNWVkNjA3OTBiNzQzOGRkNzU1ZTRlZWJjZmMyYWVmM2VjOTkwOTZlYjU3
15
- YzYyYjNkYjdhYzNhNTJmYjcyYzY1MGQyYzE1YjE2OWViMmI4ZjM=
13
+ ZGE4MjRkN2Y1NjFhYjcxNGE5MTI5ODAzMTdjNGZkMDgzZGNmYmRjODExNDBm
14
+ YTU0NDM4YjQ5ZGFhM2I0MTEyZDZlMTlmOTMxZjY4NjNiODVhYTczMjY1Njc0
15
+ YzFmMzQxOGU1MmU1ODEyYTY0MDc1YWQxODY2NmMyZmYwYTRlMjQ=
data/README.md CHANGED
@@ -25,10 +25,10 @@ First, you have to define which API your models will be bound to. For example, w
25
25
 
26
26
  ```ruby
27
27
  # config/initializers/her.rb
28
- Her::API.setup url: "https://api.example.com" do |connection|
29
- connection.use Faraday::Request::UrlEncoded
30
- connection.use Her::Middleware::DefaultParseJSON
31
- connection.use Faraday::Adapter::NetHttp
28
+ Her::API.setup url: "https://api.example.com" do |c|
29
+ c.use Faraday::Request::UrlEncoded
30
+ c.use Her::Middleware::DefaultParseJSON
31
+ c.use Faraday::Adapter::NetHttp
32
32
  end
33
33
  ```
34
34
 
@@ -44,10 +44,10 @@ After that, using Her is very similar to many ActiveRecord-like ORMs:
44
44
 
45
45
  ```ruby
46
46
  User.all
47
- # GET https://api.example.com/users and return an array of User objects
47
+ # GET "https://api.example.com/users" and return an array of User objects
48
48
 
49
49
  User.find(1)
50
- # GET https://api.example.com/users/1 and return a User object
50
+ # GET "https://api.example.com/users/1" and return a User object
51
51
 
52
52
  @user = User.create(fullname: "Tobias Fünke")
53
53
  # POST "https://api.example.com/users" with `fullname=Tobias+Fünke` and return the saved User object
@@ -55,12 +55,12 @@ User.find(1)
55
55
  @user = User.new(fullname: "Tobias Fünke")
56
56
  @user.occupation = "actor"
57
57
  @user.save
58
- # POST https://api.example.com/users with `fullname=Tobias+Fünke&occupation=actore` and return the saved User object
58
+ # POST "https://api.example.com/users" with `fullname=Tobias+Fünke&occupation=actor` and return the saved User object
59
59
 
60
60
  @user = User.find(1)
61
61
  @user.fullname = "Lindsay Fünke"
62
62
  @user.save
63
- # PUT https://api.example.com/users/1 with `fullname=Lindsay+Fünke` and return the updated User object
63
+ # PUT "https://api.example.com/users/1" with `fullname=Lindsay+Fünke` and return the updated User object
64
64
  ```
65
65
 
66
66
  ### ActiveRecord-like methods
@@ -74,30 +74,37 @@ end
74
74
 
75
75
  # Update a fetched resource
76
76
  user = User.find(1)
77
- user.fullname = "Lindsay Fünke"
78
- # OR user.assign_attributes(fullname: "Lindsay Fünke")
77
+ user.fullname = "Lindsay Fünke" # OR user.assign_attributes(fullname: "Lindsay Fünke")
79
78
  user.save
79
+ # PUT "/users/1" with `fullname=Lindsay+Fünke`
80
80
 
81
81
  # Update a resource without fetching it
82
82
  User.save_existing(1, fullname: "Lindsay Fünke")
83
+ # PUT "/users/1" with `fullname=Lindsay+Fünke`
83
84
 
84
85
  # Destroy a fetched resource
85
86
  user = User.find(1)
86
87
  user.destroy
88
+ # DELETE "/users/1"
87
89
 
88
90
  # Destroy a resource without fetching it
89
91
  User.destroy_existing(1)
92
+ # DELETE "/users/1"
90
93
 
91
94
  # Fetching a collection of resources
92
95
  User.all
96
+ # GET "/users"
93
97
  User.where(moderator: 1).all
98
+ # GET "/users?moderator=1"
94
99
 
95
100
  # Create a new resource
96
101
  User.create(fullname: "Maeby Fünke")
102
+ # POST "/users" with `fullname=Maeby+Fünke`
97
103
 
98
104
  # Save a new resource
99
105
  user = User.new(fullname: "Maeby Fünke")
100
106
  user.save
107
+ # POST "/users" with `fullname=Maeby+Fünke`
101
108
  ```
102
109
 
103
110
  You can look into the [`her-example`](https://github.com/remiprev/her-example) repository for a sample application using Her.
@@ -108,7 +115,7 @@ Since Her relies on [Faraday](https://github.com/lostisland/faraday) to send HTT
108
115
 
109
116
  ### Authentication
110
117
 
111
- Her doesn’t support authentication by default. However, it’s easy to implement one with request middleware. Using the `connection` block, we can add it to the middleware stack.
118
+ Her doesn’t support authentication by default. However, it’s easy to implement one with request middleware. Using the `setup` block, we can add it to the middleware stack.
112
119
 
113
120
  For example, to add a token header to your API requests in a Rails application, you could use the excellent [`request_store`](https://rubygems.org/gems/request_store) gem like this:
114
121
 
@@ -134,11 +141,11 @@ end
134
141
  # config/initializers/her.rb
135
142
  require "lib/my_token_authentication"
136
143
 
137
- Her::API.setup url: "https://api.example.com" do |connection|
138
- connection.use MyTokenAuthentication
139
- connection.use Faraday::Request::UrlEncoded
140
- connection.use Her::Middleware::DefaultParseJSON
141
- connection.use Faraday::Adapter::NetHttp
144
+ Her::API.setup url: "https://api.example.com" do |c|
145
+ c.use MyTokenAuthentication
146
+ c.use Faraday::Request::UrlEncoded
147
+ c.use Her::Middleware::DefaultParseJSON
148
+ c.use Faraday::Adapter::NetHttp
142
149
  end
143
150
  ```
144
151
 
@@ -167,10 +174,10 @@ TWITTER_CREDENTIALS = {
167
174
  token_secret: ""
168
175
  }
169
176
 
170
- Her::API.setup url: "https://api.twitter.com/1/" do |connection|
171
- connection.use FaradayMiddleware::OAuth, TWITTER_CREDENTIALS
172
- connection.use Her::Middleware::DefaultParseJSON
173
- connection.use Faraday::Adapter::NetHttp
177
+ Her::API.setup url: "https://api.twitter.com/1/" do |c|
178
+ c.use FaradayMiddleware::OAuth, TWITTER_CREDENTIALS
179
+ c.use Her::Middleware::DefaultParseJSON
180
+ c.use Faraday::Adapter::NetHttp
174
181
  end
175
182
 
176
183
  class Tweet
@@ -202,10 +209,7 @@ Also, you can define your own parsing method using a response middleware. The mi
202
209
  # Expects responses like:
203
210
  #
204
211
  # {
205
- # "result": {
206
- # "id": 1,
207
- # "name": "Tobias Fünke"
208
- # },
212
+ # "result": { "id": 1, "name": "Tobias Fünke" },
209
213
  # "errors": []
210
214
  # }
211
215
  #
@@ -220,9 +224,9 @@ class MyCustomParser < Faraday::Response::Middleware
220
224
  end
221
225
  end
222
226
 
223
- Her::API.setup url: "https://api.example.com" do |connection|
224
- connection.use MyCustomParser
225
- connection.use Faraday::Adapter::NetHttp
227
+ Her::API.setup url: "https://api.example.com" do |c|
228
+ c.use MyCustomParser
229
+ c.use Faraday::Adapter::NetHttp
226
230
  end
227
231
  ```
228
232
 
@@ -241,10 +245,10 @@ gem "memcached"
241
245
  In your Ruby code:
242
246
 
243
247
  ```ruby
244
- Her::API.setup url: "https://api.example.com" do |connection|
245
- connection.use FaradayMiddleware::Caching, Memcached::Rails.new('127.0.0.1:11211')
246
- connection.use Her::Middleware::DefaultParseJSON
247
- connection.use Faraday::Adapter::NetHttp
248
+ Her::API.setup url: "https://api.example.com" do |c|
249
+ c.use FaradayMiddleware::Caching, Memcached::Rails.new('127.0.0.1:11211')
250
+ c.use Her::Middleware::DefaultParseJSON
251
+ c.use Faraday::Adapter::NetHttp
248
252
  end
249
253
 
250
254
  class User
@@ -252,7 +256,7 @@ class User
252
256
  end
253
257
 
254
258
  @user = User.find(1)
255
- # GET /users/1
259
+ # GET "/users/1"
256
260
 
257
261
  @user = User.find(1)
258
262
  # This request will be fetched from memcached
@@ -264,12 +268,7 @@ Here’s a list of several useful features available in Her.
264
268
 
265
269
  ### Associations
266
270
 
267
- You can define `has_many`, `has_one` and `belongs_to` associations in your models. The association data is handled in two different ways.
268
-
269
- 1. If Her finds association data when parsing a resource, that data will be used to create the associated model objects on the resource.
270
- 2. If no association data was included when parsing a resource, calling a method with the same name as the association will fetch the data (providing there’s an HTTP request available for it in the API).
271
-
272
- For example:
271
+ Examples use this code:
273
272
 
274
273
  ```ruby
275
274
  class User
@@ -292,10 +291,18 @@ class Organization
292
291
  end
293
292
  ```
294
293
 
295
- If there’s association data in the resource, no extra HTTP request is made when calling the `#comments` method and an array of resources is returned:
294
+ #### Fetching data
295
+
296
+ You can define `has_many`, `has_one` and `belongs_to` associations in your models. The association data is handled in two different ways.
297
+
298
+ 1. If Her finds association data when parsing a resource, that data will be used to create the associated model objects on the resource.
299
+ 2. If no association data was included when parsing a resource, calling a method with the same name as the association will fetch the data (providing there’s an HTTP request available for it in the API).
300
+
301
+ For example, if there’s association data in the resource, no extra HTTP request is made when calling the `#comments` method and an array of resources is returned:
296
302
 
297
303
  ```ruby
298
304
  @user = User.find(1)
305
+ # GET "/users/1", response is:
299
306
  # {
300
307
  # "id": 1,
301
308
  # "name": "George Michael Bluth",
@@ -306,43 +313,60 @@ If there’s association data in the resource, no extra HTTP request is made whe
306
313
  # "role": { "id": 1, "name": "Admin" },
307
314
  # "organization": { "id": 2, "name": "Bluth Company" }
308
315
  # }
316
+
309
317
  @user.comments
310
- # [#<Comment id=1 text="Foo">, #<Comment id=2 text="Bar">]
318
+ # => [#<Comment id=1 text="Foo">, #<Comment id=2 text="Bar">]
319
+
311
320
  @user.role
312
- # #<Role id=1 name="Admin">
321
+ # => #<Role id=1 name="Admin">
322
+
313
323
  @user.organization
314
- # #<Organization id=2 name="Bluth Company">
324
+ # => #<Organization id=2 name="Bluth Company">
315
325
  ```
316
326
 
317
327
  If there’s no association data in the resource, Her makes a HTTP request to retrieve the data.
318
328
 
319
329
  ```ruby
320
330
  @user = User.find(1)
321
- # { "id": 1, "name": "George Michael Bluth", "organization_id": 2 }
331
+ # GET "/users/1", response is { "id": 1, "name": "George Michael Bluth", "organization_id": 2 }
322
332
 
323
333
  # has_many association:
324
334
  @user.comments
325
- # GET /users/1/comments
326
- # [#<Comment id=1>, #<Comment id=2>]
335
+ # GET "/users/1/comments"
336
+ # => [#<Comment id=1>, #<Comment id=2>]
327
337
 
328
338
  @user.comments.where(approved: 1)
329
- # GET /users/1/comments?approved=1
330
- # [#<Comment id=1>]
339
+ # GET "/users/1/comments?approved=1"
340
+ # => [#<Comment id=1>]
331
341
 
332
342
  # has_one association:
333
343
  @user.role
334
- # GET /users/1/role
335
- # #<Role id=1>
344
+ # GET "/users/1/role"
345
+ # => #<Role id=1>
336
346
 
337
347
  # belongs_to association:
338
348
  @user.organization
339
349
  # (the organization id comes from :organization_id, by default)
340
- # GET /organizations/2
341
- # #<Organization id=2>
350
+ # GET "/organizations/2"
351
+ # => #<Organization id=2>
342
352
  ```
343
353
 
344
354
  Subsequent calls to `#comments`, `#role` and `#organization` will not trigger extra HTTP requests and will return the cached objects.
345
355
 
356
+ #### Creating data
357
+
358
+ You can use the association methods to build new objects and save them.
359
+
360
+ ```ruby
361
+ @user = User.find(1)
362
+ @user.comments.build(body: "Just a draft")
363
+ # => [#<Comment body="Just a draft" user_id=1>]
364
+
365
+ @user.comments.create(body: "Hello world.")
366
+ # POST "/users/1/comments" with `body=Hello+world.`
367
+ # => [#<Comment id=3 body="Hello world." user_id=1>]
368
+ ```
369
+
346
370
  #### Notes about paths
347
371
 
348
372
  Resources must always have all the required attributes to build their complete path. For example, if you have these models:
@@ -384,7 +408,7 @@ end
384
408
  @user.valid? # => false
385
409
 
386
410
  @user.save
387
- # POST /users&fullname=Tobias+Fünke will still be called, even if the user is not valid
411
+ # POST "/users" with `fullname=Tobias+Fünke` will still be called, even if the user is not valid
388
412
  ```
389
413
 
390
414
  ### Dirty attributes
@@ -403,7 +427,7 @@ end
403
427
  @user.changes # => { :fullname => [nil, "Tobias Fünke"] }
404
428
 
405
429
  @user.save
406
- # POST /users&fullname=Tobias+Fünke
430
+ # POST "/users" with `fullname=Tobias+Fünke`
407
431
 
408
432
  @user.fullname_changed? # => false
409
433
  @user.changes # => {}
@@ -425,7 +449,7 @@ class User
425
449
  end
426
450
 
427
451
  @user = User.create(fullname: "Tobias Funke")
428
- # POST /users&fullname=Tobias+Fünke&internal_id=42
452
+ # POST "/users" with `fullname=Tobias+Fünke&internal_id=42`
429
453
 
430
454
  @user = User.find(1)
431
455
  @user.fullname # => "TOBIAS FUNKE"
@@ -442,6 +466,7 @@ The available callbacks are:
442
466
  * `after_update`
443
467
  * `after_destroy`
444
468
  * `after_find`
469
+ * `after_initialize`
445
470
 
446
471
  ### JSON attributes-wrapping
447
472
 
@@ -463,10 +488,10 @@ class Article
463
488
  end
464
489
 
465
490
  User.create(fullname: "Tobias Fünke")
466
- # POST { "user": { "fullname": "Tobias Fünke" } } to /users
491
+ # POST "/users" with `user[fullname]=Tobias+Fünke`
467
492
 
468
493
  Article.create(title: "Hello world.")
469
- # POST { "post": { "title": "Hello world." } } to /articles
494
+ # POST "/articles" with `post[title]=Hello+world`
470
495
  ```
471
496
 
472
497
  #### Parsing
@@ -484,12 +509,12 @@ class Article
484
509
  parse_root_in_json :post
485
510
  end
486
511
 
487
- # POST /users returns { "user": { "fullname": "Tobias Fünke" } }
488
512
  user = User.create(fullname: "Tobias Fünke")
513
+ # POST "/users" with `fullname=Tobias+Fünke`, response is { "user": { "fullname": "Tobias Fünke" } }
489
514
  user.fullname # => "Tobias Fünke"
490
515
 
491
- # POST /articles returns { "post": { "title": "Hello world." } }
492
516
  article = Article.create(title: "Hello world.")
517
+ # POST "/articles" with `title=Hello+world.`, response is { "post": { "title": "Hello world." } }
493
518
  article.title # => "Hello world."
494
519
  ```
495
520
 
@@ -508,16 +533,16 @@ class User
508
533
  end
509
534
 
510
535
  User.popular
511
- # GET /users/popular
512
- # [#<User id=1>, #<User id=2>]
536
+ # GET "/users/popular"
537
+ # => [#<User id=1>, #<User id=2>]
513
538
 
514
539
  User.unpopular
515
- # GET /users/unpopular
516
- # [#<User id=3>, #<User id=4>]
540
+ # GET "/users/unpopular"
541
+ # => [#<User id=3>, #<User id=4>]
517
542
 
518
543
  User.from_default(name: "Maeby Fünke")
519
- # POST /users/from_default with `name=Maeby+Fünke`
520
- # #<User id=5 name="Maeby Fünke">
544
+ # POST "/users/from_default" with `name=Maeby+Fünke`
545
+ # => #<User id=5 name="Maeby Fünke">
521
546
  ```
522
547
 
523
548
  You can also use `get`, `post`, `put` or `delete` (which maps the returned data to either a collection or a resource).
@@ -528,24 +553,20 @@ class User
528
553
  end
529
554
 
530
555
  User.get(:popular)
531
- # GET /users/popular
532
- # [#<User id=1>, #<User id=2>]
556
+ # GET "/users/popular"
557
+ # => [#<User id=1>, #<User id=2>]
533
558
 
534
559
  User.get(:single_best)
535
- # GET /users/single_best
536
- # #<User id=1>
560
+ # GET "/users/single_best"
561
+ # => #<User id=1>
537
562
  ```
538
563
 
539
- Also, `get_collection` (which maps the returned data to a collection of resources), `get_resource` (which maps the returned data to a single resource) or `get_raw` (which yields the parsed data and the raw response from the HTTP request) can also be used. Other HTTP methods are supported (`post_raw`, `put_resource`, etc.).
564
+ You can also use `get_raw` which yields the parsed data and the raw response from the HTTP request. Other HTTP methods are supported (`post_raw`, `put_raw`, etc.).
540
565
 
541
566
  ```ruby
542
567
  class User
543
568
  include Her::Model
544
569
 
545
- def self.popular
546
- get_collection(:popular)
547
- end
548
-
549
570
  def self.total
550
571
  get_raw(:stats) do |parsed_data, response|
551
572
  parsed_data[:data][:total_users]
@@ -553,12 +574,8 @@ class User
553
574
  end
554
575
  end
555
576
 
556
- User.popular
557
- # GET /users/popular
558
- # [#<User id=1>, #<User id=2>]
559
-
560
577
  User.total
561
- # GET /users/stats
578
+ # GET "/users/stats"
562
579
  # => 42
563
580
  ```
564
581
 
@@ -570,8 +587,8 @@ class User
570
587
  end
571
588
 
572
589
  User.get("/users/popular")
573
- # GET /users/popular
574
- # [#<User id=1>, #<User id=2>]
590
+ # GET "/users/popular"
591
+ # => [#<User id=1>, #<User id=2>]
575
592
  ```
576
593
 
577
594
  ### Custom paths
@@ -585,7 +602,7 @@ class User
585
602
  end
586
603
 
587
604
  @user = User.find(1)
588
- # GET /hello_users/1
605
+ # GET "/hello_users/1"
589
606
  ```
590
607
 
591
608
  You can also include custom variables in your paths:
@@ -597,14 +614,14 @@ class User
597
614
  end
598
615
 
599
616
  @user = User.find(1, _organization_id: 2)
600
- # GET /organizations/2/users/1
617
+ # GET "/organizations/2/users/1"
601
618
 
602
619
  @user = User.all(_organization_id: 2)
603
- # GET /organizations/2/users
620
+ # GET "/organizations/2/users"
604
621
 
605
622
  @user = User.new(fullname: "Tobias Fünke", organization_id: 2)
606
623
  @user.save
607
- # POST /organizations/2/users with `fullname=Tobias+Fünke`
624
+ # POST "/organizations/2/users" with `fullname=Tobias+Fünke`
608
625
  ```
609
626
 
610
627
  ### Custom primary keys
@@ -617,8 +634,11 @@ class User
617
634
  primary_key :_id
618
635
  end
619
636
 
620
- user = User.find("4fd89a42ff204b03a905c535") # GET /users/1 returns { "_id": "4fd89a42ff204b03a905c535", "name": "Tobias" }
621
- user.save # PUT /users/4fd89a42ff204b03a905c535
637
+ user = User.find("4fd89a42ff204b03a905c535")
638
+ # GET "/users/1", response is { "_id": "4fd89a42ff204b03a905c535", "name": "Tobias" }
639
+
640
+ user.destroy
641
+ # DELETE "/users/4fd89a42ff204b03a905c535"
622
642
  ```
623
643
 
624
644
  ### Inheritance
@@ -644,7 +664,7 @@ class User < MyAPI::Model
644
664
  end
645
665
 
646
666
  User.find(1)
647
- # GET /users/1
667
+ # GET "/users/1"
648
668
  ```
649
669
 
650
670
  ### Scopes
@@ -661,13 +681,13 @@ class User
661
681
  end
662
682
 
663
683
  @admins = User.admins
664
- # GET /users?role=admin
684
+ # GET "/users?role=admin"
665
685
 
666
686
  @moderators = User.by_role('moderator')
667
- # GET /users?role=moderator
687
+ # GET "/users?role=moderator"
668
688
 
669
689
  @active_admins = User.active.admins # @admins.active would have worked here too
670
- # GET /users?role=admin&active=1
690
+ # GET "/users?role=admin&active=1"
671
691
  ```
672
692
 
673
693
  A neat trick you can do with scopes is interact with complex paths.
@@ -681,10 +701,10 @@ class User
681
701
  end
682
702
 
683
703
  @user = User.for_organization(3).find(2)
684
- # GET /organizations/3/users/2
704
+ # GET "/organizations/3/users/2"
685
705
 
686
706
  @user = User.for_organization(3).create(fullname: "Tobias Fünke")
687
- # POST /organizations/3 with `fullname=Tobias+Fünke`
707
+ # POST "/organizations/3" with `fullname=Tobias+Fünke`
688
708
  ```
689
709
 
690
710
  ### Multiple APIs
@@ -694,15 +714,15 @@ It is possible to use different APIs for different models. Instead of calling `H
694
714
  ```ruby
695
715
  # config/initializers/her.rb
696
716
  MY_API = Her::API.new
697
- MY_API.setup url: "https://my-api.example.com" do |connection|
698
- connection.use Her::Middleware::DefaultParseJSON
699
- connection.use Faraday::Adapter::NetHttp
717
+ MY_API.setup url: "https://my-api.example.com" do |c|
718
+ c.use Her::Middleware::DefaultParseJSON
719
+ c.use Faraday::Adapter::NetHttp
700
720
  end
701
721
 
702
722
  OTHER_API = Her::API.new
703
- OTHER_API.setup url: "https://other-api.example.com" do |connection|
704
- connection.use Her::Middleware::DefaultParseJSON
705
- connection.use Faraday::Adapter::NetHttp
723
+ OTHER_API.setup url: "https://other-api.example.com" do |c|
724
+ c.use Her::Middleware::DefaultParseJSON
725
+ c.use Faraday::Adapter::NetHttp
706
726
  end
707
727
  ```
708
728
 
@@ -720,10 +740,10 @@ class Category
720
740
  end
721
741
 
722
742
  User.all
723
- # GET https://my-api.example.com/users
743
+ # GET "https://my-api.example.com/users"
724
744
 
725
745
  Category.all
726
- # GET https://other-api.example.com/categories
746
+ # GET "https://other-api.example.com/categories"
727
747
  ```
728
748
 
729
749
  ### SSL
@@ -732,9 +752,9 @@ When initializing `Her::API`, you can pass any parameter supported by `Faraday.n
732
752
 
733
753
  ```ruby
734
754
  ssl_options = { ca_path: "/usr/lib/ssl/certs" }
735
- Her::API.setup url: "https://api.example.com", ssl: ssl_options do |connection|
736
- connection.use Her::Middleware::DefaultParseJSON
737
- connection.use Faraday::Adapter::NetHttp
755
+ Her::API.setup url: "https://api.example.com", ssl: ssl_options do |c|
756
+ c.use Her::Middleware::DefaultParseJSON
757
+ c.use Faraday::Adapter::NetHttp
738
758
  end
739
759
  ```
740
760
 
@@ -767,9 +787,9 @@ RSpec.configure do |config|
767
787
 
768
788
  # Here, you would customize this for your own API (URL, middleware, etc)
769
789
  # like you have done in your application’s initializer
770
- api.setup url: "http://api.example.com" do |connection|
771
- connection.use Her::Middleware::FirstLevelParseJSON
772
- connection.adapter(:test) { |s| yield(s) }
790
+ api.setup url: "http://api.example.com" do |c|
791
+ c.use Her::Middleware::FirstLevelParseJSON
792
+ c.adapter(:test) { |s| yield(s) }
773
793
  end
774
794
  end
775
795
  end)
@@ -836,16 +856,18 @@ Most projects I know that use Her are internal or private projects but here’s
836
856
 
837
857
  * [tumbz](https://github.com/remiprev/tumbz)
838
858
  * [crowdher](https://github.com/simonprev/crowdher)
859
+ * [vodka](https://github.com/magnolia-fan/vodka)
860
+ * [webistrano_cli](https://github.com/chytreg/webistrano_cli)
839
861
 
840
862
  ## History
841
863
 
842
864
  I told myself a few months ago that it would be great to build a gem to replace Rails’ [ActiveResource](http://api.rubyonrails.org/classes/ActiveResource/Base.html) since it was barely maintained (and now removed from Rails 4.0), lacking features and hard to extend/customize. I had built a few of these REST-powered ORMs for client projects before but I decided I wanted to write one for myself that I could release as an open-source project.
843
865
 
844
- Most of Her’s core codebase was written on a Saturday morning of April 2012 ([first commit](https://github.com/remiprev/her/commit/689d8e88916dc2ad258e69a2a91a283f061cbef2) at 7am!).
866
+ Most of Her’s core concepts were written on a Saturday morning of April 2012 ([first commit](https://github.com/remiprev/her/commit/689d8e88916dc2ad258e69a2a91a283f061cbef2) at 7am!).
845
867
 
846
868
  ## Contribute
847
869
 
848
- Yes please! Feel free to contribute and submit issues/pull requests [on GitHub](https://github.com/remiprev/her/issues).
870
+ Yes please! Feel free to contribute and submit issues/pull requests [on GitHub](https://github.com/remiprev/her/issues). There’s no such thing as a bad pull request — even if it’s for a typo, a small improvement to the code or the documentation!
849
871
 
850
872
  See [CONTRIBUTING.md](https://github.com/remiprev/her/blob/master/CONTRIBUTING.md) for best practices.
851
873
 
data/lib/her/model.rb CHANGED
@@ -63,7 +63,7 @@ module Her
63
63
 
64
64
  # Configure ActiveModel callbacks
65
65
  extend ActiveModel::Callbacks
66
- define_model_callbacks :create, :update, :save, :find, :destroy
66
+ define_model_callbacks :create, :update, :save, :find, :destroy, :initialize
67
67
  end
68
68
  end
69
69
  end
@@ -25,6 +25,7 @@ module Her
25
25
 
26
26
  attributes = self.class.default_scope.apply_to(attributes)
27
27
  assign_attributes(attributes)
28
+ run_callbacks :initialize
28
29
  end
29
30
 
30
31
  # Initialize a collection of resources
@@ -62,6 +62,7 @@ module Her
62
62
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
63
63
  def #{method}(path, params={})
64
64
  path = build_request_path_from_string_or_symbol(path, params)
65
+ params = to_params(params) unless #{method.to_sym.inspect} == :get
65
66
  send(:'#{method}_raw', path, params) do |parsed_data, response|
66
67
  if parsed_data[:data].is_a?(Array)
67
68
  new_collection(parsed_data)
@@ -10,7 +10,7 @@ module Her
10
10
  # @user.to_params
11
11
  # # => { :id => 1, :name => 'John Smith' }
12
12
  def to_params
13
- self.class.include_root_in_json? ? { self.class.included_root_element => attributes.dup.symbolize_keys } : attributes.dup.symbolize_keys
13
+ self.class.to_params(self.attributes)
14
14
  end
15
15
 
16
16
  module ClassMethods
@@ -22,6 +22,11 @@ module Her
22
22
  parse_root_in_json? ? data[parsed_root_element] : data
23
23
  end
24
24
 
25
+ # @private
26
+ def to_params(attributes)
27
+ include_root_in_json? ? { included_root_element => attributes.dup.symbolize_keys } : attributes.dup.symbolize_keys
28
+ end
29
+
25
30
  # Return or change the value of `include_root_in_json`
26
31
  #
27
32
  # @example
data/lib/her/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Her
2
- VERSION = "0.6.5"
2
+ VERSION = "0.6.6"
3
3
  end
@@ -117,4 +117,29 @@ describe "Her::Model and ActiveModel::Callbacks" do
117
117
  its(:name) { should == "TOBIAS FUNKE" }
118
118
  end
119
119
  end
120
+
121
+ context :after_initialize do
122
+ subject { Foo::User.new(:name => "Tobias Funke") }
123
+
124
+ context "when using a symbol callback" do
125
+ before do
126
+ class Foo::User
127
+ after_initialize :alter_name
128
+ def alter_name; self.name.upcase!; end
129
+ end
130
+ end
131
+
132
+ its(:name) { should == "TOBIAS FUNKE" }
133
+ end
134
+
135
+ context "when using a block callback" do
136
+ before do
137
+ class Foo::User
138
+ after_initialize lambda { self.name.upcase! }
139
+ end
140
+ end
141
+
142
+ its(:name) { should == "TOBIAS FUNKE" }
143
+ end
144
+ end
120
145
  end
@@ -3,23 +3,43 @@ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
3
3
 
4
4
  describe Her::Model::Parse do
5
5
  context "when include_root_in_json is set" do
6
+ before do
7
+ Her::API.setup :url => "https://api.example.com" do |builder|
8
+ builder.use Her::Middleware::FirstLevelParseJSON
9
+ builder.use Faraday::Request::UrlEncoded
10
+ end
11
+
12
+ Her::API.default_api.connection.adapter :test do |stub|
13
+ stub.post("/users") { |env| [200, {}, { :user => { :id => 1, :fullname => params(env)[:user][:fullname] } }.to_json] }
14
+ stub.post("/users/admins") { |env| [200, {}, { :user => { :id => 1, :fullname => params(env)[:user][:fullname] } }.to_json] }
15
+ end
16
+ end
17
+
6
18
  context "to true" do
7
19
  before do
8
20
  spawn_model "Foo::User" do
9
21
  include_root_in_json true
22
+ parse_root_in_json true
23
+ custom_post :admins
10
24
  end
11
25
  end
12
26
 
13
- it "wraps params in the element name" do
27
+ it "wraps params in the element name in `to_params`" do
14
28
  @new_user = Foo::User.new(:fullname => "Tobias Fünke")
15
29
  @new_user.to_params.should == { :user => { :fullname => "Tobias Fünke" } }
16
30
  end
31
+
32
+ it "wraps params in the element name in `.create`" do
33
+ @new_user = Foo::User.admins(:fullname => "Tobias Fünke")
34
+ @new_user.fullname.should == "Tobias Fünke"
35
+ end
17
36
  end
18
37
 
19
38
  context "to a symbol" do
20
39
  before do
21
40
  spawn_model "Foo::User" do
22
41
  include_root_in_json :person
42
+ parse_root_in_json :person
23
43
  end
24
44
  end
25
45
 
@@ -57,11 +77,15 @@ describe Her::Model::Parse do
57
77
  Her::API.default_api.connection.adapter :test do |stub|
58
78
  stub.post("/users") { |env| [200, {}, { :user => { :id => 1, :fullname => "Lindsay Fünke" } }.to_json] }
59
79
  stub.get("/users") { |env| [200, {}, [{ :user => { :id => 1, :fullname => "Lindsay Fünke" } }].to_json] }
80
+ stub.get("/users/admins") { |env| [200, {}, [{ :user => { :id => 1, :fullname => "Lindsay Fünke" } }].to_json] }
60
81
  stub.get("/users/1") { |env| [200, {}, { :user => { :id => 1, :fullname => "Lindsay Fünke" } }.to_json] }
61
82
  stub.put("/users/1") { |env| [200, {}, { :user => { :id => 1, :fullname => "Tobias Fünke Jr." } }.to_json] }
62
83
  end
63
84
 
64
- spawn_model("Foo::User") { parse_root_in_json true }
85
+ spawn_model("Foo::User") do
86
+ parse_root_in_json true
87
+ custom_get :admins
88
+ end
65
89
  end
66
90
 
67
91
  it "parse the data from the JSON root element after .create" do
@@ -69,6 +93,11 @@ describe Her::Model::Parse do
69
93
  @new_user.fullname.should == "Lindsay Fünke"
70
94
  end
71
95
 
96
+ it "parse the data from the JSON root element after an arbitrary HTTP request" do
97
+ @new_user = Foo::User.admins
98
+ @new_user.first.fullname.should == "Lindsay Fünke"
99
+ end
100
+
72
101
  it "parse the data from the JSON root element after .all" do
73
102
  @users = Foo::User.all
74
103
  @users.first.fullname.should == "Lindsay Fünke"
@@ -204,4 +204,23 @@ describe Her::Model::Relation do
204
204
  it("should apply the scope to the request") { Foo::User.all.first.should be_active }
205
205
  end
206
206
  end
207
+
208
+ describe :map do
209
+ before do
210
+ Her::API.setup :url => "https://api.example.com" do |builder|
211
+ builder.use Her::Middleware::FirstLevelParseJSON
212
+ builder.adapter :test do |stub|
213
+ stub.get("/users") do |env|
214
+ ok! [{ :id => 1, :fullname => "Tobias Fünke" }, { :id => 2, :fullname => "Lindsay Fünke" }]
215
+ end
216
+ end
217
+ end
218
+
219
+ spawn_model 'Foo::User'
220
+ end
221
+
222
+ it "delegates the method to the fetched collection" do
223
+ Foo::User.all.map(&:fullname).should == ["Tobias Fünke", "Lindsay Fünke"]
224
+ end
225
+ end
207
226
  end
@@ -11,7 +11,7 @@ module Her
11
11
  end
12
12
 
13
13
  def params(env)
14
- @params ||= Faraday::Utils.parse_query(env[:body]).with_indifferent_access.merge(env[:params])
14
+ @params ||= Faraday::Utils.parse_nested_query(env[:body]).with_indifferent_access.merge(env[:params])
15
15
  end
16
16
  end
17
17
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: her
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.5
4
+ version: 0.6.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rémi Prévost
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-04-27 00:00:00.000000000 Z
11
+ date: 2013-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake