Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 306 lines (216 sloc) 9.439 kb
871ef96 @remiprev Add README
authored
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
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
6
7 ## Installation
8
9 In your Gemfile, add:
10
60f209e @remiprev Update README
authored
11 ```ruby
12 gem "her"
13 ```
871ef96 @remiprev Add README
authored
14
6878101 @remiprev Update documentation with new API code
authored
15 That’s it!
16
871ef96 @remiprev Add README
authored
17 ## Usage
18
6878101 @remiprev Update documentation with new API code
authored
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
20
21 ```ruby
cdeb59f @remiprev Add Multiple APIs section in README
authored
22 # config/initializers/her.rb
6878101 @remiprev Update documentation with new API code
authored
23 Her::API.setup :base_uri => "https://api.example.com"
871ef96 @remiprev Add README
authored
24 ```
25
6878101 @remiprev Update documentation with new API code
authored
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
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
37 User.all
cdeb59f @remiprev Add Multiple APIs section in README
authored
38 # GET https://api.example.com/users and return an array of User objects
a6e9967 @remiprev Update README with more examples
authored
39
40 User.find(1)
cdeb59f @remiprev Add Multiple APIs section in README
authored
41 # GET https://api.example.com/users/1 and return a User object
a6e9967 @remiprev Update README with more examples
authored
42
43 @user = User.create(:fullname => "Tobias Fünke")
bc5779a @remiprev Fix typo
authored
44 # POST "https://api.example.com/users" with the data and return a User object
a6e9967 @remiprev Update README with more examples
authored
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
49 # POST https://api.example.com/users with the data and return a User object
a6e9967 @remiprev Update README with more examples
authored
50
51 @user = User.find(1)
52 @user.fullname = "Lindsay Fünke"
53 @user.save
cdeb59f @remiprev Add Multiple APIs section in README
authored
54 # PUT https://api.example.com/users/1 with the data and return+update the User object
871ef96 @remiprev Add README
authored
55 ```
60f209e @remiprev Update README
authored
56
fd7dbca @remiprev Fix typo in README
authored
57 ## Middleware
3b3c7ac @remiprev Improve middleware handling on API#setup
authored
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
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
82 ```javascript
ab31348 @remiprev Add Parsing data section in README
authored
83 // The response of GET /users/1
3b3c7ac @remiprev Improve middleware handling on API#setup
authored
84 { "data" : { "id" : 1, "name" : "Tobias Fünke" } }
ab31348 @remiprev Add Parsing data section in README
authored
85
86 // The response of GET /users
3b3c7ac @remiprev Improve middleware handling on API#setup
authored
87 { "data" : [{ "id" : 1, "name" : "Tobias Fünke" }] }
ab31348 @remiprev Add Parsing data section in README
authored
88 ```
89
3b3c7ac @remiprev Improve middleware handling on API#setup
authored
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
91
92 ```ruby
3d1da8d @remiprev Add support for custom Faraday middleware
authored
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
98 env[:body] = { :data => json, :errors => errors, :metadata => metadata }
3d1da8d @remiprev Add support for custom Faraday middleware
authored
99 end
100 end
8a5bee6 @remiprev Fix a few typos
authored
101
3b3c7ac @remiprev Improve middleware handling on API#setup
authored
102 Her::API.setup :base_uri => "https://api.example.com", :parse_middleware => MyCustomParser
8a5bee6 @remiprev Fix a few typos
authored
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
104 ```
105
6878101 @remiprev Update documentation with new API code
authored
106 ## Relationships
107
875f744 @remiprev Update README for belongs_to
authored
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
109
875f744 @remiprev Update README for belongs_to
authored
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
111
112 For example, with this setup:
113
6878101 @remiprev Update documentation with new API code
authored
114 ```ruby
115 class User
116 include Her::Model
117 has_many :comments
d997547 Updated readme for has_one relationship
jfcixmedia authored
118 has_one :role
875f744 @remiprev Update README for belongs_to
authored
119 belongs_to :organization
6878101 @remiprev Update documentation with new API code
authored
120 end
121
122 class Comment
123 include Her::Model
124 end
d997547 Updated readme for has_one relationship
jfcixmedia authored
125
126 class Role
127 include Her::Model
128 end
875f744 @remiprev Update README for belongs_to
authored
129
130 class Organization
131 include Her::Model
132 end
313c487 @remiprev Add better documentation for relationships
authored
133 ```
134
dc37005 @remiprev Better english in README
authored
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
136
313c487 @remiprev Add better documentation for relationships
authored
137 ```ruby
875f744 @remiprev Update README for belongs_to
authored
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
139 @user.comments # => [#<Comment id=1>, #<Comment id=2>] fetched directly from @user
d997547 Updated readme for has_one relationship
jfcixmedia authored
140 @user.role # => #<Role id=1> fetched directly from @user
875f744 @remiprev Update README for belongs_to
authored
141 @user.organization # => #<Organization id=2> fetched directly from @user
6878101 @remiprev Update documentation with new API code
authored
142 ```
143
313c487 @remiprev Add better documentation for relationships
authored
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
147 @user = User.find(1) # { :data => { :id => 1, :name => "George Michael Bluth" }}
313c487 @remiprev Add better documentation for relationships
authored
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
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
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
165 However, subsequent calls to `#comments` or `#role` will not trigger the extra HTTP request.
313c487 @remiprev Add better documentation for relationships
authored
166
3bb8232 @remiprev Add documentation for hooks
authored
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
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
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
187 ## Custom requests
188
2c0c171 @remiprev Add support for custom_<method> methods
authored
189 You can easily define custom requests for your models using `custom_get`, `custom_post`, etc.
190
191 ```ruby
192 class User
193 include Her::Model
194 custom_get :popular, :unpopular
195 custom_post :from_default
196 end
197
198 User.popular # => [#<User id=1>, #<User id=2>]
199 # GET /users/popular
200
201 User.unpopular # => [#<User id=3>, #<User id=4>]
202 # GET /users/unpopular
203
204 User.from_default(:name => "Maeby Fünke") # => #<User id=5>
205 # POST /users/from_default?name=Maeby+Fünke
206 ```
207
208 You can also use `get`, `post`, `put` or `delete` (which maps the returned data to either a collection or a resource).
6878101 @remiprev Update documentation with new API code
authored
209
17d968c @remiprev Improve support for custom methods
authored
210 ```ruby
211 class User
212 include Her::Model
a7ae25f @remiprev Add support for symbols in custom requests
authored
213 end
214
37aef13 @remiprev Add support for `get`, `post`, etc. wrapper methods
authored
215 User.get(:popular) # => [#<User id=1>, #<User id=2>]
a7ae25f @remiprev Add support for symbols in custom requests
authored
216 # GET /users/popular
37aef13 @remiprev Add support for `get`, `post`, etc. wrapper methods
authored
217
218 User.get(:single_best) # => #<User id=1>
219 # GET /users/single_best
a7ae25f @remiprev Add support for symbols in custom requests
authored
220 ```
221
37aef13 @remiprev Add support for `get`, `post`, etc. wrapper methods
authored
222 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 return from the HTTP request) can also be used. Other HTTP methods are supported (`post_raw`, `put_resource`, etc.).
a7ae25f @remiprev Add support for symbols in custom requests
authored
223
224 ```ruby
225 class User
226 include Her::Model
17d968c @remiprev Improve support for custom methods
authored
227
228 def self.popular
37aef13 @remiprev Add support for `get`, `post`, etc. wrapper methods
authored
229 get_collection(:popular)
17d968c @remiprev Improve support for custom methods
authored
230 end
6878101 @remiprev Update documentation with new API code
authored
231
17d968c @remiprev Improve support for custom methods
authored
232 def self.total
37aef13 @remiprev Add support for `get`, `post`, etc. wrapper methods
authored
233 get_raw(:stats) do |parsed_data|
17d968c @remiprev Improve support for custom methods
authored
234 parsed_data[:data][:total_users]
235 end
236 end
237 end
238
a7ae25f @remiprev Add support for symbols in custom requests
authored
239 User.popular # => [#<User id=1>, #<User id=2>]
240 User.total # => 42
17d968c @remiprev Improve support for custom methods
authored
241 ```
7f7392f @remiprev Improve README and add LICENSE
authored
242
37aef13 @remiprev Add support for `get`, `post`, etc. wrapper methods
authored
243 You can also use full request paths (with strings instead of symbols).
244
245 ```ruby
246 class User
247 include Her::Model
248 end
249
250 User.get("/users/popular") # => [#<User id=1>, #<User id=2>]
251 # GET /users/popular
252 ```
253
cdeb59f @remiprev Add Multiple APIs section in README
authored
254 ## Multiple APIs
255
256 It is possible to use different APIs for different models. Instead of calling `Her::API.setup`, you can create instances of `Her::API`:
257
258 ```ruby
259 # config/initializers/her.rb
260 $my_api = Her::API.new
261 $my_api.setup :base_uri => "https://my_api.example.com"
262
263 $other_api = Her::API.new
264 $other_api.setup :base_uri => "https://other_api.example.com"
265 ```
266
267 You can then define which API a model will use:
268
269 ```ruby
270 class User
271 include Her::Model
272 uses_api $my_api
273 end
274
275 class Category
276 include Her::Model
277 uses_api $other_api
278 end
279
280 User.all
281 # GET https://my_api.example.com/users
282
283 Category.all
284 # GET https://other_api.example.com/categories
285 ```
286
7f7392f @remiprev Improve README and add LICENSE
authored
287 ## Things to be done
288
83d81a2 @remiprev Add item to todo list
authored
289 * 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
290 * Better error handling
7f7392f @remiprev Improve README and add LICENSE
authored
291 * Better documentation
292
293 ## Contributors
294
7f9f399 @remiprev Add contributors, woohoo!
authored
295 Feel free to contribute and submit issues/pull requests [on GitHub](https://github.com/remiprev/her/issues) like these fine folks did:
296
297 * [@jfcixmedia](https://github.com/jfcixmedia)
298 * [@EtienneLem](https://github.com/EtienneLem)
a366819 @remiprev Add @rafaelss to contributors
authored
299 * [@rafaelss](https://github.com/rafaelss)
7f7392f @remiprev Improve README and add LICENSE
authored
300
301 Take a look at the `spec` folder before you do, and make sure `bundle exec rake spec` passes after your modifications :)
302
303 ## License
304
e0f75c8 @remiprev Fix typo in README, d’uh.
authored
305 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.