her 0.4 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NmU4ZjBhMTAxNGJhNTE5ZThjNDY5OTcxZmFkOGM5NzQwZjBhYTIyOA==
5
+ data.tar.gz: !binary |-
6
+ MTIxOGI1NjljNzBiNjFlNmM2ODczZDZiNDlhNGIxZmQ0Njk3NjEyNw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NGNjNDg0MjIzMmRhOTA5MDU3MWIyMWZkYzIyMzFkNWViNDdiYmFkZDE3ZjQ1
10
+ ZTA1NTExMzA1YTc2MTE3MGM0NTYxNjg2ZjI1OTllYWFlZGQ3ZDY2ZWIwYjQ5
11
+ NTUwYjI4NTBhMTk3MTVjZTExYjNkZDNmM2Q1MjM5YjkwN2U2ZGY=
12
+ data.tar.gz: !binary |-
13
+ NjAzM2JlOWZlZDBjMzZjYjI2OWJkYzc5NTY4MzAyNzhhZGVkODlhMmZmZGJm
14
+ NjUzY2JlMjExZDAzYjljOTI1YzE1NWJkOWM0OTY0NzE2MDQwYjY1NmZmN2M4
15
+ YzU0YmYyNDY0YWE3ZWVjZWY0N2NkY2Y0NGQ1ODFhMzg2MTIxN2E=
File without changes
data/README.md CHANGED
@@ -143,6 +143,44 @@ end
143
143
 
144
144
  Now, each HTTP request made by Her will have the `X-API-Token` header.
145
145
 
146
+ ### OAuth
147
+
148
+ Using the `faraday_middleware` and `simple_oauth` gems, it’s fairly easy to use OAuth authentication with Her.
149
+
150
+ In your Gemfile:
151
+
152
+ ```ruby
153
+ gem "her"
154
+ gem "faraday_middleware"
155
+ gem "simple_oauth"
156
+ ```
157
+
158
+ In your Ruby code:
159
+
160
+ ```ruby
161
+ # Create an application on `https://dev.twitter.com/apps` to set these values
162
+ TWITTER_CREDENTIALS = {
163
+ :consumer_key => "",
164
+ :consumer_secret => "",
165
+ :token => "",
166
+ :token_secret => ""
167
+ }
168
+
169
+ Her::API.setup :url => "https://api.twitter.com/1/" do |connection|
170
+ connection.use FaradayMiddleware::OAuth, TWITTER_CREDENTIALS
171
+ connection.use Her::Middleware::DefaultParseJSON
172
+ connection.use Faraday::Adapter::NetHttp
173
+ end
174
+
175
+ class Tweet
176
+ include Her::Model
177
+ end
178
+
179
+ @tweets = Tweet.get("/statuses/home_timeline.json")
180
+ ```
181
+
182
+ See the *Authentication* middleware section for an example of how to pass different credentials based on the current user.
183
+
146
184
  ### Parsing JSON data
147
185
 
148
186
  By default, Her handles JSON data. It expects the resource/collection data to be returned at the first level.
@@ -155,7 +193,9 @@ By default, Her handles JSON data. It expects the resource/collection data to be
155
193
  [{ "id" : 1, "name" : "Tobias Fünke" }]
156
194
  ```
157
195
 
158
- However, you can define your own parsing method using a response middleware. The middleware should set `env[:body]` to a hash with three keys: `data`, `errors` and `metadata`. The following code uses a custom middleware to parse the JSON data:
196
+ However, if you want Her to be able to parse the data from a single root element (usually based on the model name), you’ll have to use the `parse_root_in_json` method (See the **JSON attributes-wrapping** section).
197
+
198
+ Also, you can define your own parsing method using a response middleware. The middleware should set `env[:body]` to a hash with three keys: `data`, `errors` and `metadata`. The following code uses a custom middleware to parse the JSON data:
159
199
 
160
200
  ```ruby
161
201
  # Expects responses like:
@@ -185,44 +225,6 @@ Her::API.setup :url => "https://api.example.com" do |connection|
185
225
  end
186
226
  ```
187
227
 
