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,103 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Model
|
|
3
|
+
module Hook
|
|
4
|
+
Model.append_inclusions self
|
|
5
|
+
|
|
6
|
+
extend Chainable
|
|
7
|
+
|
|
8
|
+
def self.included(model)
|
|
9
|
+
model.send(:include, DataMapper::Hook)
|
|
10
|
+
model.extend Methods
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module Methods
|
|
15
|
+
def inherited(model)
|
|
16
|
+
copy_hooks(model)
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @api public
|
|
21
|
+
def before(target_method, method_sym = nil, &block)
|
|
22
|
+
setup_hook(:before, target_method, method_sym, block) { super }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @api public
|
|
26
|
+
def after(target_method, method_sym = nil, &block)
|
|
27
|
+
setup_hook(:after, target_method, method_sym, block) { super }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @api private
|
|
31
|
+
def hooks
|
|
32
|
+
@hooks ||= {
|
|
33
|
+
:save => { :before => [], :after => [] },
|
|
34
|
+
:create => { :before => [], :after => [] },
|
|
35
|
+
:update => { :before => [], :after => [] },
|
|
36
|
+
:destroy => { :before => [], :after => [] },
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def setup_hook(type, name, method, proc)
|
|
43
|
+
types = hooks[name]
|
|
44
|
+
if types && types[type]
|
|
45
|
+
types[type] << if proc
|
|
46
|
+
ProcCommand.new(proc)
|
|
47
|
+
else
|
|
48
|
+
MethodCommand.new(self, method)
|
|
49
|
+
end
|
|
50
|
+
else
|
|
51
|
+
yield
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# deep copy hooks from the parent model
|
|
56
|
+
def copy_hooks(model)
|
|
57
|
+
hooks = Hash.new do |hooks, name|
|
|
58
|
+
hooks[name] = Hash.new do |types, type|
|
|
59
|
+
if self.hooks[name]
|
|
60
|
+
types[type] = self.hooks[name][type].map do |command|
|
|
61
|
+
command.copy(model)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
model.instance_variable_set(:@hooks, hooks)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class ProcCommand
|
|
73
|
+
def initialize(proc)
|
|
74
|
+
@proc = proc.to_proc
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def call(resource)
|
|
78
|
+
resource.instance_eval(&@proc)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def copy(model)
|
|
82
|
+
self
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
class MethodCommand
|
|
87
|
+
def initialize(model, method)
|
|
88
|
+
@model, @method = model, method.to_sym
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def call(resource)
|
|
92
|
+
resource.__send__(@method)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def copy(model)
|
|
96
|
+
self.class.new(model, @method)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
end # module Hook
|
|
102
|
+
end # module Model
|
|
103
|
+
end # module DataMapper
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Model
|
|
3
|
+
# Module that provides a common way for plugin authors
|
|
4
|
+
# to implement "is ... " traits (object behaviors that can be shared)
|
|
5
|
+
module Is
|
|
6
|
+
# A common interface to activate plugins for a resource. For instance:
|
|
7
|
+
#
|
|
8
|
+
# class Widget
|
|
9
|
+
# include DataMapper::Resource
|
|
10
|
+
#
|
|
11
|
+
# is :list
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# adds list item behavior to the model. Plugin that wants to conform
|
|
15
|
+
# to "is API" of DataMapper must supply is_+behavior name+ method,
|
|
16
|
+
# for example above it would be is_list.
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
def is(plugin, *args, &block)
|
|
20
|
+
generator_method = "is_#{plugin}".to_sym
|
|
21
|
+
|
|
22
|
+
if respond_to?(generator_method)
|
|
23
|
+
send(generator_method, *args, &block)
|
|
24
|
+
else
|
|
25
|
+
raise PluginNotFoundError, "could not find plugin named #{plugin}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end # module Is
|
|
29
|
+
|
|
30
|
+
include Is
|
|
31
|
+
end # module Model
|
|
32
|
+
end # module DataMapper
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# TODO: update Model#respond_to? to return true if method_method missing
|
|
2
|
+
# would handle the message
|
|
3
|
+
|
|
4
|
+
module DataMapper
|
|
5
|
+
module Model
|
|
6
|
+
module Property
|
|
7
|
+
Model.append_extensions self, DataMapper::Property::Lookup
|
|
8
|
+
|
|
9
|
+
def self.extended(model)
|
|
10
|
+
model.instance_variable_set(:@properties, {})
|
|
11
|
+
model.instance_variable_set(:@field_naming_conventions, {})
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def inherited(model)
|
|
16
|
+
model.instance_variable_set(:@properties, {})
|
|
17
|
+
model.instance_variable_set(:@field_naming_conventions, @field_naming_conventions.dup)
|
|
18
|
+
|
|
19
|
+
@properties.each do |repository_name, properties|
|
|
20
|
+
model_properties = model.properties(repository_name)
|
|
21
|
+
properties.each { |property| model_properties << property }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Defines a Property on the Resource
|
|
28
|
+
#
|
|
29
|
+
# @param [Symbol] name
|
|
30
|
+
# the name for which to call this property
|
|
31
|
+
# @param [Class] type
|
|
32
|
+
# the ruby type to define this property as
|
|
33
|
+
# @param [Hash(Symbol => String)] options
|
|
34
|
+
# a hash of available options
|
|
35
|
+
#
|
|
36
|
+
# @return [Property]
|
|
37
|
+
# the created Property
|
|
38
|
+
#
|
|
39
|
+
# @see Property
|
|
40
|
+
#
|
|
41
|
+
# @api public
|
|
42
|
+
def property(name, type, options = {})
|
|
43
|
+
# if the type can be found within Property then
|
|
44
|
+
# use that class rather than the primitive
|
|
45
|
+
klass = DataMapper::Property.determine_class(type)
|
|
46
|
+
|
|
47
|
+
unless klass
|
|
48
|
+
raise ArgumentError, "+type+ was #{type.inspect}, which is not a supported type"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
property = klass.new(self, name, options)
|
|
52
|
+
|
|
53
|
+
repository_name = self.repository_name
|
|
54
|
+
properties = properties(repository_name)
|
|
55
|
+
|
|
56
|
+
properties << property
|
|
57
|
+
|
|
58
|
+
# Add property to the other mappings as well if this is for the default
|
|
59
|
+
# repository.
|
|
60
|
+
|
|
61
|
+
if repository_name == default_repository_name
|
|
62
|
+
other_repository_properties = DataMapper::Ext::Hash.except(@properties, default_repository_name)
|
|
63
|
+
|
|
64
|
+
other_repository_properties.each do |other_repository_name, properties|
|
|
65
|
+
next if properties.named?(name)
|
|
66
|
+
|
|
67
|
+
# make sure the property is created within the correct repository scope
|
|
68
|
+
DataMapper.repository(other_repository_name) do
|
|
69
|
+
properties << klass.new(self, name, options)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Add the property to the lazy_loads set for this resources repository
|
|
75
|
+
# only.
|
|
76
|
+
# TODO Is this right or should we add the lazy contexts to all
|
|
77
|
+
# repositories?
|
|
78
|
+
if property.lazy?
|
|
79
|
+
context = options.fetch(:lazy, :default)
|
|
80
|
+
context = :default if context == true
|
|
81
|
+
|
|
82
|
+
Array(context).each do |context|
|
|
83
|
+
properties.lazy_context(context) << property
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# add the property to the child classes only if the property was
|
|
88
|
+
# added after the child classes' properties have been copied from
|
|
89
|
+
# the parent
|
|
90
|
+
descendants.each do |descendant|
|
|
91
|
+
descendant.properties(repository_name) << property
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
create_reader_for(property)
|
|
95
|
+
create_writer_for(property)
|
|
96
|
+
|
|
97
|
+
# FIXME: explicit return needed for YARD to parse this properly
|
|
98
|
+
return property
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Gets a list of all properties that have been defined on this Model in
|
|
102
|
+
# the requested repository
|
|
103
|
+
#
|
|
104
|
+
# @param [Symbol, String] repository_name
|
|
105
|
+
# The name of the repository to use. Uses the default Repository
|
|
106
|
+
# if none is specified.
|
|
107
|
+
#
|
|
108
|
+
# @return [PropertySet]
|
|
109
|
+
# A list of Properties defined on this Model in the given Repository
|
|
110
|
+
#
|
|
111
|
+
# @api public
|
|
112
|
+
def properties(repository_name = default_repository_name)
|
|
113
|
+
# TODO: create PropertySet#copy that will copy the properties, but assign the
|
|
114
|
+
# new Relationship objects to a supplied repository and model. dup does not really
|
|
115
|
+
# do what is needed
|
|
116
|
+
repository_name = repository_name.to_sym
|
|
117
|
+
|
|
118
|
+
default_repository_name = self.default_repository_name
|
|
119
|
+
|
|
120
|
+
@properties[repository_name] ||= if repository_name == default_repository_name
|
|
121
|
+
PropertySet.new
|
|
122
|
+
else
|
|
123
|
+
properties(default_repository_name).dup
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Gets the list of key fields for this Model in +repository_name+
|
|
128
|
+
#
|
|
129
|
+
# @param [String] repository_name
|
|
130
|
+
# The name of the Repository for which the key is to be reported
|
|
131
|
+
#
|
|
132
|
+
# @return [Array]
|
|
133
|
+
# The list of key fields for this Model in +repository_name+
|
|
134
|
+
#
|
|
135
|
+
# @api public
|
|
136
|
+
def key(repository_name = default_repository_name)
|
|
137
|
+
properties(repository_name).key
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @api public
|
|
141
|
+
def serial(repository_name = default_repository_name)
|
|
142
|
+
key(repository_name).detect { |property| property.serial? }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Gets the field naming conventions for this resource in the given Repository
|
|
146
|
+
#
|
|
147
|
+
# @param [String, Symbol] repository_name
|
|
148
|
+
# the name of the Repository for which the field naming convention
|
|
149
|
+
# will be retrieved
|
|
150
|
+
#
|
|
151
|
+
# @return [#call]
|
|
152
|
+
# The naming convention for the given Repository
|
|
153
|
+
#
|
|
154
|
+
# @api public
|
|
155
|
+
def field_naming_convention(repository_name = default_storage_name)
|
|
156
|
+
@field_naming_conventions[repository_name] ||= repository(repository_name).adapter.field_naming_convention
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @api private
|
|
160
|
+
def properties_with_subclasses(repository_name = default_repository_name)
|
|
161
|
+
properties = properties(repository_name).dup
|
|
162
|
+
|
|
163
|
+
descendants.each do |model|
|
|
164
|
+
model.properties(repository_name).each do |property|
|
|
165
|
+
properties << property
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
properties
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# @api private
|
|
173
|
+
def key_conditions(repository, key)
|
|
174
|
+
Hash[ self.key(repository.name).zip(key.nil? ? [] : key) ]
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
private
|
|
178
|
+
|
|
179
|
+
# Defines the anonymous module that is used to add properties.
|
|
180
|
+
# Using a single module here prevents having a very large number
|
|
181
|
+
# of anonymous modules, where each property has their own module.
|
|
182
|
+
# @api private
|
|
183
|
+
def property_module
|
|
184
|
+
@property_module ||= begin
|
|
185
|
+
mod = Module.new
|
|
186
|
+
class_eval do
|
|
187
|
+
include mod
|
|
188
|
+
end
|
|
189
|
+
mod
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# defines the reader method for the property
|
|
194
|
+
#
|
|
195
|
+
# @api private
|
|
196
|
+
def create_reader_for(property)
|
|
197
|
+
name = property.name.to_s
|
|
198
|
+
reader_visibility = property.reader_visibility
|
|
199
|
+
instance_variable_name = property.instance_variable_name
|
|
200
|
+
property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
201
|
+
#{reader_visibility}
|
|
202
|
+
def #{name}
|
|
203
|
+
return #{instance_variable_name} if defined?(#{instance_variable_name})
|
|
204
|
+
property = properties[#{name.inspect}]
|
|
205
|
+
#{instance_variable_name} = property ? persistence_state.get(property) : nil
|
|
206
|
+
end
|
|
207
|
+
RUBY
|
|
208
|
+
|
|
209
|
+
boolean_reader_name = "#{name}?"
|
|
210
|
+
|
|
211
|
+
if property.kind_of?(DataMapper::Property::Boolean)
|
|
212
|
+
property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
213
|
+
#{reader_visibility}
|
|
214
|
+
def #{boolean_reader_name}
|
|
215
|
+
#{name}
|
|
216
|
+
end
|
|
217
|
+
RUBY
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# defines the setter for the property
|
|
222
|
+
#
|
|
223
|
+
# @api private
|
|
224
|
+
def create_writer_for(property)
|
|
225
|
+
name = property.name
|
|
226
|
+
writer_visibility = property.writer_visibility
|
|
227
|
+
|
|
228
|
+
writer_name = "#{name}="
|
|
229
|
+
property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
230
|
+
#{writer_visibility}
|
|
231
|
+
def #{writer_name}(value)
|
|
232
|
+
property = properties[#{name.inspect}]
|
|
233
|
+
self.persistence_state = persistence_state.set(property, value)
|
|
234
|
+
persistence_state.get(property)
|
|
235
|
+
end
|
|
236
|
+
RUBY
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# @api public
|
|
240
|
+
def method_missing(method, *args, &block)
|
|
241
|
+
if property = properties(repository_name)[method]
|
|
242
|
+
return property
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
super
|
|
246
|
+
end
|
|
247
|
+
end # module Property
|
|
248
|
+
end # module Model
|
|
249
|
+
end # module DataMapper
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# TODO: update Model#respond_to? to return true if method_method missing
|
|
2
|
+
# would handle the message
|
|
3
|
+
|
|
4
|
+
module DataMapper
|
|
5
|
+
module Model
|
|
6
|
+
module Relationship
|
|
7
|
+
Model.append_extensions self
|
|
8
|
+
|
|
9
|
+
include DataMapper::Assertions
|
|
10
|
+
|
|
11
|
+
# Initializes relationships hash for extended model
|
|
12
|
+
# class.
|
|
13
|
+
#
|
|
14
|
+
# When model calls has n, has 1 or belongs_to, relationships
|
|
15
|
+
# are stored in that hash: keys are repository names and
|
|
16
|
+
# values are relationship sets.
|
|
17
|
+
#
|
|
18
|
+
# @api private
|
|
19
|
+
def self.extended(model)
|
|
20
|
+
model.instance_variable_set(:@relationships, {})
|
|
21
|
+
super
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# When DataMapper model is inherited, relationships
|
|
25
|
+
# of parent are duplicated and copied to subclass model
|
|
26
|
+
#
|
|
27
|
+
# @api private
|
|
28
|
+
def inherited(model)
|
|
29
|
+
model.instance_variable_set(:@relationships, {})
|
|
30
|
+
|
|
31
|
+
@relationships.each do |repository_name, relationships|
|
|
32
|
+
model_relationships = model.relationships(repository_name)
|
|
33
|
+
relationships.each { |relationship| model_relationships << relationship }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns copy of relationships set in given repository.
|
|
40
|
+
#
|
|
41
|
+
# @param [Symbol] repository_name
|
|
42
|
+
# Name of the repository for which relationships set is returned
|
|
43
|
+
# @return [RelationshipSet] relationships set for given repository
|
|
44
|
+
#
|
|
45
|
+
# @api semipublic
|
|
46
|
+
def relationships(repository_name = default_repository_name)
|
|
47
|
+
# TODO: create RelationshipSet#copy that will copy the relationships, but assign the
|
|
48
|
+
# new Relationship objects to a supplied repository and model. dup does not really
|
|
49
|
+
# do what is needed
|
|
50
|
+
|
|
51
|
+
default_repository_name = self.default_repository_name
|
|
52
|
+
|
|
53
|
+
@relationships[repository_name] ||= if repository_name == default_repository_name
|
|
54
|
+
RelationshipSet.new
|
|
55
|
+
else
|
|
56
|
+
relationships(default_repository_name).dup
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Used to express unlimited cardinality of association,
|
|
61
|
+
# see +has+
|
|
62
|
+
#
|
|
63
|
+
# @api public
|
|
64
|
+
def n
|
|
65
|
+
Infinity
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# A shorthand, clear syntax for defining one-to-one, one-to-many and
|
|
69
|
+
# many-to-many resource relationships.
|
|
70
|
+
#
|
|
71
|
+
# * has 1, :friend # one friend
|
|
72
|
+
# * has n, :friends # many friends
|
|
73
|
+
# * has 1..3, :friends # many friends (at least 1, at most 3)
|
|
74
|
+
# * has 3, :friends # many friends (exactly 3)
|
|
75
|
+
# * has 1, :friend, 'User' # one friend with the class User
|
|
76
|
+
# * has 3, :friends, :through => :friendships # many friends through the friendships relationship
|
|
77
|
+
#
|
|
78
|
+
# @param cardinality [Integer, Range, Infinity]
|
|
79
|
+
# cardinality that defines the association type and constraints
|
|
80
|
+
# @param name [Symbol]
|
|
81
|
+
# the name that the association will be referenced by
|
|
82
|
+
# @param *args [Model, Hash] model and/or options hash
|
|
83
|
+
#
|
|
84
|
+
# @option *args :through[Symbol] A association that this join should go through to form
|
|
85
|
+
# a many-to-many association
|
|
86
|
+
# @option *args :model[Model, String] The name of the class to associate with, if omitted
|
|
87
|
+
# then the association name is assumed to match the class name
|
|
88
|
+
# @option *args :repository[Symbol] name of child model repository
|
|
89
|
+
#
|
|
90
|
+
# @return [Association::Relationship] the relationship that was
|
|
91
|
+
# created to reflect either a one-to-one, one-to-many or many-to-many
|
|
92
|
+
# relationship
|
|
93
|
+
# @raise [ArgumentError] if the cardinality was not understood. Should be a
|
|
94
|
+
# Integer, Range or Infinity(n)
|
|
95
|
+
#
|
|
96
|
+
# @api public
|
|
97
|
+
def has(cardinality, name, *args)
|
|
98
|
+
name = name.to_sym
|
|
99
|
+
model = extract_model(args)
|
|
100
|
+
options = extract_options(args)
|
|
101
|
+
|
|
102
|
+
min, max = extract_min_max(cardinality)
|
|
103
|
+
options.update(:min => min, :max => max)
|
|
104
|
+
|
|
105
|
+
assert_valid_options(options)
|
|
106
|
+
|
|
107
|
+
if options.key?(:model) && model
|
|
108
|
+
raise ArgumentError, 'should not specify options[:model] if passing the model in the third argument'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
model ||= options.delete(:model)
|
|
112
|
+
|
|
113
|
+
repository_name = repository.name
|
|
114
|
+
|
|
115
|
+
# TODO: change to :target_respository_name and :source_repository_name
|
|
116
|
+
options[:child_repository_name] = options.delete(:repository)
|
|
117
|
+
options[:parent_repository_name] = repository_name
|
|
118
|
+
|
|
119
|
+
klass = if max > 1
|
|
120
|
+
options.key?(:through) ? Associations::ManyToMany::Relationship : Associations::OneToMany::Relationship
|
|
121
|
+
else
|
|
122
|
+
Associations::OneToOne::Relationship
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
relationship = klass.new(name, model, self, options)
|
|
126
|
+
|
|
127
|
+
relationships(repository_name) << relationship
|
|
128
|
+
|
|
129
|
+
descendants.each do |descendant|
|
|
130
|
+
descendant.relationships(repository_name) << relationship
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
create_relationship_reader(relationship)
|
|
134
|
+
create_relationship_writer(relationship)
|
|
135
|
+
|
|
136
|
+
relationship
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# A shorthand, clear syntax for defining many-to-one resource relationships.
|
|
140
|
+
#
|
|
141
|
+
# * belongs_to :user # many to one user
|
|
142
|
+
# * belongs_to :friend, :model => 'User' # many to one friend
|
|
143
|
+
# * belongs_to :reference, :repository => :pubmed # association for repository other than default
|
|
144
|
+
#
|
|
145
|
+
# @param name [Symbol]
|
|
146
|
+
# the name that the association will be referenced by
|
|
147
|
+
# @param *args [Model, Hash] model and/or options hash
|
|
148
|
+
#
|
|
149
|
+
# @option *args :model[Model, String] The name of the class to associate with, if omitted
|
|
150
|
+
# then the association name is assumed to match the class name
|
|
151
|
+
# @option *args :repository[Symbol] name of child model repository
|
|
152
|
+
#
|
|
153
|
+
# @return [Association::Relationship] The association created
|
|
154
|
+
# should not be accessed directly
|
|
155
|
+
#
|
|
156
|
+
# @api public
|
|
157
|
+
def belongs_to(name, *args)
|
|
158
|
+
name = name.to_sym
|
|
159
|
+
model_name = self.name
|
|
160
|
+
model = extract_model(args)
|
|
161
|
+
options = extract_options(args)
|
|
162
|
+
|
|
163
|
+
if options.key?(:through)
|
|
164
|
+
raise "#{model_name}#belongs_to with :through is deprecated, use 'has 1, :#{name}, #{options.inspect}' in #{model_name} instead (#{caller.first})"
|
|
165
|
+
elsif options.key?(:model) && model
|
|
166
|
+
raise ArgumentError, 'should not specify options[:model] if passing the model in the third argument'
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
assert_valid_options(options)
|
|
170
|
+
|
|
171
|
+
model ||= options.delete(:model)
|
|
172
|
+
|
|
173
|
+
repository_name = repository.name
|
|
174
|
+
|
|
175
|
+
# TODO: change to source_repository_name and target_respository_name
|
|
176
|
+
options[:child_repository_name] = repository_name
|
|
177
|
+
options[:parent_repository_name] = options.delete(:repository)
|
|
178
|
+
|
|
179
|
+
relationship = Associations::ManyToOne::Relationship.new(name, self, model, options)
|
|
180
|
+
|
|
181
|
+
relationships(repository_name) << relationship
|
|
182
|
+
|
|
183
|
+
descendants.each do |descendant|
|
|
184
|
+
descendant.relationships(repository_name) << relationship
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
create_relationship_reader(relationship)
|
|
188
|
+
create_relationship_writer(relationship)
|
|
189
|
+
|
|
190
|
+
relationship
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
private
|
|
194
|
+
|
|
195
|
+
# Extract the model from an Array of arguments
|
|
196
|
+
#
|
|
197
|
+
# @param [Array(Model, String, Hash)]
|
|
198
|
+
# The arguments passed to an relationship declaration
|
|
199
|
+
#
|
|
200
|
+
# @return [Model, #to_str]
|
|
201
|
+
# target model for the association
|
|
202
|
+
#
|
|
203
|
+
# @api private
|
|
204
|
+
def extract_model(args)
|
|
205
|
+
model = args.first
|
|
206
|
+
|
|
207
|
+
if model.kind_of?(Model)
|
|
208
|
+
model
|
|
209
|
+
elsif model.respond_to?(:to_str)
|
|
210
|
+
model.to_str
|
|
211
|
+
else
|
|
212
|
+
nil
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Extract the model from an Array of arguments
|
|
217
|
+
#
|
|
218
|
+
# @param [Array(Model, String, Hash)]
|
|
219
|
+
# The arguments passed to an relationship declaration
|
|
220
|
+
#
|
|
221
|
+
# @return [Hash]
|
|
222
|
+
# options for the association
|
|
223
|
+
#
|
|
224
|
+
# @api private
|
|
225
|
+
def extract_options(args)
|
|
226
|
+
options = args.last
|
|
227
|
+
options.respond_to?(:to_hash) ? options.to_hash.dup : {}
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# A support method for converting Integer, Range or Infinity values into two
|
|
231
|
+
# values representing the minimum and maximum cardinality of the association
|
|
232
|
+
#
|
|
233
|
+
# @return [Array] A pair of integers, min and max
|
|
234
|
+
#
|
|
235
|
+
# @api private
|
|
236
|
+
def extract_min_max(cardinality)
|
|
237
|
+
case cardinality
|
|
238
|
+
when Integer then [ cardinality, cardinality ]
|
|
239
|
+
when Range then [ cardinality.first, cardinality.last ]
|
|
240
|
+
when Infinity then [ 0, Infinity ]
|
|
241
|
+
else
|
|
242
|
+
assert_kind_of 'options', options, Integer, Range, Infinity.class
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Validates options of association method like belongs_to or has:
|
|
247
|
+
# verifies types of cardinality bounds, repository, association class,
|
|
248
|
+
# keys and possible values of :through option.
|
|
249
|
+
#
|
|
250
|
+
# @api private
|
|
251
|
+
def assert_valid_options(options)
|
|
252
|
+
# TODO: update to match Query#assert_valid_options
|
|
253
|
+
# - perform options normalization elsewhere
|
|
254
|
+
|
|
255
|
+
if options.key?(:min) && options.key?(:max)
|
|
256
|
+
min = options[:min]
|
|
257
|
+
max = options[:max]
|
|
258
|
+
|
|
259
|
+
min = min.to_int unless min == Infinity
|
|
260
|
+
max = max.to_int unless max == Infinity
|
|
261
|
+
|
|
262
|
+
if min == Infinity && max == Infinity
|
|
263
|
+
raise ArgumentError, 'Cardinality may not be n..n. The cardinality specifies the min/max number of results from the association'
|
|
264
|
+
elsif min > max
|
|
265
|
+
raise ArgumentError, "Cardinality min (#{min}) cannot be larger than the max (#{max})"
|
|
266
|
+
elsif min < 0
|
|
267
|
+
raise ArgumentError, "Cardinality min much be greater than or equal to 0, but was #{min}"
|
|
268
|
+
elsif max < 1
|
|
269
|
+
raise ArgumentError, "Cardinality max much be greater than or equal to 1, but was #{max}"
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
if options.key?(:repository)
|
|
274
|
+
options[:repository] = options[:repository].to_sym
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
if options.key?(:class_name)
|
|
278
|
+
raise "+options[:class_name]+ is deprecated, use :model instead (#{caller[1]})"
|
|
279
|
+
elsif options.key?(:remote_name)
|
|
280
|
+
raise "+options[:remote_name]+ is deprecated, use :via instead (#{caller[1]})"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
if options.key?(:through)
|
|
284
|
+
assert_kind_of 'options[:through]', options[:through], Symbol, Module
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
[ :via, :inverse ].each do |key|
|
|
288
|
+
if options.key?(key)
|
|
289
|
+
assert_kind_of "options[#{key.inspect}]", options[key], Symbol, Associations::Relationship
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# TODO: deprecate :child_key and :parent_key in favor of :source_key and
|
|
294
|
+
# :target_key (will mean something different for each relationship)
|
|
295
|
+
|
|
296
|
+
[ :child_key, :parent_key ].each do |key|
|
|
297
|
+
if options.key?(key)
|
|
298
|
+
options[key] = Array(options[key])
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
if options.key?(:limit)
|
|
303
|
+
raise ArgumentError, '+options[:limit]+ should not be specified on a relationship'
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Defines the anonymous module that is used to add relationships.
|
|
308
|
+
# Using a single module here prevents having a very large number
|
|
309
|
+
# of anonymous modules, where each property has their own module.
|
|
310
|
+
# @api private
|
|
311
|
+
def relationship_module
|
|
312
|
+
@relationship_module ||= begin
|
|
313
|
+
mod = Module.new
|
|
314
|
+
class_eval do
|
|
315
|
+
include mod
|
|
316
|
+
end
|
|
317
|
+
mod
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Dynamically defines reader method
|
|
322
|
+
#
|
|
323
|
+
# @api private
|
|
324
|
+
def create_relationship_reader(relationship)
|
|
325
|
+
name = relationship.name
|
|
326
|
+
reader_name = name.to_s
|
|
327
|
+
|
|
328
|
+
return if method_defined?(reader_name)
|
|
329
|
+
|
|
330
|
+
reader_visibility = relationship.reader_visibility
|
|
331
|
+
|
|
332
|
+
relationship_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
333
|
+
#{reader_visibility}
|
|
334
|
+
def #{reader_name}(query = nil)
|
|
335
|
+
# TODO: when no query is passed in, return the results from
|
|
336
|
+
# the ivar directly. This will require that the ivar
|
|
337
|
+
# actually hold the resource/collection, and in the case
|
|
338
|
+
# of 1:1, the underlying collection is hidden in a
|
|
339
|
+
# private ivar, and the resource is in a known ivar
|
|
340
|
+
|
|
341
|
+
persistence_state.get(relationships[#{name.inspect}], query)
|
|
342
|
+
end
|
|
343
|
+
RUBY
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Dynamically defines writer method
|
|
347
|
+
#
|
|
348
|
+
# @api private
|
|
349
|
+
def create_relationship_writer(relationship)
|
|
350
|
+
name = relationship.name
|
|
351
|
+
writer_name = "#{name}="
|
|
352
|
+
|
|
353
|
+
return if method_defined?(writer_name)
|
|
354
|
+
|
|
355
|
+
writer_visibility = relationship.writer_visibility
|
|
356
|
+
|
|
357
|
+
relationship_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
358
|
+
#{writer_visibility}
|
|
359
|
+
def #{writer_name}(target)
|
|
360
|
+
relationship = relationships[#{name.inspect}]
|
|
361
|
+
self.persistence_state = persistence_state.set(relationship, target)
|
|
362
|
+
persistence_state.get(relationship)
|
|
363
|
+
end
|
|
364
|
+
RUBY
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# @api public
|
|
368
|
+
def method_missing(method, *args, &block)
|
|
369
|
+
if relationship = relationships(repository_name)[method]
|
|
370
|
+
return Query::Path.new([ relationship ])
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
super
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
end # module Relationship
|
|
377
|
+
end # module Model
|
|
378
|
+
end # module DataMapper
|