her 0.4 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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