test_track_rails_client 4.0.0.alpha12 → 4.0.0.alpha13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/test_track_rails_client/version.rb +1 -1
  3. data/vendor/gems/fakeable_her/lib/fakeable_her/model.rb +4 -21
  4. data/vendor/gems/her/her.gemspec +5 -6
  5. data/vendor/gems/her/lib/her/api.rb +30 -22
  6. data/vendor/gems/her/lib/her/collection.rb +2 -1
  7. data/vendor/gems/her/lib/her/errors.rb +11 -1
  8. data/vendor/gems/her/lib/her/json_api/model.rb +8 -12
  9. data/vendor/gems/her/lib/her/middleware/accept_json.rb +1 -0
  10. data/vendor/gems/her/lib/her/middleware/first_level_parse_json.rb +6 -5
  11. data/vendor/gems/her/lib/her/middleware/json_api_parser.rb +6 -5
  12. data/vendor/gems/her/lib/her/middleware/parse_json.rb +2 -1
  13. data/vendor/gems/her/lib/her/middleware/second_level_parse_json.rb +6 -5
  14. data/vendor/gems/her/lib/her/middleware.rb +1 -1
  15. data/vendor/gems/her/lib/her/model/associations/association.rb +38 -16
  16. data/vendor/gems/her/lib/her/model/associations/association_proxy.rb +2 -3
  17. data/vendor/gems/her/lib/her/model/associations/belongs_to_association.rb +1 -1
  18. data/vendor/gems/her/lib/her/model/associations/has_many_association.rb +3 -3
  19. data/vendor/gems/her/lib/her/model/associations.rb +6 -6
  20. data/vendor/gems/her/lib/her/model/attributes.rb +121 -92
  21. data/vendor/gems/her/lib/her/model/base.rb +2 -2
  22. data/vendor/gems/her/lib/her/model/http.rb +4 -4
  23. data/vendor/gems/her/lib/her/model/introspection.rb +6 -4
  24. data/vendor/gems/her/lib/her/model/nested_attributes.rb +1 -1
  25. data/vendor/gems/her/lib/her/model/orm.rb +101 -29
  26. data/vendor/gems/her/lib/her/model/parse.rb +35 -28
  27. data/vendor/gems/her/lib/her/model/paths.rb +3 -4
  28. data/vendor/gems/her/lib/her/model/relation.rb +51 -24
  29. data/vendor/gems/her/lib/her/version.rb +1 -1
  30. metadata +2 -2
@@ -18,8 +18,14 @@ module Her
18
18
  # include Her::Model
19
19
  # end
20
20
  #
21
- # User.new(name: "Tobias") # => #<User name="Tobias">
22
- def initialize(attributes={})
21
+ # User.new(name: "Tobias")
22
+ # # => #<User name="Tobias">
23
+ #
24
+ # User.new do |u|
25
+ # u.name = "Tobias"
26
+ # end
27
+ # # => #<User name="Tobias">
28
+ def initialize(attributes = {})
23
29
  attributes ||= {}
24
30
  @metadata = attributes.delete(:_metadata) || {}
25
31
  @response_errors = attributes.delete(:_errors) || {}
@@ -27,70 +33,16 @@ module Her
27
33
 
28
34
  attributes = self.class.default_scope.apply_to(attributes)
29
35
  assign_attributes(attributes)
36
+ yield self if block_given?
30
37
  run_callbacks :initialize
31
38
  end
32
39
 
