sbf-dm-core 1.3.0.beta
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 +7 -0
- data/.autotest +29 -0
- data/.document +5 -0
- data/.gitignore +44 -0
- data/.rspec +1 -0
- data/.rubocop.yml +468 -0
- data/.travis.yml +57 -0
- data/.yardopts +1 -0
- data/Gemfile +70 -0
- data/LICENSE +20 -0
- data/README.md +269 -0
- data/Rakefile +4 -0
- data/dm-core.gemspec +21 -0
- data/lib/dm-core/adapters/abstract_adapter.rb +233 -0
- data/lib/dm-core/adapters/in_memory_adapter.rb +110 -0
- data/lib/dm-core/adapters.rb +249 -0
- data/lib/dm-core/associations/many_to_many.rb +477 -0
- data/lib/dm-core/associations/many_to_one.rb +282 -0
- data/lib/dm-core/associations/one_to_many.rb +332 -0
- data/lib/dm-core/associations/one_to_one.rb +84 -0
- data/lib/dm-core/associations/relationship.rb +650 -0
- data/lib/dm-core/backwards.rb +11 -0
- data/lib/dm-core/collection.rb +1486 -0
- data/lib/dm-core/core_ext/kernel.rb +21 -0
- data/lib/dm-core/core_ext/pathname.rb +4 -0
- data/lib/dm-core/core_ext/symbol.rb +10 -0
- data/lib/dm-core/identity_map.rb +6 -0
- data/lib/dm-core/model/hook.rb +99 -0
- data/lib/dm-core/model/is.rb +30 -0
- data/lib/dm-core/model/property.rb +244 -0
- data/lib/dm-core/model/relationship.rb +366 -0
- data/lib/dm-core/model/scope.rb +87 -0
- data/lib/dm-core/model.rb +876 -0
- data/lib/dm-core/property/binary.rb +19 -0
- data/lib/dm-core/property/boolean.rb +35 -0
- data/lib/dm-core/property/class.rb +23 -0
- data/lib/dm-core/property/date.rb +45 -0
- data/lib/dm-core/property/date_time.rb +44 -0
- data/lib/dm-core/property/decimal.rb +47 -0
- data/lib/dm-core/property/discriminator.rb +40 -0
- data/lib/dm-core/property/float.rb +27 -0
- data/lib/dm-core/property/integer.rb +32 -0
- data/lib/dm-core/property/invalid_value_error.rb +17 -0
- data/lib/dm-core/property/lookup.rb +26 -0
- data/lib/dm-core/property/numeric.rb +35 -0
- data/lib/dm-core/property/object.rb +33 -0
- data/lib/dm-core/property/serial.rb +13 -0
- data/lib/dm-core/property/string.rb +47 -0
- data/lib/dm-core/property/text.rb +12 -0
- data/lib/dm-core/property/time.rb +46 -0
- data/lib/dm-core/property/typecast/numeric.rb +32 -0
- data/lib/dm-core/property/typecast/time.rb +33 -0
- data/lib/dm-core/property.rb +856 -0
- data/lib/dm-core/property_set.rb +177 -0
- data/lib/dm-core/query/conditions/comparison.rb +886 -0
- data/lib/dm-core/query/conditions/operation.rb +710 -0
- data/lib/dm-core/query/direction.rb +33 -0
- data/lib/dm-core/query/operator.rb +34 -0
- data/lib/dm-core/query/path.rb +113 -0
- data/lib/dm-core/query/sort.rb +38 -0
- data/lib/dm-core/query.rb +1352 -0
- data/lib/dm-core/relationship_set.rb +69 -0
- data/lib/dm-core/repository.rb +226 -0
- data/lib/dm-core/resource/persistence_state/clean.rb +36 -0
- data/lib/dm-core/resource/persistence_state/deleted.rb +26 -0
- data/lib/dm-core/resource/persistence_state/dirty.rb +91 -0
- data/lib/dm-core/resource/persistence_state/immutable.rb +32 -0
- data/lib/dm-core/resource/persistence_state/persisted.rb +25 -0
- data/lib/dm-core/resource/persistence_state/transient.rb +87 -0
- data/lib/dm-core/resource/persistence_state.rb +70 -0
- data/lib/dm-core/resource.rb +1220 -0
- data/lib/dm-core/spec/lib/adapter_helpers.rb +63 -0
- data/lib/dm-core/spec/lib/collection_helpers.rb +21 -0
- data/lib/dm-core/spec/lib/counter_adapter.rb +38 -0
- data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
- data/lib/dm-core/spec/lib/spec_helper.rb +74 -0
- data/lib/dm-core/spec/setup.rb +164 -0
- data/lib/dm-core/spec/shared/adapter_spec.rb +366 -0
- data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
- data/lib/dm-core/spec/shared/resource_spec.rb +1221 -0
- data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
- data/lib/dm-core/spec/shared/semipublic/property_spec.rb +184 -0
- data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
- data/lib/dm-core/support/assertions.rb +8 -0
- data/lib/dm-core/support/chainable.rb +18 -0
- data/lib/dm-core/support/deprecate.rb +12 -0
- data/lib/dm-core/support/descendant_set.rb +89 -0
- data/lib/dm-core/support/equalizer.rb +48 -0
- data/lib/dm-core/support/ext/array.rb +22 -0
- data/lib/dm-core/support/ext/blank.rb +25 -0
- data/lib/dm-core/support/ext/hash.rb +67 -0
- data/lib/dm-core/support/ext/module.rb +47 -0
- data/lib/dm-core/support/ext/object.rb +57 -0
- data/lib/dm-core/support/ext/string.rb +24 -0
- data/lib/dm-core/support/ext/try_dup.rb +12 -0
- data/lib/dm-core/support/hook.rb +388 -0
- data/lib/dm-core/support/inflections.rb +60 -0
- data/lib/dm-core/support/inflector/inflections.rb +211 -0
- data/lib/dm-core/support/inflector/methods.rb +151 -0
- data/lib/dm-core/support/lazy_array.rb +451 -0
- data/lib/dm-core/support/local_object_space.rb +13 -0
- data/lib/dm-core/support/logger.rb +201 -0
- data/lib/dm-core/support/mash.rb +176 -0
- data/lib/dm-core/support/naming_conventions.rb +109 -0
- data/lib/dm-core/support/ordered_set.rb +381 -0
- data/lib/dm-core/support/subject.rb +33 -0
- data/lib/dm-core/support/subject_set.rb +251 -0
- data/lib/dm-core/version.rb +3 -0
- data/lib/dm-core.rb +274 -0
- data/script/performance.rb +275 -0
- data/script/profile.rb +218 -0
- data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
- data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +69 -0
- data/spec/public/associations/many_to_many_spec.rb +197 -0
- data/spec/public/associations/many_to_one_spec.rb +83 -0
- data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
- data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
- data/spec/public/associations/one_to_many_spec.rb +81 -0
- data/spec/public/associations/one_to_one_spec.rb +176 -0
- data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
- data/spec/public/collection_spec.rb +69 -0
- data/spec/public/finalize_spec.rb +77 -0
- data/spec/public/model/hook_spec.rb +245 -0
- data/spec/public/model/property_spec.rb +91 -0
- data/spec/public/model/relationship_spec.rb +1040 -0
- data/spec/public/model_spec.rb +456 -0
- data/spec/public/property/binary_spec.rb +43 -0
- data/spec/public/property/boolean_spec.rb +21 -0
- data/spec/public/property/class_spec.rb +27 -0
- data/spec/public/property/date_spec.rb +21 -0
- data/spec/public/property/date_time_spec.rb +21 -0
- data/spec/public/property/decimal_spec.rb +23 -0
- data/spec/public/property/discriminator_spec.rb +134 -0
- data/spec/public/property/float_spec.rb +22 -0
- data/spec/public/property/integer_spec.rb +22 -0
- data/spec/public/property/object_spec.rb +117 -0
- data/spec/public/property/serial_spec.rb +22 -0
- data/spec/public/property/string_spec.rb +21 -0
- data/spec/public/property/text_spec.rb +62 -0
- data/spec/public/property/time_spec.rb +21 -0
- data/spec/public/property_spec.rb +333 -0
- data/spec/public/resource/state_spec.rb +72 -0
- data/spec/public/resource_spec.rb +289 -0
- data/spec/public/sel_spec.rb +53 -0
- data/spec/public/setup_spec.rb +145 -0
- data/spec/public/shared/association_collection_shared_spec.rb +309 -0
- data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
- data/spec/public/shared/collection_shared_spec.rb +1637 -0
- data/spec/public/shared/finder_shared_spec.rb +1647 -0
- data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
- data/spec/semipublic/adapters/in_memory_adapter_spec.rb +13 -0
- data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
- data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
- data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
- data/spec/semipublic/associations/relationship_spec.rb +200 -0
- data/spec/semipublic/associations_spec.rb +177 -0
- data/spec/semipublic/collection_spec.rb +110 -0
- data/spec/semipublic/model_spec.rb +96 -0
- data/spec/semipublic/property/binary_spec.rb +13 -0
- data/spec/semipublic/property/boolean_spec.rb +47 -0
- data/spec/semipublic/property/class_spec.rb +33 -0
- data/spec/semipublic/property/date_spec.rb +43 -0
- data/spec/semipublic/property/date_time_spec.rb +46 -0
- data/spec/semipublic/property/decimal_spec.rb +83 -0
- data/spec/semipublic/property/discriminator_spec.rb +19 -0
- data/spec/semipublic/property/float_spec.rb +82 -0
- data/spec/semipublic/property/integer_spec.rb +82 -0
- data/spec/semipublic/property/lookup_spec.rb +29 -0
- data/spec/semipublic/property/serial_spec.rb +13 -0
- data/spec/semipublic/property/string_spec.rb +13 -0
- data/spec/semipublic/property/text_spec.rb +31 -0
- data/spec/semipublic/property/time_spec.rb +50 -0
- data/spec/semipublic/property_spec.rb +114 -0
- data/spec/semipublic/query/conditions/comparison_spec.rb +1502 -0
- data/spec/semipublic/query/conditions/operation_spec.rb +1296 -0
- data/spec/semipublic/query/path_spec.rb +471 -0
- data/spec/semipublic/query_spec.rb +3665 -0
- data/spec/semipublic/resource/state/clean_spec.rb +89 -0
- data/spec/semipublic/resource/state/deleted_spec.rb +79 -0
- data/spec/semipublic/resource/state/dirty_spec.rb +163 -0
- data/spec/semipublic/resource/state/immutable_spec.rb +107 -0
- data/spec/semipublic/resource/state/transient_spec.rb +163 -0
- data/spec/semipublic/resource/state_spec.rb +230 -0
- data/spec/semipublic/resource_spec.rb +23 -0
- data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
- data/spec/semipublic/shared/resource_shared_spec.rb +198 -0
- data/spec/semipublic/shared/resource_state_shared_spec.rb +91 -0
- data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/support/core_ext/hash.rb +10 -0
- data/spec/support/core_ext/inheritable_attributes.rb +46 -0
- data/spec/support/properties/huge_integer.rb +17 -0
- data/spec/unit/array_spec.rb +23 -0
- data/spec/unit/blank_spec.rb +73 -0
- data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
- data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
- data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
- data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
- data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
- data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
- data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
- data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
- data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
- data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
- data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
- data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
- data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
- data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
- data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
- data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
- data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
- data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
- data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
- data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
- data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
- data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
- data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
- data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
- data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
- data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
- data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
- data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
- data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
- data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
- data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
- data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
- data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
- data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
- data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
- data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
- data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
- data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
- data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
- data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
- data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
- data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
- data/spec/unit/hash_spec.rb +27 -0
- data/spec/unit/hook_spec.rb +1216 -0
- data/spec/unit/inflections_spec.rb +14 -0
- data/spec/unit/lazy_array_spec.rb +1949 -0
- data/spec/unit/mash_spec.rb +289 -0
- data/spec/unit/module_spec.rb +70 -0
- data/spec/unit/object_spec.rb +38 -0
- data/spec/unit/try_dup_spec.rb +46 -0
- data/tasks/ci.rake +1 -0
- data/tasks/spec.rake +18 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +323 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Associations
|
|
3
|
+
module ManyToOne # :nodoc:
|
|
4
|
+
# Relationship class with implementation specific
|
|
5
|
+
# to n side of 1 to n association
|
|
6
|
+
class Relationship < Associations::Relationship
|
|
7
|
+
OPTIONS = superclass::OPTIONS.dup << :required << :key << :unique
|
|
8
|
+
|
|
9
|
+
# @api semipublic
|
|
10
|
+
alias_method :source_repository_name, :child_repository_name
|
|
11
|
+
|
|
12
|
+
# @api semipublic
|
|
13
|
+
alias_method :source_model, :child_model
|
|
14
|
+
|
|
15
|
+
# @api semipublic
|
|
16
|
+
alias_method :target_repository_name, :parent_repository_name
|
|
17
|
+
|
|
18
|
+
# @api semipublic
|
|
19
|
+
alias_method :target_model, :parent_model
|
|
20
|
+
|
|
21
|
+
# @api semipublic
|
|
22
|
+
alias_method :target_key, :parent_key
|
|
23
|
+
|
|
24
|
+
# @api semipublic
|
|
25
|
+
def required?
|
|
26
|
+
@required
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @api semipublic
|
|
30
|
+
def key?
|
|
31
|
+
@key
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @api semipublic
|
|
35
|
+
def unique?
|
|
36
|
+
!!@unique
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @deprecated
|
|
40
|
+
def nullable?
|
|
41
|
+
raise "#{self.class}#nullable? is deprecated, use #{self.class}#required? instead (#{caller.first})"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns a set of keys that identify source model
|
|
45
|
+
#
|
|
46
|
+
# @return [DataMapper::PropertySet] a set of properties that identify source model
|
|
47
|
+
# @api private
|
|
48
|
+
def child_key
|
|
49
|
+
return @child_key if defined?(@child_key)
|
|
50
|
+
|
|
51
|
+
model = source_model
|
|
52
|
+
repository_name = source_repository_name || target_repository_name
|
|
53
|
+
properties = model&.properties(repository_name)
|
|
54
|
+
|
|
55
|
+
source_key = target_key.zip(@child_properties || []).map do |target_property, property_name|
|
|
56
|
+
property_name ||= "#{name}_#{target_property.name}".to_sym
|
|
57
|
+
|
|
58
|
+
properties[property_name] || begin
|
|
59
|
+
# create the property within the correct repository
|
|
60
|
+
DataMapper.repository(repository_name) do
|
|
61
|
+
model&.property(property_name, target_property.to_child_key, source_key_options(target_property))
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@child_key = properties.class.new(source_key).freeze
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @api semipublic
|
|
70
|
+
alias_method :source_key, :child_key
|
|
71
|
+
|
|
72
|
+
# Initialize the foreign key property this "many to one"
|
|
73
|
+
# relationship uses to persist itself
|
|
74
|
+
#
|
|
75
|
+
# @return [self]
|
|
76
|
+
#
|
|
77
|
+
# @api public
|
|
78
|
+
def finalize
|
|
79
|
+
child_key
|
|
80
|
+
self
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Returns a hash of conditions that scopes query that fetches
|
|
84
|
+
# target object
|
|
85
|
+
#
|
|
86
|
+
# @return [Hash]
|
|
87
|
+
# Hash of conditions that scopes query
|
|
88
|
+
#
|
|
89
|
+
# @api private
|
|
90
|
+
def source_scope(source)
|
|
91
|
+
if source.is_a?(Resource)
|
|
92
|
+
Query.target_conditions(source, source_key, target_key)
|
|
93
|
+
else
|
|
94
|
+
super
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Returns a Resource for this relationship with a given source
|
|
99
|
+
#
|
|
100
|
+
# @param [Resource] source
|
|
101
|
+
# A Resource to scope the collection with
|
|
102
|
+
# @param [Query] other_query (optional)
|
|
103
|
+
# A Query to further scope the collection with
|
|
104
|
+
#
|
|
105
|
+
# @return [Resource]
|
|
106
|
+
# The resource scoped to the relationship, source and query
|
|
107
|
+
#
|
|
108
|
+
# @api private
|
|
109
|
+
def resource_for(source, other_query = nil)
|
|
110
|
+
query = query_for(source, other_query)
|
|
111
|
+
|
|
112
|
+
# If the target key is equal to the model key, we can use the
|
|
113
|
+
# Model#get so the IdentityMap is used
|
|
114
|
+
if target_key == target_model&.key
|
|
115
|
+
target = target_model&.get(*source_key.get!(source))
|
|
116
|
+
target if query.conditions.matches?(target)
|
|
117
|
+
else
|
|
118
|
+
target_model&.first(query)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Loads and returns association target (ex.: author) for given source resource
|
|
123
|
+
# (ex.: article)
|
|
124
|
+
#
|
|
125
|
+
# @param source [DataMapper::Resource]
|
|
126
|
+
# source object (ex.: instance of article)
|
|
127
|
+
# @param query [DataMapper::Query]
|
|
128
|
+
# Query options
|
|
129
|
+
#
|
|
130
|
+
# @api semipublic
|
|
131
|
+
def get(source, query = nil)
|
|
132
|
+
lazy_load(source)
|
|
133
|
+
|
|
134
|
+
if query
|
|
135
|
+
collection = get_collection(source)
|
|
136
|
+
collection&.first(query)
|
|
137
|
+
else
|
|
138
|
+
get!(source)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def get_collection(source)
|
|
143
|
+
target = get!(source)
|
|
144
|
+
target&.collection_for_self
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Sets value of association target (ex.: author) for given source resource
|
|
148
|
+
# (ex.: article)
|
|
149
|
+
#
|
|
150
|
+
# @param source [DataMapper::Resource]
|
|
151
|
+
# source object (ex.: instance of article)
|
|
152
|
+
#
|
|
153
|
+
# @param target [DataMapper::Resource]
|
|
154
|
+
# target object (ex.: instance of author)
|
|
155
|
+
#
|
|
156
|
+
# @api semipublic
|
|
157
|
+
def set(source, target)
|
|
158
|
+
target = typecast(target)
|
|
159
|
+
source_key.set(source, target_key.get(target))
|
|
160
|
+
set!(source, target)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# @api semipublic
|
|
164
|
+
def default_for(source)
|
|
165
|
+
typecast(super)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Loads association target and sets resulting value on
|
|
169
|
+
# given source resource
|
|
170
|
+
#
|
|
171
|
+
# @param [Resource] source
|
|
172
|
+
# the source resource for the association
|
|
173
|
+
#
|
|
174
|
+
# @return [undefined]
|
|
175
|
+
#
|
|
176
|
+
# @api private
|
|
177
|
+
def lazy_load(source)
|
|
178
|
+
source_key_different = source_key_different?(source)
|
|
179
|
+
|
|
180
|
+
return if (loaded?(source) && !source_key_different) || !valid_source?(source)
|
|
181
|
+
|
|
182
|
+
# SEL: load all related resources in the source collection
|
|
183
|
+
if source.saved? && ((collection = source.collection)&.size&.> 1)
|
|
184
|
+
eager_load(collection)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
if !loaded?(source) || (source_key_dirty?(source) && source.saved?)
|
|
188
|
+
set!(source, resource_for(source))
|
|
189
|
+
elsif loaded?(source) && source_key_different
|
|
190
|
+
source_key.set(source, target_key.get!(get!(source)))
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Initializes the relationship, always using max cardinality of 1.
|
|
195
|
+
#
|
|
196
|
+
# @api semipublic
|
|
197
|
+
private def initialize(name, source_model, target_model, options = {})
|
|
198
|
+
raise ":nullable is deprecated, use :required instead (#{caller[2]})" if options.key?(:nullable)
|
|
199
|
+
|
|
200
|
+
@required = options.fetch(:required, true)
|
|
201
|
+
@key = options.fetch(:key, false)
|
|
202
|
+
@unique = options.fetch(:unique, false)
|
|
203
|
+
@unique_index = options.fetch(:unique_index, @unique)
|
|
204
|
+
target_model ||= DataMapper::Inflector.camelize(name)
|
|
205
|
+
options = {min: @required ? 1 : 0, max: 1}.update(options)
|
|
206
|
+
super
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Sets the association targets in the resource
|
|
210
|
+
#
|
|
211
|
+
# @param [Resource] source
|
|
212
|
+
# the source to set
|
|
213
|
+
# @param [Array(Resource)] targets
|
|
214
|
+
# the target resource for the association
|
|
215
|
+
# @param [Query, Hash] _query
|
|
216
|
+
# not used
|
|
217
|
+
#
|
|
218
|
+
# @return [undefined]
|
|
219
|
+
#
|
|
220
|
+
# @api private
|
|
221
|
+
private def eager_load_targets(source, targets, _query)
|
|
222
|
+
set(source, targets.first)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# @api private
|
|
226
|
+
private def typecast(target)
|
|
227
|
+
if target.is_a?(Hash)
|
|
228
|
+
target_model&.new(target)
|
|
229
|
+
else
|
|
230
|
+
target
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Returns the inverse relationship class
|
|
235
|
+
#
|
|
236
|
+
# @api private
|
|
237
|
+
private def inverse_class
|
|
238
|
+
OneToMany::Relationship
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Returns the inverse relationship name
|
|
242
|
+
#
|
|
243
|
+
# @api private
|
|
244
|
+
private def inverse_name
|
|
245
|
+
name = super
|
|
246
|
+
return name if name
|
|
247
|
+
|
|
248
|
+
name = DataMapper::Inflector.demodulize(source_model&.name)
|
|
249
|
+
name = DataMapper::Inflector.underscore(name)
|
|
250
|
+
name = DataMapper::Inflector.pluralize(name)
|
|
251
|
+
name.to_sym
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# @api private
|
|
255
|
+
private def source_key_options(target_property)
|
|
256
|
+
DataMapper::Ext::Hash.only(target_property.options, :length, :precision, :scale).update(
|
|
257
|
+
index: name,
|
|
258
|
+
required: required?,
|
|
259
|
+
key: key?,
|
|
260
|
+
unique: @unique,
|
|
261
|
+
unique_index: @unique_index
|
|
262
|
+
)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# @api private
|
|
266
|
+
private def child_properties
|
|
267
|
+
source_key.map(&:name)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# @api private
|
|
271
|
+
private def source_key_different?(source)
|
|
272
|
+
source_key.get!(source) != target_key.get!(get!(source))
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# @api private
|
|
276
|
+
private def source_key_dirty?(source)
|
|
277
|
+
source.dirty_attributes.keys.any? { |property| source_key.include?(property) }
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Associations
|
|
3
|
+
module OneToMany # :nodoc:
|
|
4
|
+
class Relationship < Associations::Relationship
|
|
5
|
+
# @api semipublic
|
|
6
|
+
alias_method :target_repository_name, :child_repository_name
|
|
7
|
+
|
|
8
|
+
# @api semipublic
|
|
9
|
+
alias_method :target_model, :child_model
|
|
10
|
+
|
|
11
|
+
# @api semipublic
|
|
12
|
+
alias_method :source_repository_name, :parent_repository_name
|
|
13
|
+
|
|
14
|
+
# @api semipublic
|
|
15
|
+
alias_method :source_model, :parent_model
|
|
16
|
+
|
|
17
|
+
# @api semipublic
|
|
18
|
+
alias_method :source_key, :parent_key
|
|
19
|
+
|
|
20
|
+
# @api semipublic
|
|
21
|
+
def child_key
|
|
22
|
+
inverse.child_key
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @api semipublic
|
|
26
|
+
alias_method :target_key, :child_key
|
|
27
|
+
|
|
28
|
+
# Returns a Collection for this relationship with a given source
|
|
29
|
+
#
|
|
30
|
+
# @param [Resource] source
|
|
31
|
+
# A Resource to scope the collection with
|
|
32
|
+
# @param [Query] other_query (optional)
|
|
33
|
+
# A Query to further scope the collection with
|
|
34
|
+
#
|
|
35
|
+
# @return [Collection]
|
|
36
|
+
# The collection scoped to the relationship, source and query
|
|
37
|
+
#
|
|
38
|
+
# @api private
|
|
39
|
+
def collection_for(source, other_query = nil)
|
|
40
|
+
query = query_for(source, other_query)
|
|
41
|
+
|
|
42
|
+
collection = collection_class.new(query)
|
|
43
|
+
collection.relationship = self
|
|
44
|
+
collection.source = source
|
|
45
|
+
|
|
46
|
+
# make the collection empty if the source is new
|
|
47
|
+
collection.replace([]) if source.new?
|
|
48
|
+
|
|
49
|
+
collection
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Loads and returns association targets (ex.: articles) for given source resource
|
|
53
|
+
# (ex.: author)
|
|
54
|
+
#
|
|
55
|
+
# @api semipublic
|
|
56
|
+
def get(source, query = nil)
|
|
57
|
+
lazy_load(source)
|
|
58
|
+
collection = get_collection(source)
|
|
59
|
+
query ? collection.all(query) : collection
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @api private
|
|
63
|
+
def get_collection(source)
|
|
64
|
+
get!(source)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Sets value of association targets (ex.: paragraphs) for given source resource
|
|
68
|
+
# (ex.: article)
|
|
69
|
+
#
|
|
70
|
+
# @api semipublic
|
|
71
|
+
def set(source, targets)
|
|
72
|
+
lazy_load(source)
|
|
73
|
+
get!(source).replace(targets)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @api private
|
|
77
|
+
def set_collection(source, target)
|
|
78
|
+
set!(source, target)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Loads association targets and sets resulting value on
|
|
82
|
+
# given source resource
|
|
83
|
+
#
|
|
84
|
+
# @param [Resource] source
|
|
85
|
+
# the source resource for the association
|
|
86
|
+
#
|
|
87
|
+
# @return [undefined]
|
|
88
|
+
#
|
|
89
|
+
# @api private
|
|
90
|
+
def lazy_load(source)
|
|
91
|
+
return if loaded?(source)
|
|
92
|
+
|
|
93
|
+
# SEL: load all related resources in the source collection
|
|
94
|
+
if source.saved? && ((collection = source.collection)&.size&.> 1)
|
|
95
|
+
eager_load(collection)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
set!(source, collection_for(source)) unless loaded?(source)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# initialize the inverse "many to one" relationships explicitly before
|
|
102
|
+
# initializing other relationships. This makes sure that foreign key
|
|
103
|
+
# properties always appear in the order they were declared.
|
|
104
|
+
#
|
|
105
|
+
# @return [self]
|
|
106
|
+
#
|
|
107
|
+
# @api public
|
|
108
|
+
def finalize
|
|
109
|
+
child_model&.relationships&.each do |relationship|
|
|
110
|
+
# TODO: should this check #inverse?
|
|
111
|
+
# relationship.child_key if inverse?(relationship)
|
|
112
|
+
relationship.finalize if relationship.is_a?(Associations::ManyToOne::Relationship)
|
|
113
|
+
end
|
|
114
|
+
inverse.finalize
|
|
115
|
+
self
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @api semipublic
|
|
119
|
+
def default_for(source)
|
|
120
|
+
collection_for(source).replace(Array(super))
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# @api semipublic
|
|
124
|
+
private def initialize(name, target_model, source_model, options = {})
|
|
125
|
+
target_model ||= DataMapper::Inflector.camelize(DataMapper::Inflector.singularize(name.to_s))
|
|
126
|
+
options = {min: 0, max: source_model.n}.update(options)
|
|
127
|
+
super
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Sets the association targets in the resource
|
|
131
|
+
#
|
|
132
|
+
# @param [Resource] source
|
|
133
|
+
# the source to set
|
|
134
|
+
# @param [Array<Resource>] targets
|
|
135
|
+
# the target collection for the association
|
|
136
|
+
# @param [Query, Hash] query
|
|
137
|
+
# the query to scope the association with
|
|
138
|
+
#
|
|
139
|
+
# @return [undefined]
|
|
140
|
+
#
|
|
141
|
+
# @api private
|
|
142
|
+
private def eager_load_targets(source, targets, query)
|
|
143
|
+
set!(source, collection_for(source, query).set(targets))
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Returns collection class used by this type of
|
|
147
|
+
# relationship
|
|
148
|
+
#
|
|
149
|
+
# @api private
|
|
150
|
+
private def collection_class
|
|
151
|
+
OneToMany::Collection
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Returns the inverse relationship class
|
|
155
|
+
#
|
|
156
|
+
# @api private
|
|
157
|
+
private def inverse_class
|
|
158
|
+
ManyToOne::Relationship
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Returns the inverse relationship name
|
|
162
|
+
#
|
|
163
|
+
# @api private
|
|
164
|
+
private def inverse_name
|
|
165
|
+
super || DataMapper::Inflector.underscore(DataMapper::Inflector.demodulize(source_model&.name)).to_sym
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# @api private
|
|
169
|
+
private def child_properties
|
|
170
|
+
super || parent_key.map do |parent_property|
|
|
171
|
+
"#{inverse_name}_#{parent_property.name}".to_sym
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
class Collection < DataMapper::Collection
|
|
177
|
+
# @api private
|
|
178
|
+
attr_accessor :relationship
|
|
179
|
+
|
|
180
|
+
# @api private
|
|
181
|
+
attr_accessor :source
|
|
182
|
+
|
|
183
|
+
# @api public
|
|
184
|
+
def reload(*)
|
|
185
|
+
assert_source_saved 'The source must be saved before reloading the collection'
|
|
186
|
+
super
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Replace the Resources within the 1:m Collection
|
|
190
|
+
#
|
|
191
|
+
# @param [Enumerable] *
|
|
192
|
+
# List of other Resources to replace with
|
|
193
|
+
#
|
|
194
|
+
# @return [Collection]
|
|
195
|
+
# self
|
|
196
|
+
#
|
|
197
|
+
# @api public
|
|
198
|
+
def replace(*)
|
|
199
|
+
lazy_load # lazy load so that targets are always orphaned
|
|
200
|
+
super
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Removes all Resources from the 1:m Collection
|
|
204
|
+
#
|
|
205
|
+
# This should remove and orphan each Resource from the 1:m Collection.
|
|
206
|
+
#
|
|
207
|
+
# @return [Collection]
|
|
208
|
+
# self
|
|
209
|
+
#
|
|
210
|
+
# @api public
|
|
211
|
+
def clear
|
|
212
|
+
lazy_load # lazy load so that targets are always orphaned
|
|
213
|
+
super
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Update every Resource in the 1:m Collection
|
|
217
|
+
#
|
|
218
|
+
# @param [Hash] *
|
|
219
|
+
# attributes to update with
|
|
220
|
+
#
|
|
221
|
+
# @return [Boolean]
|
|
222
|
+
# true if the resources were successfully updated
|
|
223
|
+
#
|
|
224
|
+
# @api public
|
|
225
|
+
def update(*)
|
|
226
|
+
assert_source_saved 'The source must be saved before mass-updating the collection'
|
|
227
|
+
super
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Update every Resource in the 1:m Collection, bypassing validation
|
|
231
|
+
#
|
|
232
|
+
# @param [Hash] *
|
|
233
|
+
# attributes to update
|
|
234
|
+
#
|
|
235
|
+
# @return [Boolean]
|
|
236
|
+
# true if the resources were successfully updated
|
|
237
|
+
#
|
|
238
|
+
# @api public
|
|
239
|
+
def update!(*)
|
|
240
|
+
assert_source_saved 'The source must be saved before mass-updating the collection'
|
|
241
|
+
super
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Remove every Resource in the 1:m Collection from the repository
|
|
245
|
+
#
|
|
246
|
+
# This performs a deletion of each Resource in the Collection from
|
|
247
|
+
# the repository and clears the Collection.
|
|
248
|
+
#
|
|
249
|
+
# @return [Boolean]
|
|
250
|
+
# true if the resources were successfully destroyed
|
|
251
|
+
#
|
|
252
|
+
# @api public
|
|
253
|
+
def destroy
|
|
254
|
+
assert_source_saved 'The source must be saved before mass-deleting the collection'
|
|
255
|
+
super
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Remove every Resource in the 1:m Collection from the repository, bypassing validation
|
|
259
|
+
#
|
|
260
|
+
# This performs a deletion of each Resource in the Collection from
|
|
261
|
+
# the repository and clears the Collection while skipping
|
|
262
|
+
# validation.
|
|
263
|
+
#
|
|
264
|
+
# @return [Boolean]
|
|
265
|
+
# true if the resources were successfully destroyed
|
|
266
|
+
#
|
|
267
|
+
# @api public
|
|
268
|
+
def destroy!
|
|
269
|
+
assert_source_saved 'The source must be saved before mass-deleting the collection'
|
|
270
|
+
super
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# @api private
|
|
274
|
+
private def _create(*)
|
|
275
|
+
assert_source_saved 'The source must be saved before creating a resource'
|
|
276
|
+
super
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# @api private
|
|
280
|
+
private def _save(execute_hooks = true)
|
|
281
|
+
assert_source_saved 'The source must be saved before saving the collection'
|
|
282
|
+
|
|
283
|
+
# update removed resources to not reference the source
|
|
284
|
+
@removed.all? { |resource| resource.destroyed? || resource.__send__(execute_hooks ? :save : :save!) } && super
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# @api private
|
|
288
|
+
private def lazy_load
|
|
289
|
+
super if source.saved?
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# @api private
|
|
293
|
+
private def new_collection(query, resources = nil, &block)
|
|
294
|
+
collection = self.class.new(query, &block)
|
|
295
|
+
|
|
296
|
+
collection.relationship = relationship
|
|
297
|
+
collection.source = source
|
|
298
|
+
|
|
299
|
+
resources ||= filter(query) if loaded?
|
|
300
|
+
|
|
301
|
+
# set the resources after the relationship and source are set
|
|
302
|
+
collection.set(resources) if resources
|
|
303
|
+
|
|
304
|
+
collection
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# @api private
|
|
308
|
+
private def resource_added(resource)
|
|
309
|
+
resource = initialize_resource(resource)
|
|
310
|
+
inverse_set(resource, source)
|
|
311
|
+
super
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# @api private
|
|
315
|
+
private def resource_removed(resource)
|
|
316
|
+
inverse_set(resource, nil)
|
|
317
|
+
super
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# @api private
|
|
321
|
+
private def inverse_set(source, target)
|
|
322
|
+
relationship.inverse.set(source, target) unless source.readonly?
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# @api private
|
|
326
|
+
private def assert_source_saved(message)
|
|
327
|
+
raise UnsavedParentError, message unless source.saved?
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Associations
|
|
3
|
+
module OneToOne # :nodoc:
|
|
4
|
+
class Relationship < Associations::Relationship
|
|
5
|
+
%w(public protected private).map do |visibility|
|
|
6
|
+
methods = superclass.send("#{visibility}_instance_methods", false) |
|
|
7
|
+
DataMapper::Subject.send("#{visibility}_instance_methods", false)
|
|
8
|
+
|
|
9
|
+
methods.each do |method|
|
|
10
|
+
undef_method method.to_sym unless method.to_s == 'initialize'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Loads (if necessary) and returns association target
|
|
15
|
+
# for given source
|
|
16
|
+
#
|
|
17
|
+
# @api semipublic
|
|
18
|
+
def get(source, query = nil)
|
|
19
|
+
relationship.get(source, query).first
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Get the resource directly
|
|
23
|
+
#
|
|
24
|
+
# @api semipublic
|
|
25
|
+
def get!(source)
|
|
26
|
+
collection = relationship.get!(source)
|
|
27
|
+
collection&.first
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Sets and returns association target
|
|
31
|
+
# for given source
|
|
32
|
+
#
|
|
33
|
+
# @api semipublic
|
|
34
|
+
def set(source, target)
|
|
35
|
+
relationship.set(source, [target].compact).first
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Sets the resource directly
|
|
39
|
+
#
|
|
40
|
+
# @api semipublic
|
|
41
|
+
def set!(source, target)
|
|
42
|
+
set(source, target)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @api semipublic
|
|
46
|
+
def default_for(source)
|
|
47
|
+
relationship.default_for(source).first
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @api public
|
|
51
|
+
def kind_of?(klass)
|
|
52
|
+
super || relationship.is_a?(klass)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @api public
|
|
56
|
+
def instance_of?(klass)
|
|
57
|
+
super || relationship.instance_of?(klass)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @api public
|
|
61
|
+
def respond_to?(method, include_private = false)
|
|
62
|
+
super || relationship.respond_to?(method, include_private)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
attr_reader :relationship
|
|
66
|
+
|
|
67
|
+
# Initializes the relationship. Always assumes target model class is
|
|
68
|
+
# a camel cased association name.
|
|
69
|
+
#
|
|
70
|
+
# @api semipublic
|
|
71
|
+
private def initialize(name, target_model, source_model, options = {})
|
|
72
|
+
klass = options.key?(:through) ? ManyToMany::Relationship : OneToMany::Relationship
|
|
73
|
+
target_model ||= DataMapper::Inflector.camelize(name).freeze
|
|
74
|
+
@relationship = klass.new(name, target_model, source_model, options)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @api private
|
|
78
|
+
private def method_missing(method, *args, &block)
|
|
79
|
+
relationship.send(method, *args, &block)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|