api_resource 0.4.3 → 0.5.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.
- data/VERSION +1 -1
- data/api_resource.gemspec +4 -74
- data/coverage/assets/0.5.3/app.js +88 -0
- data/coverage/assets/0.5.3/fancybox/blank.gif +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_close.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_loading.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_nav_left.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_nav_right.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_e.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_n.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_ne.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_nw.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_s.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_se.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_sw.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_w.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_title_left.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_title_main.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_title_over.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_title_right.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancybox-x.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancybox-y.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancybox.png +0 -0
- data/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.css +363 -0
- data/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.pack.js +44 -0
- data/coverage/assets/0.5.3/favicon_green.png +0 -0
- data/coverage/assets/0.5.3/favicon_red.png +0 -0
- data/coverage/assets/0.5.3/favicon_yellow.png +0 -0
- data/coverage/assets/0.5.3/highlight.css +129 -0
- data/coverage/assets/0.5.3/highlight.pack.js +1 -0
- data/coverage/assets/0.5.3/jquery-1.6.2.min.js +18 -0
- data/coverage/assets/0.5.3/jquery.dataTables.min.js +152 -0
- data/coverage/assets/0.5.3/jquery.timeago.js +141 -0
- data/coverage/assets/0.5.3/jquery.url.js +174 -0
- data/coverage/assets/0.5.3/loading.gif +0 -0
- data/coverage/assets/0.5.3/magnify.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/assets/0.5.3/smoothness/jquery-ui-1.8.4.custom.css +295 -0
- data/coverage/assets/0.5.3/stylesheet.css +383 -0
- data/coverage/index.html +3573 -0
- data/lib/api_resource/associations/abstract_scope.rb +191 -0
- data/lib/api_resource/associations/association_scope.rb +47 -0
- data/lib/api_resource/associations/belongs_to_remote_object_proxy.rb +5 -6
- data/lib/api_resource/associations/has_many_remote_object_proxy.rb +5 -8
- data/lib/api_resource/associations/has_one_remote_object_proxy.rb +12 -13
- data/lib/api_resource/associations/multi_object_proxy.rb +65 -39
- data/lib/api_resource/associations/resource_scope.rb +6 -17
- data/lib/api_resource/associations/scope.rb +23 -121
- data/lib/api_resource/associations/single_object_proxy.rb +41 -50
- data/lib/api_resource/associations.rb +32 -11
- data/lib/api_resource/attributes.rb +108 -69
- data/lib/api_resource/base.rb +114 -106
- data/lib/api_resource/local.rb +1 -1
- data/lib/api_resource/model_errors.rb +9 -6
- data/lib/api_resource/scopes.rb +53 -16
- data/lib/api_resource.rb +3 -1
- data/spec/lib/api_resource_spec.rb +3 -7
- data/spec/lib/associations/association_scope_spec.rb +19 -0
- data/spec/lib/associations_spec.rb +251 -162
- data/spec/lib/attributes_spec.rb +33 -15
- data/spec/lib/base_spec.rb +302 -64
- data/spec/lib/callbacks_spec.rb +4 -2
- data/spec/lib/local_spec.rb +5 -1
- data/spec/spec_helper.rb +2 -3
- data/spec/support/mocks/association_mocks.rb +9 -1
- data/spec/support/requests/association_requests.rb +5 -5
- data/spec/support/requests/test_resource_requests.rb +16 -4
- data/spec/tmp/api_resource_test_db.sqlite +0 -0
- metadata +68 -22
- data/.document +0 -5
- data/.rspec +0 -5
- data/.travis.yml +0 -4
- data/lib/api_resource/associations/association_proxy.rb +0 -121
- 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/multi_argument_resource_scope.rb +0 -15
- data/lib/api_resource/associations/relation_scope.rb +0 -25
|
@@ -1,23 +1,45 @@
|
|
|
1
|
-
require 'api_resource/associations/
|
|
1
|
+
require 'api_resource/associations/association_scope'
|
|
2
2
|
|
|
3
3
|
module ApiResource
|
|
4
4
|
|
|
5
5
|
module Associations
|
|
6
6
|
|
|
7
|
-
class SingleObjectProxy <
|
|
7
|
+
class SingleObjectProxy < AssociationScope
|
|
8
8
|
|
|
9
9
|
def serializable_hash(options = {})
|
|
10
10
|
return if self.internal_object.nil?
|
|
11
11
|
self.internal_object.serializable_hash(options)
|
|
12
12
|
end
|
|
13
|
+
|
|
14
|
+
def internal_object
|
|
15
|
+
unless instance_variable_defined?(:@internal_object)
|
|
16
|
+
if self.remote_path.present?
|
|
17
|
+
instance_variable_set(:@internal_object, self.load)
|
|
18
|
+
else
|
|
19
|
+
instance_variable_set(:@internal_object, nil)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
instance_variable_get(:@internal_object)
|
|
23
|
+
end
|
|
13
24
|
|
|
14
25
|
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?
|
|
26
|
+
if contents.is_a?(self.klass) || contents.nil?
|
|
18
27
|
return @internal_object = contents
|
|
28
|
+
elsif contents.is_a?(self.class)
|
|
29
|
+
return @internal_object = contents.internal_object
|
|
30
|
+
# a Hash may be attributes and/or a service_uri
|
|
31
|
+
elsif contents.is_a?(Hash)
|
|
32
|
+
contents = contents.symbolize_keys
|
|
33
|
+
@remote_path = contents.delete(
|
|
34
|
+
self.class.remote_path_element.to_sym
|
|
35
|
+
)
|
|
36
|
+
if contents.present?
|
|
37
|
+
return @internal_object = self.klass.instantiate_record(contents)
|
|
38
|
+
end
|
|
19
39
|
else
|
|
20
|
-
|
|
40
|
+
raise ArgumentError.new(
|
|
41
|
+
"#{contents} must be a #{self.klass}, a #{self.class} or a Hash"
|
|
42
|
+
)
|
|
21
43
|
end
|
|
22
44
|
end
|
|
23
45
|
|
|
@@ -27,53 +49,22 @@ module ApiResource
|
|
|
27
49
|
return true
|
|
28
50
|
end
|
|
29
51
|
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
52
|
+
def hash
|
|
53
|
+
self.id.hash
|
|
39
54
|
end
|
|
40
55
|
|
|
41
|
-
def
|
|
42
|
-
|
|
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
|
|
56
|
+
def eql?(other)
|
|
57
|
+
return self == other
|
|
76
58
|
end
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def load(opts = {})
|
|
62
|
+
data = self.klass.connection.get(self.build_load_path(opts))
|
|
63
|
+
@loaded = true
|
|
64
|
+
return nil if data.blank?
|
|
65
|
+
return self.klass.instantiate_record(data)
|
|
66
|
+
end
|
|
67
|
+
|
|
77
68
|
|
|
78
69
|
end
|
|
79
70
|
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
require 'active_support'
|
|
2
2
|
require 'active_support/string_inquirer'
|
|
3
3
|
require 'api_resource/association_activation'
|
|
4
|
-
require 'api_resource/associations/
|
|
4
|
+
require 'api_resource/associations/abstract_scope'
|
|
5
|
+
require 'api_resource/associations/scope'
|
|
5
6
|
require 'api_resource/associations/resource_scope'
|
|
6
|
-
require 'api_resource/associations/
|
|
7
|
-
require 'api_resource/associations/generic_scope'
|
|
8
|
-
require 'api_resource/associations/multi_argument_resource_scope'
|
|
9
|
-
require 'api_resource/associations/multi_object_proxy'
|
|
7
|
+
require 'api_resource/associations/association_scope'
|
|
10
8
|
require 'api_resource/associations/single_object_proxy'
|
|
9
|
+
require 'api_resource/associations/multi_object_proxy'
|
|
11
10
|
require 'api_resource/associations/belongs_to_remote_object_proxy'
|
|
12
11
|
require 'api_resource/associations/has_one_remote_object_proxy'
|
|
13
12
|
require 'api_resource/associations/has_many_remote_object_proxy'
|
|
@@ -129,6 +128,10 @@ module ApiResource
|
|
|
129
128
|
# structure is {:has_many => {"myname" => "ClassName"}}
|
|
130
129
|
self.related_objects.clone.delete_if{|k,v| k.to_s == "scopes"}.collect{|k,v| v.keys.collect(&:to_sym)}.flatten
|
|
131
130
|
end
|
|
131
|
+
|
|
132
|
+
def association_class(assoc)
|
|
133
|
+
self.association_class_name(assoc).constantize
|
|
134
|
+
end
|
|
132
135
|
|
|
133
136
|
def association_class_name(assoc)
|
|
134
137
|
raise ArgumentError, "#{assoc} is not a valid association of #{self}" unless self.association?(assoc)
|
|
@@ -162,18 +165,32 @@ module ApiResource
|
|
|
162
165
|
end
|
|
163
166
|
|
|
164
167
|
def define_association_as_attribute(assoc_type, assoc_name)
|
|
165
|
-
# set up dirty tracking for associations
|
|
166
|
-
|
|
168
|
+
# set up dirty tracking for associations, but only for ApiResource
|
|
169
|
+
# these methods are also used for ActiveRecord
|
|
170
|
+
# TODO: change this
|
|
171
|
+
if self.ancestors.include?(ApiResource::Base)
|
|
172
|
+
define_attribute_method(assoc_name)
|
|
173
|
+
end
|
|
174
|
+
|
|
167
175
|
self.class_eval <<-EOE, __FILE__, __LINE__ + 1
|
|
168
176
|
def #{assoc_name}
|
|
169
|
-
|
|
177
|
+
@attributes_cache[:#{assoc_name}] ||= begin
|
|
178
|
+
klass = #{self.association_types[assoc_type.to_sym].to_s.classify}ObjectProxy
|
|
179
|
+
instance = klass.new(
|
|
180
|
+
self.association_class('#{assoc_name}'), self
|
|
181
|
+
)
|
|
182
|
+
if @attributes[:#{assoc_name}].present?
|
|
183
|
+
instance.internal_object = @attributes[:#{assoc_name}]
|
|
184
|
+
end
|
|
185
|
+
instance
|
|
186
|
+
end
|
|
170
187
|
end
|
|
171
188
|
def #{assoc_name}=(val)
|
|
172
189
|
# get old internal object
|
|
173
|
-
|
|
190
|
+
unless self.#{assoc_name}.internal_object == val
|
|
191
|
+
#{assoc_name}_will_change!
|
|
192
|
+
end
|
|
174
193
|
self.#{assoc_name}.internal_object = val
|
|
175
|
-
#{assoc_name}_will_change! unless self.#{assoc_name} == old_internal_object
|
|
176
|
-
self.#{assoc_name}.internal_object
|
|
177
194
|
end
|
|
178
195
|
def #{assoc_name}?
|
|
179
196
|
self.#{assoc_name}.internal_object.present?
|
|
@@ -204,6 +221,10 @@ module ApiResource
|
|
|
204
221
|
self.class.association?(assoc)
|
|
205
222
|
end
|
|
206
223
|
|
|
224
|
+
def association_class(assoc)
|
|
225
|
+
self.class.association_class(assoc)
|
|
226
|
+
end
|
|
227
|
+
|
|
207
228
|
def association_class_name(assoc)
|
|
208
229
|
self.class.association_class_name(assoc)
|
|
209
230
|
end
|
|
@@ -10,27 +10,20 @@ module ApiResource
|
|
|
10
10
|
|
|
11
11
|
alias_method_chain :save, :dirty_tracking
|
|
12
12
|
|
|
13
|
-
class_attribute
|
|
13
|
+
class_attribute(
|
|
14
|
+
:attribute_names,
|
|
15
|
+
:public_attribute_names,
|
|
16
|
+
:protected_attribute_names,
|
|
17
|
+
:attribute_types
|
|
18
|
+
)
|
|
14
19
|
|
|
15
20
|
cattr_accessor :valid_typecasts; self.valid_typecasts = [:date, :time, :float, :integer, :int, :fixnum, :string, :array]
|
|
16
21
|
|
|
17
|
-
attr_reader :attributes
|
|
18
|
-
|
|
19
22
|
self.attribute_names = []
|
|
20
23
|
self.public_attribute_names = []
|
|
21
24
|
self.protected_attribute_names = []
|
|
22
25
|
self.attribute_types = {}.with_indifferent_access
|
|
23
26
|
|
|
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
27
|
|
|
35
28
|
# This method is important for reloading an object. If the
|
|
36
29
|
# object has already been loaded, its associations will trip
|
|
@@ -68,6 +61,7 @@ module ApiResource
|
|
|
68
61
|
module ClassMethods
|
|
69
62
|
|
|
70
63
|
def define_attributes(*args)
|
|
64
|
+
|
|
71
65
|
args.each do |arg|
|
|
72
66
|
if arg.is_a?(Array)
|
|
73
67
|
self.define_attribute_type(arg.first, arg.second)
|
|
@@ -75,24 +69,9 @@ module ApiResource
|
|
|
75
69
|
end
|
|
76
70
|
self.attribute_names += [arg.to_sym]
|
|
77
71
|
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
|
|
72
|
+
self.define_accessor_methods(arg)
|
|
95
73
|
end
|
|
74
|
+
|
|
96
75
|
self.attribute_names.uniq!
|
|
97
76
|
self.public_attribute_names.uniq!
|
|
98
77
|
end
|
|
@@ -108,28 +87,35 @@ module ApiResource
|
|
|
108
87
|
self.attribute_names += [arg.to_sym]
|
|
109
88
|
self.protected_attribute_names += [arg.to_sym]
|
|
110
89
|
|
|
111
|
-
|
|
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
|
|
90
|
+
self.define_accessor_methods(arg)
|
|
126
91
|
end
|
|
127
92
|
self.attribute_names.uniq!
|
|
128
93
|
self.protected_attribute_names.uniq!
|
|
129
94
|
end
|
|
130
95
|
|
|
96
|
+
def define_accessor_methods(meth)
|
|
97
|
+
# Override the setter for dirty tracking
|
|
98
|
+
self.class_eval <<-EOE, __FILE__, __LINE__ + 1
|
|
99
|
+
def #{meth}
|
|
100
|
+
read_attribute(:#{meth})
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def #{meth}=(new_val)
|
|
104
|
+
write_attribute(:#{meth}, new_val)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def #{meth}?
|
|
108
|
+
read_attribute(:#{meth}).present?
|
|
109
|
+
end
|
|
110
|
+
EOE
|
|
111
|
+
# sets up dirty tracking
|
|
112
|
+
define_attribute_method(meth)
|
|
113
|
+
end
|
|
114
|
+
|
|
131
115
|
def define_attribute_type(field, type)
|
|
132
|
-
|
|
116
|
+
unless self.valid_typecasts.include?(type.to_sym)
|
|
117
|
+
raise "#{type} is not a valid type"
|
|
118
|
+
end
|
|
133
119
|
self.attribute_types[field] = type.to_sym
|
|
134
120
|
end
|
|
135
121
|
|
|
@@ -148,15 +134,31 @@ module ApiResource
|
|
|
148
134
|
self.protected_attribute_names.clear
|
|
149
135
|
end
|
|
150
136
|
end
|
|
151
|
-
|
|
137
|
+
|
|
138
|
+
# override the initializer to set up some default values
|
|
139
|
+
def initialize(*args)
|
|
140
|
+
@attributes = @attributes_cache = HashWithIndifferentAccess.new
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def attributes
|
|
144
|
+
attrs = {}
|
|
145
|
+
self.attribute_names.each{|name| attrs[name] = read_attribute(name)}
|
|
146
|
+
attrs
|
|
147
|
+
end
|
|
148
|
+
|
|
152
149
|
# set new attributes
|
|
153
150
|
def attributes=(new_attrs)
|
|
154
151
|
new_attrs.each_pair do |k,v|
|
|
152
|
+
if self.protected_attribute?(k)
|
|
153
|
+
raise Exception.new(
|
|
154
|
+
"#{k} is a protected attribute and cannot be mass-assigned"
|
|
155
|
+
)
|
|
156
|
+
end
|
|
155
157
|
self.send("#{k}=",v) unless k.to_sym == :id
|
|
156
158
|
end
|
|
157
159
|
new_attrs
|
|
158
160
|
end
|
|
159
|
-
|
|
161
|
+
|
|
160
162
|
def save_with_dirty_tracking(*args)
|
|
161
163
|
if save_without_dirty_tracking(*args)
|
|
162
164
|
@previously_changed = self.changes
|
|
@@ -182,6 +184,23 @@ module ApiResource
|
|
|
182
184
|
|
|
183
185
|
set_attributes_as_current(*attrs)
|
|
184
186
|
end
|
|
187
|
+
|
|
188
|
+
def read_attribute(name)
|
|
189
|
+
self.typecasted_attribute(name.to_sym)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def write_attribute(name, val)
|
|
193
|
+
old_val = read_attribute(name)
|
|
194
|
+
new_val = self.typecast_attribute(name, val)
|
|
195
|
+
|
|
196
|
+
unless old_val == new_val
|
|
197
|
+
self.send("#{name}_will_change!")
|
|
198
|
+
end
|
|
199
|
+
# delete the old cached value and assign new val to both
|
|
200
|
+
# @attributes and @attributes_cache
|
|
201
|
+
@attributes_cache.delete(name.to_sym)
|
|
202
|
+
@attributes[name.to_sym] = @attributes_cache[name.to_sym] = new_val
|
|
203
|
+
end
|
|
185
204
|
|
|
186
205
|
def attribute?(name)
|
|
187
206
|
self.class.attribute?(name)
|
|
@@ -204,10 +223,6 @@ module ApiResource
|
|
|
204
223
|
|
|
205
224
|
protected
|
|
206
225
|
|
|
207
|
-
def attribute_with_default(field)
|
|
208
|
-
self.attributes[field].nil? ? self.default_value_for_field(field) : self.attributes[field]
|
|
209
|
-
end
|
|
210
|
-
|
|
211
226
|
def default_value_for_field(field)
|
|
212
227
|
case self.class.attribute_types[field.to_sym]
|
|
213
228
|
when :array
|
|
@@ -217,25 +232,49 @@ module ApiResource
|
|
|
217
232
|
end
|
|
218
233
|
end
|
|
219
234
|
|
|
220
|
-
def
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
+
def typecasted_attribute(field)
|
|
236
|
+
|
|
237
|
+
@attributes ||= HashWithIndifferentAccess.new
|
|
238
|
+
@attributes_cache ||= HashWithIndifferentAccess.new
|
|
239
|
+
|
|
240
|
+
if @attributes_cache.has_key?(field.to_sym)
|
|
241
|
+
return @attributes_cache[field.to_sym]
|
|
242
|
+
else
|
|
243
|
+
# pull out of the raw attributes
|
|
244
|
+
if @attributes.has_key?(field.to_sym)
|
|
245
|
+
val = @attributes[field.to_sym]
|
|
235
246
|
else
|
|
236
|
-
|
|
237
|
-
|
|
247
|
+
val = self.default_value_for_field(field)
|
|
248
|
+
end
|
|
249
|
+
# now we typecast
|
|
250
|
+
val = self.typecast_attribute(field, val)
|
|
251
|
+
return @attributes_cache[field.to_sym] = val
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def typecast_attribute(field, val)
|
|
256
|
+
# if we have a valid value and we are planning to typecast go
|
|
257
|
+
# into this case statement
|
|
258
|
+
if self.class.attribute_types.include?(field.to_sym) && val.present?
|
|
259
|
+
val = case self.class.attribute_types[field.to_sym]
|
|
260
|
+
when :date
|
|
261
|
+
val.class == Date ? val.dup : Date.parse(val)
|
|
262
|
+
when :time
|
|
263
|
+
val.class == Time ? val.dup : Time.parse(val)
|
|
264
|
+
when :integer, :int, :fixnum
|
|
265
|
+
val.class == Fixnum ? val.dup : val.to_i rescue val
|
|
266
|
+
when :float
|
|
267
|
+
val.class == Float ? val.dup : val.to_f rescue val
|
|
268
|
+
when :string
|
|
269
|
+
val.class == String ? val.dup : val.to_s rescue val
|
|
270
|
+
when :array
|
|
271
|
+
val.class == Array ? val.dup : Array.wrap(val)
|
|
272
|
+
else
|
|
273
|
+
# catches the nil case and just leaves it alone
|
|
274
|
+
val.dup rescue val
|
|
275
|
+
end
|
|
238
276
|
end
|
|
277
|
+
val
|
|
239
278
|
end
|
|
240
279
|
|
|
241
280
|
end
|