33
- # Initialize a collection of resources
34
- #
35
- # @private
36
- def self.initialize_collection(klass, parsed_data={})
37
- unless parsed_data[:errors].present?
38
- initialize_resource_collection(klass, parsed_data)
39
- else
40
- initialize_error_collection(parsed_data)
41
- end
42
- end
43
-
44
- def self.initialize_resource_collection(klass, parsed_data={})
45
- collection_data = klass.extract_array(parsed_data).map do |item_data|
46
- if item_data.kind_of?(klass)
47
- resource = item_data
48
- else
49
- resource = klass.new(klass.parse(item_data))
50
- resource.instance_variable_set(:@changed_attributes, {})
51
- resource.run_callbacks :find
52
- end
53
- resource
54
- end
55
- Her::Collection.new(collection_data, parsed_data[:metadata], parsed_data[:errors])
56
- end
57
-
58
- # Initialize an error collection
59
- #
60
- # @private
61
- def self.initialize_error_collection(parsed_data={})
62
- Her::ErrorCollection.new(parsed_data[:metadata], parsed_data[:errors])
63
- end
64
-
65
- # Use setter methods of model for each key / value pair in params
66
- # Return key / value pairs for which no setter method was defined on the model
67
- #
68
- # @private
69
- def self.use_setter_methods(model, params)
70
- params ||= {}
71
-
72
- reserved_keys = [:id, model.class.primary_key] + model.class.association_keys
73
- model.class.attributes *params.keys.reject { |k| reserved_keys.include?(k) || reserved_keys.map(&:to_s).include?(k) }
74
-
75
- setter_method_names = model.class.setter_method_names
76
- params.inject({}) do |memo, (key, value)|
77
- setter_method = key.to_s + '='
78
- if setter_method_names.include?(setter_method)
79
- model.send(setter_method, value)
80
- else
81
- key = key.to_sym if key.is_a?(String)
82
- memo[key] = value
83
- end
84
- memo
85
- end
86
- end
87
-
88
40
  # Handles missing methods
89
41
  #
90
42
  # @private
91
43
  def method_missing(method, *args, &blk)
92
44
  return super if ATTRIBUTE_BLACKLIST.include?(method)
93
- if method.to_s =~ /[?=]$/ || @attributes.include?(method)
45
+ if method.to_s =~ /[?=]$/ || attributes.include?(method)
94
46
  # Extract the attribute
95
47
  attribute = method.to_s.sub(/[?=]$/, '')
96
48
 
@@ -107,7 +59,7 @@ module Her
107
59
  # @private
108
60
  def respond_to_missing?(method, include_private = false)
109
61
  return super if ATTRIBUTE_BLACKLIST.include?(method)
110
- method.to_s.end_with?('=') || method.to_s.end_with?('?') || attributes.include?(method) || super
62
+ method.to_s =~ /[?=]$/ || attributes.include?(method) || super
111
63
  end
112
64
 
113
65
  # Assign new attributes to a resource
@@ -121,47 +73,55 @@ module Her
121
73
  # user.assign_attributes(name: "Lindsay")
122
74
  # user.changes # => { :name => ["Tobias", "Lindsay"] }
123
75
  def assign_attributes(new_attributes)
124
- @attributes ||= attributes
76
+ @_her_attributes ||= attributes
125
77
  # Use setter methods first
126
- unset_attributes = Her::Model::Attributes.use_setter_methods(self, new_attributes)
78
+ unset_attributes = self.class.use_setter_methods(self, new_attributes)
127
79
 
128
80
  # Then translate attributes of associations into association instances
129
- parsed_attributes = self.class.parse_associations(unset_attributes)
81
+ associations = self.class.parse_associations(unset_attributes)
130
82
 
131
- # Then merge the parsed_data into @attributes.
132
- @attributes.merge!(parsed_attributes)
83
+ # Then merge the associations into @_her_attributes.
84
+ @_her_attributes.merge!(associations)
133
85
  end
134
86
  alias attributes= assign_attributes
135
87
 
136
88
  def attributes
137
- @attributes ||= HashWithIndifferentAccess.new
89
+ # The natural choice of instance variable naming here would be
90
+ # `@attributes`. Unfortunately that causes a naming clash when
91
+ # used with `ActiveModel` version >= 5.2.0.
92
+ # As of v5.2.0 `ActiveModel` checks to see if `ActiveRecord`
93
+ # attributes exist, and assumes that if the instance variable
94
+ # `@attributes` exists on the instance, it is because they are
95
+ # `ActiveRecord` attributes.
96
+ @_her_attributes ||= HashWithIndifferentAccess.new
138
97
  end
139
98
 
140
99
  # Handles returning true for the accessible attributes
141
100
  #
142
101
  # @private
143
102
  def has_attribute?(attribute_name)