188
- ### OAuth
189
-
190
- Using the `faraday_middleware` and `simple_oauth` gems, it’s fairly easy to use OAuth authentication with Her.
191
-
192
- In your Gemfile:
193
-
194
- ```ruby
195
- gem "her"
196
- gem "faraday_middleware"
197
- gem "simple_oauth"
198
- ```
199
-
200
- In your Ruby code:
201
-
202
- ```ruby
203
- # Create an application on `https://dev.twitter.com/apps` to set these values
204
- TWITTER_CREDENTIALS = {
205
- :consumer_key => "",
206
- :consumer_secret => "",
207
- :token => "",
208
- :token_secret => ""
209
- }
210
-
211
- Her::API.setup :url => "https://api.twitter.com/1/" do |connection|
212
- connection.use FaradayMiddleware::OAuth, TWITTER_CREDENTIALS
213
- connection.use Her::Middleware::DefaultParseJSON
214
- connection.use Faraday::Adapter::NetHttp
215
- end
216
-
217
- class Tweet
218
- include Her::Model
219
- end
220
-
221
- @tweets = Tweet.get("/statuses/home_timeline.json")
222
- ```
223
-
224
- See the *Authentication* middleware section for an example of how to pass different credentials based on the current user.
225
-
226
228
  ### Caching
227
229
 
228
230
  Again, using the `faraday_middleware` and `memcached` gems makes it very easy to cache requests and responses.
@@ -675,7 +677,7 @@ end
675
677
 
676
678
  ## Upgrade
677
679
 
678
- See the [UPGRADE.md](https://github.com/remiprev/her/blob/master/docs/UPGRADE.md) for backward compability issues.
680
+ See the [UPGRADE.md](https://github.com/remiprev/her/blob/master/UPGRADE.md) for backward compability issues.
679
681
 
680
682
  ## Her IRL
681
683
 
@@ -686,15 +688,15 @@ Most projects I know that use Her are internal or private projects but here’s
686
688
 
687
689
  ## History
688
690
 
689
- 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, 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.
691
+ 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.
690
692
 
691
- Most of Her’s core codebase was written on a Saturday morning ([first commit](https://github.com/remiprev/her/commit/689d8e88916dc2ad258e69a2a91a283f061cbef2) at 7am!) while I was visiting my girlfiend’s family in [Ayer’s Cliff](https://en.wikipedia.org/wiki/Ayer%27s_Cliff).
693
+ 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!).
692
694
 
693
695
  ## Contribute
694
696
 
695
697
  Yes please! Feel free to contribute and submit issues/pull requests [on GitHub](https://github.com/remiprev/her/issues).
696
698
 
697
- See [CONTRIBUTING.md](https://github.com/remiprev/her/blob/master/docs/CONTRIBUTING.md) for best practices.
699
+ See [CONTRIBUTING.md](https://github.com/remiprev/her/blob/master/CONTRIBUTING.md) for best practices.
698
700
 
699
701
  ### Contributors
700
702
 
@@ -718,4 +720,4 @@ These fine folks helped with Her:
718
720
 
719
721
  ## License
720
722
 
721
- Her is © 2012 [Rémi Prévost](http://exomel.com) and may be freely distributed under the [MIT license](https://github.com/remiprev/her/blob/master/LICENSE). See the `LICENSE` file.
723
+ Her is © 2012-2013 [Rémi Prévost](http://exomel.com) and may be freely distributed under the [MIT license](https://github.com/remiprev/her/blob/master/LICENSE). See the `LICENSE` file.
File without changes
data/her.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.require_paths = ["lib"]
19
19
 
20
20
  s.add_development_dependency "rake", "~> 10.0"
21
- s.add_development_dependency "rspec", "~> 2.12"
21
+ s.add_development_dependency "rspec", "~> 2.13"
22
22
  s.add_development_dependency "mocha", "~> 0.13"
23
23
 
24
24
  s.add_runtime_dependency "activesupport", ">= 3.0.0"
data/lib/her/api.rb CHANGED
@@ -11,6 +11,22 @@ module Her
11
11
  @@default_api.setup(attrs, &block)
12
12
  end
13
13
 
14
+ # Create a new API object. This is useful to create multiple APIs and use them with the `uses_api` method.
15
+ # If your application uses only one API, you should use Her::API.setup to configure the default API
16
+ #
17
+ # @example Setting up a new API
18
+ # api = Her::API.new :url => "https://api.example" do |connection|
19
+ # connection.use Faraday::Request::UrlEncoded
20
+ # connection.use Her::Middleware::DefaultParseJSON
21
+ # end
22
+ #
23
+ # class User
24
+ # uses_api api
25
+ # end
26
+ def initialize(*args, &blk)
27
+ self.setup(*args, &blk)
28
+ end
29
+
14
30
  # Setup the API connection.
15
31
  #
16
32
  # @param [Hash] attrs the Faraday options
@@ -49,13 +65,14 @@ module Her
49
65
  # connection.use MyCustomParser
50
66
  # connection.use Faraday::Adapter::NetHttp
51
67
  # end
52
- def setup(attrs={})
68
+ def setup(attrs={}, &blk)
53
69
  attrs[:url] = attrs.delete(:base_uri) if attrs.include?(:base_uri) # Support legacy :base_uri option
54
70
  @base_uri = attrs[:url]
55
71
  @options = attrs
56
72
  @connection = Faraday.new(@options) do |connection|
57
73
  yield connection if block_given?
58
74
  end
75
+ self
59
76
  end
60
77
 
61
78
  # Define a custom parsing procedure. The procedure is passed the response object and is
data/lib/her/errors.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  module Her
2
2
  module Errors
3
3
  class PathError < StandardError; end;
4
+ class RelationshipUnknownError < StandardError; end;
4
5
  end
5
6
  end
@@ -22,7 +22,12 @@ module Her
22
22
  #
23
23
  # @param [Hash] env The response environment
24
24
  def on_complete(env)
25
- env[:body] = parse(env[:body])
25
+ case env[:status]
26
+ when 204
27
+ env[:body] = parse('{}')
28
+ else
29
+ env[:body] = parse(env[:body])
30
+ end
26
31
  end
27
32
  end
28
33
  end
data/lib/her/model.rb CHANGED
@@ -5,6 +5,7 @@ require "her/model/relationships"
5
5
  require "her/model/hooks"
6
6
  require "her/model/introspection"
7
7
  require "her/model/paths"
8
+ require "her/model/nested_attributes"
8
9
 
9
10
  module Her
10
11
  # This module is the main element of Her. After creating a Her::API object,
@@ -25,6 +26,7 @@ module Her
25
26
  include Her::Model::Introspection
26
27
  include Her::Model::Paths
27
28
  include Her::Model::Relationships
29
+ include Her::Model::NestedAttributes
28
30
 
29
31
  # Class methods
30
32
  included do
@@ -44,7 +44,7 @@ module Her
44
44
  if parsed_data[:data].is_a?(Array)
45
45
  new_collection(parsed_data)
46
46
  else
47
- new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
47
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
48
48
  end
49
49
  end
50
50
  end
@@ -56,7 +56,7 @@ module Her
56
56
  end
57
57
 
58
58
  # Make a GET request and return a collection of resources
59
- def get_collection(path, attrs={})
59
+ def get_collection(path=nil, attrs={})
60
60
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
61
61
  get_raw(path, attrs) do |parsed_data|
62
62
  new_collection(parsed_data)
@@ -67,7 +67,7 @@ module Her
67
67
  def get_resource(path, attrs={})
68
68
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
69
69
  get_raw(path, attrs) do |parsed_data|
70
- new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
70
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
71
71
  end
72
72
  end
73
73
 
@@ -78,7 +78,7 @@ module Her
78
78
  if parsed_data[:data].is_a?(Array)
79
79
  new_collection(parsed_data)
80
80
  else
81
- new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
81
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
82
82
  end
83
83
  end
84
84
  end
@@ -101,7 +101,7 @@ module Her
101
101
  def post_resource(path, attrs={})
102
102
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
103
103
  post_raw(path, attrs) do |parsed_data|
104
- new(parsed_data[:data])
104
+ new(parse(parsed_data[:data]))
105
105
  end
106
106
  end
107
107
 
@@ -112,7 +112,7 @@ module Her
112
112
  if parsed_data[:data].is_a?(Array)
113
113
  new_collection(parsed_data)
114
114
  else
115
- new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
115
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
116
116
  end
117
117
  end
118
118
  end
@@ -135,7 +135,7 @@ module Her
135
135
  def put_resource(path, attrs={})
136
136
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
137
137
  put_raw(path, attrs) do |parsed_data|
138
- new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
138
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
139
139
  end
140
140
  end
141
141
 
@@ -146,7 +146,7 @@ module Her
146
146
  if parsed_data[:data].is_a?(Array)
147
147
  new_collection(parsed_data)
148
148
  else
149
- new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
149
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
150
150
  end
151
151
  end
152
152
  end
@@ -169,7 +169,7 @@ module Her
169
169
  def patch_resource(path, attrs={})
170
170
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
171
171
  patch_raw(path, attrs) do |parsed_data|
172
- new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
172
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
173
173
  end
174
174
  end
175
175
 
@@ -180,7 +180,7 @@ module Her
180
180
  if parsed_data[:data].is_a?(Array)
181
181
  new_collection(parsed_data)
182
182
  else
183
- new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
183
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
184
184
  end
185
185
  end
186
186
  end
@@ -203,7 +203,7 @@ module Her
203
203
  def delete_resource(path, attrs={})
204
204
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
205
205
  delete_raw(path, attrs) do |parsed_data|
206
- new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
206
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
207
207
  end
208
208
  end
209
209
 
@@ -0,0 +1,57 @@
1
+ module Her
2
+ module Model
3
+ module NestedAttributes
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def accepts_nested_attributes_for(*relationship_names)
8
+ relationship_names.each do |relationship_name|
9
+ type = nil
10
+ [:belongs_to, :has_one, :has_many].each do |relation_type|
11
+ if !relationships[relation_type].nil? && relationships[relation_type].any? { |relation| relation[:name] == relationship_name }
12
+ type = relation_type
13
+ end
14
+ end
15
+ if type.nil?
16
+ raise(RelationshipUnknownError.new("Unknown relationship name :#{relationship_name}"))
17
+ end
18
+ class_eval <<-eoruby, __FILE__, __LINE__ + 1
19
+ if method_defined?(:#{relationship_name}_attributes=)
20
+ remove_method(:#{relationship_name}_attributes=)
21
+ end
22
+ def #{relationship_name}_attributes=(attributes)
23
+ assign_nested_attributes_for_#{type}_relationship(:#{relationship_name}, attributes)
24
+ end
25
+ eoruby
26
+ end
27
+ end
28
+ end
29
+
30
+ def assign_nested_attributes_for_belongs_to_relationship(relationship_name, attributes)
31
+ assign_nested_attributes_for_simple_relationship(:belongs_to, relationship_name, attributes)
32
+ end
33
+
34
+ def assign_nested_attributes_for_has_one_relationship(relationship_name, attributes)
35
+ assign_nested_attributes_for_simple_relationship(:has_one, relationship_name, attributes)
36
+ end
37
+
38
+ def assign_nested_attributes_for_has_many_relationship(relationship_name, attributes)
39
+ relationship = self.class.relationships[:has_many].find { |relation| relation[:name] == relationship_name }
40
+ klass = self.class.nearby_class(relationship[:class_name])
41
+ self.send("#{relationship[:name]}=", Her::Model::ORM.initialize_collection(klass, :data => attributes))
42
+ end
43
+
44
+ private
45
+ def assign_nested_attributes_for_simple_relationship(relationship_type, relationship_name, attributes)
46
+ relationship = self.class.relationships[relationship_type].find { |relation| relation[:name] == relationship_name }
47
+ if has_data?(relationship[:name])
48
+ self.send("#{relationship[:name]}").assign_data(attributes)
49
+ else
50
+ klass = self.class.nearby_class(relationship[:class_name])
51
+ instance = klass.new(klass.parse(attributes))
52
+ self.send("#{relationship[:name]}=", instance)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
data/lib/her/model/orm.rb CHANGED
@@ -9,15 +9,10 @@ module Her
9
9
 
10
10
  # Initialize a new object with data received from an HTTP request
11
11
  def initialize(params={})
12
- @data = {}
13
12
  @metadata = params.delete(:_metadata) || {}
14
13
  @errors = params.delete(:_errors) || {}
15
14
 
16
- # Use setter methods first, then translate attributes of relationships
17
- # into relationship instances, then merge the parsed_data into @data.
18
- unset_data = Her::Model::ORM.use_setter_methods(self, params)
19
- parsed_data = self.class.parse_relationships(unset_data)
20
- @data.update(parsed_data)
15
+ update_data(params)
21
16
  end
22
17
 
23
18
  # Initialize a collection of resources
@@ -150,7 +145,7 @@ module Her
150
145
 
151
146
  self.class.wrap_in_hooks(resource, *hooks) do |resource, klass|
152
147
  klass.request(params.merge(:_method => method, :_path => "#{request_path}")) do |parsed_data|
153
- self.data = self.class.parse(parsed_data[:data]) if parsed_data[:data].any?
148
+ update_data(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
154
149
  self.metadata = parsed_data[:metadata]
155
150
  self.errors = parsed_data[:errors]
156
151
 
@@ -171,7 +166,7 @@ module Her
171
166
  resource = self
172
167
  self.class.wrap_in_hooks(resource, :destroy) do |resource, klass|
173
168
  klass.request(:_method => :delete, :_path => "#{request_path}") do |parsed_data|
174
- self.data = self.class.parse(parsed_data[:data])
169
+ update_data(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
175
170
  self.metadata = parsed_data[:metadata]
176
171
  self.errors = parsed_data[:errors]
177
172
  end
@@ -179,6 +174,16 @@ module Her
179
174
  self
180
175
  end
181
176
 
177
+ # @private
178
+ def update_data(raw_data)
179
+ @data ||= {}
180
+ # Use setter methods first, then translate attributes of relationships
181
+ # into relationship instances, then merge the parsed_data into @data.
182
+ unset_data = Her::Model::ORM.use_setter_methods(self, raw_data)
183
+ parsed_data = self.class.parse_relationships(unset_data)
184
+ @data.update(parsed_data)
185
+ end
186
+
182
187
  # Convert into a hash of request parameters
183
188
  #
184
189
  # @example
@@ -260,7 +265,7 @@ module Her
260
265
  request(params.merge(:_method => :post, :_path => "#{build_request_path(params)}")) do |parsed_data|
261
266
  data = parse(parsed_data[:data])
262
267
  resource.instance_eval do
263
- @data = data
268
+ update_data(data)
264
269
  @metadata = parsed_data[:metadata]
265
270
  @errors = parsed_data[:errors]
266
271
  end