active_data 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rspec +0 -1
  4. data/.rvmrc +1 -1
  5. data/.travis.yml +13 -6
  6. data/Appraisals +7 -0
  7. data/Gemfile +1 -5
  8. data/Guardfile +68 -15
  9. data/README.md +144 -2
  10. data/active_data.gemspec +19 -11
  11. data/gemfiles/rails.4.0.gemfile +14 -0
  12. data/gemfiles/rails.4.1.gemfile +14 -0
  13. data/gemfiles/rails.4.2.gemfile +14 -0
  14. data/gemfiles/rails.5.0.gemfile +14 -0
  15. data/lib/active_data.rb +120 -3
  16. data/lib/active_data/active_record/associations.rb +50 -0
  17. data/lib/active_data/active_record/nested_attributes.rb +24 -0
  18. data/lib/active_data/config.rb +40 -0
  19. data/lib/active_data/errors.rb +93 -0
  20. data/lib/active_data/extensions.rb +33 -0
  21. data/lib/active_data/model.rb +16 -74
  22. data/lib/active_data/model/associations.rb +84 -15
  23. data/lib/active_data/model/associations/base.rb +79 -0
  24. data/lib/active_data/model/associations/collection/embedded.rb +12 -0
  25. data/lib/active_data/model/associations/collection/proxy.rb +32 -0
  26. data/lib/active_data/model/associations/collection/referenced.rb +26 -0
  27. data/lib/active_data/model/associations/embeds_many.rb +124 -18
  28. data/lib/active_data/model/associations/embeds_one.rb +90 -15
  29. data/lib/active_data/model/associations/nested_attributes.rb +180 -0
  30. data/lib/active_data/model/associations/references_many.rb +96 -0
  31. data/lib/active_data/model/associations/references_one.rb +83 -0
  32. data/lib/active_data/model/associations/reflections/base.rb +100 -0
  33. data/lib/active_data/model/associations/reflections/embeds_many.rb +25 -0
  34. data/lib/active_data/model/associations/reflections/embeds_one.rb +49 -0
  35. data/lib/active_data/model/associations/reflections/reference_reflection.rb +45 -0
  36. data/lib/active_data/model/associations/reflections/references_many.rb +28 -0
  37. data/lib/active_data/model/associations/reflections/references_one.rb +28 -0
  38. data/lib/active_data/model/associations/validations.rb +63 -0
  39. data/lib/active_data/model/attributes.rb +247 -0
  40. data/lib/active_data/model/attributes/attribute.rb +73 -0
  41. data/lib/active_data/model/attributes/base.rb +116 -0
  42. data/lib/active_data/model/attributes/collection.rb +17 -0
  43. data/lib/active_data/model/attributes/dictionary.rb +26 -0
  44. data/lib/active_data/model/attributes/localized.rb +42 -0
  45. data/lib/active_data/model/attributes/reference_many.rb +21 -0
  46. data/lib/active_data/model/attributes/reference_one.rb +42 -0
  47. data/lib/active_data/model/attributes/reflections/attribute.rb +55 -0
  48. data/lib/active_data/model/attributes/reflections/base.rb +62 -0
  49. data/lib/active_data/model/attributes/reflections/collection.rb +10 -0
  50. data/lib/active_data/model/attributes/reflections/dictionary.rb +13 -0
  51. data/lib/active_data/model/attributes/reflections/localized.rb +43 -0
  52. data/lib/active_data/model/attributes/reflections/reference_many.rb +10 -0
  53. data/lib/active_data/model/attributes/reflections/reference_one.rb +58 -0
  54. data/lib/active_data/model/attributes/reflections/represents.rb +55 -0
  55. data/lib/active_data/model/attributes/represents.rb +64 -0
  56. data/lib/active_data/model/callbacks.rb +71 -0
  57. data/lib/active_data/model/conventions.rb +35 -0
  58. data/lib/active_data/model/dirty.rb +77 -0
  59. data/lib/active_data/model/lifecycle.rb +307 -0
  60. data/lib/active_data/model/localization.rb +21 -0
  61. data/lib/active_data/model/persistence.rb +57 -0
  62. data/lib/active_data/model/primary.rb +51 -0
  63. data/lib/active_data/model/scopes.rb +77 -0
  64. data/lib/active_data/model/validations.rb +27 -0
  65. data/lib/active_data/model/validations/associated.rb +19 -0
  66. data/lib/active_data/model/validations/nested.rb +39 -0
  67. data/lib/active_data/railtie.rb +7 -0
  68. data/lib/active_data/version.rb +1 -1
  69. data/spec/lib/active_data/active_record/associations_spec.rb +149 -0
  70. data/spec/lib/active_data/active_record/nested_attributes_spec.rb +16 -0
  71. data/spec/lib/active_data/config_spec.rb +44 -0
  72. data/spec/lib/active_data/model/associations/embeds_many_spec.rb +362 -52
  73. data/spec/lib/active_data/model/associations/embeds_one_spec.rb +250 -31
  74. data/spec/lib/active_data/model/associations/nested_attributes_spec.rb +23 -0
  75. data/spec/lib/active_data/model/associations/references_many_spec.rb +196 -0
  76. data/spec/lib/active_data/model/associations/references_one_spec.rb +134 -0
  77. data/spec/lib/active_data/model/associations/reflections/embeds_many_spec.rb +144 -0
  78. data/spec/lib/active_data/model/associations/reflections/embeds_one_spec.rb +116 -0
  79. data/spec/lib/active_data/model/associations/reflections/references_many_spec.rb +255 -0
  80. data/spec/lib/active_data/model/associations/reflections/references_one_spec.rb +208 -0
  81. data/spec/lib/active_data/model/associations/validations_spec.rb +153 -0
  82. data/spec/lib/active_data/model/associations_spec.rb +189 -0
  83. data/spec/lib/active_data/model/attributes/attribute_spec.rb +144 -0
  84. data/spec/lib/active_data/model/attributes/base_spec.rb +82 -0
  85. data/spec/lib/active_data/model/attributes/collection_spec.rb +73 -0
  86. data/spec/lib/active_data/model/attributes/dictionary_spec.rb +93 -0
  87. data/spec/lib/active_data/model/attributes/localized_spec.rb +88 -33
  88. data/spec/lib/active_data/model/attributes/reflections/attribute_spec.rb +72 -0
  89. data/spec/lib/active_data/model/attributes/reflections/base_spec.rb +56 -0
  90. data/spec/lib/active_data/model/attributes/reflections/collection_spec.rb +37 -0
  91. data/spec/lib/active_data/model/attributes/reflections/dictionary_spec.rb +43 -0
  92. data/spec/lib/active_data/model/attributes/reflections/localized_spec.rb +37 -0
  93. data/spec/lib/active_data/model/attributes/reflections/represents_spec.rb +70 -0
  94. data/spec/lib/active_data/model/attributes/represents_spec.rb +153 -0
  95. data/spec/lib/active_data/model/attributes_spec.rb +243 -0
  96. data/spec/lib/active_data/model/callbacks_spec.rb +338 -0
  97. data/spec/lib/active_data/model/conventions_spec.rb +12 -0
  98. data/spec/lib/active_data/model/dirty_spec.rb +75 -0
  99. data/spec/lib/active_data/model/lifecycle_spec.rb +330 -0
  100. data/spec/lib/active_data/model/nested_attributes.rb +202 -0
  101. data/spec/lib/active_data/model/persistence_spec.rb +47 -0
  102. data/spec/lib/active_data/model/primary_spec.rb +84 -0
  103. data/spec/lib/active_data/model/scopes_spec.rb +88 -0
  104. data/spec/lib/active_data/model/typecasting_spec.rb +192 -0
  105. data/spec/lib/active_data/model/validations/associated_spec.rb +94 -0
  106. data/spec/lib/active_data/model/validations/nested_spec.rb +93 -0
  107. data/spec/lib/active_data/model/validations_spec.rb +31 -0
  108. data/spec/lib/active_data/model_spec.rb +1 -32
  109. data/spec/lib/active_data_spec.rb +12 -0
  110. data/spec/spec_helper.rb +39 -0
  111. data/spec/support/model_helpers.rb +10 -0
  112. metadata +246 -54
  113. data/gemfiles/Gemfile.rails-3 +0 -14
  114. data/lib/active_data/attributes/base.rb +0 -69
  115. data/lib/active_data/attributes/localized.rb +0 -42
  116. data/lib/active_data/model/associations/association.rb +0 -30
  117. data/lib/active_data/model/attributable.rb +0 -122
  118. data/lib/active_data/model/collectionizable.rb +0 -55
  119. data/lib/active_data/model/collectionizable/proxy.rb +0 -42
  120. data/lib/active_data/model/extensions.rb +0 -9
  121. data/lib/active_data/model/extensions/array.rb +0 -24
  122. data/lib/active_data/model/extensions/big_decimal.rb +0 -17
  123. data/lib/active_data/model/extensions/boolean.rb +0 -38
  124. data/lib/active_data/model/extensions/date.rb +0 -17
  125. data/lib/active_data/model/extensions/date_time.rb +0 -17
  126. data/lib/active_data/model/extensions/float.rb +0 -17
  127. data/lib/active_data/model/extensions/hash.rb +0 -22
  128. data/lib/active_data/model/extensions/integer.rb +0 -17
  129. data/lib/active_data/model/extensions/localized.rb +0 -22
  130. data/lib/active_data/model/extensions/object.rb +0 -17
  131. data/lib/active_data/model/extensions/string.rb +0 -17
  132. data/lib/active_data/model/extensions/time.rb +0 -17
  133. data/lib/active_data/model/localizable.rb +0 -31
  134. data/lib/active_data/model/nested_attributes.rb +0 -58
  135. data/lib/active_data/model/parameterizable.rb +0 -29
  136. data/lib/active_data/validations.rb +0 -7
  137. data/lib/active_data/validations/associated.rb +0 -17
  138. data/spec/lib/active_data/model/attributable_spec.rb +0 -191
  139. data/spec/lib/active_data/model/collectionizable_spec.rb +0 -60
  140. data/spec/lib/active_data/model/nested_attributes_spec.rb +0 -67
  141. data/spec/lib/active_data/model/type_cast_spec.rb +0 -116
  142. data/spec/lib/active_data/validations/associated_spec.rb +0 -88