144
- @attributes.include?(attribute_name)
103
+ @_her_attributes.include?(attribute_name)
145
104
  end
146
105
 
147
106
  # Handles returning data for a specific attribute
148
107
  #
149
108
  # @private
150
109
  def get_attribute(attribute_name)
151
- @attributes[attribute_name]
110
+ @_her_attributes[attribute_name]
152
111
  end
153
112
  alias attribute get_attribute
154
113
 
155
114
  # Return the value of the model `primary_key` attribute
156
115
  def id
157
- @attributes[self.class.primary_key]
116
+ @_her_attributes[self.class.primary_key]
158
117
  end
159
118
 
160
- # Return `true` if the other object is also a Her::Model and has matching data
119
+ # Return `true` if the other object is also a Her::Model and has matching
120
+ # data
161
121
  #
162
122
  # @private
163
123
  def ==(other)
164
- other.is_a?(Her::Model) && @attributes == other.attributes
124
+ other.is_a?(Her::Model) && @_her_attributes == other.attributes
165
125
  end
166
126
 
167
127
  # Delegate to the == method
@@ -171,47 +131,108 @@ module Her
171
131
  self == other
172
132
  end
173
133
 
174
- # Delegate to @attributes, allowing models to act correctly in code like:
134
+ # Delegate to @_her_attributes, allowing models to act correctly in code like:
175
135
  # [ Model.find(1), Model.find(1) ].uniq # => [ Model.find(1) ]
176
136
  # @private
177
137
  def hash
178
- @attributes.hash
138
+ @_her_attributes.hash
179
139
  end
180
140
 
181
141
  # Assign attribute value (ActiveModel convention method).
182
142
  #
183
143
  # @private
184
144
  def attribute=(attribute, value)
185
- @attributes[attribute] = nil unless @attributes.include?(attribute)
186
- self.send(:"#{attribute}_will_change!") if @attributes[attribute] != value
187
- @attributes[attribute] = value
145
+ @_her_attributes[attribute] = nil unless @_her_attributes.include?(attribute)
146
+ send("#{attribute}_will_change!") unless value == @_her_attributes[attribute]
147
+ @_her_attributes[attribute] = value
188
148
  end
189
149
 
190
150
  # Check attribute value to be present (ActiveModel convention method).
191
151
  #
192
152
  # @private
193
153
  def attribute?(attribute)
194
- @attributes.include?(attribute) && @attributes[attribute].present?
154
+ @_her_attributes.include?(attribute) && @_her_attributes[attribute].present?
195
155
  end
196
156
 
197
157
  module ClassMethods
158
+ # Initialize a single resource
159
+ #
160
+ # @private
161
+ def instantiate_record(klass, parsed_data)
162
+ if (record = parsed_data[:data]) && record.is_a?(klass)
163
+ record
164
+ else
165
+ attributes = klass.parse(record).merge(_metadata: parsed_data[:metadata],
166
+ _errors: parsed_data[:errors])
167
+ klass.new(attributes).tap do |record_instance|
168
+ record_instance.send :clear_changes_information
169
+ record_instance.run_callbacks :find
170
+ end
171
+ end
172
+ end
173
+
174
+ # Initialize a collection of resources
175
+ #
176
+ # @private
177
+ def instantiate_collection(klass, parsed_data = {})
178
+ unless parsed_data[:errors].present?
179
+ instantiate_resource_collection(klass, parsed_data)
180
+ else
181
+ instantiate_error_collection(parsed_data)
182
+ end
183
+ end
184
+
185
+ def instantiate_resource_collection(klass, parsed_data = {})
186
+ records = klass.extract_array(parsed_data).map do |record|
187
+ instantiate_record(klass, data: record)
188
+ end
189
+ Her::Collection.new(records, parsed_data[:metadata], parsed_data[:errors])
190
+ end
191
+
192
+ # Initialize an error collection
193
+ #
194
+ # @private
195
+ def instantiate_error_collection(parsed_data={})
196
+ Her::ErrorCollection.new(parsed_data[:metadata], parsed_data[:errors])
197
+ end
198
+
198
199
  # Initialize a collection of resources with raw data from an HTTP request
