activeentity 0.0.1.beta14 → 6.1.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.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +24 -7
- data/Rakefile +7 -7
- data/lib/active_entity.rb +30 -7
- data/lib/active_entity/aggregations.rb +2 -1
- data/lib/active_entity/associations.rb +46 -24
- data/lib/active_entity/associations/{embedded → embeds}/association.rb +2 -2
- data/lib/active_entity/associations/{embedded → embeds}/builder/association.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/builder/collection_association.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/builder/embedded_in.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/builder/embeds_many.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/builder/embeds_one.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/builder/singular_association.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/collection_association.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/collection_proxy.rb +2 -2
- data/lib/active_entity/associations/{embedded → embeds}/embedded_in_association.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/embeds_many_association.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/embeds_one_association.rb +2 -1
- data/lib/active_entity/associations/{embedded → embeds}/singular_association.rb +1 -1
- data/lib/active_entity/attribute_assignment.rb +10 -8
- data/lib/active_entity/attribute_methods.rb +52 -61
- data/lib/active_entity/attribute_methods/before_type_cast.rb +6 -6
- data/lib/active_entity/attribute_methods/dirty.rb +13 -0
- data/lib/active_entity/attribute_methods/primary_key.rb +6 -8
- data/lib/active_entity/attribute_methods/query.rb +11 -8
- data/lib/active_entity/attribute_methods/read.rb +10 -13
- data/lib/active_entity/attribute_methods/time_zone_conversion.rb +2 -0
- data/lib/active_entity/attribute_methods/write.rb +16 -25
- data/lib/active_entity/attributes.rb +76 -2
- data/lib/active_entity/base.rb +3 -14
- data/lib/active_entity/{define_callbacks.rb → callbacks.rb} +5 -1
- data/lib/active_entity/core.rb +97 -39
- data/lib/active_entity/enum.rb +30 -4
- data/lib/active_entity/errors.rb +0 -11
- data/lib/active_entity/gem_version.rb +4 -4
- data/lib/active_entity/inheritance.rb +4 -106
- data/lib/active_entity/integration.rb +1 -1
- data/lib/active_entity/model_schema.rb +0 -12
- data/lib/active_entity/nested_attributes.rb +5 -12
- data/lib/active_entity/railtie.rb +61 -1
- data/lib/active_entity/readonly_attributes.rb +9 -1
- data/lib/active_entity/reflection.rb +22 -19
- data/lib/active_entity/serialization.rb +9 -6
- data/lib/active_entity/store.rb +51 -2
- data/lib/active_entity/type.rb +8 -8
- data/lib/active_entity/type/registry.rb +5 -5
- data/lib/active_entity/{validate_embedded_association.rb → validate_embeds_association.rb} +6 -6
- data/lib/active_entity/validations.rb +2 -6
- data/lib/active_entity/validations/associated.rb +1 -1
- data/lib/active_entity/validations/{uniqueness_in_embedding.rb → uniqueness_in_embeds.rb} +1 -1
- data/lib/active_entity/validations/uniqueness_on_active_record.rb +42 -47
- metadata +33 -36
- data/lib/active_entity/type/decimal_without_scale.rb +0 -15
- data/lib/active_entity/type/hash_lookup_type_map.rb +0 -25
- data/lib/active_entity/type/type_map.rb +0 -62
- data/lib/tasks/active_entity_tasks.rake +0 -6
@@ -6,7 +6,7 @@ module ActiveEntity
|
|
6
6
|
# == Single table inheritance
|
7
7
|
#
|
8
8
|
# Active Entity allows inheritance by storing the name of the class in a column that by
|
9
|
-
# default is named "type" (can be changed by overwriting <tt>Base.
|
9
|
+
# default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
|
10
10
|
# This means that an inheritance looking like this:
|
11
11
|
#
|
12
12
|
# class Company < ActiveEntity::Base; end
|
@@ -37,12 +37,6 @@ module ActiveEntity
|
|
37
37
|
module Inheritance
|
38
38
|
extend ActiveSupport::Concern
|
39
39
|
|
40
|
-
included do
|
41
|
-
# Determines whether to store the full constant name including namespace when using STI.
|
42
|
-
# This is true, by default.
|
43
|
-
class_attribute :store_full_sti_class, instance_writer: false, default: true
|
44
|
-
end
|
45
|
-
|
46
40
|
module ClassMethods
|
47
41
|
# Determines if one of the attributes passed in is the inheritance column,
|
48
42
|
# and if the inheritance column is attr accessible, it initializes an
|
@@ -52,19 +46,7 @@ module ActiveEntity
|
|
52
46
|
raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
|
53
47
|
end
|
54
48
|
|
55
|
-
|
56
|
-
subclass = subclass_from_attributes(attributes)
|
57
|
-
|
58
|
-
if subclass.nil? && base_class?
|
59
|
-
subclass = subclass_from_attributes(_default_attributes)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
if subclass && subclass != self
|
64
|
-
subclass.new(attributes, &block)
|
65
|
-
else
|
66
|
-
super
|
67
|
-
end
|
49
|
+
super
|
68
50
|
end
|
69
51
|
|
70
52
|
# Returns +true+ if this does not need STI type condition. Returns
|
@@ -75,15 +57,10 @@ module ActiveEntity
|
|
75
57
|
elsif superclass.abstract_class?
|
76
58
|
superclass.descends_from_active_entity?
|
77
59
|
else
|
78
|
-
superclass == Base
|
60
|
+
superclass == Base
|
79
61
|
end
|
80
62
|
end
|
81
63
|
|
82
|
-
def finder_needs_type_condition? #:nodoc:
|
83
|
-
# This is like this because benchmarking justifies the strange :false stuff
|
84
|
-
:true == (@finder_needs_type_condition ||= descends_from_active_entity? ? :false : :true)
|
85
|
-
end
|
86
|
-
|
87
64
|
# Returns the class descending directly from ActiveEntity::Base, or
|
88
65
|
# an abstract class, if any, in the inheritance hierarchy.
|
89
66
|
#
|
@@ -94,7 +71,7 @@ module ActiveEntity
|
|
94
71
|
# and C.base_class would return B as the answer since A is an abstract_class.
|
95
72
|
def base_class
|
96
73
|
unless self < Base
|
97
|
-
raise ActiveEntityError, "#{name} doesn't belong in a hierarchy descending from
|
74
|
+
raise ActiveEntityError, "#{name} doesn't belong in a hierarchy descending from ActiveEntity"
|
98
75
|
end
|
99
76
|
|
100
77
|
if superclass == Base || superclass.abstract_class?
|
@@ -158,10 +135,6 @@ module ActiveEntity
|
|
158
135
|
defined?(@abstract_class) && @abstract_class == true
|
159
136
|
end
|
160
137
|
|
161
|
-
def sti_name
|
162
|
-
store_full_sti_class ? name : name.demodulize
|
163
|
-
end
|
164
|
-
|
165
138
|
def inherited(subclass)
|
166
139
|
subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new)
|
167
140
|
super
|
@@ -198,81 +171,6 @@ module ActiveEntity
|
|
198
171
|
raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
|
199
172
|
end
|
200
173
|
end
|
201
|
-
|
202
|
-
private
|
203
|
-
|
204
|
-
# Called by +instantiate+ to decide which class to use for a new
|
205
|
-
# record instance. For single-table inheritance, we check the record
|
206
|
-
# for a +type+ column and return the corresponding class.
|
207
|
-
def discriminate_class_for_record(record)
|
208
|
-
if using_single_table_inheritance?(record)
|
209
|
-
find_sti_class(record[inheritance_attribute])
|
210
|
-
else
|
211
|
-
super
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
def using_single_table_inheritance?(record)
|
216
|
-
record[inheritance_attribute].present? && has_attribute?(inheritance_attribute)
|
217
|
-
end
|
218
|
-
|
219
|
-
def find_sti_class(type_name)
|
220
|
-
type_name = base_class.type_for_attribute(inheritance_attribute).cast(type_name)
|
221
|
-
subclass = begin
|
222
|
-
if store_full_sti_class
|
223
|
-
ActiveSupport::Dependencies.constantize(type_name)
|
224
|
-
else
|
225
|
-
compute_type(type_name)
|
226
|
-
end
|
227
|
-
rescue NameError
|
228
|
-
raise SubclassNotFound,
|
229
|
-
"The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
|
230
|
-
"This error is raised because the attribute '#{inheritance_attribute}' is reserved for storing the class in case of inheritance. " \
|
231
|
-
"Please rename this attribute if you didn't intend it to be used for storing the inheritance class " \
|
232
|
-
"or overwrite #{name}.inheritance_attribute to use another attribute for that information."
|
233
|
-
end
|
234
|
-
unless subclass == self || descendants.include?(subclass)
|
235
|
-
raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}"
|
236
|
-
end
|
237
|
-
subclass
|
238
|
-
end
|
239
|
-
|
240
|
-
# Detect the subclass from the inheritance column of attrs. If the inheritance column value
|
241
|
-
# is not self or a valid subclass, raises ActiveEntity::SubclassNotFound
|
242
|
-
def subclass_from_attributes(attrs)
|
243
|
-
attrs = attrs.to_h if attrs.respond_to?(:permitted?)
|
244
|
-
if attrs.is_a?(Hash)
|
245
|
-
subclass_name = attrs[inheritance_attribute] || attrs[inheritance_attribute.to_sym]
|
246
|
-
|
247
|
-
if subclass_name.present?
|
248
|
-
find_sti_class(subclass_name)
|
249
|
-
end
|
250
|
-
end
|
251
|
-
end
|
252
174
|
end
|
253
|
-
|
254
|
-
def initialize_dup(other)
|
255
|
-
super
|
256
|
-
ensure_proper_type
|
257
|
-
end
|
258
|
-
|
259
|
-
private
|
260
|
-
|
261
|
-
def initialize_internals_callback
|
262
|
-
super
|
263
|
-
ensure_proper_type
|
264
|
-
end
|
265
|
-
|
266
|
-
# Sets the attribute used for single table inheritance to this class name if this is not the
|
267
|
-
# ActiveEntity::Base descendant.
|
268
|
-
# Considering the hierarchy Reply < Message < ActiveEntity::Base, this makes it possible to
|
269
|
-
# do Reply.new without having to set <tt>Reply[Reply.inheritance_attribute] = "Reply"</tt> yourself.
|
270
|
-
# No such attribute would be set for objects of the Message class in that example.
|
271
|
-
def ensure_proper_type
|
272
|
-
klass = self.class
|
273
|
-
if klass.finder_needs_type_condition?
|
274
|
-
_write_attribute(klass.inheritance_attribute, klass.sti_name)
|
275
|
-
end
|
276
|
-
end
|
277
175
|
end
|
278
176
|
end
|
@@ -30,7 +30,7 @@ module ActiveEntity
|
|
30
30
|
# user_path(user) # => "/users/Phusion"
|
31
31
|
def to_param
|
32
32
|
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
|
33
|
-
id
|
33
|
+
id&.to_s # Be sure to stringify the id for routes
|
34
34
|
end
|
35
35
|
|
36
36
|
module ClassMethods
|
@@ -9,22 +9,10 @@ module ActiveEntity
|
|
9
9
|
included do
|
10
10
|
delegate :type_for_attribute, to: :class
|
11
11
|
|
12
|
-
self.inheritance_attribute = "type"
|
13
|
-
|
14
12
|
initialize_load_schema_monitor
|
15
13
|
end
|
16
14
|
|
17
15
|
module ClassMethods
|
18
|
-
def inheritance_attribute
|
19
|
-
(@inheritance_attribute ||= nil) || superclass.inheritance_attribute
|
20
|
-
end
|
21
|
-
|
22
|
-
# Sets the value of inheritance_attribute
|
23
|
-
def inheritance_attribute=(value)
|
24
|
-
@inheritance_attribute = value.to_s
|
25
|
-
@explicit_inheritance_attribute = true
|
26
|
-
end
|
27
|
-
|
28
16
|
def attributes_builder # :nodoc:
|
29
17
|
unless defined?(@attributes_builder) && @attributes_builder
|
30
18
|
defaults = _default_attributes
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require "active_support/core_ext/hash/except"
|
4
4
|
require "active_support/core_ext/module/redefine_method"
|
5
|
-
require "active_support/core_ext/object/try"
|
6
5
|
require "active_support/core_ext/hash/indifferent_access"
|
7
6
|
|
8
7
|
module ActiveEntity
|
@@ -289,7 +288,7 @@ module ActiveEntity
|
|
289
288
|
# [:allow_destroy]
|
290
289
|
# If true, destroys any members from the attributes hash with a
|
291
290
|
# <tt>_destroy</tt> key and a value that evaluates to +true+
|
292
|
-
# (
|
291
|
+
# (e.g. 1, '1', true, or 'true'). This option is off by default.
|
293
292
|
# [:reject_if]
|
294
293
|
# Allows you to specify a Proc or a Symbol pointing to a method
|
295
294
|
# that checks whether a record should be built for a certain attribute
|
@@ -465,10 +464,10 @@ module ActiveEntity
|
|
465
464
|
if attributes_collection.is_a? Hash
|
466
465
|
keys = attributes_collection.keys
|
467
466
|
attributes_collection = if keys.include?("id") || keys.include?(:id)
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
467
|
+
[attributes_collection]
|
468
|
+
else
|
469
|
+
attributes_collection.values
|
470
|
+
end
|
472
471
|
end
|
473
472
|
|
474
473
|
association = association(association_name)
|
@@ -551,11 +550,5 @@ module ActiveEntity
|
|
551
550
|
def allow_destroy?(association_name)
|
552
551
|
nested_attributes_options[association_name][:allow_destroy]
|
553
552
|
end
|
554
|
-
|
555
|
-
def raise_nested_attributes_record_not_found!(association_name, record_id)
|
556
|
-
model = self.class._reflect_on_association(association_name).klass.name
|
557
|
-
raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
|
558
|
-
model, "id", record_id)
|
559
|
-
end
|
560
553
|
end
|
561
554
|
end
|
@@ -2,15 +2,75 @@
|
|
2
2
|
|
3
3
|
require "active_entity"
|
4
4
|
require "rails"
|
5
|
+
require "active_support/core_ext/object/try"
|
5
6
|
require "active_model/railtie"
|
6
7
|
|
8
|
+
# For now, action_controller must always be present with
|
9
|
+
# Rails, so let's make sure that it gets required before
|
10
|
+
# here. This is needed for correctly setting up the middleware.
|
11
|
+
# In the future, this might become an optional require.
|
12
|
+
require "action_controller/railtie"
|
13
|
+
|
7
14
|
module ActiveEntity
|
8
15
|
# = Active Entity Railtie
|
9
16
|
class Railtie < Rails::Railtie # :nodoc:
|
17
|
+
config.active_entity = ActiveSupport::OrderedOptions.new
|
18
|
+
|
10
19
|
config.eager_load_namespaces << ActiveEntity
|
11
20
|
|
21
|
+
# When loading console, force ActiveEntity::Base to be loaded
|
22
|
+
# to avoid cross references when loading a constant for the
|
23
|
+
# first time. Also, make it output to STDERR.
|
24
|
+
console do |_app|
|
25
|
+
require "active_entity/base"
|
26
|
+
unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT)
|
27
|
+
console = ActiveSupport::Logger.new(STDERR)
|
28
|
+
Rails.logger.extend ActiveSupport::Logger.broadcast console
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
12
32
|
runner do
|
13
|
-
require "
|
33
|
+
require "active_entity/base"
|
34
|
+
end
|
35
|
+
|
36
|
+
initializer "active_entity.initialize_timezone" do
|
37
|
+
ActiveSupport.on_load(:active_entity) do
|
38
|
+
self.time_zone_aware_attributes = true
|
39
|
+
self.default_timezone = :utc
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
initializer "active_entity.logger" do
|
44
|
+
ActiveSupport.on_load(:active_entity) { self.logger ||= ::Rails.logger }
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
initializer "active_entity.define_attribute_methods" do |app|
|
49
|
+
config.after_initialize do
|
50
|
+
ActiveSupport.on_load(:active_entity) do
|
51
|
+
if app.config.eager_load
|
52
|
+
descendants.each do |model|
|
53
|
+
model.define_attribute_methods
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
initializer "active_entity.set_configs" do |app|
|
61
|
+
ActiveSupport.on_load(:active_entity) do
|
62
|
+
configs = app.config.active_entity
|
63
|
+
|
64
|
+
configs.each do |k, v|
|
65
|
+
send "#{k}=", v
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
initializer "active_entity.set_filter_attributes" do
|
71
|
+
ActiveSupport.on_load(:active_entity) do
|
72
|
+
self.filter_attributes += Rails.application.config.filter_parameters
|
73
|
+
end
|
14
74
|
end
|
15
75
|
end
|
16
76
|
end
|
@@ -16,7 +16,7 @@ module ActiveEntity
|
|
16
16
|
@_attr_readonly_enabled = true
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
19
|
+
def without_attr_readonly
|
20
20
|
return unless block_given?
|
21
21
|
|
22
22
|
disable_attr_readonly!
|
@@ -31,6 +31,10 @@ module ActiveEntity
|
|
31
31
|
end
|
32
32
|
alias attr_readonly_enabled? _attr_readonly_enabled
|
33
33
|
|
34
|
+
def readonly_attribute?(name)
|
35
|
+
self.class.readonly_attribute?(name)
|
36
|
+
end
|
37
|
+
|
34
38
|
module ClassMethods
|
35
39
|
# Attributes listed as readonly will be used to create a new record but update operations will
|
36
40
|
# ignore these fields.
|
@@ -42,6 +46,10 @@ module ActiveEntity
|
|
42
46
|
def readonly_attributes
|
43
47
|
_attr_readonly
|
44
48
|
end
|
49
|
+
|
50
|
+
def readonly_attribute?(name) # :nodoc:
|
51
|
+
_attr_readonly.include?(name)
|
52
|
+
end
|
45
53
|
end
|
46
54
|
end
|
47
55
|
end
|
@@ -17,7 +17,7 @@ module ActiveEntity
|
|
17
17
|
def create(macro, name, scope, options, ar_or_ae)
|
18
18
|
reflection_class_for(macro).new(name, scope, options, ar_or_ae)
|
19
19
|
|
20
|
-
# TODO: Support bridge to Active
|
20
|
+
# TODO: Support bridge to Active Entity
|
21
21
|
# reflection = reflection_class_for(macro).new(name, scope, options, ar_or_ae)
|
22
22
|
# options[:through] ? ActiveRecord::ThroughReflection.new(reflection) : reflection
|
23
23
|
end
|
@@ -33,6 +33,7 @@ module ActiveEntity
|
|
33
33
|
end
|
34
34
|
|
35
35
|
private
|
36
|
+
|
36
37
|
def reflection_class_for(macro)
|
37
38
|
case macro
|
38
39
|
when :composed_of
|
@@ -77,21 +78,21 @@ module ActiveEntity
|
|
77
78
|
#
|
78
79
|
def reflections
|
79
80
|
@__reflections ||= begin
|
80
|
-
|
81
|
+
ref = {}
|
81
82
|
|
82
|
-
|
83
|
-
|
83
|
+
_reflections.each do |name, reflection|
|
84
|
+
parent_reflection = reflection.parent_reflection
|
84
85
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
86
|
+
if parent_reflection
|
87
|
+
parent_name = parent_reflection.name
|
88
|
+
ref[parent_name.to_s] = parent_reflection
|
89
|
+
else
|
90
|
+
ref[name] = reflection
|
91
|
+
end
|
92
|
+
end
|
92
93
|
|
93
|
-
|
94
|
-
|
94
|
+
ref
|
95
|
+
end
|
95
96
|
end
|
96
97
|
|
97
98
|
# Returns an array of AssociationReflection objects for all the
|
@@ -184,6 +185,7 @@ module ActiveEntity
|
|
184
185
|
end
|
185
186
|
|
186
187
|
protected
|
188
|
+
|
187
189
|
def actual_source_reflection # FIXME: this is a horrible name
|
188
190
|
self
|
189
191
|
end
|
@@ -242,12 +244,13 @@ module ActiveEntity
|
|
242
244
|
def ==(other_aggregation)
|
243
245
|
super ||
|
244
246
|
other_aggregation.kind_of?(self.class) &&
|
245
|
-
|
246
|
-
|
247
|
-
|
247
|
+
name == other_aggregation.name &&
|
248
|
+
!other_aggregation.options.nil? &&
|
249
|
+
active_entity == other_aggregation.active_entity
|
248
250
|
end
|
249
251
|
|
250
252
|
private
|
253
|
+
|
251
254
|
def derive_class_name
|
252
255
|
name.to_s.camelize
|
253
256
|
end
|
@@ -417,7 +420,7 @@ module ActiveEntity
|
|
417
420
|
def collection?; true; end
|
418
421
|
|
419
422
|
def association_class
|
420
|
-
Associations::
|
423
|
+
Associations::Embeds::EmbedsManyAssociation
|
421
424
|
end
|
422
425
|
end
|
423
426
|
|
@@ -427,7 +430,7 @@ module ActiveEntity
|
|
427
430
|
def embeds_one?; true; end
|
428
431
|
|
429
432
|
def association_class
|
430
|
-
Associations::
|
433
|
+
Associations::Embeds::EmbedsOneAssociation
|
431
434
|
end
|
432
435
|
end
|
433
436
|
|
@@ -437,7 +440,7 @@ module ActiveEntity
|
|
437
440
|
def embedded_in?; true; end
|
438
441
|
|
439
442
|
def association_class
|
440
|
-
Associations::
|
443
|
+
Associations::Embeds::EmbeddedInAssociation
|
441
444
|
end
|
442
445
|
end
|
443
446
|
end
|