Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 264 lines (194 sloc) 7.779 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
ab31348 @remiprev Add Parsing data section in README
authored
57 ## Parsing data
58
59 By default, Her handles JSON data. It expects the data to be formatted in a certain structure. The default is this:
60
8f55b37 @remiprev Fix typo in README
authored
61 ```javascript
ab31348 @remiprev Add Parsing data section in README
authored
62 // The response of GET /users/1
63 {
64 "data" : {
65 "id" : 1,
66 "name" : "Tobias Fünke"
67 }
68 }
69
70 // The response of GET /users
71 {
72 "data" : [
73 {
74 "id" : 1,
75 "name" : "Tobias Fünke"
76 },
77 {
78 "id" : 2,
79 "name" : "Lindsay Fünke"
80 }
8f55b37 @remiprev Fix typo in README
authored
81 ],
ab31348 @remiprev Add Parsing data section in README
authored
82 "metadata" : {
83 "page" : 1,
84 "per_page" : 10
85 }
86 }
87 ```
88
3d1da8d @remiprev Add support for custom Faraday middleware
authored
89 However, you can define your own parsing method, using a Faraday response middleware. The middleware is expected to return a hash with three keys: `data`, `errors` and `metadata`. The following code enables parsing JSON data and treating this data as first-level properties:
ab31348 @remiprev Add Parsing data section in README
authored
90
91 ```ruby
3d1da8d @remiprev Add support for custom Faraday middleware
authored
92 class MyCustomParser < Faraday::Response::Middleware
93 def on_complete(env)
94 json = JSON.parse(env[:body], :symbolize_names => true)
95 errors = json.delete(:errors) || []
96 metadata = json.delete(:metadata) || []
97 env[:body] = {
98 :data => json,
99 :errors => errors,
100 :metadata => metadata,
101 }
102 end
103 end
104 Her::API.setup :base_uri => "https://api.example.com", :middleware => [MyCustomParser] + Her::API.default_middleware
ab31348 @remiprev Add Parsing data section in README
authored
105 end
8a5bee6 @remiprev Fix a few typos
authored
106
107 # 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
108 ```
109
6878101 @remiprev Update documentation with new API code
authored
110 ## Relationships
111
875f744 @remiprev Update README for belongs_to
authored
112 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
113
875f744 @remiprev Update README for belongs_to
authored
114 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
115
116 For example, with this setup:
117
6878101 @remiprev Update documentation with new API code
authored
118 ```ruby
119 class User
120 include Her::Model
121 has_many :comments
d997547 Updated readme for has_one relationship
jfcixmedia authored
122 has_one :role
875f744 @remiprev Update README for belongs_to
authored
123 belongs_to :organization
6878101 @remiprev Update documentation with new API code
authored
124 end
125
126 class Comment
127 include Her::Model
128 end
d997547 Updated readme for has_one relationship
jfcixmedia authored
129
130 class Role
131 include Her::Model
132 end
875f744 @remiprev Update README for belongs_to
authored
133
134 class Organization
135 include Her::Model
136 end
313c487 @remiprev Add better documentation for relationships
authored
137 ```
138
dc37005 @remiprev Better english in README
authored
139 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
140
313c487 @remiprev Add better documentation for relationships
authored
141 ```ruby
875f744 @remiprev Update README for belongs_to
authored
142 @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
143 @user.comments # => [#<Comment id=1>, #<Comment id=2>] fetched directly from @user
d997547 Updated readme for has_one relationship
jfcixmedia authored
144 @user.role # => #<Role id=1> fetched directly from @user
875f744 @remiprev Update README for belongs_to
authored
145 @user.organization # => #<Organization id=2> fetched directly from @user
6878101 @remiprev Update documentation with new API code
authored
146 ```
147
313c487 @remiprev Add better documentation for relationships
authored
148 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:
149
150 ```ruby
a6e9967 @remiprev Update README with more examples
authored
151 @user = User.find(1) # { :data => { :id => 1, :name => "George Michael Bluth" }}
313c487 @remiprev Add better documentation for relationships
authored
152 @user.comments # => [#<Comment id=1>, #<Comment id=2>] fetched from /users/1/comments
153 ```
154
d997547 Updated readme for has_one relationship
jfcixmedia authored
155 For `has_one` relationship, an extra HTTP request (to `GET /users/1/role`) is made when calling the `#role` method:
156
157 ```ruby
158 @user = User.find(1) # { :data => { :id => 1, :name => "George Michael Bluth" }}
159 @user.role # => #<Role id=1> fetched from /users/1/role
160 ```
161
875f744 @remiprev Update README for belongs_to
authored
162 For `belongs_to` relationship, an extra HTTP request (to `GET /organizations/2`) is made when calling the `#organization` method:
163
164 ```ruby
165 @user = User.find(1) # { :data => { :id => 1, :name => "George Michael Bluth", :organization_id => 2 }}
166 @user.organization # => #<Organization id=2> fetched from /organizations/2
167 ```
168
d997547 Updated readme for has_one relationship
jfcixmedia authored
169 However, subsequent calls to `#comments` or `#role` will not trigger the extra HTTP request.
313c487 @remiprev Add better documentation for relationships
authored
170
3bb8232 @remiprev Add documentation for hooks
authored
171 ## Hooks
172
173 You can add *before* and *after* hooks to your models that are triggered on specific actions (`save`, `update`, `create`, `destroy`):
174
175 ```ruby
176 class User
177 include Her::Model
178 before_save :set_internal_id
179
180 def set_internal_id
181 self.internal_id = 42 # Will be passed in the HTTP request
182 end
183 end
ee90237 @remiprev Add example for hooks
authored
184
185 @user = User.create(:fullname => "Tobias Fünke")
186 # POST /users&fullname=Tobias+Fünke&internal_id=42
3bb8232 @remiprev Add documentation for hooks
authored
187 ```
188
189 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).
190
6878101 @remiprev Update documentation with new API code
authored
191 ## Custom requests
192
a6e9967 @remiprev Update README with more examples
authored
193 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
194
17d968c @remiprev Improve support for custom methods
authored
195 ```ruby
196 class User
197 include Her::Model
198
199 def self.popular
200 get_collection("/users/popular")
201 end
6878101 @remiprev Update documentation with new API code
authored
202
17d968c @remiprev Improve support for custom methods
authored
203 def self.total
204 get_raw("/users/stats") do |parsed_data|
205 parsed_data[:data][:total_users]
206 end
207 end
208 end
209
210 User.popular # => [#<User id=1>, #<User id=2>]
211 User.total # => 42
212 ```
7f7392f @remiprev Improve README and add LICENSE
authored
213
cdeb59f @remiprev Add Multiple APIs section in README
authored
214 ## Multiple APIs
215
216 It is possible to use different APIs for different models. Instead of calling `Her::API.setup`, you can create instances of `Her::API`:
217
218 ```ruby
219 # config/initializers/her.rb
220 $my_api = Her::API.new
221 $my_api.setup :base_uri => "https://my_api.example.com"
222
223 $other_api = Her::API.new
224 $other_api.setup :base_uri => "https://other_api.example.com"
225 ```
226
227 You can then define which API a model will use:
228
229 ```ruby
230 class User
231 include Her::Model
232 uses_api $my_api
233 end
234
235 class Category
236 include Her::Model
237 uses_api $other_api
238 end
239
240 User.all
241 # GET https://my_api.example.com/users
242
243 Category.all
244 # GET https://other_api.example.com/categories
245 ```
246
7f7392f @remiprev Improve README and add LICENSE
authored
247 ## Things to be done
248
88fe92f @remiprev Add note in README for middlware
authored
249 * Support for Faraday middleware to handle caching, alternative formats, etc.
7f7392f @remiprev Improve README and add LICENSE
authored
250 * Hooks before save, update, create, destroy, etc.
a6e9967 @remiprev Update README with more examples
authored
251 * Better error handling
7f7392f @remiprev Improve README and add LICENSE
authored
252 * Better introspection for debug
253 * Better documentation
254
255 ## Contributors
256
257 Feel free to contribute and submit issues/pull requests [on GitHub](https://github.com/remiprev/her/issues).
258
259 Take a look at the `spec` folder before you do, and make sure `bundle exec rake spec` passes after your modifications :)
260
261 ## License
262
e0f75c8 @remiprev Fix typo in README, d’uh.
authored
263 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.