ghost_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.
- data/.autotest +29 -0
- data/.document +5 -0
- data/.gitignore +35 -0
- data/.yardopts +1 -0
- data/Gemfile +65 -0
- data/LICENSE +20 -0
- data/README.md +269 -0
- data/Rakefile +4 -0
- data/dm-core.gemspec +24 -0
- data/lib/dm-core.rb +292 -0
- data/lib/dm-core/adapters.rb +222 -0
- data/lib/dm-core/adapters/abstract_adapter.rb +237 -0
- data/lib/dm-core/adapters/in_memory_adapter.rb +113 -0
- data/lib/dm-core/associations/many_to_many.rb +499 -0
- data/lib/dm-core/associations/many_to_one.rb +290 -0
- data/lib/dm-core/associations/one_to_many.rb +348 -0
- data/lib/dm-core/associations/one_to_one.rb +86 -0
- data/lib/dm-core/associations/relationship.rb +663 -0
- data/lib/dm-core/backwards.rb +13 -0
- data/lib/dm-core/collection.rb +1515 -0
- data/lib/dm-core/core_ext/kernel.rb +23 -0
- data/lib/dm-core/core_ext/pathname.rb +6 -0
- data/lib/dm-core/core_ext/symbol.rb +10 -0
- data/lib/dm-core/identity_map.rb +7 -0
- data/lib/dm-core/model.rb +874 -0
- data/lib/dm-core/model/hook.rb +103 -0
- data/lib/dm-core/model/is.rb +32 -0
- data/lib/dm-core/model/property.rb +249 -0
- data/lib/dm-core/model/relationship.rb +378 -0
- data/lib/dm-core/model/scope.rb +89 -0
- data/lib/dm-core/property.rb +866 -0
- data/lib/dm-core/property/binary.rb +21 -0
- data/lib/dm-core/property/boolean.rb +20 -0
- data/lib/dm-core/property/class.rb +17 -0
- data/lib/dm-core/property/date.rb +10 -0
- data/lib/dm-core/property/date_time.rb +10 -0
- data/lib/dm-core/property/decimal.rb +36 -0
- data/lib/dm-core/property/discriminator.rb +44 -0
- data/lib/dm-core/property/float.rb +16 -0
- data/lib/dm-core/property/integer.rb +22 -0
- data/lib/dm-core/property/invalid_value_error.rb +22 -0
- data/lib/dm-core/property/lookup.rb +27 -0
- data/lib/dm-core/property/numeric.rb +38 -0
- data/lib/dm-core/property/object.rb +34 -0
- data/lib/dm-core/property/serial.rb +14 -0
- data/lib/dm-core/property/string.rb +38 -0
- data/lib/dm-core/property/text.rb +9 -0
- data/lib/dm-core/property/time.rb +10 -0
- data/lib/dm-core/property_set.rb +177 -0
- data/lib/dm-core/query.rb +1366 -0
- data/lib/dm-core/query/conditions/comparison.rb +911 -0
- data/lib/dm-core/query/conditions/operation.rb +721 -0
- data/lib/dm-core/query/direction.rb +36 -0
- data/lib/dm-core/query/operator.rb +35 -0
- data/lib/dm-core/query/path.rb +114 -0
- data/lib/dm-core/query/sort.rb +39 -0
- data/lib/dm-core/relationship_set.rb +72 -0
- data/lib/dm-core/repository.rb +226 -0
- data/lib/dm-core/resource.rb +1214 -0
- data/lib/dm-core/resource/persistence_state.rb +75 -0
- data/lib/dm-core/resource/persistence_state/clean.rb +40 -0
- data/lib/dm-core/resource/persistence_state/deleted.rb +30 -0
- data/lib/dm-core/resource/persistence_state/dirty.rb +96 -0
- data/lib/dm-core/resource/persistence_state/immutable.rb +34 -0
- data/lib/dm-core/resource/persistence_state/persisted.rb +29 -0
- data/lib/dm-core/resource/persistence_state/transient.rb +80 -0
- data/lib/dm-core/spec/lib/adapter_helpers.rb +64 -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 +174 -0
- data/lib/dm-core/spec/shared/adapter_spec.rb +341 -0
- data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
- data/lib/dm-core/spec/shared/resource_spec.rb +1232 -0
- data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
- data/lib/dm-core/spec/shared/semipublic/property_spec.rb +176 -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 +405 -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 +90 -0
- data/lib/dm-core/support/ordered_set.rb +380 -0
- data/lib/dm-core/support/subject.rb +33 -0
- data/lib/dm-core/support/subject_set.rb +250 -0
- data/lib/dm-core/version.rb +3 -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 +68 -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 +76 -0
- data/spec/public/model/hook_spec.rb +246 -0
- data/spec/public/model/property_spec.rb +88 -0
- data/spec/public/model/relationship_spec.rb +1040 -0
- data/spec/public/model_spec.rb +462 -0
- data/spec/public/property/binary_spec.rb +41 -0
- data/spec/public/property/boolean_spec.rb +22 -0
- data/spec/public/property/class_spec.rb +28 -0
- data/spec/public/property/date_spec.rb +22 -0
- data/spec/public/property/date_time_spec.rb +22 -0
- data/spec/public/property/decimal_spec.rb +23 -0
- data/spec/public/property/discriminator_spec.rb +135 -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 +107 -0
- data/spec/public/property/serial_spec.rb +22 -0
- data/spec/public/property/string_spec.rb +22 -0
- data/spec/public/property/text_spec.rb +63 -0
- data/spec/public/property/time_spec.rb +22 -0
- data/spec/public/property_spec.rb +341 -0
- data/spec/public/resource_spec.rb +288 -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 +1667 -0
- data/spec/public/shared/finder_shared_spec.rb +1629 -0
- data/spec/rcov.opts +6 -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 +1501 -0
- data/spec/semipublic/query/conditions/operation_spec.rb +1294 -0
- data/spec/semipublic/query/path_spec.rb +471 -0
- data/spec/semipublic/query_spec.rb +3682 -0
- data/spec/semipublic/resource/state/clean_spec.rb +88 -0
- data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
- data/spec/semipublic/resource/state/dirty_spec.rb +162 -0
- data/spec/semipublic/resource/state/immutable_spec.rb +105 -0
- data/spec/semipublic/resource/state/transient_spec.rb +162 -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 +199 -0
- data/spec/semipublic/shared/resource_state_shared_spec.rb +79 -0
- data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +38 -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 +28 -0
- data/spec/unit/hook_spec.rb +1235 -0
- data/spec/unit/inflections_spec.rb +16 -0
- data/spec/unit/lazy_array_spec.rb +1949 -0
- data/spec/unit/mash_spec.rb +312 -0
- data/spec/unit/module_spec.rb +71 -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 +38 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +365 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Adapters
|
|
3
|
+
# Specific adapters extend this class and implement
|
|
4
|
+
# methods for creating, reading, updating and deleting records.
|
|
5
|
+
#
|
|
6
|
+
# Adapters may only implement method for reading or (less common case)
|
|
7
|
+
# writing. Read only adapter may be useful when one needs to work
|
|
8
|
+
# with legacy data that should not be changed or web services that
|
|
9
|
+
# only provide read access to data (from Wordnet and Medline to
|
|
10
|
+
# Atom and RSS syndication feeds)
|
|
11
|
+
#
|
|
12
|
+
# Note that in case of adapters to relational databases it makes
|
|
13
|
+
# sense to inherit from DataObjectsAdapter class.
|
|
14
|
+
class AbstractAdapter
|
|
15
|
+
include DataMapper::Assertions
|
|
16
|
+
extend DataMapper::Assertions
|
|
17
|
+
extend Equalizer
|
|
18
|
+
|
|
19
|
+
equalize :name, :options, :resource_naming_convention, :field_naming_convention
|
|
20
|
+
|
|
21
|
+
# @api semipublic
|
|
22
|
+
def self.descendants
|
|
23
|
+
@descendants ||= DescendantSet.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @api private
|
|
27
|
+
def self.inherited(descendant)
|
|
28
|
+
descendants << descendant
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Adapter name
|
|
33
|
+
#
|
|
34
|
+
# @example
|
|
35
|
+
# adapter.name # => :default
|
|
36
|
+
#
|
|
37
|
+
# Note that when you use
|
|
38
|
+
#
|
|
39
|
+
# DataMapper.setup(:default, 'postgres://postgres@localhost/dm_core_test')
|
|
40
|
+
#
|
|
41
|
+
# the adapter name is currently set to :default
|
|
42
|
+
#
|
|
43
|
+
# @return [Symbol]
|
|
44
|
+
# the adapter name
|
|
45
|
+
#
|
|
46
|
+
# @api semipublic
|
|
47
|
+
attr_reader :name
|
|
48
|
+
|
|
49
|
+
# Options with which adapter was set up
|
|
50
|
+
#
|
|
51
|
+
# @example
|
|
52
|
+
# adapter.options # => { :adapter => 'yaml', :path => '/tmp' }
|
|
53
|
+
#
|
|
54
|
+
# @return [Hash]
|
|
55
|
+
# adapter configuration options
|
|
56
|
+
#
|
|
57
|
+
# @api semipublic
|
|
58
|
+
attr_reader :options
|
|
59
|
+
|
|
60
|
+
# A callable object returning a naming convention for model storage
|
|
61
|
+
#
|
|
62
|
+
# @example
|
|
63
|
+
# adapter.resource_naming_convention # => Proc for model storage name
|
|
64
|
+
#
|
|
65
|
+
# @return [#call]
|
|
66
|
+
# object to return the naming convention for each model
|
|
67
|
+
#
|
|
68
|
+
# @api semipublic
|
|
69
|
+
attr_accessor :resource_naming_convention
|
|
70
|
+
|
|
71
|
+
# A callable object returning a naming convention for property fields
|
|
72
|
+
#
|
|
73
|
+
# @example
|
|
74
|
+
# adapter.field_naming_convention # => Proc for field name
|
|
75
|
+
#
|
|
76
|
+
# @return [#call]
|
|
77
|
+
# object to return the naming convention for each field
|
|
78
|
+
#
|
|
79
|
+
# @api semipublic
|
|
80
|
+
attr_accessor :field_naming_convention
|
|
81
|
+
|
|
82
|
+
# Persists one or many new resources
|
|
83
|
+
#
|
|
84
|
+
# @example
|
|
85
|
+
# adapter.create(collection) # => 1
|
|
86
|
+
#
|
|
87
|
+
# Adapters provide specific implementation of this method
|
|
88
|
+
#
|
|
89
|
+
# @param [Enumerable<Resource>] resources
|
|
90
|
+
# The list of resources (model instances) to create
|
|
91
|
+
#
|
|
92
|
+
# @return [Integer]
|
|
93
|
+
# The number of records that were actually saved into the data-store
|
|
94
|
+
#
|
|
95
|
+
# @api semipublic
|
|
96
|
+
def create(resources)
|
|
97
|
+
raise NotImplementedError, "#{self.class}#create not implemented"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Reads one or many resources from a datastore
|
|
101
|
+
#
|
|
102
|
+
# @example
|
|
103
|
+
# adapter.read(query) # => [ { 'name' => 'Dan Kubb' } ]
|
|
104
|
+
#
|
|
105
|
+
# Adapters provide specific implementation of this method
|
|
106
|
+
#
|
|
107
|
+
# @param [Query] query
|
|
108
|
+
# the query to match resources in the datastore
|
|
109
|
+
#
|
|
110
|
+
# @return [Enumerable<Hash>]
|
|
111
|
+
# an array of hashes to become resources
|
|
112
|
+
#
|
|
113
|
+
# @api semipublic
|
|
114
|
+
def read(query)
|
|
115
|
+
raise NotImplementedError, "#{self.class}#read not implemented"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Updates one or many existing resources
|
|
119
|
+
#
|
|
120
|
+
# @example
|
|
121
|
+
# adapter.update(attributes, collection) # => 1
|
|
122
|
+
#
|
|
123
|
+
# Adapters provide specific implementation of this method
|
|
124
|
+
#
|
|
125
|
+
# @param [Hash(Property => Object)] attributes
|
|
126
|
+
# hash of attribute values to set, keyed by Property
|
|
127
|
+
# @param [Collection] collection
|
|
128
|
+
# collection of records to be updated
|
|
129
|
+
#
|
|
130
|
+
# @return [Integer]
|
|
131
|
+
# the number of records updated
|
|
132
|
+
#
|
|
133
|
+
# @api semipublic
|
|
134
|
+
def update(attributes, collection)
|
|
135
|
+
raise NotImplementedError, "#{self.class}#update not implemented"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Deletes one or many existing resources
|
|
139
|
+
#
|
|
140
|
+
# @example
|
|
141
|
+
# adapter.delete(collection) # => 1
|
|
142
|
+
#
|
|
143
|
+
# Adapters provide specific implementation of this method
|
|
144
|
+
#
|
|
145
|
+
# @param [Collection] collection
|
|
146
|
+
# collection of records to be deleted
|
|
147
|
+
#
|
|
148
|
+
# @return [Integer]
|
|
149
|
+
# the number of records deleted
|
|
150
|
+
#
|
|
151
|
+
# @api semipublic
|
|
152
|
+
def delete(collection)
|
|
153
|
+
raise NotImplementedError, "#{self.class}#delete not implemented"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Create a Query object or subclass.
|
|
157
|
+
#
|
|
158
|
+
# Alter this method if you'd like to return an adapter specific Query subclass.
|
|
159
|
+
#
|
|
160
|
+
# @param [Repository] repository
|
|
161
|
+
# the Repository to retrieve results from
|
|
162
|
+
# @param [Model] model
|
|
163
|
+
# the Model to retrieve results from
|
|
164
|
+
# @param [Hash] options
|
|
165
|
+
# the conditions and scope
|
|
166
|
+
#
|
|
167
|
+
# @return [Query]
|
|
168
|
+
#
|
|
169
|
+
# @api semipublic
|
|
170
|
+
#--
|
|
171
|
+
# TODO: DataObjects::Connection.create_command style magic (Adapter)::Query?
|
|
172
|
+
def new_query(repository, model, options = {})
|
|
173
|
+
Query.new(repository, model, options)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
protected
|
|
177
|
+
|
|
178
|
+
# Set the serial value of the Resource
|
|
179
|
+
#
|
|
180
|
+
# @param [Resource] resource
|
|
181
|
+
# the resource to set the serial property in
|
|
182
|
+
# @param [Integer] next_id
|
|
183
|
+
# the identifier to set in the resource
|
|
184
|
+
#
|
|
185
|
+
# @return [undefined]
|
|
186
|
+
#
|
|
187
|
+
# @api semipublic
|
|
188
|
+
def initialize_serial(resource, next_id)
|
|
189
|
+
return unless serial = resource.model.serial(name)
|
|
190
|
+
return unless serial.get!(resource).nil?
|
|
191
|
+
serial.set!(resource, next_id)
|
|
192
|
+
|
|
193
|
+
# TODO: replace above with this, once
|
|
194
|
+
# specs can handle random, non-sequential ids
|
|
195
|
+
#serial.set!(resource, rand(2**32))
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Translate the attributes into a Hash with the field as the key
|
|
199
|
+
#
|
|
200
|
+
# @example
|
|
201
|
+
# attributes = { User.properties[:name] => 'Dan Kubb' }
|
|
202
|
+
# adapter.attributes_as_fields(attributes) # => { 'name' => 'Dan Kubb' }
|
|
203
|
+
#
|
|
204
|
+
# @param [Hash] attributes
|
|
205
|
+
# the attributes with the Property as the key
|
|
206
|
+
#
|
|
207
|
+
# @return [Hash]
|
|
208
|
+
# the attributes with the Property#field as the key
|
|
209
|
+
#
|
|
210
|
+
# @api semipublic
|
|
211
|
+
def attributes_as_fields(attributes)
|
|
212
|
+
Hash[ attributes.map { |property, value| [ property.field, property.dump(value) ] } ]
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
private
|
|
216
|
+
|
|
217
|
+
# Initialize an AbstractAdapter instance
|
|
218
|
+
#
|
|
219
|
+
# @param [Symbol] name
|
|
220
|
+
# the adapter repository name
|
|
221
|
+
# @param [Hash] options
|
|
222
|
+
# the adapter configuration options
|
|
223
|
+
#
|
|
224
|
+
# @return [undefined]
|
|
225
|
+
#
|
|
226
|
+
# @api semipublic
|
|
227
|
+
def initialize(name, options)
|
|
228
|
+
@name = name
|
|
229
|
+
@options = options.dup.freeze
|
|
230
|
+
@resource_naming_convention = NamingConventions::Resource::UnderscoredAndPluralized
|
|
231
|
+
@field_naming_convention = NamingConventions::Field::Underscored
|
|
232
|
+
end
|
|
233
|
+
end # class AbstractAdapter
|
|
234
|
+
|
|
235
|
+
const_added(:AbstractAdapter)
|
|
236
|
+
end # module Adapters
|
|
237
|
+
end # module DataMapper
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Adapters
|
|
3
|
+
# This is probably the simplest functional adapter possible. It simply
|
|
4
|
+
# stores and queries from a hash containing the model classes as keys,
|
|
5
|
+
# and an array of hashes. It is not persistent whatsoever; when the Ruby
|
|
6
|
+
# process finishes, everything that was stored it lost. However, it doesn't
|
|
7
|
+
# require any other external libraries, such as data_objects, so it is ideal
|
|
8
|
+
# for writing specs against. It also serves as an excellent example for
|
|
9
|
+
# budding adapter developers, so it is critical that it remains well documented
|
|
10
|
+
# and up to date.
|
|
11
|
+
class InMemoryAdapter < AbstractAdapter
|
|
12
|
+
# Used by DataMapper to put records into a data-store: "INSERT" in SQL-speak.
|
|
13
|
+
# It takes an array of the resources (model instances) to be saved. Resources
|
|
14
|
+
# each have a key that can be used to quickly look them up later without
|
|
15
|
+
# searching, if the adapter supports it.
|
|
16
|
+
#
|
|
17
|
+
# @param [Enumerable(Resource)] resources
|
|
18
|
+
# The set of resources (model instances)
|
|
19
|
+
#
|
|
20
|
+
# @api semipublic
|
|
21
|
+
def create(resources)
|
|
22
|
+
records = records_for(resources.first.model)
|
|
23
|
+
|
|
24
|
+
resources.each do |resource|
|
|
25
|
+
initialize_serial(resource, records.size.succ)
|
|
26
|
+
records << attributes_as_fields(resource.attributes(nil))
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Looks up one record or a collection of records from the data-store:
|
|
31
|
+
# "SELECT" in SQL.
|
|
32
|
+
#
|
|
33
|
+
# @param [Query] query
|
|
34
|
+
# The query to be used to seach for the resources
|
|
35
|
+
#
|
|
36
|
+
# @return [Array]
|
|
37
|
+
# An Array of Hashes containing the key-value pairs for
|
|
38
|
+
# each record
|
|
39
|
+
#
|
|
40
|
+
# @api semipublic
|
|
41
|
+
def read(query)
|
|
42
|
+
query.filter_records(records_for(query.model).dup)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Used by DataMapper to update the attributes on existing records in a
|
|
46
|
+
# data-store: "UPDATE" in SQL-speak. It takes a hash of the attributes
|
|
47
|
+
# to update with, as well as a collection object that specifies which resources
|
|
48
|
+
# should be updated.
|
|
49
|
+
#
|
|
50
|
+
# @param [Hash] attributes
|
|
51
|
+
# A set of key-value pairs of the attributes to update the resources with.
|
|
52
|
+
# @param [DataMapper::Collection] resources
|
|
53
|
+
# The collection of resources to update.
|
|
54
|
+
#
|
|
55
|
+
# @api semipublic
|
|
56
|
+
def update(attributes, collection)
|
|
57
|
+
attributes = attributes_as_fields(attributes)
|
|
58
|
+
read(collection.query).each { |record| record.update(attributes) }.size
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Destroys all the records matching the given query. "DELETE" in SQL.
|
|
62
|
+
#
|
|
63
|
+
# @param [DataMapper::Collection] resources
|
|
64
|
+
# The collection of resources to delete.
|
|
65
|
+
#
|
|
66
|
+
# @return [Integer]
|
|
67
|
+
# The number of records that were deleted.
|
|
68
|
+
#
|
|
69
|
+
# @api semipublic
|
|
70
|
+
def delete(collection)
|
|
71
|
+
records = records_for(collection.model)
|
|
72
|
+
records_to_delete = collection.query.filter_records(records.dup)
|
|
73
|
+
records.replace(records - records_to_delete)
|
|
74
|
+
records_to_delete.size
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# TODO consider proper automigrate functionality
|
|
78
|
+
def reset
|
|
79
|
+
@records = {}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
# Make a new instance of the adapter. The @records ivar is the 'data-store'
|
|
85
|
+
# for this adapter. It is not shared amongst multiple incarnations of this
|
|
86
|
+
# adapter, eg DataMapper.setup(:default, :adapter => :in_memory);
|
|
87
|
+
# DataMapper.setup(:alternate, :adapter => :in_memory) do not share the
|
|
88
|
+
# data-store between them.
|
|
89
|
+
#
|
|
90
|
+
# @param [String, Symbol] name
|
|
91
|
+
# The name of the Repository using this adapter.
|
|
92
|
+
# @param [String, Hash] uri_or_options
|
|
93
|
+
# The connection uri string, or a hash of options to set up
|
|
94
|
+
# the adapter
|
|
95
|
+
#
|
|
96
|
+
# @api semipublic
|
|
97
|
+
def initialize(name, options = {})
|
|
98
|
+
super
|
|
99
|
+
@records = {}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# All the records we're storing. This method will look them up by model name
|
|
103
|
+
#
|
|
104
|
+
# @api private
|
|
105
|
+
def records_for(model)
|
|
106
|
+
@records[model.storage_name(name)] ||= []
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end # class InMemoryAdapter
|
|
110
|
+
|
|
111
|
+
const_added(:InMemoryAdapter)
|
|
112
|
+
end # module Adapters
|
|
113
|
+
end # module DataMapper
|
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Associations
|
|
3
|
+
module ManyToMany #:nodoc:
|
|
4
|
+
class Relationship < Associations::OneToMany::Relationship
|
|
5
|
+
extend Chainable
|
|
6
|
+
|
|
7
|
+
OPTIONS = superclass::OPTIONS.dup << :through << :via
|
|
8
|
+
|
|
9
|
+
# Returns a set of keys that identify the target model
|
|
10
|
+
#
|
|
11
|
+
# @return [DataMapper::PropertySet]
|
|
12
|
+
# a set of properties that identify the target model
|
|
13
|
+
#
|
|
14
|
+
# @api semipublic
|
|
15
|
+
def child_key
|
|
16
|
+
return @child_key if defined?(@child_key)
|
|
17
|
+
|
|
18
|
+
repository_name = child_repository_name || parent_repository_name
|
|
19
|
+
properties = child_model.properties(repository_name)
|
|
20
|
+
|
|
21
|
+
@child_key = if @child_properties
|
|
22
|
+
child_key = properties.values_at(*@child_properties)
|
|
23
|
+
properties.class.new(child_key).freeze
|
|
24
|
+
else
|
|
25
|
+
properties.key
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @api semipublic
|
|
30
|
+
alias_method :target_key, :child_key
|
|
31
|
+
|
|
32
|
+
# Intermediate association for through model
|
|
33
|
+
# relationships
|
|
34
|
+
#
|
|
35
|
+
# Example: for :bugs association in
|
|
36
|
+
#
|
|
37
|
+
# class Software::Engineer
|
|
38
|
+
# include DataMapper::Resource
|
|
39
|
+
#
|
|
40
|
+
# has n, :missing_tests
|
|
41
|
+
# has n, :bugs, :through => :missing_tests
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# through is :missing_tests
|
|
45
|
+
#
|
|
46
|
+
# TODO: document a case when
|
|
47
|
+
# through option is a model and
|
|
48
|
+
# not an association name
|
|
49
|
+
#
|
|
50
|
+
# @api semipublic
|
|
51
|
+
def through
|
|
52
|
+
return @through if defined?(@through)
|
|
53
|
+
|
|
54
|
+
@through = options[:through]
|
|
55
|
+
|
|
56
|
+
if @through.kind_of?(Associations::Relationship)
|
|
57
|
+
return @through
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
model = source_model
|
|
61
|
+
repository_name = source_repository_name
|
|
62
|
+
relationships = model.relationships(repository_name)
|
|
63
|
+
name = through_relationship_name
|
|
64
|
+
|
|
65
|
+
@through = relationships[name] ||
|
|
66
|
+
DataMapper.repository(repository_name) do
|
|
67
|
+
model.has(min..max, name, through_model, one_to_many_options)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
@through.child_key
|
|
71
|
+
|
|
72
|
+
@through
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @api semipublic
|
|
76
|
+
def via
|
|
77
|
+
return @via if defined?(@via)
|
|
78
|
+
|
|
79
|
+
@via = options[:via]
|
|
80
|
+
|
|
81
|
+
if @via.kind_of?(Associations::Relationship)
|
|
82
|
+
return @via
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
name = self.name
|
|
86
|
+
through = self.through
|
|
87
|
+
repository_name = through.relative_target_repository_name
|
|
88
|
+
through_model = through.target_model
|
|
89
|
+
relationships = through_model.relationships(repository_name)
|
|
90
|
+
singular_name = DataMapper::Inflector.singularize(name.to_s).to_sym
|
|
91
|
+
|
|
92
|
+
@via = relationships[@via] ||
|
|
93
|
+
relationships[name] ||
|
|
94
|
+
relationships[singular_name]
|
|
95
|
+
|
|
96
|
+
@via ||= if anonymous_through_model?
|
|
97
|
+
DataMapper.repository(repository_name) do
|
|
98
|
+
through_model.belongs_to(singular_name, target_model, many_to_one_options)
|
|
99
|
+
end
|
|
100
|
+
else
|
|
101
|
+
raise UnknownRelationshipError, "No relationships named #{name} or #{singular_name} in #{through_model}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
@via.child_key
|
|
105
|
+
|
|
106
|
+
@via
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @api semipublic
|
|
110
|
+
def links
|
|
111
|
+
return @links if defined?(@links)
|
|
112
|
+
|
|
113
|
+
@links = []
|
|
114
|
+
links = [ through, via ]
|
|
115
|
+
|
|
116
|
+
while relationship = links.shift
|
|
117
|
+
if relationship.respond_to?(:links)
|
|
118
|
+
links.unshift(*relationship.links)
|
|
119
|
+
else
|
|
120
|
+
@links << relationship
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
@links.freeze
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Initialize the chain for "many to many" relationships
|
|
128
|
+
#
|
|
129
|
+
# @return [self]
|
|
130
|
+
#
|
|
131
|
+
# @api public
|
|
132
|
+
def finalize
|
|
133
|
+
through
|
|
134
|
+
via
|
|
135
|
+
self
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# @api private
|
|
139
|
+
def source_scope(source)
|
|
140
|
+
{ through.inverse => source }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @api private
|
|
144
|
+
def query
|
|
145
|
+
# TODO: consider making this a query_for method, so that ManyToMany::Relationship#query only
|
|
146
|
+
# returns the query supplied in the definition
|
|
147
|
+
@many_to_many_query ||= super.merge(:links => links).freeze
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Eager load the collection using the source as a base
|
|
151
|
+
#
|
|
152
|
+
# @param [Resource, Collection] source
|
|
153
|
+
# the source to query with
|
|
154
|
+
# @param [Query, Hash] other_query
|
|
155
|
+
# optional query to restrict the collection
|
|
156
|
+
#
|
|
157
|
+
# @return [ManyToMany::Collection]
|
|
158
|
+
# the loaded collection for the source
|
|
159
|
+
#
|
|
160
|
+
# @api private
|
|
161
|
+
def eager_load(source, other_query = nil)
|
|
162
|
+
# FIXME: enable SEL for m:m relationships
|
|
163
|
+
source.model.all(query_for(source, other_query))
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
# @api private
|
|
169
|
+
def through_model
|
|
170
|
+
namespace, name = through_model_namespace_name
|
|
171
|
+
|
|
172
|
+
if namespace.const_defined?(name)
|
|
173
|
+
namespace.const_get(name)
|
|
174
|
+
else
|
|
175
|
+
Model.new(name, namespace) do
|
|
176
|
+
# all properties added to the anonymous through model are keys
|
|
177
|
+
def property(name, type, options = {})
|
|
178
|
+
options[:key] = true
|
|
179
|
+
options.delete(:index)
|
|
180
|
+
super
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# @api private
|
|
187
|
+
def through_model_namespace_name
|
|
188
|
+
target_parts = target_model.base_model.name.split('::')
|
|
189
|
+
source_parts = source_model.base_model.name.split('::')
|
|
190
|
+
|
|
191
|
+
name = [ target_parts.pop, source_parts.pop ].sort.join
|
|
192
|
+
|
|
193
|
+
namespace = Object
|
|
194
|
+
|
|
195
|
+
# find the common namespace between the target_model and source_model
|
|
196
|
+
target_parts.zip(source_parts) do |target_part, source_part|
|
|
197
|
+
break if target_part != source_part
|
|
198
|
+
namespace = namespace.const_get(target_part)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
return namespace, name
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# @api private
|
|
205
|
+
def through_relationship_name
|
|
206
|
+
if anonymous_through_model?
|
|
207
|
+
namespace = through_model_namespace_name.first
|
|
208
|
+
relationship_name = DataMapper::Inflector.underscore(through_model.name.sub(/\A#{namespace.name}::/, '')).tr('/', '_')
|
|
209
|
+
DataMapper::Inflector.pluralize(relationship_name).to_sym
|
|
210
|
+
else
|
|
211
|
+
options[:through]
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Check if the :through association uses an anonymous model
|
|
216
|
+
#
|
|
217
|
+
# An anonymous model means that DataMapper creates the model
|
|
218
|
+
# in-memory, and sets the relationships to join the source
|
|
219
|
+
# and the target model.
|
|
220
|
+
#
|
|
221
|
+
# @return [Boolean]
|
|
222
|
+
# true if the through model is anonymous
|
|
223
|
+
#
|
|
224
|
+
# @api private
|
|
225
|
+
def anonymous_through_model?
|
|
226
|
+
options[:through] == Resource
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# @api private
|
|
230
|
+
def nearest_relationship
|
|
231
|
+
return @nearest_relationship if defined?(@nearest_relationship)
|
|
232
|
+
|
|
233
|
+
nearest_relationship = self
|
|
234
|
+
|
|
235
|
+
while nearest_relationship.respond_to?(:through)
|
|
236
|
+
nearest_relationship = nearest_relationship.through
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
@nearest_relationship = nearest_relationship
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# @api private
|
|
243
|
+
def valid_target?(target)
|
|
244
|
+
relationship = via
|
|
245
|
+
source_key = relationship.source_key
|
|
246
|
+
target_key = relationship.target_key
|
|
247
|
+
|
|
248
|
+
target.kind_of?(target_model) &&
|
|
249
|
+
source_key.valid?(target_key.get(target))
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# @api private
|
|
253
|
+
def valid_source?(source)
|
|
254
|
+
relationship = nearest_relationship
|
|
255
|
+
source_key = relationship.source_key
|
|
256
|
+
target_key = relationship.target_key
|
|
257
|
+
|
|
258
|
+
source.kind_of?(source_model) &&
|
|
259
|
+
target_key.valid?(source_key.get(source))
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
chainable do
|
|
263
|
+
# @api semipublic
|
|
264
|
+
def many_to_one_options
|
|
265
|
+
{ :parent_key => target_key.map { |property| property.name } }
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# @api semipublic
|
|
269
|
+
def one_to_many_options
|
|
270
|
+
{ :parent_key => source_key.map { |property| property.name } }
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Returns the inverse relationship class
|
|
275
|
+
#
|
|
276
|
+
# @api private
|
|
277
|
+
def inverse_class
|
|
278
|
+
self.class
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# @api private
|
|
282
|
+
def invert
|
|
283
|
+
inverse_class.new(inverse_name, parent_model, child_model, inverted_options)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# @api private
|
|
287
|
+
def inverted_options
|
|
288
|
+
links = self.links.dup
|
|
289
|
+
through = links.pop.inverse
|
|
290
|
+
|
|
291
|
+
links.reverse_each do |relationship|
|
|
292
|
+
inverse = relationship.inverse
|
|
293
|
+
|
|
294
|
+
through = self.class.new(
|
|
295
|
+
inverse.name,
|
|
296
|
+
inverse.child_model,
|
|
297
|
+
inverse.parent_model,
|
|
298
|
+
inverse.options.merge(:through => through)
|
|
299
|
+
)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
options = self.options
|
|
303
|
+
|
|
304
|
+
DataMapper::Ext::Hash.only(options, *OPTIONS - [ :min, :max ]).update(
|
|
305
|
+
:through => through,
|
|
306
|
+
:child_key => options[:parent_key],
|
|
307
|
+
:parent_key => options[:child_key],
|
|
308
|
+
:inverse => self
|
|
309
|
+
)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Returns collection class used by this type of
|
|
313
|
+
# relationship
|
|
314
|
+
#
|
|
315
|
+
# @api private
|
|
316
|
+
def collection_class
|
|
317
|
+
ManyToMany::Collection
|
|
318
|
+
end
|
|
319
|
+
end # class Relationship
|
|
320
|
+
|
|
321
|
+
class Collection < Associations::OneToMany::Collection
|
|
322
|
+
# Remove every Resource in the m:m Collection from the repository
|
|
323
|
+
#
|
|
324
|
+
# This performs a deletion of each Resource in the Collection from
|
|
325
|
+
# the repository and clears the Collection.
|
|
326
|
+
#
|
|
327
|
+
# @return [Boolean]
|
|
328
|
+
# true if the resources were successfully destroyed
|
|
329
|
+
#
|
|
330
|
+
# @api public
|
|
331
|
+
def destroy
|
|
332
|
+
assert_source_saved 'The source must be saved before mass-deleting the collection'
|
|
333
|
+
|
|
334
|
+
# make sure the records are loaded so they can be found when
|
|
335
|
+
# the intermediaries are removed
|
|
336
|
+
lazy_load
|
|
337
|
+
|
|
338
|
+
unless intermediaries.all(via => self).destroy
|
|
339
|
+
return false
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
super
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Remove every Resource in the m:m Collection from the repository, bypassing validation
|
|
346
|
+
#
|
|
347
|
+
# This performs a deletion of each Resource in the Collection from
|
|
348
|
+
# the repository and clears the Collection while skipping
|
|
349
|
+
# validation.
|
|
350
|
+
#
|
|
351
|
+
# @return [Boolean]
|
|
352
|
+
# true if the resources were successfully destroyed
|
|
353
|
+
#
|
|
354
|
+
# @api public
|
|
355
|
+
def destroy!
|
|
356
|
+
assert_source_saved 'The source must be saved before mass-deleting the collection'
|
|
357
|
+
|
|
358
|
+
model = self.model
|
|
359
|
+
key = model.key(repository_name)
|
|
360
|
+
conditions = Query.target_conditions(self, key, key)
|
|
361
|
+
|
|
362
|
+
unless intermediaries.all(via => self).destroy!
|
|
363
|
+
return false
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
unless model.all(:repository => repository, :conditions => conditions).destroy!
|
|
367
|
+
return false
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
each do |resource|
|
|
371
|
+
resource.persistence_state = Resource::PersistenceState::Immutable.new(resource)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
clear
|
|
375
|
+
|
|
376
|
+
true
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# Return the intermediaries linking the source to the targets
|
|
380
|
+
#
|
|
381
|
+
# @return [Collection]
|
|
382
|
+
# the intermediary collection
|
|
383
|
+
#
|
|
384
|
+
# @api public
|
|
385
|
+
def intermediaries
|
|
386
|
+
through = self.through
|
|
387
|
+
source = self.source
|
|
388
|
+
|
|
389
|
+
@intermediaries ||= if through.loaded?(source)
|
|
390
|
+
through.get_collection(source)
|
|
391
|
+
else
|
|
392
|
+
reset_intermediaries
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
protected
|
|
397
|
+
|
|
398
|
+
# Map the resources in the collection to the intermediaries
|
|
399
|
+
#
|
|
400
|
+
# @return [Hash]
|
|
401
|
+
# the map of resources to their intermediaries
|
|
402
|
+
#
|
|
403
|
+
# @api private
|
|
404
|
+
def intermediary_for
|
|
405
|
+
@intermediary_for ||= {}
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# @api private
|
|
409
|
+
def through
|
|
410
|
+
relationship.through
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# @api private
|
|
414
|
+
def via
|
|
415
|
+
relationship.via
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
private
|
|
419
|
+
|
|
420
|
+
# @api private
|
|
421
|
+
def _create(attributes, execute_hooks = true)
|
|
422
|
+
via = self.via
|
|
423
|
+
if via.respond_to?(:resource_for)
|
|
424
|
+
resource = super
|
|
425
|
+
if create_intermediary(execute_hooks, resource)
|
|
426
|
+
resource
|
|
427
|
+
end
|
|
428
|
+
else
|
|
429
|
+
if intermediary = create_intermediary(execute_hooks)
|
|
430
|
+
super(attributes.merge(via.inverse => intermediary), execute_hooks)
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# @api private
|
|
436
|
+
def _save(execute_hooks = true)
|
|
437
|
+
via = self.via
|
|
438
|
+
|
|
439
|
+
if @removed.any?
|
|
440
|
+
# delete only intermediaries linked to the removed targets
|
|
441
|
+
return false unless intermediaries.all(via => @removed).send(execute_hooks ? :destroy : :destroy!)
|
|
442
|
+
|
|
443
|
+
# reset the intermediaries so that it reflects the current state of the datastore
|
|
444
|
+
reset_intermediaries
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
loaded_entries = self.loaded_entries
|
|
448
|
+
|
|
449
|
+
if via.respond_to?(:resource_for)
|
|
450
|
+
super
|
|
451
|
+
loaded_entries.all? { |resource| create_intermediary(execute_hooks, resource) }
|
|
452
|
+
else
|
|
453
|
+
if loaded_entries.any? && (intermediary = create_intermediary(execute_hooks))
|
|
454
|
+
inverse = via.inverse
|
|
455
|
+
loaded_entries.each { |resource| inverse.set(resource, intermediary) }
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
super
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
# @api private
|
|
463
|
+
def create_intermediary(execute_hooks, resource = nil)
|
|
464
|
+
intermediary_for = self.intermediary_for
|
|
465
|
+
|
|
466
|
+
intermediary_resource = intermediary_for[resource]
|
|
467
|
+
return intermediary_resource if intermediary_resource
|
|
468
|
+
|
|
469
|
+
intermediaries = self.intermediaries
|
|
470
|
+
method = execute_hooks ? :save : :save!
|
|
471
|
+
|
|
472
|
+
return unless intermediaries.send(method)
|
|
473
|
+
|
|
474
|
+
attributes = {}
|
|
475
|
+
attributes[via] = resource if resource
|
|
476
|
+
|
|
477
|
+
intermediary = intermediaries.first_or_new(attributes)
|
|
478
|
+
return unless intermediary.__send__(method)
|
|
479
|
+
|
|
480
|
+
# map the resource, even if it is nil, to the intermediary
|
|
481
|
+
intermediary_for[resource] = intermediary
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
# @api private
|
|
485
|
+
def reset_intermediaries
|
|
486
|
+
through = self.through
|
|
487
|
+
source = self.source
|
|
488
|
+
|
|
489
|
+
through.set_collection(source, through.collection_for(source))
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# @api private
|
|
493
|
+
def inverse_set(*)
|
|
494
|
+
# do nothing
|
|
495
|
+
end
|
|
496
|
+
end # class Collection
|
|
497
|
+
end # module ManyToMany
|
|
498
|
+
end # module Associations
|
|
499
|
+
end # module DataMapper
|