her 0.9.0 → 0.10.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 458601a1de64f520013856ef805b17e8293bfb9d
4
- data.tar.gz: 1e965c550664039d6b6f649a9b10a7821d90f717
3
+ metadata.gz: 9b11481769c945a97654d3d95432044346945d41
4
+ data.tar.gz: 5d9d9d9f02a10ccd3424314ed13b870651995637
5
5
  SHA512:
6
- metadata.gz: e7d147d58050f847942df92440c9e71013c1fc54fbb56d0a6e5dd9d6167a1ffc4e30c329524f1bac07a378d06991198cb1bf44215067ab911f2d0ff1ee5b95f7
7
- data.tar.gz: 1c3ab969c7fbe8f90f730945cd9392f9e2d3df6b3df577291f86f97aa7405758f1086a7708e2586760d674a362703c465bd66516f828d1c1a389fb7c304f4a16
6
+ metadata.gz: 0ba766a53928bf5a48027e5caaee70081768a8c924c1a306afca64dd6e72ded1dc5999da454f3cdd66ca47c96ad41e639ae4bd8bed8df8bd33a269d7d28584cc
7
+ data.tar.gz: cf562af326ca2bc7d77be3f27de84cfd8d17620775833947815ff985751c11fa89153c4bb4de78027f7bbd36d55209b4cd2f36095e003c250b0ccd9ae5b14d1b
data/.travis.yml CHANGED
@@ -16,4 +16,7 @@ gemfile:
16
16
  - gemfiles/Gemfile.activemodel-4.0
17
17
  - gemfiles/Gemfile.activemodel-3.2.x
18
18
 
19
+ before_install:
20
+ - gem install bundler
21
+
19
22
  script: "echo 'COME ON!' && bundle exec rake spec"
data/README.md CHANGED
@@ -234,7 +234,7 @@ end
234
234
  @tweets = Tweet.get("/statuses/home_timeline.json")
235
235
  ```
236
236
 
237
- See the *Authentication* middleware section for an example of how to pass different credentials based on the current user.
237
+ See the [*Authentication middleware section*](#authentication) for an example of how to pass different credentials based on the current user.
238
238
 
239
239
  ### Parsing JSON data
240
240
 
@@ -248,7 +248,7 @@ By default, Her handles JSON data. It expects the resource/collection data to be
248
248
  [{ "id" : 1, "name" : "Tobias Fünke" }]
249
249
  ```
250
250
 
