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.
- 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
|