Skip to content
Newer
Older
100644 261 lines (183 sloc) 8.29 KB
871ef96 @remiprev Add README
authored Apr 8, 2012
1 # Her
2
3 [![Build Status](https://secure.travis-ci.org/remiprev/her.png)](http://travis-ci.org/remiprev/her)
4
a6e9967 @remiprev Update README with more examples
authored Apr 9, 2012
5 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.
871ef96 @remiprev Add README
authored Apr 9, 2012
6
7 ## Installation
8
9 In your Gemfile, add:
10
60f209e @remiprev Update README
authored Apr 8, 2012
11 ```ruby
12 gem "her"
13 ```
871ef96 @remiprev Add README
authored Apr 9, 2012
14
6878101 @remiprev Update documentation with new API code
authored Apr 8, 2012
15 That’s it!
16
871ef96 @remiprev Add README
authored Apr 9, 2012
17 ## Usage
18
6878101 @remiprev Update documentation with new API code
authored Apr 9, 2012
19 First, you have to define which API your models will be bound to. For example, with Rails, you would create a new `config/initializers/her.rb` file with this line:
871ef96 @remiprev Add README
authored Apr 9, 2012
20
21 ```ruby
cdeb59f @remiprev Add Multiple APIs section in README
authored Apr 9, 2012
22 # config/initializers/her.rb
6878101 @remiprev Update documentation with new API code
authored Apr 9, 2012
23 Her::API.setup :base_uri => "https://api.example.com"
871ef96 @remiprev Add README
authored Apr 9, 2012
24 ```
25
6878101 @remiprev Update documentation with new API code
authored Apr 9, 2012
26 And then to add the ORM behavior to a class, you just have to include `Her::Model` in it:
871ef96 @remiprev Add README
authored Apr 9, 2012
27
28 ```ruby
29 class User
30 include Her::Model
31 end
32 ```
33
34 After that, using Her is very similar to many ActiveModel-like ORMs:
35
36 ```ruby
a6e9967 @remiprev Update README with more examples
authored Apr 9, 2012
37 User.all
cdeb59f @remiprev Add Multiple APIs section in README
authored Apr 9, 2012
38 # GET https://api.example.com/users and return an array of User objects
a6e9967 @remiprev Update README with more examples
authored Apr 9, 2012
39
40 User.find(1)
cdeb59f @remiprev Add Multiple APIs section in README
authored Apr 9, 2012
41 # GET https://api.example.com/users/1 and return a User object
a6e9967 @remiprev Update README with more examples
authored Apr 9, 2012
42
43 @user = User.create(:fullname => "Tobias Fünke")
bc5779a @remiprev Fix typo
authored Apr 9, 2012
44 # POST "https://api.example.com/users" with the data and return a User object
a6e9967 @remiprev Update README with more examples
authored Apr 9, 2012
45
46 @user = User.new(:fullname => "Tobias Fünke")
47 @user.occupation = "actor"
48 @user.save
cdeb59f @remiprev Add Multiple APIs section in README
authored Apr 9, 2012
49 # POST https://api.example.com/users with the data and return a User object
a6e9967 @remiprev Update README with more examples
authored Apr 9, 2012
50
51 @user = User.find(1)
52 @user.fullname = "Lindsay Fünke"
53 @user.save
cdeb59f @remiprev Add Multiple APIs section in README
authored Apr 9, 2012
54 # PUT https://api.example.com/users/1 with the data and return+update the User object
871ef96 @remiprev Add README
authored Apr 9, 2012
55 ```
60f209e @remiprev Update README
authored Apr 9, 2012
56
fd7dbca @remiprev Fix typo in README
authored Apr 16, 2012
57 ## Middleware
3b3c7ac @remiprev Improve middleware handling on API#setup
authored Apr 16, 2012
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.
60
61 ### Authentication
62
63 Her doesn’t support any kind of authentication. However, it’s very easy to implement one with a request middleware. Using the `add_middleware` key, we add it to the default list of middleware.
64
65 ```ruby
66 class MyAuthentication < Faraday::Middleware
67 def call(env)
68 env[:request_headers]["X-API-Token"] = "bb2b2dd75413d32c1ac421d39e95b978d1819ff611f68fc2fdd5c8b9c7331192"
69 @all.call(env)
70 end
71 end
72
73 Her::API.setup :base_uri => "https://api.example.com", :add_middleware => [MyAuthentication]
74 ```
75
76 Now, each HTTP request made by Her will have the `X-API-Token` header.
77
78 ### Parsing data
ab31348 @remiprev Add Parsing data section in README
authored Apr 9, 2012
79
80 By default, Her handles JSON data. It expects the data to be formatted in a certain structure. The default is this:
81
8f55b37 @remiprev Fix typo in README
authored Apr 9, 2012
82 ```javascript
ab31348 @remiprev Add Parsing data section in README
authored Apr 9, 2012
83 // The response of GET /users/1
3b3c7ac @remiprev Improve middleware handling on API#setup
authored Apr 16, 2012
84 { "data" : { "id" : 1, "name" : "Tobias Fünke" } }
ab31348 @remiprev Add Parsing data section in README
authored Apr 9, 2012
85
86 // The response of GET /users
3b3c7ac @remiprev Improve middleware handling on API#setup
authored Apr 16, 2012
87 { "data" : [{ "id" : 1, "name" : "Tobias Fünke" }] }
ab31348 @remiprev Add Parsing data section in README
authored Apr 9, 2012
88 ```
89
3b3c7ac @remiprev Improve middleware handling on API#setup
authored Apr 16, 2012
90 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 `parse_middleware` key, we then replace the default parser.
ab31348 @remiprev Add Parsing data section in README
authored Apr 9, 2012
91
92 ```ruby
3d1da8d @remiprev Add support for custom Faraday middleware
authored Apr 15, 2012
93 class MyCustomParser < Faraday::Response::Middleware
94 def on_complete(env)
95 json = JSON.parse(env[:body], :symbolize_names => true)
96 errors = json.delete(:errors) || []
97 metadata = json.delete(:metadata) || []
3b3c7ac @remiprev Improve middleware handling on API#setup
authored Apr 16, 2012
98 env[:body] = { :data => json, :errors => errors, :metadata => metadata }
3d1da8d @remiprev Add support for custom Faraday middleware
authored Apr 15, 2012
99 end
100 end
8a5bee6 @remiprev Fix a few typos
authored Apr 9, 2012
101
3b3c7ac @remiprev Improve middleware handling on API#setup
authored Apr 16, 2012
102 Her::API.setup :base_uri => "https://api.example.com", :parse_middleware => MyCustomParser
8a5bee6 @remiprev Fix a few typos
authored Apr 9, 2012
103 # User.find(1) will now expect "https://api.example.com/users/1" to return something like '{ "id": 1, "name": "Tobias Fünke" }'
ab31348 @remiprev Add Parsing data section in README
authored Apr 9, 2012
104 ```
105
6878101 @remiprev Update documentation with new API code
authored Apr 9, 2012
106 ## Relationships
107
875f744 @remiprev Update README for belongs_to
authored Apr 10, 2012
108 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.
313c487 @remiprev Add better documentation for relationships
authored Apr 9, 2012
109
875f744 @remiprev Update README for belongs_to
authored Apr 11, 2012
110 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).
313c487 @remiprev Add better documentation for relationships
authored Apr 9, 2012
111
112 For example, with this setup:
113
6878101 @remiprev Update documentation with new API code
authored Apr 9, 2012
114 ```ruby
115 class User
116 include Her::Model
117 has_many :comments
d997547 Updated readme for has_one relationship
jfcixmedia authored Apr 10, 2012
118 has_one :role
875f744 @remiprev Update README for belongs_to
authored Apr 11, 2012
119 belongs_to :organization
6878101 @remiprev Update documentation with new API code
authored Apr 9, 2012
120 end
121
122 class Comment
123 include Her::Model
124 end
d997547 Updated readme for has_one relationship
jfcixmedia authored Apr 11, 2012
125
126 class Role
127 include Her::Model
128 end
875f744 @remiprev Update README for belongs_to
authored Apr 11, 2012
129
130 class Organization
131 include Her::Model
132 end
313c487 @remiprev Add better documentation for relationships
authored Apr 9, 2012
133 ```
134
dc37005 @remiprev Better english in README
authored Apr 9, 2012
135 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:
6878101 @remiprev Update documentation with new API code
authored Apr 9, 2012
136
313c487 @remiprev Add better documentation for relationships
authored Apr 9, 2012
137 ```ruby
875f744 @remiprev Update README for belongs_to
authored Apr 11, 2012
138 @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" } }}
313c487 @remiprev Add better documentation for relationships
authored Apr 9, 2012
139 @user.comments # => [#<Comment id=1>, #<Comment id=2>] fetched directly from @user
d997547 Updated readme for has_one relationship
jfcixmedia authored Apr 11, 2012
140 @user.role # => #<Role id=1> fetched directly from @user
875f744 @remiprev Update README for belongs_to
authored Apr 11, 2012
141 @user.organization # => #<Organization id=2> fetched directly from @user
6878101 @remiprev Update documentation with new API code
authored Apr 9, 2012
142 ```
143
313c487 @remiprev Add better documentation for relationships
authored Apr 9, 2012
144 If there’s no relationship data in the resource, an extra HTTP request (to `GET /users/1/comments`) is made when calling the `#comments` method:
145
146 ```ruby
a6e9967 @remiprev Update README with more examples
authored Apr 9, 2012
147 @user = User.find(1) # { :data => { :id => 1, :name => "George Michael Bluth" }}
313c487 @remiprev Add better documentation for relationships
authored Apr 9, 2012
148 @user.comments # => [#<Comment id=1>, #<Comment id=2>] fetched from /users/1/comments
149 ```
150
d997547 Updated readme for has_one relationship
jfcixmedia authored Apr 11, 2012
151 For `has_one` relationship, an extra HTTP request (to `GET /users/1/role`) is made when calling the `#role` method:
152
153 ```ruby
154 @user = User.find(1) # { :data => { :id => 1, :name => "George Michael Bluth" }}
155 @user.role # => #<Role id=1> fetched from /users/1/role
156 ```
157
875f744 @remiprev Update README for belongs_to
authored Apr 11, 2012
158 For `belongs_to` relationship, an extra HTTP request (to `GET /organizations/2`) is made when calling the `#organization` method:
159
160 ```ruby
161 @user = User.find(1) # { :data => { :id => 1, :name => "George Michael Bluth", :organization_id => 2 }}
162 @user.organization # => #<Organization id=2> fetched from /organizations/2
163 ```
164
d997547 Updated readme for has_one relationship
jfcixmedia authored Apr 11, 2012
165 However, subsequent calls to `#comments` or `#role` will not trigger the extra HTTP request.
313c487 @remiprev Add better documentation for relationships
authored Apr 9, 2012
166
3bb8232 @remiprev Add documentation for hooks
authored Apr 15, 2012
167 ## Hooks
168
169 You can add *before* and *after* hooks to your models that are triggered on specific actions (`save`, `update`, `create`, `destroy`):
170
171 ```ruby
172 class User
173 include Her::Model
174 before_save :set_internal_id
175
176 def set_internal_id
177 self.internal_id = 42 # Will be passed in the HTTP request
178 end
179 end
ee90237 @remiprev Add example for hooks
authored Apr 15, 2012
180
181 @user = User.create(:fullname => "Tobias Fünke")
182 # POST /users&fullname=Tobias+Fünke&internal_id=42
3bb8232 @remiprev Add documentation for hooks
authored Apr 15, 2012
183 ```
184
185 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).
186
6878101 @remiprev Update documentation with new API code
authored Apr 9, 2012
187 ## Custom requests
188
a6e9967 @remiprev Update README with more examples
authored Apr 9, 2012
189 You can easily add custom methods for your models. You can either use `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 return from the HTTP request). Other HTTP methods are supported (`post_raw`, `put_resource`, etc.)
6878101 @remiprev Update documentation with new API code
authored Apr 9, 2012
190
17d968c @remiprev Improve support for custom methods
authored Apr 9, 2012
191 ```ruby
192 class User
193 include Her::Model
194
195 def self.popular
196 get_collection("/users/popular")
197 end
6878101 @remiprev Update documentation with new API code
authored Apr 9, 2012
198
17d968c @remiprev Improve support for custom methods
authored Apr 9, 2012
199 def self.total
200 get_raw("/users/stats") do |parsed_data|
201 parsed_data[:data][:total_users]
202 end
203 end
204 end
205
206 User.popular # => [#<User id=1>, #<User id=2>]
207 User.total # => 42
208 ```
7f7392f @remiprev Improve README and add LICENSE
authored Apr 9, 2012
209
cdeb59f @remiprev Add Multiple APIs section in README
authored Apr 9, 2012
210 ## Multiple APIs
211
212 It is possible to use different APIs for different models. Instead of calling `Her::API.setup`, you can create instances of `Her::API`:
213
214 ```ruby
215 # config/initializers/her.rb
216 $my_api = Her::API.new
217 $my_api.setup :base_uri => "https://my_api.example.com"
218
219 $other_api = Her::API.new
220 $other_api.setup :base_uri => "https://other_api.example.com"
221 ```
222
223 You can then define which API a model will use:
224
225 ```ruby
226 class User
227 include Her::Model
228 uses_api $my_api
229 end
230
231 class Category
232 include Her::Model
233 uses_api $other_api
234 end
235
236 User.all
237 # GET https://my_api.example.com/users
238
239 Category.all
240 # GET https://other_api.example.com/categories
241 ```
242
7f7392f @remiprev Improve README and add LICENSE
authored Apr 9, 2012
243 ## Things to be done
244
83d81a2 @remiprev Add item to todo list
authored Apr 15, 2012
245 * Add support for collection paths of nested resources (eg. `/users/:user_id/comments` for the `Comment` model)
a6e9967 @remiprev Update README with more examples
authored Apr 9, 2012
246 * Better error handling
7f7392f @remiprev Improve README and add LICENSE
authored Apr 9, 2012
247 * Better documentation
248
249 ## Contributors
250
7f9f399 @remiprev Add contributors, woohoo!
authored Apr 15, 2012
251 Feel free to contribute and submit issues/pull requests [on GitHub](https://github.com/remiprev/her/issues) like these fine folks did:
252
253 * [@jfcixmedia](https://github.com/jfcixmedia)
254 * [@EtienneLem](https://github.com/EtienneLem)
7f7392f @remiprev Improve README and add LICENSE
authored Apr 9, 2012
255
256 Take a look at the `spec` folder before you do, and make sure `bundle exec rake spec` passes after your modifications :)
257
258 ## License
259
e0f75c8 @remiprev Fix typo in README, d’uh.
authored Apr 9, 2012
260 Her is © 2012 [Rémi Prévost](http://exomel.com) and may be freely distributed under the [LITL license](https://github.com/remiprev/her/blob/master/LICENSE). See the `LICENSE` file.
Something went wrong with that request. Please try again.