her 0.6.5 → 0.6.6

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.
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