restorm 1.0.0
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 +7 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.rubocop.yml +31 -0
- data/.rubocop_todo.yml +232 -0
- data/.ruby-version +1 -0
- data/.travis.yml +55 -0
- data/.yardopts +2 -0
- data/CONTRIBUTING.md +26 -0
- data/Gemfile +10 -0
- data/HER_README.md +1065 -0
- data/LICENSE +7 -0
- data/README.md +7 -0
- data/Rakefile +11 -0
- data/UPGRADE.md +101 -0
- data/gemfiles/Gemfile.activemodel-4.2 +6 -0
- data/gemfiles/Gemfile.activemodel-5.0 +6 -0
- data/gemfiles/Gemfile.activemodel-5.1 +6 -0
- data/gemfiles/Gemfile.activemodel-5.2 +6 -0
- data/gemfiles/Gemfile.faraday-1.0 +6 -0
- data/lib/restorm/api.rb +121 -0
- data/lib/restorm/collection.rb +13 -0
- data/lib/restorm/errors.rb +29 -0
- data/lib/restorm/json_api/model.rb +42 -0
- data/lib/restorm/middleware/accept_json.rb +18 -0
- data/lib/restorm/middleware/first_level_parse_json.rb +37 -0
- data/lib/restorm/middleware/json_api_parser.rb +37 -0
- data/lib/restorm/middleware/parse_json.rb +22 -0
- data/lib/restorm/middleware/second_level_parse_json.rb +37 -0
- data/lib/restorm/middleware.rb +12 -0
- data/lib/restorm/model/associations/association.rb +128 -0
- data/lib/restorm/model/associations/association_proxy.rb +44 -0
- data/lib/restorm/model/associations/belongs_to_association.rb +95 -0
- data/lib/restorm/model/associations/has_many_association.rb +100 -0
- data/lib/restorm/model/associations/has_one_association.rb +79 -0
- data/lib/restorm/model/associations.rb +141 -0
- data/lib/restorm/model/attributes.rb +322 -0
- data/lib/restorm/model/base.rb +33 -0
- data/lib/restorm/model/deprecated_methods.rb +61 -0
- data/lib/restorm/model/http.rb +119 -0
- data/lib/restorm/model/introspection.rb +67 -0
- data/lib/restorm/model/nested_attributes.rb +45 -0
- data/lib/restorm/model/orm.rb +299 -0
- data/lib/restorm/model/parse.rb +223 -0
- data/lib/restorm/model/paths.rb +125 -0
- data/lib/restorm/model/relation.rb +209 -0
- data/lib/restorm/model.rb +75 -0
- data/lib/restorm/version.rb +3 -0
- data/lib/restorm.rb +19 -0
- data/restorm.gemspec +29 -0
- data/spec/api_spec.rb +120 -0
- data/spec/collection_spec.rb +41 -0
- data/spec/json_api/model_spec.rb +169 -0
- data/spec/middleware/accept_json_spec.rb +11 -0
- data/spec/middleware/first_level_parse_json_spec.rb +63 -0
- data/spec/middleware/json_api_parser_spec.rb +52 -0
- data/spec/middleware/second_level_parse_json_spec.rb +35 -0
- data/spec/model/associations/association_proxy_spec.rb +29 -0
- data/spec/model/associations_spec.rb +911 -0
- data/spec/model/attributes_spec.rb +354 -0
- data/spec/model/callbacks_spec.rb +176 -0
- data/spec/model/dirty_spec.rb +133 -0
- data/spec/model/http_spec.rb +201 -0
- data/spec/model/introspection_spec.rb +81 -0
- data/spec/model/nested_attributes_spec.rb +135 -0
- data/spec/model/orm_spec.rb +704 -0
- data/spec/model/parse_spec.rb +520 -0
- data/spec/model/paths_spec.rb +348 -0
- data/spec/model/relation_spec.rb +247 -0
- data/spec/model/validations_spec.rb +43 -0
- data/spec/model_spec.rb +45 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/macros/her_macros.rb +17 -0
- data/spec/support/macros/model_macros.rb +36 -0
- data/spec/support/macros/request_macros.rb +27 -0
- metadata +203 -0
@@ -0,0 +1,322 @@
|
|
1
|
+
module Restorm
|
2
|
+
module Model
|
3
|
+
# This module handles all methods related to model attributes
|
4
|
+
module Attributes
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Initialize a new object with data
|
8
|
+
#
|
9
|
+
# @param [Hash] attributes The attributes to initialize the object with
|
10
|
+
# @option attributes [Hash,Array] :_metadata
|
11
|
+
# @option attributes [Hash,Array] :_errors
|
12
|
+
# @option attributes [Boolean] :_destroyed
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# class User
|
16
|
+
# include Restorm::Model
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# User.new(name: "Tobias")
|
20
|
+
# # => #<User name="Tobias">
|
21
|
+
#
|
22
|
+
# User.new do |u|
|
23
|
+
# u.name = "Tobias"
|
24
|
+
# end
|
25
|
+
# # => #<User name="Tobias">
|
26
|
+
def initialize(attributes = {})
|
27
|
+
attributes ||= {}
|
28
|
+
@metadata = attributes.delete(:_metadata) || {}
|
29
|
+
@response_errors = attributes.delete(:_errors) || {}
|
30
|
+
@destroyed = attributes.delete(:_destroyed) || false
|
31
|
+
|
32
|
+
attributes = self.class.default_scope.apply_to(attributes)
|
33
|
+
assign_attributes(attributes)
|
34
|
+
yield self if block_given?
|
35
|
+
run_callbacks :initialize
|
36
|
+
end
|
37
|
+
|
38
|
+
# Handles missing methods
|
39
|
+
#
|
40
|
+
# @private
|
41
|
+
def method_missing(method, *args, &blk)
|
42
|
+
if method.to_s =~ /[?=]$/ || @_restorm_attributes.include?(method)
|
43
|
+
# Extract the attribute
|
44
|
+
attribute = method.to_s.sub(/[?=]$/, '')
|
45
|
+
|
46
|
+
# Create a new `attribute` methods set
|
47
|
+
self.class.attributes(*attribute)
|
48
|
+
|
49
|
+
# Resend the method!
|
50
|
+
send(method, *args, &blk)
|
51
|
+
else
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @private
|
57
|
+
def respond_to_missing?(method, include_private = false)
|
58
|
+
method.to_s =~ /[?=]$/ || @_restorm_attributes.include?(method) || super
|
59
|
+
end
|
60
|
+
|
61
|
+
# Assign new attributes to a resource
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# class User
|
65
|
+
# include Restorm::Model
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# user = User.find(1) # => #<User id=1 name="Tobias">
|
69
|
+
# user.assign_attributes(name: "Lindsay")
|
70
|
+
# user.changes # => { :name => ["Tobias", "Lindsay"] }
|
71
|
+
def assign_attributes(new_attributes)
|
72
|
+
if !new_attributes.respond_to?(:to_hash)
|
73
|
+
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
|
74
|
+
end
|
75
|
+
|
76
|
+
# Coerce new_attributes to hash in case of strong parameters
|
77
|
+
new_attributes = new_attributes.to_hash
|
78
|
+
|
79
|
+
@_restorm_attributes ||= attributes
|
80
|
+
|
81
|
+
# Use setter methods first
|
82
|
+
unset_attributes = self.class.use_setter_methods(self, new_attributes)
|
83
|
+
|
84
|
+
# Then translate attributes of associations into association instances
|
85
|
+
associations = self.class.parse_associations(unset_attributes)
|
86
|
+
|
87
|
+
# Then merge the associations into @_restorm_attributes.
|
88
|
+
@_restorm_attributes.merge!(associations)
|
89
|
+
end
|
90
|
+
alias attributes= assign_attributes
|
91
|
+
|
92
|
+
def attributes
|
93
|
+
# The natural choice of instance variable naming here would be
|
94
|
+
# `@attributes`. Unfortunately that causes a naming clash when
|
95
|
+
# used with `ActiveModel` version >= 5.2.0.
|
96
|
+
# As of v5.2.0 `ActiveModel` checks to see if `ActiveRecord`
|
97
|
+
# attributes exist, and assumes that if the instance variable
|
98
|
+
# `@attributes` exists on the instance, it is because they are
|
99
|
+
# `ActiveRecord` attributes.
|
100
|
+
@_restorm_attributes ||= HashWithIndifferentAccess.new
|
101
|
+
end
|
102
|
+
|
103
|
+
# Handles returning true for the accessible attributes
|
104
|
+
#
|
105
|
+
# @private
|
106
|
+
def has_attribute?(attribute_name)
|
107
|
+
@_restorm_attributes.include?(attribute_name)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Handles returning data for a specific attribute
|
111
|
+
#
|
112
|
+
# @private
|
113
|
+
def get_attribute(attribute_name)
|
114
|
+
@_restorm_attributes[attribute_name]
|
115
|
+
end
|
116
|
+
alias attribute get_attribute
|
117
|
+
|
118
|
+
# Return the value of the model `primary_key` attribute
|
119
|
+
def id
|
120
|
+
@_restorm_attributes[self.class.primary_key]
|
121
|
+
end
|
122
|
+
|
123
|
+
# Return `true` if the other object is also a Restorm::Model and has matching
|
124
|
+
# data
|
125
|
+
#
|
126
|
+
# @private
|
127
|
+
def ==(other)
|
128
|
+
other.is_a?(Restorm::Model) && @_restorm_attributes == other.attributes
|
129
|
+
end
|
130
|
+
|
131
|
+
# Delegate to the == method
|
132
|
+
#
|
133
|
+
# @private
|
134
|
+
def eql?(other)
|
135
|
+
self == other
|
136
|
+
end
|
137
|
+
|
138
|
+
# Delegate to @_restorm_attributes, allowing models to act correctly in code like:
|
139
|
+
# [ Model.find(1), Model.find(1) ].uniq # => [ Model.find(1) ]
|
140
|
+
# @private
|
141
|
+
def hash
|
142
|
+
@_restorm_attributes.hash
|
143
|
+
end
|
144
|
+
|
145
|
+
# Assign attribute value (ActiveModel convention method).
|
146
|
+
#
|
147
|
+
# @private
|
148
|
+
def attribute=(attribute, value)
|
149
|
+
@_restorm_attributes[attribute] = nil unless @_restorm_attributes.include?(attribute)
|
150
|
+
send("#{attribute}_will_change!") unless value == @_restorm_attributes[attribute]
|
151
|
+
@_restorm_attributes[attribute] = value
|
152
|
+
end
|
153
|
+
|
154
|
+
# Check attribute value to be present (ActiveModel convention method).
|
155
|
+
#
|
156
|
+
# @private
|
157
|
+
def attribute?(attribute)
|
158
|
+
@_restorm_attributes.include?(attribute) && @_restorm_attributes[attribute].present?
|
159
|
+
end
|
160
|
+
|
161
|
+
module ClassMethods
|
162
|
+
# Initialize a single resource
|
163
|
+
#
|
164
|
+
# @private
|
165
|
+
def instantiate_record(klass, parsed_data)
|
166
|
+
if (record = parsed_data[:data]) && record.is_a?(klass)
|
167
|
+
record
|
168
|
+
else
|
169
|
+
attributes = klass.parse(record).merge(_metadata: parsed_data[:metadata],
|
170
|
+
_errors: parsed_data[:errors])
|
171
|
+
klass.new(attributes).tap do |record_instance|
|
172
|
+
record_instance.send :clear_changes_information
|
173
|
+
record_instance.run_callbacks :find
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Initialize a collection of resources
|
179
|
+
#
|
180
|
+
# @private
|
181
|
+
def instantiate_collection(klass, parsed_data = {})
|
182
|
+
records = klass.extract_array(parsed_data).map do |record|
|
183
|
+
instantiate_record(klass, data: record)
|
184
|
+
end
|
185
|
+
Restorm::Collection.new(records, parsed_data[:metadata], parsed_data[:errors])
|
186
|
+
end
|
187
|
+
|
188
|
+
# Initialize a collection of resources with raw data from an HTTP request
|
189
|
+
#
|
190
|
+
# @param [Array] parsed_data
|
191
|
+
# @private
|
192
|
+
def new_collection(parsed_data)
|
193
|
+
instantiate_collection(self, parsed_data)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Initialize a new object with the "raw" parsed_data from the parsing middleware
|
197
|
+
#
|
198
|
+
# @private
|
199
|
+
def new_from_parsed_data(parsed_data)
|
200
|
+
instantiate_record(self, parsed_data)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Use setter methods of model for each key / value pair in params
|
204
|
+
# Return key / value pairs for which no setter method was defined on the
|
205
|
+
# model
|
206
|
+
#
|
207
|
+
# @private
|
208
|
+
def use_setter_methods(model, params = {})
|
209
|
+
reserved = [:id, model.class.primary_key, *model.class.association_keys]
|
210
|
+
model.class.attributes *params.keys.reject { |k| reserved.include?(k) }
|
211
|
+
|
212
|
+
setter_method_names = model.class.setter_method_names
|
213
|
+
params.each_with_object({}) do |(key, value), memo|
|
214
|
+
setter_method = "#{key}="
|
215
|
+
if setter_method_names.include?(setter_method)
|
216
|
+
model.send setter_method, value
|
217
|
+
else
|
218
|
+
memo[key.to_sym] = value
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Define attribute method matchers to automatically define them using
|
224
|
+
# ActiveModel's define_attribute_methods.
|
225
|
+
#
|
226
|
+
# @private
|
227
|
+
def define_attribute_method_matchers
|
228
|
+
attribute_method_suffix '='
|
229
|
+
attribute_method_suffix '?'
|
230
|
+
end
|
231
|
+
|
232
|
+
# Create a mutex for dynamically generated attribute methods or use one
|
233
|
+
# defined by ActiveModel.
|
234
|
+
#
|
235
|
+
# @private
|
236
|
+
def attribute_methods_mutex
|
237
|
+
@attribute_methods_mutex ||= begin
|
238
|
+
if generated_attribute_methods.respond_to? :mu_synchronize
|
239
|
+
generated_attribute_methods
|
240
|
+
else
|
241
|
+
Mutex.new
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Define the attributes that will be used to track dirty attributes and
|
247
|
+
# validations
|
248
|
+
#
|
249
|
+
# @param [Array] attributes
|
250
|
+
# @example
|
251
|
+
# class User
|
252
|
+
# include Restorm::Model
|
253
|
+
# attributes :name, :email
|
254
|
+
# end
|
255
|
+
def attributes(*attributes)
|
256
|
+
attribute_methods_mutex.synchronize do
|
257
|
+
define_attribute_methods attributes
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Define the accessor in which the API response errors (obtained from
|
262
|
+
# the parsing middleware) will be stored
|
263
|
+
#
|
264
|
+
# @param [Symbol] store_response_errors
|
265
|
+
#
|
266
|
+
# @example
|
267
|
+
# class User
|
268
|
+
# include Restorm::Model
|
269
|
+
# store_response_errors :server_errors
|
270
|
+
# end
|
271
|
+
def store_response_errors(value = nil)
|
272
|
+
store_restorm_data(:response_errors, value)
|
273
|
+
end
|
274
|
+
|
275
|
+
# Define the accessor in which the API response metadata (obtained from
|
276
|
+
# the parsing middleware) will be stored
|
277
|
+
#
|
278
|
+
# @param [Symbol] store_metadata
|
279
|
+
#
|
280
|
+
# @example
|
281
|
+
# class User
|
282
|
+
# include Restorm::Model
|
283
|
+
# store_metadata :server_data
|
284
|
+
# end
|
285
|
+
def store_metadata(value = nil)
|
286
|
+
store_restorm_data(:metadata, value)
|
287
|
+
end
|
288
|
+
|
289
|
+
# @private
|
290
|
+
def setter_method_names
|
291
|
+
@_restorm_setter_method_names ||= begin
|
292
|
+
instance_methods.each_with_object(Set.new) do |method, memo|
|
293
|
+
memo << method.to_s if method.to_s.end_with?('=')
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
private
|
299
|
+
|
300
|
+
# @private
|
301
|
+
def store_restorm_data(name, value)
|
302
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
303
|
+
if @_restorm_store_#{name} && value.present?
|
304
|
+
remove_method @_restorm_store_#{name}.to_sym
|
305
|
+
remove_method @_restorm_store_#{name}.to_s + '='
|
306
|
+
end
|
307
|
+
|
308
|
+
@_restorm_store_#{name} ||= begin
|
309
|
+
superclass.store_#{name} if superclass.respond_to?(:store_#{name})
|
310
|
+
end
|
311
|
+
|
312
|
+
return @_restorm_store_#{name} unless value
|
313
|
+
@_restorm_store_#{name} = value
|
314
|
+
|
315
|
+
define_method(value) { @#{name} }
|
316
|
+
define_method(value.to_s+'=') { |value| @#{name} = value }
|
317
|
+
RUBY
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Restorm
|
2
|
+
module Model
|
3
|
+
# This module includes basic functionnality to Restorm::Model
|
4
|
+
module Base
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Returns true if attribute_name is
|
8
|
+
# * in resource attributes
|
9
|
+
# * an association
|
10
|
+
#
|
11
|
+
# @private
|
12
|
+
def has_key?(attribute_name)
|
13
|
+
has_attribute?(attribute_name) ||
|
14
|
+
has_association?(attribute_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns
|
18
|
+
# * the value of the attribute_name attribute if it's in orm data
|
19
|
+
# * the resource/collection corrsponding to attribute_name if it's an association
|
20
|
+
#
|
21
|
+
# @private
|
22
|
+
def [](attribute_name)
|
23
|
+
get_attribute(attribute_name) ||
|
24
|
+
get_association(attribute_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @private
|
28
|
+
def singularized_resource_name
|
29
|
+
self.class.name.split('::').last.tableize.singularize
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Restorm
|
2
|
+
module Model
|
3
|
+
# @private
|
4
|
+
module DeprecatedMethods
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def self.deprecate!(old, new, object, *args)
|
8
|
+
line = begin
|
9
|
+
raise StandardError
|
10
|
+
rescue StandardError => e
|
11
|
+
e.backtrace[2]
|
12
|
+
end
|
13
|
+
|
14
|
+
warn "#{line} - The `#{old}` method is deprecated and may be removed soon. Please update your code with `#{new}` instead."
|
15
|
+
object.send(new, *args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def data(*args)
|
19
|
+
Restorm::Model::DeprecatedMethods.deprecate! :data, :attributes, self, *args
|
20
|
+
end
|
21
|
+
|
22
|
+
def data=(*args)
|
23
|
+
Restorm::Model::DeprecatedMethods.deprecate! :data=, :attributes=, self, *args
|
24
|
+
end
|
25
|
+
|
26
|
+
def update_attributes(*args)
|
27
|
+
Restorm::Model::DeprecatedMethods.deprecate! :update_attributes, :assign_attributes, self, *args
|
28
|
+
end
|
29
|
+
|
30
|
+
def assign_data(*args)
|
31
|
+
Restorm::Model::DeprecatedMethods.deprecate! :assign_data, :assign_attributes, self, *args
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_data?(*args)
|
35
|
+
Restorm::Model::DeprecatedMethods.deprecate! :has_data?, :has_attribute?, self, *args
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_data(*args)
|
39
|
+
Restorm::Model::DeprecatedMethods.deprecate! :get_data, :get_attribute, self, *args
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
def has_relationship?(*args)
|
44
|
+
Restorm::Model::DeprecatedMethods.deprecate! :has_relationship?, :has_association?, self, *args
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_relationship(*args)
|
48
|
+
Restorm::Model::DeprecatedMethods.deprecate! :get_relationship, :get_association, self, *args
|
49
|
+
end
|
50
|
+
|
51
|
+
def relationships(*args)
|
52
|
+
Restorm::Model::DeprecatedMethods.deprecate! :relationships, :associations, self, *args
|
53
|
+
end
|
54
|
+
|
55
|
+
def her_api(*args)
|
56
|
+
Restorm::Model::DeprecatedMethods.deprecate! :restorm_api, :use_api, self, *args
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Restorm
|
2
|
+
module Model
|
3
|
+
# This module interacts with Restorm::API to fetch HTTP data
|
4
|
+
module HTTP
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
METHODS = [:get, :post, :put, :patch, :delete, :options]
|
7
|
+
|
8
|
+
# For each HTTP method, define these class methods:
|
9
|
+
#
|
10
|
+
# - <method>(path, params)
|
11
|
+
# - <method>_raw(path, params, &block)
|
12
|
+
# - <method>_collection(path, params, &block)
|
13
|
+
# - <method>_resource(path, params, &block)
|
14
|
+
# - custom_<method>(*paths)
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# class User
|
18
|
+
# include Restorm::Model
|
19
|
+
# custom_get :active
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# User.get(:popular) # GET "/users/popular"
|
23
|
+
# User.active # GET "/users/active"
|
24
|
+
module ClassMethods
|
25
|
+
# Change which API the model will use to make its HTTP requests
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# secondary_api = Restorm::API.new :url => "https://api.example" do |connection|
|
29
|
+
# connection.use Faraday::Request::UrlEncoded
|
30
|
+
# connection.use Restorm::Middleware::DefaultParseJSON
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# class User
|
34
|
+
# include Restorm::Model
|
35
|
+
# use_api secondary_api
|
36
|
+
# end
|
37
|
+
def use_api(value = nil)
|
38
|
+
@_restorm_use_api ||= begin
|
39
|
+
superclass.use_api if superclass.respond_to?(:use_api)
|
40
|
+
end
|
41
|
+
|
42
|
+
unless value
|
43
|
+
return (@_restorm_use_api.respond_to? :call) ? @_restorm_use_api.call : @_restorm_use_api
|
44
|
+
end
|
45
|
+
|
46
|
+
@_restorm_use_api = value
|
47
|
+
end
|
48
|
+
|
49
|
+
alias restorm_api use_api
|
50
|
+
alias uses_api use_api
|
51
|
+
|
52
|
+
# Main request wrapper around Restorm::API. Used to make custom request to the API.
|
53
|
+
#
|
54
|
+
# @private
|
55
|
+
def request(params = {})
|
56
|
+
request = restorm_api.request(params)
|
57
|
+
|
58
|
+
if block_given?
|
59
|
+
yield request[:parsed_data], request[:response]
|
60
|
+
else
|
61
|
+
{ :parsed_data => request[:parsed_data], :response => request[:response] }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
METHODS.each do |method|
|
66
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
67
|
+
def #{method}(path, params={})
|
68
|
+
path = build_request_path_from_string_or_symbol(path, params)
|
69
|
+
params = to_params(params) unless #{method.to_sym.inspect} == :get
|
70
|
+
send(:'#{method}_raw', path, params) do |parsed_data, response|
|
71
|
+
if parsed_data[:data].is_a?(Array) || active_model_serializers_format? || json_api_format?
|
72
|
+
new_collection(parsed_data)
|
73
|
+
else
|
74
|
+
new_from_parsed_data(parsed_data)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def #{method}_raw(path, params={}, &block)
|
80
|
+
path = build_request_path_from_string_or_symbol(path, params)
|
81
|
+
request(params.merge(:_method => #{method.to_sym.inspect}, :_path => path), &block)
|
82
|
+
end
|
83
|
+
|
84
|
+
def #{method}_collection(path, params={})
|
85
|
+
path = build_request_path_from_string_or_symbol(path, params)
|
86
|
+
send(:'#{method}_raw', build_request_path_from_string_or_symbol(path, params), params) do |parsed_data, response|
|
87
|
+
new_collection(parsed_data)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def #{method}_resource(path, params={})
|
92
|
+
path = build_request_path_from_string_or_symbol(path, params)
|
93
|
+
send(:"#{method}_raw", path, params) do |parsed_data, response|
|
94
|
+
new_from_parsed_data(parsed_data)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def custom_#{method}(*paths)
|
99
|
+
metaclass = (class << self; self; end)
|
100
|
+
|
101
|
+
# TODO: Remove this check after January 2020
|
102
|
+
if paths.last.is_a?(Hash)
|
103
|
+
warn("[DEPRECATION] options for custom request methods are deprecated and will be removed on or after January 2020.")
|
104
|
+
paths.pop
|
105
|
+
end
|
106
|
+
|
107
|
+
paths.each do |path|
|
108
|
+
metaclass.send(:define_method, path) do |*params|
|
109
|
+
params = params.first || Hash.new
|
110
|
+
send(#{method.to_sym.inspect}, path, params)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
RUBY
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Restorm
|
2
|
+
module Model
|
3
|
+
module Introspection
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
# Inspect an element, returns it for introspection.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# class User
|
9
|
+
# include Restorm::Model
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# @user = User.find(1)
|
13
|
+
# p @user # => #<User(/users/1) id=1 name="Tobias Fünke">
|
14
|
+
def inspect
|
15
|
+
resource_path = begin
|
16
|
+
request_path
|
17
|
+
rescue Restorm::Errors::PathError => e
|
18
|
+
"<unknown path, missing `#{e.missing_parameter}`>"
|
19
|
+
end
|
20
|
+
|
21
|
+
"#<#{self.class}(#{resource_path}) #{attributes.keys.map { |k| "#{k}=#{attribute_for_inspect(send(k))}" }.join(" ")}>"
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def attribute_for_inspect(value)
|
27
|
+
if value.is_a?(String) && value.length > 50
|
28
|
+
"#{value[0..50]}...".inspect
|
29
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
30
|
+
%("#{value}")
|
31
|
+
else
|
32
|
+
value.inspect
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @private
|
37
|
+
module ClassMethods
|
38
|
+
# Finds a class at the same level as this one or at the global level.
|
39
|
+
#
|
40
|
+
# @private
|
41
|
+
def restorm_nearby_class(name)
|
42
|
+
restorm_sibling_class(name) || name.constantize
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
# Looks for a class at the same level as this one with the given name.
|
48
|
+
#
|
49
|
+
# @private
|
50
|
+
def restorm_sibling_class(name)
|
51
|
+
if mod = restorm_containing_module
|
52
|
+
@_restorm_sibling_class ||= Hash.new { Hash.new }
|
53
|
+
@_restorm_sibling_class[mod][name] ||= "#{mod.name}::#{name}".constantize rescue nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# If available, returns the containing Module for this class.
|
58
|
+
#
|
59
|
+
# @private
|
60
|
+
def restorm_containing_module
|
61
|
+
return unless name =~ /::/
|
62
|
+
name.split("::")[0..-2].join("::").constantize
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Restorm
|
2
|
+
module Model
|
3
|
+
module NestedAttributes
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
# Allow nested attributes for an association
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# class User
|
11
|
+
# include Restorm::Model
|
12
|
+
#
|
13
|
+
# has_one :role
|
14
|
+
# accepts_nested_attributes_for :role
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# class Role
|
18
|
+
# include Restorm::Model
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# user = User.new(name: "Tobias", role_attributes: { title: "moderator" })
|
22
|
+
# user.role # => #<Role title="moderator">
|
23
|
+
def accepts_nested_attributes_for(*associations)
|
24
|
+
allowed_association_names = association_names
|
25
|
+
|
26
|
+
associations.each do |association_name|
|
27
|
+
unless allowed_association_names.include?(association_name)
|
28
|
+
raise Restorm::Errors::AssociationUnknownError, "Unknown association name :#{association_name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
32
|
+
if method_defined?(:#{association_name}_attributes=)
|
33
|
+
remove_method(:#{association_name}_attributes=)
|
34
|
+
end
|
35
|
+
|
36
|
+
def #{association_name}_attributes=(attributes)
|
37
|
+
self.#{association_name}.assign_nested_attributes(attributes)
|
38
|
+
end
|
39
|
+
RUBY
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|