api_resource 0.4.0 → 0.4.1
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.
- metadata +179 -123
- data/.document +0 -5
- data/.rspec +0 -5
- data/.travis.yml +0 -4
- data/Gemfile +0 -37
- data/Gemfile.lock +0 -190
- data/Guardfile +0 -27
- data/Rakefile +0 -49
- data/VERSION +0 -1
- data/api_resource.gemspec +0 -180
- data/lib/api_resource.rb +0 -130
- data/lib/api_resource/association_activation.rb +0 -19
- data/lib/api_resource/associations.rb +0 -218
- data/lib/api_resource/associations/association_proxy.rb +0 -116
- data/lib/api_resource/associations/belongs_to_remote_object_proxy.rb +0 -16
- data/lib/api_resource/associations/dynamic_resource_scope.rb +0 -23
- data/lib/api_resource/associations/generic_scope.rb +0 -68
- data/lib/api_resource/associations/has_many_remote_object_proxy.rb +0 -16
- data/lib/api_resource/associations/has_many_through_remote_object_proxy.rb +0 -13
- data/lib/api_resource/associations/has_one_remote_object_proxy.rb +0 -24
- data/lib/api_resource/associations/multi_argument_resource_scope.rb +0 -15
- data/lib/api_resource/associations/multi_object_proxy.rb +0 -84
- data/lib/api_resource/associations/related_object_hash.rb +0 -12
- data/lib/api_resource/associations/relation_scope.rb +0 -25
- data/lib/api_resource/associations/resource_scope.rb +0 -32
- data/lib/api_resource/associations/scope.rb +0 -132
- data/lib/api_resource/associations/single_object_proxy.rb +0 -82
- data/lib/api_resource/attributes.rb +0 -243
- data/lib/api_resource/base.rb +0 -717
- data/lib/api_resource/callbacks.rb +0 -45
- data/lib/api_resource/connection.rb +0 -195
- data/lib/api_resource/core_extensions.rb +0 -7
- data/lib/api_resource/custom_methods.rb +0 -117
- data/lib/api_resource/decorators.rb +0 -6
- data/lib/api_resource/decorators/caching_decorator.rb +0 -20
- data/lib/api_resource/exceptions.rb +0 -99
- data/lib/api_resource/formats.rb +0 -22
- data/lib/api_resource/formats/json_format.rb +0 -25
- data/lib/api_resource/formats/xml_format.rb +0 -36
- data/lib/api_resource/local.rb +0 -12
- data/lib/api_resource/log_subscriber.rb +0 -15
- data/lib/api_resource/mocks.rb +0 -277
- data/lib/api_resource/model_errors.rb +0 -82
- data/lib/api_resource/observing.rb +0 -27
- data/lib/api_resource/railtie.rb +0 -24
- data/lib/api_resource/scopes.rb +0 -48
- data/nohup.out +0 -63
- data/spec/lib/api_resource_spec.rb +0 -43
- data/spec/lib/associations_spec.rb +0 -751
- data/spec/lib/attributes_spec.rb +0 -191
- data/spec/lib/base_spec.rb +0 -655
- data/spec/lib/callbacks_spec.rb +0 -68
- data/spec/lib/connection_spec.rb +0 -137
- data/spec/lib/local_spec.rb +0 -20
- data/spec/lib/mocks_spec.rb +0 -45
- data/spec/lib/model_errors_spec.rb +0 -29
- data/spec/lib/prefixes_spec.rb +0 -107
- data/spec/spec_helper.rb +0 -82
- data/spec/support/mocks/association_mocks.rb +0 -63
- data/spec/support/mocks/error_resource_mocks.rb +0 -21
- data/spec/support/mocks/prefix_model_mocks.rb +0 -5
- data/spec/support/mocks/test_resource_mocks.rb +0 -44
- data/spec/support/requests/association_requests.rb +0 -31
- data/spec/support/requests/error_resource_requests.rb +0 -25
- data/spec/support/requests/prefix_model_requests.rb +0 -7
- data/spec/support/requests/test_resource_requests.rb +0 -38
- data/spec/support/test_resource.rb +0 -72
- data/spec/tmp/DIR +0 -0
@@ -1,82 +0,0 @@
|
|
1
|
-
require 'api_resource/associations/association_proxy'
|
2
|
-
|
3
|
-
module ApiResource
|
4
|
-
|
5
|
-
module Associations
|
6
|
-
|
7
|
-
class SingleObjectProxy < AssociationProxy
|
8
|
-
|
9
|
-
def serializable_hash(options = {})
|
10
|
-
return if self.internal_object.nil?
|
11
|
-
self.internal_object.serializable_hash(options)
|
12
|
-
end
|
13
|
-
|
14
|
-
def internal_object=(contents)
|
15
|
-
if contents.is_a?(self.klass) ||
|
16
|
-
contents.respond_to?(:internal_object) && contents.internal_object.is_a?(self.klass) ||
|
17
|
-
contents.nil?
|
18
|
-
return @internal_object = contents
|
19
|
-
else
|
20
|
-
return load(contents)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def ==(other)
|
25
|
-
return false if self.class != other.class
|
26
|
-
return false if other.internal_object.attributes != self.internal_object.attributes
|
27
|
-
return true
|
28
|
-
end
|
29
|
-
|
30
|
-
protected
|
31
|
-
def load_scope_with_options(scope, options)
|
32
|
-
scope = self.loaded_hash_key(scope.to_s, options)
|
33
|
-
# If the service uri is blank you can't load
|
34
|
-
return nil if self.remote_path.blank?
|
35
|
-
self.loaded[scope] ||= begin
|
36
|
-
self.times_loaded += 1
|
37
|
-
self.klass.new(self.load_from_remote(options))
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def load(contents)
|
42
|
-
# If we get something nil this should just behave like nil
|
43
|
-
return if contents.nil?
|
44
|
-
# if we get an array with a length of one, make it a hash
|
45
|
-
if contents.is_a?(self.class)
|
46
|
-
contents = contents.internal_object.serializable_hash
|
47
|
-
elsif contents.is_a?(Array) && contents.length == 1
|
48
|
-
contents = contents.first
|
49
|
-
end
|
50
|
-
raise "Expected an attributes hash got #{contents}" unless contents.is_a?(Hash)
|
51
|
-
contents = contents.with_indifferent_access
|
52
|
-
# If we don't have a 'service_uri' just assume that these are all attributes and make an object
|
53
|
-
return @internal_object = self.klass.new(contents) unless contents[self.class.remote_path_element]
|
54
|
-
# allow for symbols vs strings with these elements
|
55
|
-
self.remote_path = contents.delete(self.class.remote_path_element)
|
56
|
-
# There's only one hash here so it's hard to distinguish attributes from scopes, the key scopes_only says everything
|
57
|
-
# in this hash is a scope
|
58
|
-
no_attrs = (contents.delete(:scopes_only) || false)
|
59
|
-
attrs = {}
|
60
|
-
contents.each do |key, val|
|
61
|
-
# if this key is an attribute add it to attrs, warn if we've set scopes_only
|
62
|
-
if self.klass.attribute_names.include?(key.to_sym) && !no_attrs
|
63
|
-
attrs[key] = val
|
64
|
-
else
|
65
|
-
warn("#{key} is an attribute of #{self.klass}, beware of name collisions") if no_attrs && self.klass.attribute_names.include?(key)
|
66
|
-
raise "Expected the scope #{key} to have a hash for a value, got #{val}" unless val.is_a?(Hash)
|
67
|
-
self.instance_eval <<-EOE, __FILE__, __LINE__ + 1
|
68
|
-
def #{key}(opts = {})
|
69
|
-
@#{key} ||= ApiResource::Associations::RelationScope.new(self, :#{key}, opts)
|
70
|
-
end
|
71
|
-
EOE
|
72
|
-
self.scopes[key.to_s] = val
|
73
|
-
end
|
74
|
-
end
|
75
|
-
@internal_object = attrs.present? ? self.klass.new(attrs) : nil
|
76
|
-
end
|
77
|
-
|
78
|
-
end
|
79
|
-
|
80
|
-
end
|
81
|
-
|
82
|
-
end
|
@@ -1,243 +0,0 @@
|
|
1
|
-
module ApiResource
|
2
|
-
|
3
|
-
module Attributes
|
4
|
-
|
5
|
-
extend ActiveSupport::Concern
|
6
|
-
include ActiveModel::AttributeMethods
|
7
|
-
include ActiveModel::Dirty
|
8
|
-
|
9
|
-
included do
|
10
|
-
|
11
|
-
alias_method_chain :save, :dirty_tracking
|
12
|
-
|
13
|
-
class_attribute :attribute_names, :public_attribute_names, :protected_attribute_names, :attribute_types
|
14
|
-
|
15
|
-
cattr_accessor :valid_typecasts; self.valid_typecasts = [:date, :time, :float, :integer, :int, :fixnum, :string, :array]
|
16
|
-
|
17
|
-
attr_reader :attributes
|
18
|
-
|
19
|
-
self.attribute_names = []
|
20
|
-
self.public_attribute_names = []
|
21
|
-
self.protected_attribute_names = []
|
22
|
-
self.attribute_types = {}.with_indifferent_access
|
23
|
-
|
24
|
-
define_method(:attributes) do
|
25
|
-
return @attributes if @attributes
|
26
|
-
# Otherwise make the attributes hash of all the attributes
|
27
|
-
@attributes = HashWithIndifferentAccess.new
|
28
|
-
self.class.attribute_names.each do |attr|
|
29
|
-
@attributes[attr] = self.send("#{attr}")
|
30
|
-
end
|
31
|
-
@attributes
|
32
|
-
end
|
33
|
-
|
34
|
-
|
35
|
-
# This method is important for reloading an object. If the
|
36
|
-
# object has already been loaded, its associations will trip
|
37
|
-
# up the load method unless we pass in the internal objects.
|
38
|
-
|
39
|
-
define_method(:attributes_without_proxies) do
|
40
|
-
attributes = @attributes
|
41
|
-
|
42
|
-
if attributes.nil?
|
43
|
-
attributes = self.class.attribute_names.each do |attr|
|
44
|
-
attributes[attr] = self.send("#{attr}")
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
attributes.each do |k,v|
|
49
|
-
if v.respond_to?(:internal_object)
|
50
|
-
if v.internal_object.present?
|
51
|
-
internal = v.internal_object
|
52
|
-
if internal.is_a?(Array)
|
53
|
-
attributes[k] = internal.collect{|item| item.attributes}
|
54
|
-
else
|
55
|
-
attributes[k] = internal.attributes
|
56
|
-
end
|
57
|
-
else
|
58
|
-
attributes[k] = nil
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
attributes
|
64
|
-
end
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
|
-
module ClassMethods
|
69
|
-
|
70
|
-
def define_attributes(*args)
|
71
|
-
args.each do |arg|
|
72
|
-
if arg.is_a?(Array)
|
73
|
-
self.define_attribute_type(arg.first, arg.second)
|
74
|
-
arg = arg.first
|
75
|
-
end
|
76
|
-
self.attribute_names += [arg.to_sym]
|
77
|
-
self.public_attribute_names += [arg.to_sym]
|
78
|
-
|
79
|
-
# Override the setter for dirty tracking
|
80
|
-
self.class_eval <<-EOE, __FILE__, __LINE__ + 1
|
81
|
-
def #{arg}
|
82
|
-
attribute_with_default(:#{arg})
|
83
|
-
end
|
84
|
-
|
85
|
-
def #{arg}=(val)
|
86
|
-
real_val = typecast_attribute(:#{arg}, val)
|
87
|
-
#{arg}_will_change! unless self.#{arg} == real_val
|
88
|
-
attributes[:#{arg}] = real_val
|
89
|
-
end
|
90
|
-
|
91
|
-
def #{arg}?
|
92
|
-
attributes[:#{arg}].present?
|
93
|
-
end
|
94
|
-
EOE
|
95
|
-
end
|
96
|
-
self.attribute_names.uniq!
|
97
|
-
self.public_attribute_names.uniq!
|
98
|
-
end
|
99
|
-
|
100
|
-
def define_protected_attributes(*args)
|
101
|
-
args.each do |arg|
|
102
|
-
|
103
|
-
if arg.is_a?(Array)
|
104
|
-
self.define_attribute_type(arg.first, arg.second)
|
105
|
-
arg = arg.first
|
106
|
-
end
|
107
|
-
|
108
|
-
self.attribute_names += [arg.to_sym]
|
109
|
-
self.protected_attribute_names += [arg.to_sym]
|
110
|
-
|
111
|
-
# These attributes cannot be set, throw an error if you try
|
112
|
-
self.class_eval <<-EOE, __FILE__, __LINE__ + 1
|
113
|
-
|
114
|
-
def #{arg}
|
115
|
-
self.attribute_with_default(:#{arg})
|
116
|
-
end
|
117
|
-
|
118
|
-
def #{arg}=(val)
|
119
|
-
raise "#{arg} is a protected attribute and cannot be set"
|
120
|
-
end
|
121
|
-
|
122
|
-
def #{arg}?
|
123
|
-
self.attributes[:#{arg}].present?
|
124
|
-
end
|
125
|
-
EOE
|
126
|
-
end
|
127
|
-
self.attribute_names.uniq!
|
128
|
-
self.protected_attribute_names.uniq!
|
129
|
-
end
|
130
|
-
|
131
|
-
def define_attribute_type(field, type)
|
132
|
-
raise "#{type} is not a valid type" unless self.valid_typecasts.include?(type.to_sym)
|
133
|
-
self.attribute_types[field] = type.to_sym
|
134
|
-
end
|
135
|
-
|
136
|
-
|
137
|
-
def attribute?(name)
|
138
|
-
self.attribute_names.include?(name.to_sym)
|
139
|
-
end
|
140
|
-
|
141
|
-
def protected_attribute?(name)
|
142
|
-
self.protected_attribute_names.include?(name.to_sym)
|
143
|
-
end
|
144
|
-
|
145
|
-
def clear_attributes
|
146
|
-
self.attribute_names.clear
|
147
|
-
self.public_attribute_names.clear
|
148
|
-
self.protected_attribute_names.clear
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
# set new attributes
|
153
|
-
def attributes=(new_attrs)
|
154
|
-
new_attrs.each_pair do |k,v|
|
155
|
-
self.send("#{k}=",v) unless k.to_sym == :id
|
156
|
-
end
|
157
|
-
new_attrs
|
158
|
-
end
|
159
|
-
|
160
|
-
def save_with_dirty_tracking(*args)
|
161
|
-
if save_without_dirty_tracking(*args)
|
162
|
-
@previously_changed = self.changes
|
163
|
-
@changed_attributes.clear
|
164
|
-
return true
|
165
|
-
else
|
166
|
-
return false
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
def set_attributes_as_current(*attrs)
|
171
|
-
@changed_attributes.clear and return if attrs.blank?
|
172
|
-
attrs.each do |attr|
|
173
|
-
@changed_attributes.delete(attr.to_s)
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
def reset_attribute_changes(*attrs)
|
178
|
-
attrs = self.class.public_attribute_names if attrs.blank?
|
179
|
-
attrs.each do |attr|
|
180
|
-
self.send("reset_#{attr}!")
|
181
|
-
end
|
182
|
-
|
183
|
-
set_attributes_as_current(*attrs)
|
184
|
-
end
|
185
|
-
|
186
|
-
def attribute?(name)
|
187
|
-
self.class.attribute?(name)
|
188
|
-
end
|
189
|
-
|
190
|
-
def protected_attribute?(name)
|
191
|
-
self.class.protected_attribute?(name)
|
192
|
-
end
|
193
|
-
|
194
|
-
def respond_to?(sym, include_private_methods = false)
|
195
|
-
if sym =~ /\?$/
|
196
|
-
return true if self.attribute?($`)
|
197
|
-
elsif sym =~ /=$/
|
198
|
-
return true if self.class.public_attribute_names.include?($`)
|
199
|
-
else
|
200
|
-
return true if self.attribute?(sym.to_sym)
|
201
|
-
end
|
202
|
-
super
|
203
|
-
end
|
204
|
-
|
205
|
-
protected
|
206
|
-
|
207
|
-
def attribute_with_default(field)
|
208
|
-
self.attributes[field].nil? ? self.default_value_for_field(field) : self.attributes[field]
|
209
|
-
end
|
210
|
-
|
211
|
-
def default_value_for_field(field)
|
212
|
-
case self.class.attribute_types[field.to_sym]
|
213
|
-
when :array
|
214
|
-
return []
|
215
|
-
else
|
216
|
-
return nil
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
def typecast_attribute(field, val)
|
221
|
-
return val unless self.class.attribute_types.include?(field)
|
222
|
-
case self.class.attribute_types[field.to_sym]
|
223
|
-
when :date
|
224
|
-
return val.class == Date ? val.dup : Date.parse(val)
|
225
|
-
when :time
|
226
|
-
return val.class == Time ? val.dup : Time.parse(val)
|
227
|
-
when :integer, :int, :fixnum
|
228
|
-
return val.class == Fixnum ? val.dup : val.to_i rescue val
|
229
|
-
when :float
|
230
|
-
return val.class == Float ? val.dup : val.to_f rescue val
|
231
|
-
when :string
|
232
|
-
return val.class == String ? val.dup : val.to_s rescue val
|
233
|
-
when :array
|
234
|
-
return val.class == Array ? val.dup : Array.wrap(val)
|
235
|
-
else
|
236
|
-
# catches the nil case and just leaves it alone
|
237
|
-
return val.dup rescue val
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
end
|
242
|
-
|
243
|
-
end
|
data/lib/api_resource/base.rb
DELETED
@@ -1,717 +0,0 @@
|
|
1
|
-
require 'pp'
|
2
|
-
require 'active_support'
|
3
|
-
require 'active_support/core_ext'
|
4
|
-
require 'active_support/string_inquirer'
|
5
|
-
|
6
|
-
module ApiResource
|
7
|
-
|
8
|
-
class Base
|
9
|
-
|
10
|
-
class_attribute :site, :proxy, :user, :password, :auth_type, :format,
|
11
|
-
:timeout, :open_timeout, :ssl_options, :token, :ttl
|
12
|
-
|
13
|
-
|
14
|
-
class_attribute :include_root_in_json
|
15
|
-
self.include_root_in_json = true
|
16
|
-
|
17
|
-
class_attribute :include_nil_attributes_on_create
|
18
|
-
self.include_nil_attributes_on_create = false
|
19
|
-
|
20
|
-
class_attribute :include_all_attributes_on_update
|
21
|
-
self.include_nil_attributes_on_create = false
|
22
|
-
|
23
|
-
class_attribute :format
|
24
|
-
self.format = ApiResource::Formats::JsonFormat
|
25
|
-
|
26
|
-
class_attribute :primary_key
|
27
|
-
self.primary_key = "id"
|
28
|
-
|
29
|
-
class << self
|
30
|
-
|
31
|
-
# writers - accessors with defaults were not working
|
32
|
-
attr_writer :element_name, :collection_name
|
33
|
-
|
34
|
-
def inherited(klass)
|
35
|
-
# Call the methods of the superclass to make sure inheritable accessors and the like have been inherited
|
36
|
-
super
|
37
|
-
# Now we need to define the inherited method on the klass that's doing the inheriting
|
38
|
-
# it calls super which will allow the chaining effect we need
|
39
|
-
klass.instance_eval <<-EOE, __FILE__, __LINE__ + 1
|
40
|
-
def inherited(klass)
|
41
|
-
klass.send(:define_singleton_method, :collection_name, lambda {self.superclass.collection_name})
|
42
|
-
super(klass)
|
43
|
-
end
|
44
|
-
EOE
|
45
|
-
true
|
46
|
-
end
|
47
|
-
# This makes a request to new_element_path
|
48
|
-
def set_class_attributes_upon_load
|
49
|
-
return true if self == ApiResource::Base
|
50
|
-
begin
|
51
|
-
class_data = self.connection.get(
|
52
|
-
self.new_element_path, self.headers
|
53
|
-
)
|
54
|
-
# Attributes go first
|
55
|
-
if class_data["attributes"]
|
56
|
-
|
57
|
-
define_attributes(
|
58
|
-
*(class_data["attributes"]["public"] || [])
|
59
|
-
)
|
60
|
-
define_protected_attributes(
|
61
|
-
*(class_data["attributes"]["protected"] || [])
|
62
|
-
)
|
63
|
-
|
64
|
-
end
|
65
|
-
# Then scopes
|
66
|
-
if class_data["scopes"]
|
67
|
-
class_data["scopes"].each_pair do |scope_name, opts|
|
68
|
-
self.scope(scope_name, opts)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
# Then associations
|
72
|
-
if class_data["associations"]
|
73
|
-
class_data["associations"].each_pair do |key, hash|
|
74
|
-
hash.each_pair do |assoc_name, assoc_options|
|
75
|
-
self.send(key, assoc_name, assoc_options)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# This is provided by ActiveModel::AttributeMethods, it should
|
81
|
-
# define the basic methods but we need to override all the setters
|
82
|
-
# so we do dirty tracking
|
83
|
-
attrs = []
|
84
|
-
if class_data["attributes"] && class_data["attributes"]["public"]
|
85
|
-
attrs += class_data["attributes"]["public"].collect{|v|
|
86
|
-
v.is_a?(Array) ? v.first : v
|
87
|
-
}.flatten
|
88
|
-
end
|
89
|
-
if class_data["associations"]
|
90
|
-
attrs += class_data["associations"].values.collect(&:keys).flatten
|
91
|
-
end
|
92
|
-
define_attribute_methods(attrs)
|
93
|
-
|
94
|
-
# Swallow up any loading errors because the site may be incorrect
|
95
|
-
rescue Exception => e
|
96
|
-
if ApiResource.raise_missing_definition_error
|
97
|
-
raise e
|
98
|
-
end
|
99
|
-
ApiResource.logger.warn(
|
100
|
-
"#{self} accessing #{self.new_element_path}"
|
101
|
-
)
|
102
|
-
ApiResource.logger.warn(
|
103
|
-
"#{self}: #{e.message[0..60].gsub(/[\n\r]/, '')} ...\n"
|
104
|
-
)
|
105
|
-
ApiResource.logger.debug(e.backtrace.pretty_inspect)
|
106
|
-
return e.respond_to?(:request) ? e.request : nil
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def reset_connection
|
111
|
-
remove_instance_variable(:@connection) if @connection.present?
|
112
|
-
end
|
113
|
-
|
114
|
-
# load our resource definition to make sure we know what this class
|
115
|
-
# responds to
|
116
|
-
def respond_to?(*args)
|
117
|
-
unless self.instance_variable_defined?(:@class_data)
|
118
|
-
self.instance_variable_set(:@class_data, true)
|
119
|
-
self.set_class_attributes_upon_load
|
120
|
-
end
|
121
|
-
super
|
122
|
-
end
|
123
|
-
|
124
|
-
def reload_class_attributes
|
125
|
-
# clear the public_attribute_names, protected_attribute_names
|
126
|
-
remove_instance_variable(:@class_data) if instance_variable_defined?(:@class_data)
|
127
|
-
self.clear_attributes
|
128
|
-
self.clear_related_objects
|
129
|
-
self.set_class_attributes_upon_load
|
130
|
-
end
|
131
|
-
|
132
|
-
def token_with_new_token_set=(new_token)
|
133
|
-
self.token_without_new_token_set = new_token
|
134
|
-
self.connection(true)
|
135
|
-
self.descendants.each do |child|
|
136
|
-
child.send(:token=, new_token)
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
alias_method_chain :token=, :new_token_set
|
141
|
-
|
142
|
-
def site_with_connection_reset=(site)
|
143
|
-
# store so we can reload attributes if the site changed
|
144
|
-
old_site = self.site.to_s.clone
|
145
|
-
@connection = nil
|
146
|
-
|
147
|
-
if site.nil?
|
148
|
-
self.site_without_connection_reset = nil
|
149
|
-
# no site, so we'll skip the reload
|
150
|
-
return site
|
151
|
-
else
|
152
|
-
self.site_without_connection_reset = create_site_uri_from(site)
|
153
|
-
end
|
154
|
-
|
155
|
-
# reset class attributes and try to reload them if the site changed
|
156
|
-
unless self.site.to_s == old_site
|
157
|
-
self.reload_class_attributes
|
158
|
-
end
|
159
|
-
|
160
|
-
return site
|
161
|
-
end
|
162
|
-
|
163
|
-
alias_method_chain :site=, :connection_reset
|
164
|
-
|
165
|
-
|
166
|
-
def format_with_mimetype_or_format_set=(mime_type_or_format)
|
167
|
-
format = mime_type_or_format.is_a?(Symbol) ? ApiResource::Formats[mime_type_or_format] : mime_type_or_format
|
168
|
-
self.format_without_mimetype_or_format_set = format
|
169
|
-
self.connection.format = format if self.site
|
170
|
-
end
|
171
|
-
|
172
|
-
alias_method_chain :format=, :mimetype_or_format_set
|
173
|
-
|
174
|
-
def timeout_with_connection_reset=(timeout)
|
175
|
-
@connection = nil
|
176
|
-
self.timeout_without_connection_reset = timeout
|
177
|
-
end
|
178
|
-
|
179
|
-
alias_method_chain :timeout=, :connection_reset
|
180
|
-
|
181
|
-
def open_timeout_with_connection_reset=(timeout)
|
182
|
-
@connection = nil
|
183
|
-
self.open_timeout_without_connection_reset = timeout
|
184
|
-
end
|
185
|
-
|
186
|
-
alias_method_chain :open_timeout=, :connection_reset
|
187
|
-
|
188
|
-
def connection(refresh = false)
|
189
|
-
@connection = Connection.new(self.site, self.format, self.headers) if refresh || @connection.nil?
|
190
|
-
@connection.timeout = self.timeout
|
191
|
-
@connection
|
192
|
-
end
|
193
|
-
|
194
|
-
def headers
|
195
|
-
{}.tap do |ret|
|
196
|
-
ret['Lifebooker-Token'] = self.token if self.token.present?
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
def prefix(options = {})
|
201
|
-
default = (self.site ? self.site.path : '/')
|
202
|
-
default << '/' unless default[-1..-1] == '/'
|
203
|
-
self.prefix = default
|
204
|
-
prefix(options)
|
205
|
-
end
|
206
|
-
|
207
|
-
def prefix_source
|
208
|
-
prefix
|
209
|
-
prefix_source
|
210
|
-
end
|
211
|
-
|
212
|
-
def prefix=(value = '/')
|
213
|
-
prefix_call = value.gsub(/:\w+/) { |key| "\#{URI.escape options[#{key}].to_s}"}
|
214
|
-
@prefix_parameters = nil
|
215
|
-
silence_warnings do
|
216
|
-
instance_eval <<-EOE, __FILE__, __LINE__ + 1
|
217
|
-
def prefix_source() "#{value}" end
|
218
|
-
def prefix(options={}) "#{prefix_call}" end
|
219
|
-
EOE
|
220
|
-
end
|
221
|
-
rescue Exception => e
|
222
|
-
logger.error "Couldn't set prefix: #{e}\n #{code}" if logger
|
223
|
-
raise
|
224
|
-
end
|
225
|
-
|
226
|
-
# element_name with default
|
227
|
-
def element_name
|
228
|
-
@element_name ||= self.model_name.element
|
229
|
-
end
|
230
|
-
# collection_name with default
|
231
|
-
def collection_name
|
232
|
-
@collection_name ||= ActiveSupport::Inflector.pluralize(self.element_name)
|
233
|
-
end
|
234
|
-
|
235
|
-
# alias_method :set_prefix, :prefix=
|
236
|
-
# alias_method :set_element_name, :element_name=
|
237
|
-
# alias_method :set_collection_name, :collection_name=
|
238
|
-
|
239
|
-
def element_path(id, prefix_options = {}, query_options = nil)
|
240
|
-
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
|
241
|
-
|
242
|
-
# If we have a prefix, we need a foreign key id
|
243
|
-
# This regex detects '//', which means no foreign key id is present.
|
244
|
-
if prefix(prefix_options) =~ /\/\/$/
|
245
|
-
"/#{collection_name}/#{URI.escape id.to_s}.#{format.extension}#{query_string(query_options)}"
|
246
|
-
else
|
247
|
-
# Fall back on this rather than search without the id
|
248
|
-
"#{prefix(prefix_options)}#{collection_name}/#{URI.escape id.to_s}.#{format.extension}#{query_string(query_options)}"
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
# TODO: Add back in support for non-dynamic prefix paths (e.g. /subdir/resources/new.json)
|
253
|
-
def new_element_path
|
254
|
-
"/#{collection_name}/new.#{format.extension}"
|
255
|
-
end
|
256
|
-
|
257
|
-
def collection_path(prefix_options = {}, query_options = nil)
|
258
|
-
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
|
259
|
-
|
260
|
-
# If we have a prefix, we need a foreign key id
|
261
|
-
# This regex detects '//', which means no foreign key id is present.
|
262
|
-
if prefix(prefix_options) =~ /\/\/$/
|
263
|
-
"/#{collection_name}.#{format.extension}#{query_string(query_options)}"
|
264
|
-
else
|
265
|
-
# Fall back on this rather than search without the id
|
266
|
-
"#{prefix(prefix_options)}#{collection_name}.#{format.extension}#{query_string(query_options)}"
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
def build(attributes = {})
|
271
|
-
self.new(attributes)
|
272
|
-
end
|
273
|
-
|
274
|
-
def create(attributes = {})
|
275
|
-
self.new(attributes).tap{ |resource| resource.save }
|
276
|
-
end
|
277
|
-
|
278
|
-
# This decides which finder method to call.
|
279
|
-
# It accepts arguments of the form "scope", "options={}"
|
280
|
-
# where options can be standard rails options or :expires_in.
|
281
|
-
# If :expires_in is set, it caches it for expires_in seconds.
|
282
|
-
def find(*arguments)
|
283
|
-
scope = arguments.slice!(0)
|
284
|
-
options = arguments.slice!(0) || {}
|
285
|
-
|
286
|
-
expiry = options.delete(:expires_in) || ApiResource::Base.ttl || 0
|
287
|
-
ApiResource.with_ttl(expiry.to_f) do
|
288
|
-
case scope
|
289
|
-
when :all then find_every(options)
|
290
|
-
when :first then find_every(options).first
|
291
|
-
when :last then find_every(options).last
|
292
|
-
when :one then find_one(options)
|
293
|
-
else find_single(scope, options)
|
294
|
-
end
|
295
|
-
end
|
296
|
-
end
|
297
|
-
|
298
|
-
|
299
|
-
# A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass
|
300
|
-
# in all the same arguments to this method as you can to
|
301
|
-
# <tt>find(:first)</tt>.
|
302
|
-
def first(*args)
|
303
|
-
find(:first, *args)
|
304
|
-
end
|
305
|
-
|
306
|
-
# A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass
|
307
|
-
# in all the same arguments to this method as you can to
|
308
|
-
# <tt>find(:last)</tt>.
|
309
|
-
def last(*args)
|
310
|
-
find(:last, *args)
|
311
|
-
end
|
312
|
-
|
313
|
-
# This is an alias for find(:all). You can pass in all the same
|
314
|
-
# arguments to this method as you can to <tt>find(:all)</tt>
|
315
|
-
def all(*args)
|
316
|
-
find(:all, *args)
|
317
|
-
end
|
318
|
-
|
319
|
-
|
320
|
-
# Deletes the resources with the ID in the +id+ parameter.
|
321
|
-
#
|
322
|
-
# ==== Options
|
323
|
-
# All options specify \prefix and query parameters.
|
324
|
-
#
|
325
|
-
# ==== Examples
|
326
|
-
# Event.delete(2) # sends DELETE /events/2
|
327
|
-
#
|
328
|
-
# Event.create(:name => 'Free Concert', :location => 'Community Center')
|
329
|
-
# my_event = Event.find(:first) # let's assume this is event with ID 7
|
330
|
-
# Event.delete(my_event.id) # sends DELETE /events/7
|
331
|
-
#
|
332
|
-
# # Let's assume a request to events/5/cancel.xml
|
333
|
-
# Event.delete(params[:id]) # sends DELETE /events/5
|
334
|
-
def delete(id, options = {})
|
335
|
-
connection.delete(element_path(id, options))
|
336
|
-
end
|
337
|
-
|
338
|
-
protected
|
339
|
-
def method_missing(meth, *args, &block)
|
340
|
-
# make one attempt to load remote attrs
|
341
|
-
unless self.instance_variable_defined?(:@class_data)
|
342
|
-
self.set_class_attributes_upon_load
|
343
|
-
self.instance_variable_set(:@class_data, true)
|
344
|
-
return self.send(meth, *args, &block)
|
345
|
-
end
|
346
|
-
super
|
347
|
-
end
|
348
|
-
|
349
|
-
private
|
350
|
-
# Find every resource
|
351
|
-
def find_every(options)
|
352
|
-
begin
|
353
|
-
case from = options[:from]
|
354
|
-
when Symbol
|
355
|
-
instantiate_collection(get(from, options[:params]))
|
356
|
-
when String
|
357
|
-
path = "#{from}#{query_string(options[:params])}"
|
358
|
-
instantiate_collection(connection.get(path, headers) || [])
|
359
|
-
else
|
360
|
-
prefix_options, query_options = split_options(options[:params])
|
361
|
-
path = collection_path(prefix_options, query_options)
|
362
|
-
instantiate_collection( (connection.get(path, headers) || []))
|
363
|
-
end
|
364
|
-
rescue ApiResource::ResourceNotFound
|
365
|
-
# Swallowing ResourceNotFound exceptions and return nil - as per
|
366
|
-
# ActiveRecord.
|
367
|
-
nil
|
368
|
-
end
|
369
|
-
end
|
370
|
-
|
371
|
-
# Find a single resource from a one-off URL
|
372
|
-
def find_one(options)
|
373
|
-
case from = options[:from]
|
374
|
-
when Symbol
|
375
|
-
instantiate_record(get(from, options[:params]))
|
376
|
-
when String
|
377
|
-
path = "#{from}#{query_string(options[:params])}"
|
378
|
-
instantiate_record(connection.get(path, headers))
|
379
|
-
end
|
380
|
-
end
|
381
|
-
|
382
|
-
# Find a single resource from the default URL
|
383
|
-
def find_single(scope, options)
|
384
|
-
prefix_options, query_options = split_options(options[:params])
|
385
|
-
path = element_path(scope, prefix_options, query_options)
|
386
|
-
instantiate_record(connection.get(path, headers))
|
387
|
-
end
|
388
|
-
|
389
|
-
def instantiate_collection(collection)
|
390
|
-
collection.collect! { |record| instantiate_record(record) }
|
391
|
-
end
|
392
|
-
|
393
|
-
def instantiate_record(record)
|
394
|
-
new(record)
|
395
|
-
end
|
396
|
-
|
397
|
-
|
398
|
-
# Accepts a URI and creates the site URI from that.
|
399
|
-
def create_site_uri_from(site)
|
400
|
-
site.is_a?(URI) ? site.dup : uri_parser.parse(site)
|
401
|
-
end
|
402
|
-
|
403
|
-
# Accepts a URI and creates the proxy URI from that.
|
404
|
-
def create_proxy_uri_from(proxy)
|
405
|
-
proxy.is_a?(URI) ? proxy.dup : uri_parser.parse(proxy)
|
406
|
-
end
|
407
|
-
|
408
|
-
# contains a set of the current prefix parameters.
|
409
|
-
def prefix_parameters
|
410
|
-
@prefix_parameters ||= prefix_source.scan(/:\w+/).map { |key| key[1..-1].to_sym }.to_set
|
411
|
-
end
|
412
|
-
|
413
|
-
# Builds the query string for the request.
|
414
|
-
def query_string(options)
|
415
|
-
"?#{options.to_query}" unless options.nil? || options.empty?
|
416
|
-
end
|
417
|
-
|
418
|
-
# split an option hash into two hashes, one containing the prefix options,
|
419
|
-
# and the other containing the leftovers.
|
420
|
-
def split_options(options = {})
|
421
|
-
prefix_options, query_options = {}, {}
|
422
|
-
(options || {}).each do |key, value|
|
423
|
-
next if key.blank?
|
424
|
-
(prefix_parameters.include?(key.to_sym) ? prefix_options : query_options)[key.to_sym] = value
|
425
|
-
end
|
426
|
-
|
427
|
-
[ prefix_options, query_options ]
|
428
|
-
end
|
429
|
-
|
430
|
-
def uri_parser
|
431
|
-
@uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
|
432
|
-
end
|
433
|
-
|
434
|
-
end
|
435
|
-
|
436
|
-
def initialize(attributes = {})
|
437
|
-
# if we initialize this class, load the attributes
|
438
|
-
unless self.class.instance_variable_defined?(:@class_data)
|
439
|
-
self.class.set_class_attributes_upon_load
|
440
|
-
self.class.instance_variable_set(:@class_data, true)
|
441
|
-
end
|
442
|
-
# Now we can make a call to setup the inheriting klass with its attributes
|
443
|
-
load(attributes)
|
444
|
-
end
|
445
|
-
|
446
|
-
def new?
|
447
|
-
id.blank?
|
448
|
-
end
|
449
|
-
alias :new_record? :new?
|
450
|
-
|
451
|
-
def persisted?
|
452
|
-
!new?
|
453
|
-
end
|
454
|
-
|
455
|
-
def id
|
456
|
-
self.attributes[self.class.primary_key]
|
457
|
-
end
|
458
|
-
|
459
|
-
# Bypass dirty tracking for this field
|
460
|
-
def id=(id)
|
461
|
-
attributes[self.class.primary_key] = id
|
462
|
-
end
|
463
|
-
|
464
|
-
def ==(other)
|
465
|
-
other.equal?(self) || (other.instance_of?(self.class) && other.id == self.id)
|
466
|
-
end
|
467
|
-
|
468
|
-
def eql?(other)
|
469
|
-
self == other
|
470
|
-
end
|
471
|
-
|
472
|
-
def hash
|
473
|
-
id.hash
|
474
|
-
end
|
475
|
-
|
476
|
-
def dup
|
477
|
-
self.class.new.tap do |resource|
|
478
|
-
resource.attributes = self.attributes
|
479
|
-
end
|
480
|
-
end
|
481
|
-
|
482
|
-
def update_attributes(attrs)
|
483
|
-
self.attributes = attrs
|
484
|
-
self.save
|
485
|
-
end
|
486
|
-
|
487
|
-
def save(*args)
|
488
|
-
new? ? create(*args) : update(*args)
|
489
|
-
end
|
490
|
-
|
491
|
-
def save!(*args)
|
492
|
-
save(*args) || raise(ApiResource::ResourceInvalid.new(self))
|
493
|
-
end
|
494
|
-
|
495
|
-
def destroy
|
496
|
-
connection.delete(element_path(self.id), self.class.headers)
|
497
|
-
end
|
498
|
-
|
499
|
-
def encode(options = {})
|
500
|
-
self.send("to_#{self.class.format.extension}", options)
|
501
|
-
end
|
502
|
-
|
503
|
-
def reload
|
504
|
-
remove_instance_variable(:@assoc_attributes) if instance_variable_defined?(:@assoc_attributes)
|
505
|
-
self.load(self.class.find(to_param, :params => @prefix_options).attributes_without_proxies)
|
506
|
-
end
|
507
|
-
|
508
|
-
def to_param
|
509
|
-
# Stolen from active_record.
|
510
|
-
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
|
511
|
-
id && id.to_s # Be sure to stringify the id for routes
|
512
|
-
end
|
513
|
-
|
514
|
-
def prefix_options
|
515
|
-
return {} unless self.class.prefix_source =~ /\:/
|
516
|
-
ret = {}
|
517
|
-
self.prefix_attribute_names.each do |name|
|
518
|
-
ret[name] = self.send(name)
|
519
|
-
end
|
520
|
-
ret
|
521
|
-
end
|
522
|
-
|
523
|
-
def prefix_attribute_names
|
524
|
-
return [] unless self.class.prefix_source =~ /\:/
|
525
|
-
self.class.prefix_source.scan(/\:(\w+)/).collect{|match| match.first.to_sym}
|
526
|
-
end
|
527
|
-
|
528
|
-
def load(attributes)
|
529
|
-
return if attributes.nil?
|
530
|
-
raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
|
531
|
-
|
532
|
-
attributes.symbolize_keys.each do |key, value|
|
533
|
-
# If this attribute doesn't exist define it as a protected attribute
|
534
|
-
self.class.define_protected_attributes(key) unless self.respond_to?(key)
|
535
|
-
#self.send("#{key}_will_change!") if self.respond_to?("#{key}_will_change!")
|
536
|
-
self.attributes[key] =
|
537
|
-
case value
|
538
|
-
when Array
|
539
|
-
if self.has_many?(key)
|
540
|
-
MultiObjectProxy.new(self.has_many_class_name(key), value)
|
541
|
-
elsif self.association?(key)
|
542
|
-
raise ArgumentError, "Expected a hash value or nil, got: #{value.inspect}"
|
543
|
-
else
|
544
|
-
typecast_attribute(key, value)
|
545
|
-
end
|
546
|
-
when Hash
|
547
|
-
if self.has_many?(key)
|
548
|
-
MultiObjectProxy.new(self.has_many_class_name(key), value)
|
549
|
-
elsif self.association?(key)
|
550
|
-
#binding.pry
|
551
|
-
SingleObjectProxy.new(self.association_class_name(key), value)
|
552
|
-
else
|
553
|
-
typecast_attribute(key, value)
|
554
|
-
end
|
555
|
-
when NilClass
|
556
|
-
# If it's nil and an association then create a blank object
|
557
|
-
if self.has_many?(key)
|
558
|
-
return MultiObjectProxy.new(self.has_many_class_name(key), [])
|
559
|
-
elsif self.association?(key)
|
560
|
-
SingleObjectProxy.new(self.association_class_name(key), value)
|
561
|
-
end
|
562
|
-
else
|
563
|
-
raise ArgumentError, "expected an array or a hash for the association #{key}, got: #{value.inspect}" if self.association?(key)
|
564
|
-
typecast_attribute(key, value)
|
565
|
-
end
|
566
|
-
end
|
567
|
-
return self
|
568
|
-
end
|
569
|
-
|
570
|
-
# Override to_s and inspect so they only show attributes
|
571
|
-
# and not associations, this prevents force loading of associations
|
572
|
-
# when we call to_s or inspect on a descendent of base but allows it if we
|
573
|
-
# try to evaluate an association directly
|
574
|
-
def to_s
|
575
|
-
return "#<#{self.class}:#{(self.object_id * 2).to_s(16)} @attributes=#{self.attributes.inject({}){|accum,(k,v)| self.association?(k) ? accum : accum.merge(k => v)}}"
|
576
|
-
end
|
577
|
-
|
578
|
-
alias_method :inspect, :to_s
|
579
|
-
|
580
|
-
# Methods for serialization as json or xml, relying on the serializable_hash method
|
581
|
-
def to_xml(options = {})
|
582
|
-
self.serializable_hash(options).to_xml(:root => self.class.element_name)
|
583
|
-
end
|
584
|
-
|
585
|
-
def to_json(options = {})
|
586
|
-
self.class.include_root_in_json ? {self.class.element_name => self.serializable_hash(options)}.to_json : self.serializable_hash(options).to_json
|
587
|
-
end
|
588
|
-
|
589
|
-
def serializable_hash(options = {})
|
590
|
-
action = options[:action]
|
591
|
-
include_nil_attributes = options[:include_nil_attributes]
|
592
|
-
options[:include_associations] = options[:include_associations] ? options[:include_associations].symbolize_array : self.changes.keys.symbolize_array.select{|k| self.association?(k)}
|
593
|
-
options[:include_extras] = options[:include_extras] ? options[:include_extras].symbolize_array : []
|
594
|
-
options[:except] ||= []
|
595
|
-
ret = self.attributes.inject({}) do |accum, (key,val)|
|
596
|
-
# If this is an association and it's in include_associations then include it
|
597
|
-
if options[:include_extras].include?(key.to_sym)
|
598
|
-
accum.merge(key => val)
|
599
|
-
elsif options[:except].include?(key.to_sym)
|
600
|
-
accum
|
601
|
-
# this attribute is already accounted for in the URL
|
602
|
-
elsif self.prefix_attribute_names.include?(key.to_sym)
|
603
|
-
accum
|
604
|
-
elsif(!include_nil_attributes && val.nil? && self.changes[key].blank?)
|
605
|
-
accum
|
606
|
-
else
|
607
|
-
!self.attribute?(key) || self.protected_attribute?(key) ? accum : accum.merge(key => val)
|
608
|
-
end
|
609
|
-
end
|
610
|
-
options[:include_associations].each do |assoc|
|
611
|
-
ret[assoc] = self.send(assoc).serializable_hash({:include_id => true, :include_nil_attributes => include_nil_attributes, :action => action}) if self.association?(assoc)
|
612
|
-
end
|
613
|
-
# include id - this is for nested updates
|
614
|
-
ret[:id] = self.id if options[:include_id] && !self.new?
|
615
|
-
ret
|
616
|
-
end
|
617
|
-
|
618
|
-
protected
|
619
|
-
def connection(refresh = false)
|
620
|
-
self.class.connection(refresh)
|
621
|
-
end
|
622
|
-
|
623
|
-
def load_attributes_from_response(response)
|
624
|
-
load(response)
|
625
|
-
end
|
626
|
-
|
627
|
-
def element_path(id, prefix_override_options = {}, query_options = nil)
|
628
|
-
self.class.element_path(
|
629
|
-
id,
|
630
|
-
self.prefix_options.merge(prefix_override_options),
|
631
|
-
query_options
|
632
|
-
)
|
633
|
-
end
|
634
|
-
|
635
|
-
# list of all attributes that are not nil
|
636
|
-
def nil_attributes
|
637
|
-
self.attributes.select{|k,v|
|
638
|
-
# if our value is actually nil or if we are an association
|
639
|
-
# or array and we are blank
|
640
|
-
v.nil? || ((self.association?(k) || v.is_a?(Array)) && v.blank?)
|
641
|
-
}
|
642
|
-
end
|
643
|
-
|
644
|
-
def new_element_path(prefix_options = {})
|
645
|
-
self.class.new_element_path(prefix_options)
|
646
|
-
end
|
647
|
-
|
648
|
-
def collection_path(override_prefix_options = {},query_options = nil)
|
649
|
-
self.class.collection_path(
|
650
|
-
self.prefix_options.merge(override_prefix_options),
|
651
|
-
query_options
|
652
|
-
)
|
653
|
-
end
|
654
|
-
|
655
|
-
def create(*args)
|
656
|
-
body = setup_create_call(*args)
|
657
|
-
connection.post(collection_path, body, self.class.headers).tap do |response|
|
658
|
-
load_attributes_from_response(response)
|
659
|
-
end
|
660
|
-
end
|
661
|
-
|
662
|
-
def setup_create_call(*args)
|
663
|
-
opts = args.extract_options!
|
664
|
-
# When we create we should not include any blank attributes unless they are associations
|
665
|
-
except = self.class.include_nil_attributes_on_create ?
|
666
|
-
{} : self.nil_attributes
|
667
|
-
opts[:except] = opts[:except] ? opts[:except].concat(except.keys).uniq.symbolize_array : except.keys.symbolize_array
|
668
|
-
opts[:include_nil_attributes] = self.class.include_nil_attributes_on_create
|
669
|
-
opts[:include_associations] = opts[:include_associations] ? opts[:include_associations].concat(args) : []
|
670
|
-
opts[:include_extras] ||= []
|
671
|
-
opts[:action] = "create"
|
672
|
-
body = RestClient::Payload.has_file?(self.attributes) ? self.serializable_hash(opts) : encode(opts)
|
673
|
-
end
|
674
|
-
|
675
|
-
|
676
|
-
def update(*args)
|
677
|
-
body = setup_update_call(*args)
|
678
|
-
# We can just ignore the response
|
679
|
-
connection.put(element_path(self.id), body, self.class.headers).tap do |response|
|
680
|
-
load_attributes_from_response(response)
|
681
|
-
end
|
682
|
-
end
|
683
|
-
|
684
|
-
def setup_update_call(*args)
|
685
|
-
opts = args.extract_options!
|
686
|
-
# When we create we should not include any blank attributes
|
687
|
-
except = self.class.attribute_names - self.changed.symbolize_array
|
688
|
-
changed_associations = self.changed.symbolize_array.select{|item| self.association?(item)}
|
689
|
-
opts[:except] = opts[:except] ? opts[:except].concat(except).uniq.symbolize_array : except.symbolize_array
|
690
|
-
opts[:include_nil_attributes] = self.include_all_attributes_on_update
|
691
|
-
opts[:include_associations] = opts[:include_associations] ? opts[:include_associations].concat(args).concat(changed_associations).uniq : changed_associations.concat(args)
|
692
|
-
opts[:include_extras] ||= []
|
693
|
-
opts[:action] = "update"
|
694
|
-
opts[:except] = [:id] if self.class.include_all_attributes_on_update
|
695
|
-
body = RestClient::Payload.has_file?(self.attributes) ? self.serializable_hash(opts) : encode(opts)
|
696
|
-
end
|
697
|
-
|
698
|
-
private
|
699
|
-
|
700
|
-
def split_options(options = {})
|
701
|
-
self.class.__send__(:split_options, options)
|
702
|
-
end
|
703
|
-
|
704
|
-
end
|
705
|
-
|
706
|
-
class Base
|
707
|
-
extend ActiveModel::Naming
|
708
|
-
# Order is important here
|
709
|
-
# It should be Validations, Dirty Tracking, Callbacks so the include order is the opposite
|
710
|
-
include AssociationActivation
|
711
|
-
self.activate_associations
|
712
|
-
|
713
|
-
include Scopes, Callbacks, Attributes, ModelErrors
|
714
|
-
|
715
|
-
end
|
716
|
-
|
717
|
-
end
|