@@ -0,0 +1,63 @@
1
+ module ActiveData
2
+ module Model
3
+ module Associations
4
+ module Validations
5
+ def valid_ancestry?
6
+ errors.clear
7
+ validate_nested!
8
+ run_validations!
9
+ end
10
+ alias_method :validate_ancestry, :valid_ancestry?
11
+
12
+ def invalid_ancestry?
13
+ !valid_ancestry?
14
+ end
15
+
16
+ def validate_ancestry!
17
+ valid_ancestry? || raise_validation_error
18
+ end
19
+
20
+ private
21
+
22
+ def run_validations! #:nodoc:
23
+ super
24
+ emerge_represented_attributes_errors!
25
+ errors.empty?
26
+ end
27
+
28
+ def validate_nested!
29
+ association_names.each do |name|
30
+ association = association(name)
31
+ invalid_block = if association.reflection.klass.method_defined?(:invalid_ansestry?)
32
+ lambda { |object| object.invalid_ansestry? }
33
+ else
34
+ lambda { |object| object.invalid? }
35
+ end
36
+
37
+ ActiveData::Model::Validations::NestedValidator
38
+ .validate_nested(self, name, association.target, &invalid_block)
39
+ end
40
+ end
41
+
42
+ # Move represent attribute errors to the top level:
43
+ #
44
+ # {:'role.email' => ['Some error']}
45
+ #
46
+ # to:
47
+ #
48
+ # {email: ['Some error']}
49
+ #
50
+ def emerge_represented_attributes_errors!
51
+ self.class.represented_attributes.each do |attribute|
52
+ key = :"#{attribute.reference}.#{attribute.column}"
53
+ messages = errors.messages[key]
54
+ if messages.present?
55
+ errors[attribute.column].concat(messages)
56
+ errors.delete(key)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,247 @@
1
+ require 'active_data/model/attributes/reflections/base'
2
+ require 'active_data/model/attributes/reflections/reference_one'
3
+ require 'active_data/model/attributes/reflections/reference_many'
4
+ require 'active_data/model/attributes/reflections/attribute'
5
+ require 'active_data/model/attributes/reflections/collection'
6
+ require 'active_data/model/attributes/reflections/dictionary'
7
+ require 'active_data/model/attributes/reflections/localized'
8
+ require 'active_data/model/attributes/reflections/represents'
9
+
10
+ require 'active_data/model/attributes/base'
11
+ require 'active_data/model/attributes/reference_one'
12
+ require 'active_data/model/attributes/reference_many'
13
+ require 'active_data/model/attributes/attribute'
14
+ require 'active_data/model/attributes/collection'
15
+ require 'active_data/model/attributes/dictionary'
16
+ require 'active_data/model/attributes/localized'
17
+ require 'active_data/model/attributes/represents'
18
+
19
+ module ActiveData
20
+ module Model
21
+ module Attributes
22
+ extend ActiveSupport::Concern
23
+
24
+ included do
25
+ class_attribute :_attributes, :_attribute_aliases, :_sanitize, instance_reader: false, instance_writer: false
26
+ self._attributes = {}
27
+ self._attribute_aliases = {}
28
+ self._sanitize = true
29
+
30
+ delegate :attribute_names, :has_attribute?, to: 'self.class'
31
+
32
+ %w[attribute collection dictionary].each do |kind|
33
+ define_singleton_method kind do |*args, &block|
34
+ add_attribute("ActiveData::Model::Attributes::Reflections::#{kind.camelize}".constantize, *args, &block)
35
+ end
36
+ end
37
+ end
38
+
39
+ module ClassMethods
40
+ def represents(*names, &block)
41
+ options = names.extract_options!
42
+ names.each do |name|
43
+ add_attribute(Reflections::Represents, name, options, &block)
44
+ end
45
+ end
46
+
47
+ def add_attribute(reflection_class, *args, &block)
48
+ reflection = reflection_class.build(self, generated_attributes_methods, *args, &block)
49
+ self._attributes = _attributes.merge(reflection.name => reflection)
50
+ if dirty? && reflection_class != ActiveData::Model::Attributes::Reflections::Base
51
+ define_dirty reflection.name, generated_attributes_methods
52
+ end
53
+ reflection
54
+ end
55
+
56
+ def alias_attribute(alias_name, attribute_name)
57
+ reflection = reflect_on_attribute(attribute_name)
58
+ raise ArgumentError.new("Unable to alias undefined attribute `#{attribute_name}` on #{self}") unless reflection
59
+ raise ArgumentError.new("Unable to alias base attribute `#{attribute_name}`") if reflection.class == ActiveData::Model::Attributes::Reflections::Base
60
+ reflection.class.generate_methods alias_name, generated_attributes_methods
61
+ self._attribute_aliases = _attribute_aliases.merge(alias_name.to_s => reflection.name)
62
+ if dirty?
63
+ define_dirty alias_name, generated_attributes_methods
64
+ end
65
+ reflection
66
+ end
67
+
68
+ def reflect_on_attribute(name)
69
+ name = name.to_s
70
+ _attributes[_attribute_aliases[name] || name]
71
+ end
72
+
73
+ def has_attribute? name
74
+ name = name.to_s
75
+ _attributes.key?(_attribute_aliases[name] || name)
76
+ end
77
+
78
+ def attribute_names(include_associations = true)
79
+ if include_associations
80
+ _attributes.keys
81
+ else
82
+ _attributes.map do |name, attribute|
83
+ name unless attribute.class == ActiveData::Model::Attributes::Reflections::Base
84
+ end.compact
85
+ end
86
+ end
87
+
88
+ def inspect
89
+ "#{original_inspect}(#{attributes_for_inspect.presence || 'no attributes'})"
90
+ end
91
+
92
+ def represented_attributes
93
+ @represented_attributes ||= _attributes.values.select do |attribute|
94
+ attribute.is_a? ActiveData::Model::Attributes::Reflections::Represents
95
+ end
96
+ end
97
+
98
+ def represented_names_and_aliases
99
+ @represented_names_and_aliases ||= represented_attributes.flat_map do |attribute|
100
+ [attribute.name, *inverted_attribute_aliases[attribute.name]]
101
+ end
102
+ end
103
+
104
+ def dirty?
105
+ false
106
+ end
107
+
108
+ def with_sanitize(value)
109
+ previous_sanitize, self._sanitize = _sanitize, value
110
+ yield
111
+ ensure
112
+ self._sanitize = previous_sanitize
113
+ end
114
+
115
+ private
116
+
117
+ def original_inspect
118
+ Object.method(:inspect).unbind.bind(self).call
119
+ end
120
+
121
+ def attributes_for_inspect
122
+ attribute_names(false).map do |name|
123
+ prefix = respond_to?(:_primary_name) && _primary_name == name ? ?* : ''
124
+ "#{prefix}#{_attributes[name].inspect_reflection}"
125
+ end.join(', ')
126
+ end
127
+
128
+ def generated_attributes_methods
129
+ @generated_attributes_methods ||=
130
+ const_set(:GeneratedAttributesMethods, Module.new)
131
+ .tap { |proxy| include proxy }
132
+ end
133
+
134
+ def inverted_attribute_aliases
135
+ @inverted_attribute_aliases ||=
136
+ _attribute_aliases.each.with_object({}) do |(alias_name, attribute_name), result|
137
+ (result[attribute_name] ||= []).push(alias_name)
138
+ end
139
+ end
140
+ end
141
+
142
+ def initialize attrs = {}
143
+ assign_attributes attrs
144
+ end
145
+
146
+ def == other
147
+ super || other.instance_of?(self.class) && other.attributes(false) == attributes(false)
148
+ end
149
+ alias_method :eql?, :==
150
+
151
+ def attribute(name)
152
+ if reflection = self.class.reflect_on_attribute(name)
153
+ (@_attributes ||= {})[reflection.name] ||= reflection
154
+ .build_attribute(self, @initial_attributes.try(:[], reflection.name))
155
+ end
156
+ end
157
+
158
+ def write_attribute name, value
159
+ attribute(name).write(value)
160
+ end
161
+ alias_method :[]=, :write_attribute
162
+
163
+ def read_attribute name
164
+ attribute(name).read
165
+ end
166
+ alias_method :[], :read_attribute
167
+
168
+ def read_attribute_before_type_cast name
169
+ attribute(name).read_before_type_cast
170
+ end
171
+
172
+ def attribute_present? name
173
+ attribute(name).value_present?
174
+ end
175
+
176
+ def attributes(include_associations = true)
177
+ Hash[attribute_names(include_associations).map { |name| [name, read_attribute(name)] }]
178
+ end
179
+
180
+ def update attrs
181
+ assign_attributes(attrs)
182
+ end
183
+ alias_method :update_attributes, :update
184
+
185
+ def assign_attributes attrs
186
+ if self.class.represented_attributes.present? ||
187
+ (self.class.is_a?(ActiveData::Model::Associations::NestedAttributes) &&
188
+ self.class.nested_attributes_options.present?)
189
+ attrs.stringify_keys!
190
+ represented_attrs = self.class.represented_names_and_aliases
191
+ .each_with_object({}) do |name, result|
192
+ result[name] = attrs.delete(name) if attrs.has_key?(name)
193
+ end
194
+ if self.class.is_a?(ActiveData::Model::Associations::NestedAttributes)
195
+ nested_attrs = self.class.nested_attributes_options.keys
196
+ .each_with_object({}) do |association_name, result|
197
+ name = "#{association_name}_attributes"
198
+ result[name] = attrs.delete(name) if attrs.has_key?(name)
199
+ end
200
+ end
201
+
202
+ _assign_attributes(attrs)
203
+ _assign_attributes(represented_attrs)
204
+ _assign_attributes(nested_attrs) if nested_attrs
205
+ else
206
+ _assign_attributes(attrs)
207
+ end
208
+ true
209
+ end
210
+ alias_method :attributes=, :assign_attributes
211
+
212
+ def inspect
213
+ "#<#{self.class.send(:original_inspect)} #{attributes_for_inspect.presence || '(no attributes)'}>"
214
+ end
215
+
216
+ def initialize_copy _
217
+ @initial_attributes = Hash[attribute_names.map do |name|
218
+ [name, read_attribute_before_type_cast(name)]
219
+ end]
220
+ @_attributes = nil
221
+ super
222
+ end
223
+
224
+ private
225
+
226
+ def _assign_attributes attrs
227
+ attrs.each do |name, value|
228
+ name = name.to_s
229
+ sanitize_value = self.class._sanitize && name == self.class.primary_name
230
+
231
+ if respond_to?("#{name}=") && !sanitize_value
232
+ public_send("#{name}=", value)
233
+ else
234
+ logger.info("Ignoring #{sanitize_value ? 'primary' : 'undefined'} `#{name}` attribute value for #{self} during mass-assignment")
235
+ end
236
+ end
237
+ end
238
+
239
+ def attributes_for_inspect
240
+ attribute_names(false).map do |name|
241
+ prefix = self.class.primary_name == name ? ?* : ''
242
+ "#{prefix}#{attribute(name).inspect_attribute}"
243
+ end.join(', ')
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,73 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ class Attribute < Base
5
+ delegate :defaultizer, :enumerizer, :normalizers, to: :reflection
6
+
7
+ def write value
8
+ return if readonly?
9
+ pollute do
10
+ write_value value
11
+ end
12
+ end
13
+
14
+ def read
15
+ variable_cache(:value) do
16
+ normalize(enumerize(typecast(read_before_type_cast)))
17
+ end
18
+ end
19
+
20
+ def read_before_type_cast
21
+ variable_cache(:value_before_type_cast) do
22
+ defaultize(@value_cache)
23
+ end
24
+ end
25
+
26
+ def default
27
+ defaultizer.is_a?(Proc) ? evaluate(&defaultizer) : defaultizer
28
+ end
29
+
30
+ def defaultize value, default_value = nil
31
+ defaultizer && value.nil? ? default_value || default : value
32
+ end
33
+
34
+ def enum
35
+ source = enumerizer.is_a?(Proc) ? evaluate(&enumerizer) : enumerizer
36
+
37
+ case source
38
+ when Range
39
+ source.to_a
40
+ when Set
41
+ source
42
+ else
43
+ Array.wrap(source)
44
+ end.to_set
45
+ end
46
+
47
+ def enumerize value
48
+ set = enum if enumerizer
49
+ value if !set || (set.none? || set.include?(value))
50
+ end
51
+
52
+ def normalize value
53
+ if normalizers.none?
54
+ value
55
+ else
56
+ normalizers.inject(value) do |value, normalizer|
57
+ case normalizer
58
+ when Proc
59
+ evaluate(value, &normalizer)
60
+ when Hash
61
+ normalizer.inject(value) do |value, (name, options)|
62
+ ActiveData.normalizer(name).call(value, options, self)
63
+ end
64
+ else
65
+ ActiveData.normalizer(normalizer).call(value, {}, self)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,116 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ class Base
5
+ attr_reader :name, :owner
6
+ delegate :type, :typecaster, :readonly, to: :reflection
7
+
8
+ def initialize name, owner
9
+ @name, @owner = name, owner
10
+ end
11
+
12
+ def reflection
13
+ @owner.class._attributes[name]
14
+ end
15
+
16
+ def write_value value
17
+ reset
18
+ @value_cache = value
19
+ end
20
+
21
+ def write value
22
+ return if readonly?
23
+ write_value value
24
+ end
25
+
26
+ def reset
27
+ remove_variable(:value, :value_before_type_cast)
28
+ end
29
+
30
+ def read
31
+ @value_cache
32
+ end
33
+
34
+ def read_before_type_cast
35
+ @value_cache
36
+ end
37
+
38
+ def value_present?
39
+ !read.nil? && !(read.respond_to?(:empty?) && read.empty?)
40
+ end
41
+
42
+ def query
43
+ !(read.respond_to?(:zero?) ? read.zero? : read.blank?)
44
+ end
45
+
46
+ def typecast value
47
+ if value.instance_of?(type)
48
+ value
49
+ else
50
+ typecaster.call(value, self) unless value.nil?
51
+ end
52
+ end
53
+
54
+ def readonly?
55
+ !!(readonly.is_a?(Proc) ? evaluate(&readonly) : readonly)
56
+ end
57
+
58
+ def inspect_attribute
59
+ value = case read
60
+ when Date, Time, DateTime
61
+ %("#{read.to_s(:db)}")
62
+ else
63
+ inspection = read.inspect
64
+ inspection.size > 100 ? inspection.truncate(50) : inspection
65
+ end
66
+ "#{name}: #{value}"
67
+ end
68
+
69
+ def pollute
70
+ pollute = owner.class.dirty? && !owner.send(:attribute_changed?, name)
71
+
72
+ if pollute
73
+ previous_value = read
74
+ result = yield
75
+ if previous_value != read || (
76
+ read.respond_to?(:changed?) &&
77
+ read.changed?
78
+ )
79
+ owner.send(:set_attribute_was, name, previous_value)
80
+ end
81
+ result
82
+ else
83
+ yield
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def evaluate *args, &block
90
+ if block.arity >= 0 && block.arity <= args.length
91
+ owner.instance_exec(*args.first(block.arity), &block)
92
+ else
93
+ args = block.arity < 0 ? args : args.first(block.arity)
94
+ block.call(*args, owner)
95
+ end
96
+ end
97
+
98
+ def remove_variable(*names)
99
+ names.flatten.each do |name|
100
+ name = :"@#{name}"
101
+ remove_instance_variable(name) if instance_variable_defined?(name)
102
+ end
103
+ end
104
+
105
+ def variable_cache(name)
106
+ name = :"@#{name}"
107
+ if instance_variable_defined?(name)
108
+ instance_variable_get(name)
109
+ else
110
+ instance_variable_set(name, yield)
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end