active_data 0.3.0 → 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.
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