251
- 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).
251
+ 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**](#json-attributes-wrapping) section).
252
252
 
253
253
  Also, you can define your own parsing method using a response middleware. The middleware should set `env[:body]` to a hash with three symbol keys: `:data`, `:errors` and `:metadata`. The following code uses a custom middleware to parse the JSON data:
254
254
 
@@ -452,7 +452,7 @@ class Organization
452
452
  end
453
453
  ```
454
454
 
455
- Her expects all `User` resources to have an `:organization_id` (or `:_organization_id`) attribute. Otherwise, calling mostly all methods, like `User.all`, will thrown an exception like this one:
455
+ Her expects all `User` resources to have an `:organization_id` (or `:_organization_id`) attribute. Otherwise, calling mostly all methods, like `User.all`, will throw an exception like this one:
456
456
 
457
457
  ```ruby
458
458
  Her::Errors::PathError: Missing :_organization_id parameter to build the request path. Path is `organizations/:organization_id/users`. Parameters are `{ … }`.
data/lib/her/api.rb CHANGED
@@ -89,19 +89,26 @@ module Her
89
89
  path = opts.delete(:_path)
90
90
  headers = opts.delete(:_headers)
91
91
  opts.delete_if { |key, value| key.to_s =~ /^_/ } # Remove all internal parameters
92
- response = @connection.send method do |request|
92
+ if method == :options
93
+ # Faraday doesn't support the OPTIONS verb because of a name collision with an internal options method
94
+ # so we need to call run_request directly.
93
95
  request.headers.merge!(headers) if headers
94
- if method == :get
95
- # For GET requests, treat additional parameters as querystring data
96
- request.url path, opts
97
- else
98
- # For POST, PUT and DELETE requests, treat additional parameters as request body
99
- request.url path
100
- request.body = opts
96
+ response = @connection.run_request method, path, opts, headers
97
+ else
98
+ response = @connection.send method do |request|
99
+ request.headers.merge!(headers) if headers
100
+ if method == :get
101
+ # For GET requests, treat additional parameters as querystring data
102
+ request.url path, opts
103
+ else
104
+ # For POST, PUT and DELETE requests, treat additional parameters as request body
105
+ request.url path
106
+ request.body = opts
107
+ end
101
108
  end
102
109
  end
103
-
104
110
  { :parsed_data => response.env[:body], :response => response }
111
+
105
112
  end
106
113
 
107
114
  private
@@ -26,11 +26,7 @@ module Her
26
26
  return {} unless data[data_key]
27
27
 
28
28
  klass = klass.her_nearby_class(association[:class_name])
29
- if data[data_key].kind_of?(klass)
30
- { association[:name] => data[data_key] }
31
- else
32
- { association[:name] => klass.new(klass.parse(data[data_key])) }
33
- end
29
+ { association[:name] => klass.instantiate_record(klass, data: data[data_key]) }
34
30
  end
35
31
 
36
32
  # @private
@@ -31,7 +31,7 @@ module Her
31
31
  return {} unless data[data_key]
32
32
 
33
33
  klass = klass.her_nearby_class(association[:class_name])
34
- { association[:name] => Her::Model::Attributes.initialize_collection(klass, :data => data[data_key]) }
34
+ { association[:name] => klass.instantiate_collection(klass, :data => data[data_key]) }
35
35
  end
36
36
 
37
37
  # Initialize a new object with a foreign key to the parent
@@ -92,7 +92,7 @@ module Her
92
92
  # @private
93
93
  def assign_nested_attributes(attributes)
94
94
  data = attributes.is_a?(Hash) ? attributes.values : attributes
95
- @parent.attributes[@name] = Her::Model::Attributes.initialize_collection(@klass, :data => data)
95
+ @parent.attributes[@name] = @klass.instantiate_collection(@klass, :data => data)
96
96
  end
97
97
  end
98
98
  end
@@ -16,7 +16,13 @@ module Her
16
16
  # include Her::Model
17
17
  # end
18
18
  #
19
- # User.new(name: "Tobias") # => #<User name="Tobias">
19
+ # User.new(name: "Tobias")
20
+ # # => #<User name="Tobias">
21
+ #
22
+ # User.new do |u|
23
+ # u.name = "Tobias"
24
+ # end
25
+ # # => #<User name="Tobias">
20
26
  def initialize(attributes={})
21
27
  attributes ||= {}
22
28
  @metadata = attributes.delete(:_metadata) || {}
@@ -25,50 +31,10 @@ module Her
25
31
 
26
32
  attributes = self.class.default_scope.apply_to(attributes)
27
33
  assign_attributes(attributes)
28
-
29
34
  yield self if block_given?
30
35
  run_callbacks :initialize
31
36
  end
32
37
 
33
- # Initialize a collection of resources
34
- #
35
- # @private
36
- def self.initialize_collection(klass, parsed_data={})
37
- collection_data = klass.extract_array(parsed_data).map do |item_data|
38
- if item_data.kind_of?(klass)
39
- resource = item_data
40
- else
41
- resource = klass.new(klass.parse(item_data))
42
- resource.run_callbacks :find
43
- end
44
- resource
45
- end
46
- Her::Collection.new(collection_data, parsed_data[:metadata], parsed_data[:errors])
47
- end
48
-
49
- # Use setter methods of model for each key / value pair in params
50
- # Return key / value pairs for which no setter method was defined on the model
51
- #
52
- # @private
53
- def self.use_setter_methods(model, params)
54
- params ||= {}
55
-
56
- reserved_keys = [:id, model.class.primary_key] + model.class.association_keys
57
- model.class.attributes *params.keys.reject { |k| reserved_keys.include?(k) || reserved_keys.map(&:to_s).include?(k) }
58
-
59
- setter_method_names = model.class.setter_method_names
60
- params.inject({}) do |memo, (key, value)|
61
- setter_method = key.to_s + '='
62
- if setter_method_names.include?(setter_method)
63
- model.send(setter_method, value)
64
- else
65
- key = key.to_sym if key.is_a?(String)
66
- memo[key] = value
67
- end
68
- memo
69
- end
70
- end
71
-
72
38
  # Handles missing methods
73
39
  #
74
40
  # @private
@@ -89,7 +55,7 @@ module Her
89
55
 
90
56
  # @private
91
57
  def respond_to_missing?(method, include_private = false)
92
- method.to_s.end_with?('=') || method.to_s.end_with?('?') || @attributes.include?(method) || super
58
+ method.to_s =~ /[?=]$/ || @attributes.include?(method) || super
93
59
  end
94
60
 
95
61
  # Assign new attributes to a resource
@@ -105,7 +71,7 @@ module Her
105
71
  def assign_attributes(new_attributes)
106
72
  @attributes ||= attributes
107
73
  # Use setter methods first
108
- unset_attributes = Her::Model::Attributes.use_setter_methods(self, new_attributes)
74
+ unset_attributes = self.class.use_setter_methods(self, new_attributes)
109
75
 
110
76
  # Then translate attributes of associations into association instances
111
77
  parsed_attributes = self.class.parse_associations(unset_attributes)
@@ -139,7 +105,8 @@ module Her
139
105
  @attributes[self.class.primary_key]
140
106
  end
141
107
 
142
- # Return `true` if the other object is also a Her::Model and has matching data
108
+ # Return `true` if the other object is also a Her::Model and has matching
109
+ # data
143
110
  #
144
111
  # @private
145
112
  def ==(other)
@@ -177,23 +144,69 @@ module Her
177
144
  end
178
145
 
179
146
  module ClassMethods
147
+
148
+ # Initialize a single resource
149
+ #
150
+ # @private
151
+ def instantiate_record(klass, parsed_data)
152
+ if record = parsed_data[:data] and record.kind_of?(klass)
153
+ record
154
+ else
155
+ attributes = klass.parse(record).merge(_metadata: parsed_data[:metadata],
156
+ _errors: parsed_data[:errors])
157
+ klass.new(attributes).tap do |record|
158
+ record.run_callbacks :find
159
+ end
160
+ end
161
+ end
162
+
163
+ # Initialize a collection of resources
164
+ #
165
+ # @private
166
+ def instantiate_collection(klass, parsed_data = {})
167
+ items = klass.extract_array(parsed_data).map do |item|
168
+ instantiate_record(klass, data: item)
169
+ end
170
+ Her::Collection.new(items, parsed_data[:metadata], parsed_data[:errors])
171
+ end
172
+
180
173
  # Initialize a collection of resources with raw data from an HTTP request
181
174
  #
182
175
  # @param [Array] parsed_data
183
176
  # @private
184
177
  def new_collection(parsed_data)
185
- Her::Model::Attributes.initialize_collection(self, parsed_data)
178
+ instantiate_collection(self, parsed_data)
186
179
  end
187
180
 
188
181
  # Initialize a new object with the "raw" parsed_data from the parsing middleware
189
182
  #
190
183
  # @private
191
184
  def new_from_parsed_data(parsed_data)
192
- parsed_data = parsed_data.with_indifferent_access
193
- new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
185
+ instantiate_record(self, parsed_data)
186
+ end
187
+
188
+ # Use setter methods of model for each key / value pair in params
189
+ # Return key / value pairs for which no setter method was defined on the
190
+ # model
191
+ #
192
+ # @private
193
+ def use_setter_methods(model, params = {})
194
+ reserved = [:id, model.class.primary_key, *model.class.association_keys]
195
+ model.class.attributes *params.keys.reject { |k| reserved.include?(k) }
196
+
197
+ setter_method_names = model.class.setter_method_names
198
+ params.each_with_object({}) do |(key, value), memo|
199
+ setter_method = key.to_s + '='
200
+ if setter_method_names.include?(setter_method)
201
+ model.send(setter_method, value)
202
+ else
203
+ memo[key.to_sym] = value
204
+ end
205
+ end
194
206
  end
195
207
 
196
- # Define attribute method matchers to automatically define them using ActiveModel's define_attribute_methods.
208
+ # Define attribute method matchers to automatically define them using
209
+ # ActiveModel's define_attribute_methods.
197
210
  #
198
211
  # @private
199
212
  def define_attribute_method_matchers
@@ -201,18 +214,22 @@ module Her
201
214
  attribute_method_suffix '?'
202
215
  end
203
216
 
204
- # Create a mutex for dynamically generated attribute methods or use one defined by ActiveModel.
217
+ # Create a mutex for dynamically generated attribute methods or use one
218
+ # defined by ActiveModel.
205
219
  #
206
220
  # @private
207
221
  def attribute_methods_mutex
208
- @attribute_methods_mutex ||= if generated_attribute_methods.respond_to? :mu_synchronize
209
- generated_attribute_methods
210
- else
211
- Mutex.new
212
- end
222
+ @attribute_methods_mutex ||= begin
223
+ if generated_attribute_methods.respond_to? :mu_synchronize
224
+ generated_attribute_methods
225
+ else
226
+ Mutex.new
227
+ end
228
+ end
213
229
  end
214
230
 
215
- # Define the attributes that will be used to track dirty attributes and validations
231
+ # Define the attributes that will be used to track dirty attributes and
232
+ # validations
216
233
  #
217
234
  # @param [Array] attributes
218
235
  # @example
@@ -226,7 +243,8 @@ module Her
226
243
  end
227
244
  end
228
245
 
229
- # Define the accessor in which the API response errors (obtained from the parsing middleware) will be stored
246
+ # Define the accessor in which the API response errors (obtained from
247
+ # the parsing middleware) will be stored
230
248
  #
231
249
  # @param [Symbol] store_response_errors
232
250
  #
@@ -239,7 +257,8 @@ module Her
239
257
  store_her_data(:response_errors, value)
240
258
  end
241
259
 
242
- # Define the accessor in which the API response metadata (obtained from the parsing middleware) will be stored
260
+ # Define the accessor in which the API response metadata (obtained from
261
+ # the parsing middleware) will be stored
243
262
  #
244
263
  # @param [Symbol] store_metadata
245
264
  #
@@ -254,9 +273,10 @@ module Her
254
273
 
255
274
  # @private
256
275
  def setter_method_names
257
- @_her_setter_method_names ||= instance_methods.inject(Set.new) do |memo, method_name|
258
- memo << method_name.to_s if method_name.to_s.end_with?('=')
259
- memo
276
+ @_her_setter_method_names ||= begin
277
+ instance_methods.each_with_object(Set.new) do |method, memo|
278
+ memo << method.to_s if method.to_s.end_with?('=')
279
+ end
260
280
  end
261
281
  end
262
282
 
@@ -3,7 +3,7 @@ module Her
3
3
  # This module interacts with Her::API to fetch HTTP data
4
4
  module HTTP
5
5
  extend ActiveSupport::Concern
6
- METHODS = [:get, :post, :put, :patch, :delete]
6
+ METHODS = [:get, :post, :put, :patch, :delete, :options]
7
7
 
8
8
  # For each HTTP method, define these class methods:
9
9
  #
@@ -71,7 +71,7 @@ module Her
71
71
  if parsed_data[:data].is_a?(Array) || active_model_serializers_format? || json_api_format?
72
72
  new_collection(parsed_data)
73
73
  else
74
- new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
74
+ new_from_parsed_data(parsed_data)
75
75
  end
76
76
  end
77
77
  end
@@ -91,7 +91,7 @@ module Her
91
91
  def #{method}_resource(path, params={})
92
92
  path = build_request_path_from_string_or_symbol(path, params)
93
93
  send(:"#{method}_raw", path, params) do |parsed_data, response|
94
- new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
94
+ new_from_parsed_data(parsed_data)
95
95
  end
96
96
  end
97
97
 
data/lib/her/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Her
2
- VERSION = "0.9.0"
2
+ VERSION = "0.10.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: her
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rémi Prévost
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-06-25 00:00:00.000000000 Z
11
+ date: 2017-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake