test_track_rails_client 4.0.0.alpha12 → 4.0.0.alpha13
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 +4 -4
- data/lib/test_track_rails_client/version.rb +1 -1
- data/vendor/gems/fakeable_her/lib/fakeable_her/model.rb +4 -21
- data/vendor/gems/her/her.gemspec +5 -6
- data/vendor/gems/her/lib/her/api.rb +30 -22
- data/vendor/gems/her/lib/her/collection.rb +2 -1
- data/vendor/gems/her/lib/her/errors.rb +11 -1
- data/vendor/gems/her/lib/her/json_api/model.rb +8 -12
- data/vendor/gems/her/lib/her/middleware/accept_json.rb +1 -0
- data/vendor/gems/her/lib/her/middleware/first_level_parse_json.rb +6 -5
- data/vendor/gems/her/lib/her/middleware/json_api_parser.rb +6 -5
- data/vendor/gems/her/lib/her/middleware/parse_json.rb +2 -1
- data/vendor/gems/her/lib/her/middleware/second_level_parse_json.rb +6 -5
- data/vendor/gems/her/lib/her/middleware.rb +1 -1
- data/vendor/gems/her/lib/her/model/associations/association.rb +38 -16
- data/vendor/gems/her/lib/her/model/associations/association_proxy.rb +2 -3
- data/vendor/gems/her/lib/her/model/associations/belongs_to_association.rb +1 -1
- data/vendor/gems/her/lib/her/model/associations/has_many_association.rb +3 -3
- data/vendor/gems/her/lib/her/model/associations.rb +6 -6
- data/vendor/gems/her/lib/her/model/attributes.rb +121 -92
- data/vendor/gems/her/lib/her/model/base.rb +2 -2
- data/vendor/gems/her/lib/her/model/http.rb +4 -4
- data/vendor/gems/her/lib/her/model/introspection.rb +6 -4
- data/vendor/gems/her/lib/her/model/nested_attributes.rb +1 -1
- data/vendor/gems/her/lib/her/model/orm.rb +101 -29
- data/vendor/gems/her/lib/her/model/parse.rb +35 -28
- data/vendor/gems/her/lib/her/model/paths.rb +3 -4
- data/vendor/gems/her/lib/her/model/relation.rb +51 -24
- data/vendor/gems/her/lib/her/version.rb +1 -1
- metadata +2 -2
@@ -18,8 +18,14 @@ module Her
|
|
18
18
|
# include Her::Model
|
19
19
|
# end
|
20
20
|
#
|
21
|
-
#
|
22
|
-
|
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 =~ /[?=]$/ ||
|
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
|
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
|
-
@
|
76
|
+
@_her_attributes ||= attributes
|
125
77
|
# Use setter methods first
|
126
|
-
unset_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
|
-
|
81
|
+
associations = self.class.parse_associations(unset_attributes)
|
130
82
|
|
131
|
-
# Then merge the
|
132
|
-
@
|
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
|
-
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
|
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) && @
|
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 @
|
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
|
-
@
|
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
|
-
@
|
186
|
-
|
187
|
-
@
|
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
|
-
@
|
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
|
-
|
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
|
-
|
211
|
-
|
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
|
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
|
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 ||=
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
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
|
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
|
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
|
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 ||=
|
276
|
-
|
277
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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 =
|
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
|
60
|
-
|
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
|
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
|
44
|
-
run_callbacks
|
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
|
-
|
48
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
125
|
-
|
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, :
|
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
|
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
|
281
|
+
return new(params) unless request_new_object_on_build?
|
210
282
|
|
211
|
-
path =
|
212
|
-
method =
|
283
|
+
path = build_request_path(params.merge(primary_key => 'new'))
|
284
|
+
method = method_for(:new)
|
213
285
|
|
214
286
|
resource = nil
|
215
|
-
|
287
|
+
request(params.merge(:_method => method, :_path => path, :_headers => request_headers)) do |parsed_data, response|
|
216
288
|
if response.success?
|
217
|
-
resource =
|
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
|