199
200
  #
200
201
  # @param [Array] parsed_data
201
202
  # @private
202
203
  def new_collection(parsed_data)
203
- Her::Model::Attributes.initialize_collection(self, parsed_data)
204
+ instantiate_collection(self, parsed_data)
204
205
  end
205
206
 
206
207
  # Initialize a new object with the "raw" parsed_data from the parsing middleware
207
208
  #
208
209
  # @private
209
210
  def new_from_parsed_data(parsed_data)
210
- parsed_data = parsed_data.with_indifferent_access
211
- new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
211
+ instantiate_record(self, parsed_data)
212
+ end
213
+
214
+ # Use setter methods of model for each key / value pair in params
215
+ # Return key / value pairs for which no setter method was defined on the
216
+ # model
217
+ #
218
+ # @private
219
+ def use_setter_methods(model, params = {})
220
+ reserved = [:id, model.class.primary_key, *model.class.association_keys]
221
+ model.class.attributes *params.keys.reject { |k| reserved.include?(k) }
222
+
223
+ setter_method_names = model.class.setter_method_names
224
+ params.each_with_object({}) do |(key, value), memo|
225
+ setter_method = "#{key}="
226
+ if setter_method_names.include?(setter_method)
227
+ model.send setter_method, value
228
+ else
229
+ memo[key.to_sym] = value
230
+ end
231
+ end
212
232
  end
213
233
 
214
- # Define attribute method matchers to automatically define them using ActiveModel's define_attribute_methods.
234
+ # Define attribute method matchers to automatically define them using
235
+ # ActiveModel's define_attribute_methods.
215
236
  #
216
237
  # @private
217
238
  def define_attribute_method_matchers
@@ -219,18 +240,22 @@ module Her
219
240
  attribute_method_suffix '?'
220
241
  end
221
242
 
222
- # Create a mutex for dynamically generated attribute methods or use one defined by ActiveModel.
243
+ # Create a mutex for dynamically generated attribute methods or use one
244
+ # defined by ActiveModel.
223
245
  #
224
246
  # @private
225
247
  def attribute_methods_mutex
226
- @attribute_methods_mutex ||= if generated_attribute_methods.respond_to? :mu_synchronize
227
- generated_attribute_methods
228
- else
229
- Mutex.new
230
- end
248
+ @attribute_methods_mutex ||= begin
249
+ if generated_attribute_methods.respond_to? :mu_synchronize
250
+ generated_attribute_methods
251
+ else
252
+ Mutex.new
253
+ end
254
+ end
231
255
  end
232
256
 
233
- # Define the attributes that will be used to track dirty attributes and validations
257
+ # Define the attributes that will be used to track dirty attributes and
258
+ # validations
234
259
  #
235
260
  # @param [Array] attributes
236
261
  # @example
@@ -244,7 +269,8 @@ module Her
244
269
  end
245
270
  end
246
271
 
247
- # Define the accessor in which the API response errors (obtained from the parsing middleware) will be stored
272
+ # Define the accessor in which the API response errors (obtained from
273
+ # the parsing middleware) will be stored
248
274
  #
249
275
  # @param [Symbol] store_response_errors
250
276
  #
@@ -257,7 +283,8 @@ module Her
257
283
  store_her_data(:response_errors, value)
258
284
  end
259
285
 
260
- # Define the accessor in which the API response metadata (obtained from the parsing middleware) will be stored
286
+ # Define the accessor in which the API response metadata (obtained from
287
+ # the parsing middleware) will be stored
261
288
  #
262
289
  # @param [Symbol] store_metadata
263
290
  #
@@ -272,13 +299,15 @@ module Her
272
299
 
273
300
  # @private
274
301
  def setter_method_names
275
- @_her_setter_method_names ||= instance_methods.inject(Set.new) do |memo, method_name|
276
- memo << method_name.to_s if method_name.to_s.end_with?('=')
277
- memo
302
+ @_her_setter_method_names ||= begin
303
+ instance_methods.each_with_object(Set.new) do |method, memo|
304
+ memo << method.to_s if method.to_s.end_with?('=')
305
+ end
278
306
  end
