her 0.4.1 → 0.5

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,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NmU4ZjBhMTAxNGJhNTE5ZThjNDY5OTcxZmFkOGM5NzQwZjBhYTIyOA==
4
+ ODkyMGNiNzY1MDY3MGY1YjBhMTQ1YTQ5Mjk3NWZmNmIwMDVmZWI3Mg==
5
5
  data.tar.gz: !binary |-
6
- MTIxOGI1NjljNzBiNjFlNmM2ODczZDZiNDlhNGIxZmQ0Njk3NjEyNw==
6
+ YjAzYjJkNWQ2YjIyZTIwOThmYjY5ZTRhNzJkOWZkZGZmNzlmMmY3Mg==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- NGNjNDg0MjIzMmRhOTA5MDU3MWIyMWZkYzIyMzFkNWViNDdiYmFkZDE3ZjQ1
10
- ZTA1NTExMzA1YTc2MTE3MGM0NTYxNjg2ZjI1OTllYWFlZGQ3ZDY2ZWIwYjQ5
11
- NTUwYjI4NTBhMTk3MTVjZTExYjNkZDNmM2Q1MjM5YjkwN2U2ZGY=
9
+ YzgxZTFkNWI1NGUzZWYzNDk2NTQ1NjhlYWYzNmZiMzAwNmEyYjE2MWUxYTU3
10
+ YTc5MDRlYjZhZDBmZmQ0YTUzOGNmNTM4NDViMzliMDQ4MmZhZGE3NjUxZDIy
11
+ NjJjYWNhZWM0ODYwODg5ZTEzNGVjYWZjNTVlYjIzY2JiNTNjY2Q=
12
12
  data.tar.gz: !binary |-
13
- NjAzM2JlOWZlZDBjMzZjYjI2OWJkYzc5NTY4MzAyNzhhZGVkODlhMmZmZGJm
14
- NjUzY2JlMjExZDAzYjljOTI1YzE1NWJkOWM0OTY0NzE2MDQwYjY1NmZmN2M4
15
- YzU0YmYyNDY0YWE3ZWVjZWY0N2NkY2Y0NGQ1ODFhMzg2MTIxN2E=
13
+ NTdjYmNjZTQ1ZDVjYmM5NGZmNmRmN2M3NjhjNjJjZWU4MTljOWE1YjJmZmY4
14
+ YTg4NzgxNmI0YWQzOTljZGJlNjUzNzI5NDI2NzMxNjI3Mzk4MTNjMDNkOGNi
15
+ YTQ1ZGExNjU0OGRiZWJmYzNlZTM1ZDY5NWMwNTQxNWViZGNmMTM=
data/README.md CHANGED
@@ -340,9 +340,31 @@ If there’s no relationship data in the resource, Her makes a HTTP request to r
340
340
 
341
341
  Subsequent calls to `#comments`, `#role` and `#organization` will not trigger extra HTTP requests and will return the cached objects.
342
342
 
343
- ### Hooks (callbacks)
343
+ ### Validations
344
344
 
345
- You can add *before* and *after* hooks to your models that are triggered on specific actions. You can use symbols or blocks.
345
+ Her includes `ActiveModel::Validations` so you can declare validations the same way you do in Rails.
346
+
347
+ However, validations must be triggered manually — they are not run, for example, when calling `#save` on an object, or `#create` on a model class.
348
+
349
+ ```ruby
350
+ class User
351
+ include Her::Model
352
+
353
+ attr_accessor :fullname, :email
354
+ validates :fullname, :presence => true
355
+ validates :email, :presence => true
356
+ end
357
+
358
+ @user = User.new(:fullname => "Tobias Fünke")
359
+ @user.valid? # => false
360
+
361
+ @user.save
362
+ # POST /users&fullname=Tobias+Fünke will still be called, even if the user is not valid
363
+ ```
364
+
365
+ ### Callbacks
366
+
367
+ You can add *before* and *after* callbacks to your models that are triggered on specific actions. You can use symbols or blocks.
346
368
 
347
369
  ```ruby
348
370
  class User
@@ -362,7 +384,7 @@ end
362
384
  @user.fullname # => "TOBIAS FUNKE"
363
385
  ```
364
386
 
365
- The available hooks are:
387
+ The available callbacks are:
366
388
 
367
389
  * `before_save`
368
390
  * `before_create`
