her 0.4.1 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
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