279
307
  end
280
308
 
281
309
  private
310
+
282
311
  # @private
283
312
  def store_her_data(name, value)
284
313
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -11,7 +11,7 @@ module Her
11
11
  # @private
12
12
  def has_key?(attribute_name)
13
13
  has_attribute?(attribute_name) ||
14
- has_association?(attribute_name)
14
+ has_association?(attribute_name)
15
15
  end
16
16
 
17
17
  # Returns
@@ -21,7 +21,7 @@ module Her
21
21
  # @private
22
22
  def [](attribute_name)
23
23
  get_attribute(attribute_name) ||
24
- get_association(attribute_name)
24
+ get_association(attribute_name)
25
25
  end
26
26
 
27
27
  # @private
@@ -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
  #
@@ -52,7 +52,7 @@ module Her
52
52
  # Main request wrapper around Her::API. Used to make custom request to the API.
53
53
  #
54
54
  # @private
55
- def request(params={})
55
+ def request(params = {})
56
56
  request = her_api.request(params)
57
57
  status = request[:response].status
58
58
 
@@ -74,7 +74,7 @@ module Her
74
74
  if parsed_data[:data].is_a?(Array) || active_model_serializers_format? || json_api_format?
75
75
  new_collection(parsed_data)
76
76
  else
77
- new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
77
+ new_from_parsed_data(parsed_data)
78
78
  end
79
79
  end
80
80
  end
@@ -94,7 +94,7 @@ module Her
94
94
  def #{method}_resource(path, params={})
95
95
  path = build_request_path_from_string_or_symbol(path, params)
96
96
  send(:"#{method}_raw", path, params) do |parsed_data, response|
97
- new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
97
+ new_from_parsed_data(parsed_data)
98
98
  end
99
99
  end
100
100
 
@@ -22,6 +22,7 @@ module Her
22
22
  end
23
23
 
24
24
  private
25
+
25
26
  def attribute_for_inspect(value)
26
27
  if value.is_a?(String) && value.length > 50
27
28
  "#{value[0..50]}...".inspect
@@ -38,15 +39,16 @@ module Her
38
39
  #
39
40
  # @private
40
41
  def her_nearby_class(name)
41
- her_sibling_class(name) || name.constantize rescue nil
42
+ her_sibling_class(name) || name.constantize
42
43
  end
43
44
 
44
45
  protected
46
+
45
47
  # Looks for a class at the same level as this one with the given name.
46
48
  #
47
49
  # @private
48
50
  def her_sibling_class(name)
49
- if mod = self.her_containing_module
51
+ if mod = her_containing_module
50
52
  @_her_sibling_class ||= Hash.new { Hash.new }
51
53
  @_her_sibling_class[mod][name] ||= "#{mod.name}::#{name}".constantize rescue nil
52
54
  end
@@ -56,8 +58,8 @@ module Her
56
58
  #
57
59
  # @private
58
60
  def her_containing_module
59
- return unless self.name =~ /::/
60
- self.name.split("::")[0..-2].join("::").constantize
61
+ return unless name =~ /::/
62
+ name.split("::")[0..-2].join("::").constantize
61
63
  end
62
64
  end
63
65
  end
@@ -25,7 +25,7 @@ module Her
25
25
 
26
26
  associations.each do |association_name|
27
27
  unless allowed_association_names.include?(association_name)
28
- raise Her::Errors::AssociationUnknownError.new("Unknown association name :#{association_name}")
28
+ raise Her::Errors::AssociationUnknownError, "Unknown association name :#{association_name}"
29
29
  end
30
30
 
31
31
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+
2
3
  module Her
3
4
  module Model
4
5
  # This module adds ORM-like capabilities to the model
@@ -40,20 +41,13 @@ module Her
40
41
  callback = new? ? :create : :update
41
42
  method = self.class.method_for(callback)
42
43
 
43
- run_callbacks callback do
44
- run_callbacks :save do
45
- params = to_params
44
+ run_callbacks :save do
45
+ run_callbacks callback do
46
46
  self.class.request(to_params.merge(:_method => method, :_path => request_path, :_headers => request_headers, :_options => request_options)) do |parsed_data, response|