data/UPGRADE.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  Here is a list of backward-incompatible changes that were introduced while Her is pre-1.0. After reaching 1.0, it will follow the [Semantic Versioning](http://semver.org/) system.
4
4
 
5
+ ## 0.5
6
+
7
+ * Her is now compatible with `ActiveModel` and includes `ActiveModel::Validations`.
8
+
9
+ Before 0.5, the `errors` method on an object would return an error list received from the server (the `:errors` key defined by the parsing middleware). But now, `errors` returns the error list generated after calling the `valid?` method (or any other similar validation method from `ActiveModel::Validations`). The error list returned from the server is now accessible from the `response_errors` method.
10
+
5
11
  ## 0.2.4
6
12
 
7
13
  * Her no longer includes default middleware when making HTTP requests. The user has now to define all the needed middleware. Before:
data/her.gemspec CHANGED
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.add_development_dependency "rspec", "~> 2.13"
22
22
  s.add_development_dependency "mocha", "~> 0.13"
23
23
 
24
+ s.add_runtime_dependency "activemodel", ">= 3.0.0"
24
25
  s.add_runtime_dependency "activesupport", ">= 3.0.0"
25
26
  s.add_runtime_dependency "faraday", "~> 0.8"
26
27
  s.add_runtime_dependency "multi_json", "~> 1.5"
data/lib/her/api.rb CHANGED
@@ -96,7 +96,8 @@ module Her
96
96
  request.body = attrs
97
97
  end
98
98
  end
99
- response.env[:body]
99
+
100
+ { :parsed_data => response.env[:body], :response => response }
100
101
  end
101
102
 
102
103
  private
data/lib/her/model.rb CHANGED
@@ -2,10 +2,10 @@ require "her/model/base"
2
2
  require "her/model/http"
3
3
  require "her/model/orm"
4
4
  require "her/model/relationships"
5
- require "her/model/hooks"
6
5
  require "her/model/introspection"
7
6
  require "her/model/paths"
8
7
  require "her/model/nested_attributes"
8
+ require "active_model"
9
9
 
10
10
  module Her
11
11
  # This module is the main element of Her. After creating a Her::API object,
@@ -27,12 +27,19 @@ module Her
27
27
  include Her::Model::Paths
28
28
  include Her::Model::Relationships
29
29
  include Her::Model::NestedAttributes
30
+ include ActiveModel::Validations
31
+ include ActiveModel::Conversion
32
+ include ActiveModel::Dirty
30
33
 
31
34
  # Class methods
32
35
  included do
33
36
  extend Her::Model::Base
34
37
  extend Her::Model::HTTP
35
- extend Her::Model::Hooks
38
+ extend ActiveModel::Naming
39
+ extend ActiveModel::Translation
40
+
41
+ extend ActiveModel::Callbacks
42
+ define_model_callbacks :create, :update, :save, :find, :destroy
36
43
 
37
44
  # Define default settings
38
45
  root_element self.name.split("::").last.underscore
@@ -21,11 +21,11 @@ module Her
21
21
  # Main request wrapper around Her::API. Used to make custom request to the API.
22
22
  # @private
23
23
  def request(attrs={})
24
- parsed_data = her_api.request(attrs)
24
+ request = her_api.request(attrs)
25
25
  if block_given?
26
- yield parsed_data
26
+ yield request[:parsed_data], request[:response]
27
27
  else
28
- parsed_data
28
+ { :parsed_data => request[:parsed_data], :response => request[:response] }
29
29
  end
30
30
  end
31
31
 
@@ -40,7 +40,7 @@ module Her
40
40
  # # Fetched via GET "/users/popular"
41
41
  def get(path, attrs={})
42
42
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
43
- get_raw(path, attrs) do |parsed_data|
43
+ get_raw(path, attrs) do |parsed_data, response|
44
44
  if parsed_data[:data].is_a?(Array)
45
45
  new_collection(parsed_data)
46
46
  else
@@ -58,7 +58,7 @@ module Her
58
58
  # Make a GET request and return a collection of resources
59
59
  def get_collection(path=nil, attrs={})
60
60
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
61
- get_raw(path, attrs) do |parsed_data|
61
+ get_raw(path, attrs) do |parsed_data, response|
62
62
  new_collection(parsed_data)
63
63
  end
64
64
  end
@@ -66,7 +66,7 @@ module Her
66
66
  # Make a GET request and return a collection of resources
67
67
  def get_resource(path, attrs={})
68
68
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
69
- get_raw(path, attrs) do |parsed_data|
69
+ get_raw(path, attrs) do |parsed_data, response|
70
70
  new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
71
71
  end
72
72
  end
@@ -74,7 +74,7 @@ module Her
74
74
  # Make a POST request and return either a collection or a resource
75
75
  def post(path, attrs={})
76
76
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
77
- post_raw(path, attrs) do |parsed_data|
77
+ post_raw(path, attrs) do |parsed_data, response|
78
78
  if parsed_data[:data].is_a?(Array)
79
79
  new_collection(parsed_data)
80
80
  else
@@ -92,7 +92,7 @@ module Her
92
92
  # Make a POST request and return a collection of resources
93
93
  def post_collection(path, attrs={})
94
94
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
95
- post_raw(path, attrs) do |parsed_data|
95
+ post_raw(path, attrs) do |parsed_data, response|
96
96
  new_collection(parsed_data)
97
97
  end
98
98
  end
@@ -100,7 +100,7 @@ module Her
100
100
  # Make a POST request and return a collection of resources
101
101
  def post_resource(path, attrs={})
102
102
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
103
- post_raw(path, attrs) do |parsed_data|
103
+ post_raw(path, attrs) do |parsed_data, response|
104
104
  new(parse(parsed_data[:data]))
105
105
  end
106
106
  end
@@ -108,7 +108,7 @@ module Her
108
108
  # Make a PUT request and return either a collection or a resource
109
109
  def put(path, attrs={})
110
110
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
111
- put_raw(path, attrs) do |parsed_data|
111
+ put_raw(path, attrs) do |parsed_data, response|
112
112
  if parsed_data[:data].is_a?(Array)
113
113
  new_collection(parsed_data)
114
114
  else
@@ -126,7 +126,7 @@ module Her
126
126
  # Make a PUT request and return a collection of resources
127
127
  def put_collection(path, attrs={})
128
128
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
129
- put_raw(path, attrs) do |parsed_data|
129
+ put_raw(path, attrs) do |parsed_data, response|
130
130
  new_collection(parsed_data)
131
131
  end
132
132
  end
@@ -134,7 +134,7 @@ module Her
134
134
  # Make a PUT request and return a collection of resources
135
135
  def put_resource(path, attrs={})
136
136
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
137
- put_raw(path, attrs) do |parsed_data|
137
+ put_raw(path, attrs) do |parsed_data, response|
138
138
  new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
139
139
  end
140
140
  end
@@ -142,7 +142,7 @@ module Her
142
142
  # Make a PATCH request and return either a collection or a resource
143
143
  def patch(path, attrs={})
144
144
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
145
- patch_raw(path, attrs) do |parsed_data|
145
+ patch_raw(path, attrs) do |parsed_data, response|
146
146
  if parsed_data[:data].is_a?(Array)
147
147
  new_collection(parsed_data)
148
148
  else
@@ -160,7 +160,7 @@ module Her
160
160
  # Make a PATCH request and return a collection of resources
161
161
  def patch_collection(path, attrs={})
162
162
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
163
- patch_raw(path, attrs) do |parsed_data|
163
+ patch_raw(path, attrs) do |parsed_data, response|
164
164
  new_collection(parsed_data)
165
165
  end
166
166
  end
@@ -168,7 +168,7 @@ module Her
168
168
  # Make a PATCH request and return a collection of resources
169
169
  def patch_resource(path, attrs={})
170
170
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
171
- patch_raw(path, attrs) do |parsed_data|
171
+ patch_raw(path, attrs) do |parsed_data, response|
172
172
  new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
173
173
  end
174
174
  end
@@ -176,7 +176,7 @@ module Her
176
176
  # Make a DELETE request and return either a collection or a resource
177
177
  def delete(path, attrs={})
178
178
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
179
- delete_raw(path, attrs) do |parsed_data|
179
+ delete_raw(path, attrs) do |parsed_data, response|
180
180
  if parsed_data[:data].is_a?(Array)
181
181
  new_collection(parsed_data)
182
182
  else
@@ -194,7 +194,7 @@ module Her
194
194
  # Make a DELETE request and return a collection of resources
195
195
  def delete_collection(path, attrs={})
196
196
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
197
- delete_raw(path, attrs) do |parsed_data|
197
+ delete_raw(path, attrs) do |parsed_data, response|
198
198
  new_collection(parsed_data)
199
199
  end
200
200
  end
@@ -202,7 +202,7 @@ module Her
202
202
  # Make a DELETE request and return a collection of resources
203
203
  def delete_resource(path, attrs={})
204
204
  path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
205
- delete_raw(path, attrs) do |parsed_data|
205
+ delete_raw(path, attrs) do |parsed_data, response|
206
206
  new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
207
207
  end
208
208
  end
data/lib/her/model/orm.rb CHANGED
@@ -3,14 +3,15 @@ module Her
3
3
  # This module adds ORM-like capabilities to the model
4
4
  module ORM
5
5
  extend ActiveSupport::Concern
6
- attr_accessor :data, :metadata, :errors
6
+ attr_accessor :data, :metadata, :response_errors
7
7
  alias :attributes :data
8
8
  alias :attributes= :data=
9
9
 
10
10
  # Initialize a new object with data received from an HTTP request
11
11
  def initialize(params={})
12
12
  @metadata = params.delete(:_metadata) || {}
13
- @errors = params.delete(:_errors) || {}
13
+ @response_errors = params.delete(:_errors) || {}
14
+ @destroyed = params.delete(:_destroyed) || false
14
15
 
15
16
  update_data(params)
16
17
  end
@@ -20,7 +21,7 @@ module Her
20
21
  def self.initialize_collection(klass, parsed_data={})
21
22
  collection_data = parsed_data[:data].map do |item_data|
22
23
  resource = klass.new(klass.parse(item_data))
23
- klass.wrap_in_hooks(resource, :find)
24
+ resource.run_callbacks :find
24
25
  resource
25
26
  end
26
27
  Her::Collection.new(collection_data, parsed_data[:metadata], parsed_data[:errors])
@@ -92,16 +93,6 @@ module Her
92
93
  !@data.include?(:id)
93
94
  end
94
95
 
95
- # Return `true` if a resource does not contain errors
96
- def valid?
97
- @errors.empty?
98
- end
99
-
100
- # Return `true` if a resource contains errors
101
- def invalid?
102
- @errors.any?
103
- end
104
-
105
96
  # Return `true` if the other object is also a Her::Model and has matching data
106
97
  def ==(other)
107
98
  other.is_a?(Her::Model) && @data == other.data
@@ -118,6 +109,11 @@ module Her
118
109
  @data.hash
119
110
  end
120
111
 
112
+ # Return whether the object has been destroyed
113
+ def destroyed?
114
+ @destroyed
115
+ end
116
+
121
117
  # Save a resource
122
118
  #
123
119
  # @example Save a resource after fetching it
@@ -136,20 +132,22 @@ module Her
136
132
  resource = self
137
133
 
138
134
  if @data[:id]
139
- hooks = [:update, :save]
135
+ callback = :update
140
136
  method = :put
141
137
  else
142
- hooks = [:create, :save]
138
+ callback = :create
143
139
  method = :post
144
140
  end
145
141
 
146
- self.class.wrap_in_hooks(resource, *hooks) do |resource, klass|
147
- klass.request(params.merge(:_method => method, :_path => "#{request_path}")) do |parsed_data|
148
- update_data(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
149
- self.metadata = parsed_data[:metadata]
150
- self.errors = parsed_data[:errors]
142
+ run_callbacks callback do
143
+ run_callbacks :save do
144
+ self.class.request(params.merge(:_method => method, :_path => "#{request_path}")) do |parsed_data, response|
145
+ update_data(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
146
+ self.metadata = parsed_data[:metadata]
147
+ self.response_errors = parsed_data[:errors]
151
148
 
152
- return false if self.errors.any?
149
+ return false if self.response_errors.any?
150
+ end
153
151
  end
154
152
  end
155
153
 
@@ -164,11 +162,12 @@ module Her
164
162
  # # Called via DELETE "/users/1"
165
163
  def destroy
166
164
  resource = self
167
- self.class.wrap_in_hooks(resource, :destroy) do |resource, klass|
168
- klass.request(:_method => :delete, :_path => "#{request_path}") do |parsed_data|
165
+ run_callbacks :destroy do
166
+ self.class.request(:_method => :delete, :_path => "#{request_path}") do |parsed_data, response|
169
167
  update_data(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
170
168
  self.metadata = parsed_data[:metadata]
171
- self.errors = parsed_data[:errors]
169
+ self.response_errors = parsed_data[:errors]
170
+ @destroyed = true
172
171
  end
173
172
  end
174
173
  self
@@ -229,9 +228,13 @@ module Her
229
228
  params = ids.last.is_a?(Hash) ? ids.pop : {}
230
229
  results = ids.flatten.compact.uniq.map do |id|
231
230
  resource = nil
232
- request(params.merge(:_method => :get, :_path => "#{build_request_path(params.merge(:id => id))}")) do |parsed_data|
233
- resource = new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
234
- wrap_in_hooks(resource, :find)
231
+ request(params.merge(:_method => :get, :_path => "#{build_request_path(params.merge(:id => id))}")) do |parsed_data, response|
232
+ if response.success?
233
+ resource = new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
234
+ resource.run_callbacks :find
235
+ else
236
+ return nil
237
+ end
235
238
  end
236
239
  resource
237
240
  end
@@ -248,7 +251,7 @@ module Her
248
251
  # @users = User.all
249
252
  # # Fetched via GET "/users"
250
253
  def all(params={})
251
- request(params.merge(:_method => :get, :_path => "#{build_request_path(params)}")) do |parsed_data|
254
+ request(params.merge(:_method => :get, :_path => "#{build_request_path(params)}")) do |parsed_data, response|
252
255
  new_collection(parsed_data)
253
256
  end
254
257
  end
@@ -260,14 +263,16 @@ module Her
260
263
  # # Called via POST "/users/1"
261
264
  def create(params={})
262
265
  resource = new(params)
263
- wrap_in_hooks(resource, :create, :save) do |resource, klass|
264
- params = resource.to_params
265
- request(params.merge(:_method => :post, :_path => "#{build_request_path(params)}")) do |parsed_data|
266
- data = parse(parsed_data[:data])
267
- resource.instance_eval do
268
- update_data(data)
269
- @metadata = parsed_data[:metadata]
270
- @errors = parsed_data[:errors]
266
+ resource.run_callbacks :create do
267
+ resource.run_callbacks :save do
268
+ params = resource.to_params
269
+ request(params.merge(:_method => :post, :_path => "#{build_request_path(params)}")) do |parsed_data, response|
270
+ data = parse(parsed_data[:data])
271
+ resource.instance_eval do
272
+ update_data(data)
273
+ @metadata = parsed_data[:metadata]
274
+ @response_errors = parsed_data[:errors]
275
+ end
271
276
  end
272
277
  end
273
278
  end
@@ -291,8 +296,8 @@ module Her
291
296
  # User.destroy_existing(1)
292
297
  # # Called via DELETE "/users/1"
293
298
  def destroy_existing(id, params={})
294
- request(params.merge(:_method => :delete, :_path => "#{build_request_path(params.merge(:id => id))}")) do |parsed_data|
295
- new(parse(parsed_data[:data]))
299
+ request(params.merge(:_method => :delete, :_path => "#{build_request_path(params.merge(:id => id))}")) do |parsed_data, response|
300
+ new(parse(parsed_data[:data]).merge(:_destroyed => true))
296
301
  end
297
302
  end
298
303
 
@@ -83,7 +83,7 @@ module Her
83
83
  method_attrs = method_attrs[0] || {}
84
84
  klass = self.class.nearby_class(attrs[:class_name])
85
85
  if method_attrs.any?
86
- @data[name] = klass.get_collection("#{self.class.build_request_path(method_attrs.merge(:id => id))}#{attrs[:path]}")
86
+ @data[name] = klass.get_collection("#{self.class.build_request_path(method_attrs.merge(:id => id))}#{attrs[:path]}", method_attrs)
87
87
  else
88
88
  @data[name] ||= klass.get_collection("#{self.class.build_request_path(:id => id)}#{attrs[:path]}")
89
89
  end
@@ -131,7 +131,7 @@ module Her
131
131
  method_attrs = method_attrs[0] || {}
132
132
  klass = self.class.nearby_class(attrs[:class_name])
133
133
  if method_attrs.any?
134
- klass.get_resource("#{self.class.build_request_path(method_attrs.merge(:id => id))}#{attrs[:path]}")
134
+ klass.get_resource("#{self.class.build_request_path(method_attrs.merge(:id => id))}#{attrs[:path]}", method_attrs)
135
135
  else
136
136
  @data[name] ||= klass.get_resource("#{self.class.build_request_path(:id => id)}#{attrs[:path]}")
137
137
  end
@@ -169,7 +169,7 @@ module Her
169
169
  method_attrs = method_attrs[0] || {}
170
170
  klass = self.class.nearby_class(attrs[:class_name])
171
171
  if method_attrs.any?
172
- klass.get_resource("#{klass.build_request_path(method_attrs.merge(:id => @data[attrs[:foreign_key].to_sym]))}")
172
+ klass.get_resource("#{klass.build_request_path(method_attrs.merge(:id => @data[attrs[:foreign_key].to_sym]))}", method_attrs)
173
173
  else
174
174
  @data[name] ||= klass.get_resource("#{klass.build_request_path(:id => @data[attrs[:foreign_key].to_sym])}")
175
175
  end