Skip to content

HTTPS clone URL

Subversion checkout URL

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