47
- assign_attributes(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
48
- @metadata = parsed_data[:metadata]
49
- @response_errors = parsed_data[:errors]
50
- self.assign_attributes(status_code: response.status)
51
-
47
+ load_from_parsed_data(parsed_data)
48
+ assign_attributes(status_code: response.status)
52
49
  return false if !response.success? || @response_errors.any?
53
- if self.changed_attributes.present?
54
- @previously_changed = self.changed_attributes.clone
55
- self.changed_attributes.clear
56
- end
50
+ changes_applied
57
51
  end
58
52
  end
59
53
  end
@@ -87,9 +81,7 @@ module Her
87
81
  run_callbacks :destroy do
88
82
  self.class.request(params.merge(:_method => method, :_path => request_path, :_headers => request_headers, :_options => request_options)) do |parsed_data, response|
89
83
  raise Her::Errors::ResponseError.for(response.status).new("Request against #{self.class} returned a #{response.status}") if response.status >= 400
90
- assign_attributes(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
91
- @metadata = parsed_data[:metadata]
92
- @response_errors = parsed_data[:errors]
84
+ load_from_parsed_data(parsed_data)
93
85
  @destroyed = response.success?
94
86
  end
95
87
  end
@@ -97,11 +89,90 @@ module Her
97
89
  end
98
90
 
99
91
  def full_error_messages
100
- self.response_errors.map do |field, messages|
92
+ response_errors.map do |field, messages|
101
93
  "#{field} #{messages.join(', ')}"
102
94
  end.join('. ')
103
95
  end
104
96
 
97
+ # Initializes +attribute+ to zero if +nil+ and adds the value passed as
98
+ # +by+ (default is 1). The increment is performed directly on the
99
+ # underlying attribute, no setter is invoked. Only makes sense for
100
+ # number-based attributes. Returns +self+.
101
+ def increment(attribute, by = 1)
102
+ attributes[attribute] ||= 0
103
+ attributes[attribute] += by
104
+ self
105
+ end
106
+
107
+ # Wrapper around #increment that saves the resource. Saving is subjected
108
+ # to validation checks. Returns +self+.
109
+ def increment!(attribute, by = 1)
110
+ increment(attribute, by) && save
111
+ self
112
+ end
113
+
114
+ # Initializes +attribute+ to zero if +nil+ and substracts the value passed as
115
+ # +by+ (default is 1). The decrement is performed directly on the
116
+ # underlying attribute, no setter is invoked. Only makes sense for
117
+ # number-based attributes. Returns +self+.
118
+ def decrement(attribute, by = 1)
119
+ increment(attribute, -by)
120
+ end
121
+
122
+ # Wrapper around #decrement that saves the resource. Saving is subjected
123
+ # to validation checks. Returns +self+.
124
+ def decrement!(attribute, by = 1)
125
+ increment!(attribute, -by)
126
+ end
127
+
128
+ # Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
129
+ # if the predicate returns +true+ the attribute will become +false+. This
130
+ # method toggles directly the underlying value without calling any setter.
131
+ # Returns +self+.
132
+ #
133
+ # @example
134
+ # user = User.first
135
+ # user.admin? # => false
136
+ # user.toggle(:admin)
137
+ # user.admin? # => true
138
+ def toggle(attribute)
139
+ attributes[attribute] = !public_send("#{attribute}?")
140
+ self
141
+ end
142
+
143
+ # Wrapper around #toggle that saves the resource. Saving is subjected to
144
+ # validation checks. Returns +true+ if the record could be saved.
145
+ def toggle!(attribute)
146
+ toggle(attribute) && save
147
+ end
148
+
149
+ # Refetches the resource
150
+ #
151
+ # This method finds the resource by its primary key (which could be
152
+ # assigned manually) and modifies the object in-place.
153
+ #
154
+ # @example
155
+ # user = User.find(1)
156
+ # # => #<User(users/1) id=1 name="Tobias Fünke">
157
+ # user.name = "Oops"
158
+ # user.reload # Fetched again via GET "/users/1"
159
+ # # => #<User(users/1) id=1 name="Tobias Fünke">
160
+ def reload(options = nil)
161
+ fresh_object = self.class.find(id)
162
+ assign_attributes(fresh_object.attributes)
163
+ self
164
+ end
165
+
166
+ # Uses parsed response to assign attributes and metadata
167
+ #
168
+ # @private
169
+ def load_from_parsed_data(parsed_data)
170
+ data = parsed_data[:data]
171
+ assign_attributes(self.class.parse(data)) if data.any?
172
+ @metadata = parsed_data[:metadata]
173
+ @response_errors = parsed_data[:errors]
174
+ end
175
+
105
176
  module ClassMethods
106
177
  # Create a new chainable scope
107
178
  #
@@ -121,10 +192,8 @@ module Her
121
192
  instance_exec(*args, &code)
122
193
  end
123
194
 
124
- # Add the scope method to the Relation class
125
- Relation.instance_eval do
126
- define_method(name) { |*args| instance_exec(*args, &code) }
127
- end
195
+ # Add the scope method to the default/blank relation
196
+ scoped.define_singleton_method(name) { |*args| instance_exec(*args, &code) }
128
197
  end
129
198
 
130
199
  # @private
@@ -143,14 +212,15 @@ module Her
143
212
  #
144
213
  # User.all # Called via GET "/users?admin=1"
145
214
  # User.new.admin # => 1
146
- def default_scope(block=nil)
215
+ def default_scope(block = nil)
147
216
  @_her_default_scope ||= (!respond_to?(:default_scope) && superclass.respond_to?(:default_scope)) ? superclass.default_scope : scoped
148
217
  @_her_default_scope = @_her_default_scope.instance_exec(&block) unless block.nil?
149
218
  @_her_default_scope
150
219
  end
151
220
 
152
221
  # Delegate the following methods to `scoped`
153
- [:all, :where, :create, :create!, :build, :find, :find_by, :first, :first_or_create, :first_or_initialize].each do |method|
222
+ [:all, :where, :create, :create!, :build, :find, :find_by, :first, :find_or_create_by,
223
+ :find_or_initialize_by, :first_or_create, :first_or_initialize].each do |method|
154
224
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
155
225
  def #{method}(*params)
156
226
  scoped.send(#{method.to_sym.inspect}, *params)
@@ -184,7 +254,9 @@ module Her
184
254
  data = parse(parsed_data[:data])
185
255
  metadata = parsed_data[:metadata]
186
256
  response_errors = parsed_data[:errors]
187
- new(data.merge(:_destroyed => response.success?, :metadata => metadata, :response_errors => response_errors))
257
+ record = new(data.merge(:_destroyed => response.success?, :metadata => metadata))
258
+ record.response_errors = response_errors
259
+ record
188
260
  end
189
261
  end
190
262
 
@@ -206,23 +278,23 @@ module Her
206
278
  # If the request_new_object_on_build flag is set, the new object is requested via API.
207
279
  def build(attributes = {})
208
280
  params = attributes
209
- return self.new(params) unless self.request_new_object_on_build?
281
+ return new(params) unless request_new_object_on_build?
210
282
 
211
- path = self.build_request_path(params.merge(self.primary_key => 'new'))
212
- method = self.method_for(:new)
283
+ path = build_request_path(params.merge(primary_key => 'new'))
284
+ method = method_for(:new)
213
285
 
214
286
  resource = nil
215
- self.request(params.merge(:_method => method, :_path => path, :_headers => request_headers)) do |parsed_data, response|
287
+ request(params.merge(:_method => method, :_path => path, :_headers => request_headers)) do |parsed_data, response|
216
288
  if response.success?
217
- resource = self.new_from_parsed_data(parsed_data)
289
+ resource = new_from_parsed_data(parsed_data)
218
290
  end
219
291
  end
220
292
  resource
221
293
  end
222
294
 
223
- private
224
295
  # @private
225
296
  def blank_relation
297
+ @blank_relation ||= superclass.blank_relation.clone.tap { |r| r.parent = self } if superclass.respond_to?(:blank_relation)
226
298
  @blank_relation ||= Relation.new(self)
227
299
  end
228
300
  end