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.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.rspec +0 -1
- data/.rvmrc +1 -1
- data/.travis.yml +13 -6
- data/Appraisals +7 -0
- data/Gemfile +1 -5
- data/Guardfile +68 -15
- data/README.md +144 -2
- data/active_data.gemspec +19 -11
- data/gemfiles/rails.4.0.gemfile +14 -0
- data/gemfiles/rails.4.1.gemfile +14 -0
- data/gemfiles/rails.4.2.gemfile +14 -0
- data/gemfiles/rails.5.0.gemfile +14 -0
- data/lib/active_data.rb +120 -3
- data/lib/active_data/active_record/associations.rb +50 -0
- data/lib/active_data/active_record/nested_attributes.rb +24 -0
- data/lib/active_data/config.rb +40 -0
- data/lib/active_data/errors.rb +93 -0
- data/lib/active_data/extensions.rb +33 -0
- data/lib/active_data/model.rb +16 -74
- data/lib/active_data/model/associations.rb +84 -15
- data/lib/active_data/model/associations/base.rb +79 -0
- data/lib/active_data/model/associations/collection/embedded.rb +12 -0
- data/lib/active_data/model/associations/collection/proxy.rb +32 -0
- data/lib/active_data/model/associations/collection/referenced.rb +26 -0
- data/lib/active_data/model/associations/embeds_many.rb +124 -18
- data/lib/active_data/model/associations/embeds_one.rb +90 -15
- data/lib/active_data/model/associations/nested_attributes.rb +180 -0
- data/lib/active_data/model/associations/references_many.rb +96 -0
- data/lib/active_data/model/associations/references_one.rb +83 -0
- data/lib/active_data/model/associations/reflections/base.rb +100 -0
- data/lib/active_data/model/associations/reflections/embeds_many.rb +25 -0
- data/lib/active_data/model/associations/reflections/embeds_one.rb +49 -0
- data/lib/active_data/model/associations/reflections/reference_reflection.rb +45 -0
- data/lib/active_data/model/associations/reflections/references_many.rb +28 -0
- data/lib/active_data/model/associations/reflections/references_one.rb +28 -0
- data/lib/active_data/model/associations/validations.rb +63 -0
- data/lib/active_data/model/attributes.rb +247 -0
- data/lib/active_data/model/attributes/attribute.rb +73 -0
- data/lib/active_data/model/attributes/base.rb +116 -0
- data/lib/active_data/model/attributes/collection.rb +17 -0
- data/lib/active_data/model/attributes/dictionary.rb +26 -0
- data/lib/active_data/model/attributes/localized.rb +42 -0
- data/lib/active_data/model/attributes/reference_many.rb +21 -0
- data/lib/active_data/model/attributes/reference_one.rb +42 -0
- data/lib/active_data/model/attributes/reflections/attribute.rb +55 -0
- data/lib/active_data/model/attributes/reflections/base.rb +62 -0
- data/lib/active_data/model/attributes/reflections/collection.rb +10 -0
- data/lib/active_data/model/attributes/reflections/dictionary.rb +13 -0
- data/lib/active_data/model/attributes/reflections/localized.rb +43 -0
- data/lib/active_data/model/attributes/reflections/reference_many.rb +10 -0
- data/lib/active_data/model/attributes/reflections/reference_one.rb +58 -0
- data/lib/active_data/model/attributes/reflections/represents.rb +55 -0
- data/lib/active_data/model/attributes/represents.rb +64 -0
- data/lib/active_data/model/callbacks.rb +71 -0
- data/lib/active_data/model/conventions.rb +35 -0
- data/lib/active_data/model/dirty.rb +77 -0
- data/lib/active_data/model/lifecycle.rb +307 -0
- data/lib/active_data/model/localization.rb +21 -0
- data/lib/active_data/model/persistence.rb +57 -0
- data/lib/active_data/model/primary.rb +51 -0
- data/lib/active_data/model/scopes.rb +77 -0
- data/lib/active_data/model/validations.rb +27 -0
- data/lib/active_data/model/validations/associated.rb +19 -0
- data/lib/active_data/model/validations/nested.rb +39 -0
- data/lib/active_data/railtie.rb +7 -0
- data/lib/active_data/version.rb +1 -1
- data/spec/lib/active_data/active_record/associations_spec.rb +149 -0
- data/spec/lib/active_data/active_record/nested_attributes_spec.rb +16 -0
- data/spec/lib/active_data/config_spec.rb +44 -0
- data/spec/lib/active_data/model/associations/embeds_many_spec.rb +362 -52
- data/spec/lib/active_data/model/associations/embeds_one_spec.rb +250 -31
- data/spec/lib/active_data/model/associations/nested_attributes_spec.rb +23 -0
- data/spec/lib/active_data/model/associations/references_many_spec.rb +196 -0
- data/spec/lib/active_data/model/associations/references_one_spec.rb +134 -0
- data/spec/lib/active_data/model/associations/reflections/embeds_many_spec.rb +144 -0
- data/spec/lib/active_data/model/associations/reflections/embeds_one_spec.rb +116 -0
- data/spec/lib/active_data/model/associations/reflections/references_many_spec.rb +255 -0
- data/spec/lib/active_data/model/associations/reflections/references_one_spec.rb +208 -0
- data/spec/lib/active_data/model/associations/validations_spec.rb +153 -0
- data/spec/lib/active_data/model/associations_spec.rb +189 -0
- data/spec/lib/active_data/model/attributes/attribute_spec.rb +144 -0
- data/spec/lib/active_data/model/attributes/base_spec.rb +82 -0
- data/spec/lib/active_data/model/attributes/collection_spec.rb +73 -0
- data/spec/lib/active_data/model/attributes/dictionary_spec.rb +93 -0
- data/spec/lib/active_data/model/attributes/localized_spec.rb +88 -33
- data/spec/lib/active_data/model/attributes/reflections/attribute_spec.rb +72 -0
- data/spec/lib/active_data/model/attributes/reflections/base_spec.rb +56 -0
- data/spec/lib/active_data/model/attributes/reflections/collection_spec.rb +37 -0
- data/spec/lib/active_data/model/attributes/reflections/dictionary_spec.rb +43 -0
- data/spec/lib/active_data/model/attributes/reflections/localized_spec.rb +37 -0
- data/spec/lib/active_data/model/attributes/reflections/represents_spec.rb +70 -0
- data/spec/lib/active_data/model/attributes/represents_spec.rb +153 -0
- data/spec/lib/active_data/model/attributes_spec.rb +243 -0
- data/spec/lib/active_data/model/callbacks_spec.rb +338 -0
- data/spec/lib/active_data/model/conventions_spec.rb +12 -0
- data/spec/lib/active_data/model/dirty_spec.rb +75 -0
- data/spec/lib/active_data/model/lifecycle_spec.rb +330 -0
- data/spec/lib/active_data/model/nested_attributes.rb +202 -0
- data/spec/lib/active_data/model/persistence_spec.rb +47 -0
- data/spec/lib/active_data/model/primary_spec.rb +84 -0
- data/spec/lib/active_data/model/scopes_spec.rb +88 -0
- data/spec/lib/active_data/model/typecasting_spec.rb +192 -0
- data/spec/lib/active_data/model/validations/associated_spec.rb +94 -0
- data/spec/lib/active_data/model/validations/nested_spec.rb +93 -0
- data/spec/lib/active_data/model/validations_spec.rb +31 -0
- data/spec/lib/active_data/model_spec.rb +1 -32
- data/spec/lib/active_data_spec.rb +12 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/model_helpers.rb +10 -0
- metadata +246 -54
- data/gemfiles/Gemfile.rails-3 +0 -14
- data/lib/active_data/attributes/base.rb +0 -69
- data/lib/active_data/attributes/localized.rb +0 -42
- data/lib/active_data/model/associations/association.rb +0 -30
- data/lib/active_data/model/attributable.rb +0 -122
- data/lib/active_data/model/collectionizable.rb +0 -55
- data/lib/active_data/model/collectionizable/proxy.rb +0 -42
- data/lib/active_data/model/extensions.rb +0 -9
- data/lib/active_data/model/extensions/array.rb +0 -24
- data/lib/active_data/model/extensions/big_decimal.rb +0 -17
- data/lib/active_data/model/extensions/boolean.rb +0 -38
- data/lib/active_data/model/extensions/date.rb +0 -17
- data/lib/active_data/model/extensions/date_time.rb +0 -17
- data/lib/active_data/model/extensions/float.rb +0 -17
- data/lib/active_data/model/extensions/hash.rb +0 -22
- data/lib/active_data/model/extensions/integer.rb +0 -17
- data/lib/active_data/model/extensions/localized.rb +0 -22
- data/lib/active_data/model/extensions/object.rb +0 -17
- data/lib/active_data/model/extensions/string.rb +0 -17
- data/lib/active_data/model/extensions/time.rb +0 -17
- data/lib/active_data/model/localizable.rb +0 -31
- data/lib/active_data/model/nested_attributes.rb +0 -58
- data/lib/active_data/model/parameterizable.rb +0 -29
- data/lib/active_data/validations.rb +0 -7
- data/lib/active_data/validations/associated.rb +0 -17
- data/spec/lib/active_data/model/attributable_spec.rb +0 -191
- data/spec/lib/active_data/model/collectionizable_spec.rb +0 -60
- data/spec/lib/active_data/model/nested_attributes_spec.rb +0 -67
- data/spec/lib/active_data/model/type_cast_spec.rb +0 -116
- 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
|