ardm-core 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.autotest +29 -0
- data/.document +5 -0
- data/.gitignore +35 -0
- data/.travis.yml +23 -0
- data/.yardopts +1 -0
- data/Gemfile +63 -0
- data/LICENSE +20 -0
- data/README.rdoc +237 -0
- data/Rakefile +4 -0
- data/VERSION +1 -0
- data/ardm-core.gemspec +25 -0
- data/lib/ardm-core.rb +1 -0
- data/lib/dm-core.rb +285 -0
- data/lib/dm-core/adapters.rb +222 -0
- data/lib/dm-core/adapters/abstract_adapter.rb +236 -0
- data/lib/dm-core/adapters/in_memory_adapter.rb +113 -0
- data/lib/dm-core/associations/many_to_many.rb +496 -0
- data/lib/dm-core/associations/many_to_one.rb +296 -0
- data/lib/dm-core/associations/one_to_many.rb +345 -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 +1514 -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 +869 -0
- data/lib/dm-core/model/hook.rb +102 -0
- data/lib/dm-core/model/is.rb +32 -0
- data/lib/dm-core/model/property.rb +253 -0
- data/lib/dm-core/model/relationship.rb +377 -0
- data/lib/dm-core/model/scope.rb +89 -0
- data/lib/dm-core/property.rb +839 -0
- data/lib/dm-core/property/binary.rb +22 -0
- data/lib/dm-core/property/boolean.rb +31 -0
- data/lib/dm-core/property/class.rb +24 -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 +50 -0
- data/lib/dm-core/property/discriminator.rb +46 -0
- data/lib/dm-core/property/float.rb +28 -0
- data/lib/dm-core/property/integer.rb +32 -0
- data/lib/dm-core/property/lookup.rb +29 -0
- data/lib/dm-core/property/numeric.rb +40 -0
- data/lib/dm-core/property/object.rb +28 -0
- data/lib/dm-core/property/serial.rb +13 -0
- data/lib/dm-core/property/string.rb +50 -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_set.rb +177 -0
- data/lib/dm-core/query.rb +1444 -0
- data/lib/dm-core/query/conditions/comparison.rb +910 -0
- data/lib/dm-core/query/conditions/operation.rb +720 -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 +1228 -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 +78 -0
- data/lib/dm-core/spec/lib/adapter_helpers.rb +54 -0
- data/lib/dm-core/spec/lib/collection_helpers.rb +20 -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 +173 -0
- data/lib/dm-core/spec/shared/adapter_spec.rb +326 -0
- data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
- data/lib/dm-core/spec/shared/resource_spec.rb +1236 -0
- data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
- data/lib/dm-core/spec/shared/semipublic/property_spec.rb +134 -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 +402 -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 +12 -0
- data/lib/dm-core/support/logger.rb +199 -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 +458 -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 +284 -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 +1669 -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 +12 -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 +3777 -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 +156 -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 +37 -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/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/db.rake +11 -0
- data/tasks/spec.rake +38 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +491 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Model
|
3
|
+
# Module with query scoping functionality.
|
4
|
+
#
|
5
|
+
# Scopes are implemented using simple array based
|
6
|
+
# stack that is thread local. Default scope can be set
|
7
|
+
# on a per repository basis.
|
8
|
+
#
|
9
|
+
# Scopes are merged as new queries are nested.
|
10
|
+
# It is also possible to get exclusive scope access
|
11
|
+
# using +with_exclusive_scope+
|
12
|
+
module Scope
|
13
|
+
# @api private
|
14
|
+
def default_scope(repository_name = default_repository_name)
|
15
|
+
@default_scope ||= {}
|
16
|
+
|
17
|
+
default_repository_name = self.default_repository_name
|
18
|
+
|
19
|
+
@default_scope[repository_name] ||= if repository_name == default_repository_name
|
20
|
+
{}
|
21
|
+
else
|
22
|
+
default_scope(default_repository_name).dup
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns query on top of scope stack
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
def query
|
30
|
+
repository.new_query(self, current_scope).freeze
|
31
|
+
end
|
32
|
+
|
33
|
+
# @api private
|
34
|
+
def current_scope
|
35
|
+
scope_stack.last || default_scope(repository.name)
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
# Pushes given query on top of the stack
|
41
|
+
#
|
42
|
+
# @param [Hash, Query] Query to add to current scope nesting
|
43
|
+
#
|
44
|
+
# @api private
|
45
|
+
def with_scope(query)
|
46
|
+
options = if query.kind_of?(Hash)
|
47
|
+
query
|
48
|
+
else
|
49
|
+
query.options
|
50
|
+
end
|
51
|
+
|
52
|
+
# merge the current scope with the passed in query
|
53
|
+
with_exclusive_scope(self.query.merge(options)) { |*block_args| yield(*block_args) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Pushes given query on top of scope stack and yields
|
57
|
+
# given block, then pops the stack. During block execution
|
58
|
+
# queries previously pushed onto the stack
|
59
|
+
# have no effect.
|
60
|
+
#
|
61
|
+
# @api private
|
62
|
+
def with_exclusive_scope(query)
|
63
|
+
query = if query.kind_of?(Hash)
|
64
|
+
repository.new_query(self, query)
|
65
|
+
else
|
66
|
+
query.dup
|
67
|
+
end
|
68
|
+
|
69
|
+
scope_stack = self.scope_stack
|
70
|
+
scope_stack << query.options
|
71
|
+
|
72
|
+
begin
|
73
|
+
yield query.freeze
|
74
|
+
ensure
|
75
|
+
scope_stack.pop
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Initializes (if necessary) and returns current scope stack
|
80
|
+
# @api private
|
81
|
+
def scope_stack
|
82
|
+
scope_stack_for = Thread.current[:dm_scope_stack] ||= {}
|
83
|
+
scope_stack_for[object_id] ||= []
|
84
|
+
end
|
85
|
+
end # module Scope
|
86
|
+
|
87
|
+
include Scope
|
88
|
+
end # module Model
|
89
|
+
end # module DataMapper
|
@@ -0,0 +1,839 @@
|
|
1
|
+
module DataMapper
|
2
|
+
# = Properties
|
3
|
+
# Properties for a model are not derived from a database structure, but
|
4
|
+
# instead explicitly declared inside your model class definitions. These
|
5
|
+
# properties then map (or, if using automigrate, generate) fields in your
|
6
|
+
# repository/database.
|
7
|
+
#
|
8
|
+
# If you are coming to DataMapper from another ORM framework, such as
|
9
|
+
# ActiveRecord, this may be a fundamental difference in thinking to you.
|
10
|
+
# However, there are several advantages to defining your properties in your
|
11
|
+
# models:
|
12
|
+
#
|
13
|
+
# * information about your model is centralized in one place: rather than
|
14
|
+
# having to dig out migrations, xml or other configuration files.
|
15
|
+
# * use of mixins can be applied to model properties: better code reuse
|
16
|
+
# * having information centralized in your models, encourages you and the
|
17
|
+
# developers on your team to take a model-centric view of development.
|
18
|
+
# * it provides the ability to use Ruby's access control functions.
|
19
|
+
# * and, because DataMapper only cares about properties explicitly defined
|
20
|
+
# in your models, DataMapper plays well with legacy databases, and shares
|
21
|
+
# databases easily with other applications.
|
22
|
+
#
|
23
|
+
# == Declaring Properties
|
24
|
+
# Inside your class, you call the property method for each property you want
|
25
|
+
# to add. The only two required arguments are the name and type, everything
|
26
|
+
# else is optional.
|
27
|
+
#
|
28
|
+
# class Post
|
29
|
+
# include DataMapper::Resource
|
30
|
+
#
|
31
|
+
# property :title, String, :required => true # Cannot be null
|
32
|
+
# property :publish, Boolean, :default => false # Default value for new records is false
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# By default, DataMapper supports the following primitive (Ruby) types
|
36
|
+
# also called core properties:
|
37
|
+
#
|
38
|
+
# * Boolean
|
39
|
+
# * Class (datastore primitive is the same as String. Used for Inheritance)
|
40
|
+
# * Date
|
41
|
+
# * DateTime
|
42
|
+
# * Decimal
|
43
|
+
# * Float
|
44
|
+
# * Integer
|
45
|
+
# * Object (marshalled out during serialization)
|
46
|
+
# * String (default length is 50)
|
47
|
+
# * Text (limit of 65k characters by default)
|
48
|
+
# * Time
|
49
|
+
#
|
50
|
+
# == Limiting Access
|
51
|
+
# Property access control is uses the same terminology Ruby does. Properties
|
52
|
+
# are public by default, but can also be declared private or protected as
|
53
|
+
# needed (via the :accessor option).
|
54
|
+
#
|
55
|
+
# class Post
|
56
|
+
# include DataMapper::Resource
|
57
|
+
#
|
58
|
+
# property :title, String, :accessor => :private # Both reader and writer are private
|
59
|
+
# property :body, Text, :accessor => :protected # Both reader and writer are protected
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# Access control is also analogous to Ruby attribute readers and writers, and can
|
63
|
+
# be declared using :reader and :writer, in addition to :accessor.
|
64
|
+
#
|
65
|
+
# class Post
|
66
|
+
# include DataMapper::Resource
|
67
|
+
#
|
68
|
+
# property :title, String, :writer => :private # Only writer is private
|
69
|
+
# property :tags, String, :reader => :protected # Only reader is protected
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# == Overriding Accessors
|
73
|
+
# The reader/writer for any property can be overridden in the same manner that Ruby
|
74
|
+
# attr readers/writers can be. After the property is defined, just add your custom
|
75
|
+
# reader or writer:
|
76
|
+
#
|
77
|
+
# class Post
|
78
|
+
# include DataMapper::Resource
|
79
|
+
#
|
80
|
+
# property :title, String
|
81
|
+
#
|
82
|
+
# def title=(new_title)
|
83
|
+
# raise ArgumentError if new_title != 'Lee is l337'
|
84
|
+
# super(new_title)
|
85
|
+
# end
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# Calling super ensures that any validators defined for the property are kept active.
|
89
|
+
#
|
90
|
+
# == Lazy Loading
|
91
|
+
# By default, some properties are not loaded when an object is fetched in
|
92
|
+
# DataMapper. These lazily loaded properties are fetched on demand when their
|
93
|
+
# accessor is called for the first time (as it is often unnecessary to
|
94
|
+
# instantiate -every- property -every- time an object is loaded). For
|
95
|
+
# instance, DataMapper::Property::Text fields are lazy loading by default,
|
96
|
+
# although you can over-ride this behavior if you wish:
|
97
|
+
#
|
98
|
+
# Example:
|
99
|
+
#
|
100
|
+
# class Post
|
101
|
+
# include DataMapper::Resource
|
102
|
+
#
|
103
|
+
# property :title, String # Loads normally
|
104
|
+
# property :body, Text # Is lazily loaded by default
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# If you want to over-ride the lazy loading on any field you can set it to a
|
108
|
+
# context or false to disable it with the :lazy option. Contexts allow
|
109
|
+
# multiple lazy properties to be loaded at one time. If you set :lazy to
|
110
|
+
# true, it is placed in the :default context
|
111
|
+
#
|
112
|
+
# class Post
|
113
|
+
# include DataMapper::Resource
|
114
|
+
#
|
115
|
+
# property :title, String # Loads normally
|
116
|
+
# property :body, Text, :lazy => false # The default is now over-ridden
|
117
|
+
# property :comment, String, :lazy => [ :detailed ] # Loads in the :detailed context
|
118
|
+
# property :author, String, :lazy => [ :summary, :detailed ] # Loads in :summary & :detailed context
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# Delaying the request for lazy-loaded attributes even applies to objects
|
122
|
+
# accessed through associations. In a sense, DataMapper anticipates that
|
123
|
+
# you will likely be iterating over objects in associations and rolls all
|
124
|
+
# of the load commands for lazy-loaded properties into one request from
|
125
|
+
# the database.
|
126
|
+
#
|
127
|
+
# Example:
|
128
|
+
#
|
129
|
+
# Widget.get(1).components
|
130
|
+
# # loads when the post object is pulled from database, by default
|
131
|
+
#
|
132
|
+
# Widget.get(1).components.first.body
|
133
|
+
# # loads the values for the body property on all objects in the
|
134
|
+
# # association, rather than just this one.
|
135
|
+
#
|
136
|
+
# Widget.get(1).components.first.comment
|
137
|
+
# # loads both comment and author for all objects in the association
|
138
|
+
# # since they are both in the :detailed context
|
139
|
+
#
|
140
|
+
# == Keys
|
141
|
+
# Properties can be declared as primary or natural keys on a table.
|
142
|
+
# You should a property as the primary key of the table:
|
143
|
+
#
|
144
|
+
# Examples:
|
145
|
+
#
|
146
|
+
# property :id, Serial # auto-incrementing key
|
147
|
+
# property :legacy_pk, String, :key => true # 'natural' key
|
148
|
+
#
|
149
|
+
# This is roughly equivalent to ActiveRecord's <tt>set_primary_key</tt>,
|
150
|
+
# though non-integer data types may be used, thus DataMapper supports natural
|
151
|
+
# keys. When a property is declared as a natural key, accessing the object
|
152
|
+
# using the indexer syntax <tt>Class[key]</tt> remains valid.
|
153
|
+
#
|
154
|
+
# User.get(1)
|
155
|
+
# # when :id is the primary key on the users table
|
156
|
+
# User.get('bill')
|
157
|
+
# # when :name is the primary (natural) key on the users table
|
158
|
+
#
|
159
|
+
# == Indices
|
160
|
+
# You can add indices for your properties by using the <tt>:index</tt>
|
161
|
+
# option. If you use <tt>true</tt> as the option value, the index will be
|
162
|
+
# automatically named. If you want to name the index yourself, use a symbol
|
163
|
+
# as the value.
|
164
|
+
#
|
165
|
+
# property :last_name, String, :index => true
|
166
|
+
# property :first_name, String, :index => :name
|
167
|
+
#
|
168
|
+
# You can create multi-column composite indices by using the same symbol in
|
169
|
+
# all the columns belonging to the index. The columns will appear in the
|
170
|
+
# index in the order they are declared.
|
171
|
+
#
|
172
|
+
# property :last_name, String, :index => :name
|
173
|
+
# property :first_name, String, :index => :name
|
174
|
+
# # => index on (last_name, first_name)
|
175
|
+
#
|
176
|
+
# If you want to make the indices unique, use <tt>:unique_index</tt> instead
|
177
|
+
# of <tt>:index</tt>
|
178
|
+
#
|
179
|
+
# == Inferred Validations
|
180
|
+
# If you require the dm-validations plugin, auto-validations will
|
181
|
+
# automatically be mixed-in in to your model classes: validation rules that
|
182
|
+
# are inferred when properties are declared with specific column restrictions.
|
183
|
+
#
|
184
|
+
# class Post
|
185
|
+
# include DataMapper::Resource
|
186
|
+
#
|
187
|
+
# property :title, String, :length => 250, :min => 0, :max => 250
|
188
|
+
# # => infers 'validates_length :title'
|
189
|
+
#
|
190
|
+
# property :title, String, :required => true
|
191
|
+
# # => infers 'validates_present :title'
|
192
|
+
#
|
193
|
+
# property :email, String, :format => :email_address
|
194
|
+
# # => infers 'validates_format :email, :with => :email_address'
|
195
|
+
#
|
196
|
+
# property :title, String, :length => 255, :required => true
|
197
|
+
# # => infers both 'validates_length' as well as 'validates_present'
|
198
|
+
# # better: property :title, String, :length => 1..255
|
199
|
+
# end
|
200
|
+
#
|
201
|
+
# This functionality is available with the dm-validations gem. For more information
|
202
|
+
# about validations, check the documentation for dm-validations.
|
203
|
+
#
|
204
|
+
# == Default Values
|
205
|
+
# To set a default for a property, use the <tt>:default</tt> key. The
|
206
|
+
# property will be set to the value associated with that key the first time
|
207
|
+
# it is accessed, or when the resource is saved if it hasn't been set with
|
208
|
+
# another value already. This value can be a static value, such as 'hello'
|
209
|
+
# but it can also be a proc that will be evaluated when the property is read
|
210
|
+
# before its value has been set. The property is set to the return of the
|
211
|
+
# proc. The proc is passed two values, the resource the property is being set
|
212
|
+
# for and the property itself.
|
213
|
+
#
|
214
|
+
# property :display_name, String, :default => lambda { |resource, property| resource.login }
|
215
|
+
#
|
216
|
+
# Word of warning. Don't try to read the value of the property you're setting
|
217
|
+
# the default for in the proc. An infinite loop will ensue.
|
218
|
+
#
|
219
|
+
# == Embedded Values (not implemented yet)
|
220
|
+
# As an alternative to extraneous has_one relationships, consider using an
|
221
|
+
# EmbeddedValue.
|
222
|
+
#
|
223
|
+
# == Property options reference
|
224
|
+
#
|
225
|
+
# :accessor if false, neither reader nor writer methods are
|
226
|
+
# created for this property
|
227
|
+
#
|
228
|
+
# :reader if false, reader method is not created for this property
|
229
|
+
#
|
230
|
+
# :writer if false, writer method is not created for this property
|
231
|
+
#
|
232
|
+
# :lazy if true, property value is only loaded when on first read
|
233
|
+
# if false, property value is always loaded
|
234
|
+
# if a symbol, property value is loaded with other properties
|
235
|
+
# in the same group
|
236
|
+
#
|
237
|
+
# :default default value of this property
|
238
|
+
#
|
239
|
+
# :allow_nil if true, property may have a nil value on save
|
240
|
+
#
|
241
|
+
# :key name of the key associated with this property.
|
242
|
+
#
|
243
|
+
# :field field in the data-store which the property corresponds to
|
244
|
+
#
|
245
|
+
# :length string field length
|
246
|
+
#
|
247
|
+
# :format format for autovalidation. Use with dm-validations plugin.
|
248
|
+
#
|
249
|
+
# :index if true, index is created for the property. If a Symbol, index
|
250
|
+
# is named after Symbol value instead of being based on property name.
|
251
|
+
#
|
252
|
+
# :unique_index true specifies that index on this property should be unique
|
253
|
+
#
|
254
|
+
# :auto_validation if true, automatic validation is performed on the property
|
255
|
+
#
|
256
|
+
# :validates validation context. Use together with dm-validations.
|
257
|
+
#
|
258
|
+
# :unique if true, property column is unique. Properties of type Serial
|
259
|
+
# are unique by default.
|
260
|
+
#
|
261
|
+
# :precision Indicates the number of significant digits. Usually only makes sense
|
262
|
+
# for float type properties. Must be >= scale option value. Default is 10.
|
263
|
+
#
|
264
|
+
# :scale The number of significant digits to the right of the decimal point.
|
265
|
+
# Only makes sense for float type properties. Must be > 0.
|
266
|
+
# Default is nil for Float type and 10 for BigDecimal
|
267
|
+
#
|
268
|
+
# == Overriding default Property options
|
269
|
+
#
|
270
|
+
# There is the ability to reconfigure a Property and it's subclasses by explicitly
|
271
|
+
# setting a value in the Property, eg:
|
272
|
+
#
|
273
|
+
# # set all String properties to have a default length of 255
|
274
|
+
# DataMapper::Property::String.length(255)
|
275
|
+
#
|
276
|
+
# # set all Boolean properties to not allow nil (force true or false)
|
277
|
+
# DataMapper::Property::Boolean.allow_nil(false)
|
278
|
+
#
|
279
|
+
# # set all properties to be required by default
|
280
|
+
# DataMapper::Property.required(true)
|
281
|
+
#
|
282
|
+
# # turn off auto-validation for all properties by default
|
283
|
+
# DataMapper::Property.auto_validation(false)
|
284
|
+
#
|
285
|
+
# # set all mutator methods to be private by default
|
286
|
+
# DataMapper::Property.writer(:private)
|
287
|
+
#
|
288
|
+
# Please note that this has no effect when a subclass has explicitly
|
289
|
+
# defined it's own option. For example, setting the String length to
|
290
|
+
# 255 will not affect the Text property even though it inherits from
|
291
|
+
# String, because it sets it's own default length to 65535.
|
292
|
+
#
|
293
|
+
# == Misc. Notes
|
294
|
+
# * Properties declared as strings will default to a length of 50, rather than
|
295
|
+
# 255 (typical max varchar column size). To overload the default, pass
|
296
|
+
# <tt>:length => 255</tt> or <tt>:length => 0..255</tt>. Since DataMapper
|
297
|
+
# does not introspect for properties, this means that legacy database tables
|
298
|
+
# may need their <tt>String</tt> columns defined with a <tt>:length</tt> so
|
299
|
+
# that DM does not apply an un-needed length validation, or allow overflow.
|
300
|
+
# * You may declare a Property with the data-type of <tt>Class</tt>.
|
301
|
+
# see SingleTableInheritance for more on how to use <tt>Class</tt> columns.
|
302
|
+
class Property
|
303
|
+
module PassThroughLoadDump
|
304
|
+
# @api semipublic
|
305
|
+
def load(value)
|
306
|
+
typecast(value) unless value.nil?
|
307
|
+
end
|
308
|
+
|
309
|
+
# Stub instance method for dumping
|
310
|
+
#
|
311
|
+
# @param value [Object, nil] value to dump
|
312
|
+
#
|
313
|
+
# @return [Object] Dumped object
|
314
|
+
#
|
315
|
+
# @api semipublic
|
316
|
+
def dump(value)
|
317
|
+
value
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
include DataMapper::Assertions
|
322
|
+
include Subject
|
323
|
+
extend Equalizer
|
324
|
+
|
325
|
+
equalize :model, :name, :options
|
326
|
+
|
327
|
+
PRIMITIVES = [
|
328
|
+
TrueClass,
|
329
|
+
::String,
|
330
|
+
::Float,
|
331
|
+
::Integer,
|
332
|
+
::BigDecimal,
|
333
|
+
::DateTime,
|
334
|
+
::Date,
|
335
|
+
::Time,
|
336
|
+
::Class
|
337
|
+
].to_set.freeze
|
338
|
+
|
339
|
+
OPTIONS = [
|
340
|
+
:accessor, :reader, :writer,
|
341
|
+
:lazy, :default, :key, :field,
|
342
|
+
:index, :unique_index,
|
343
|
+
:unique, :allow_nil, :allow_blank, :required
|
344
|
+
]
|
345
|
+
|
346
|
+
# Possible :visibility option values
|
347
|
+
VISIBILITY_OPTIONS = [ :public, :protected, :private ].to_set.freeze
|
348
|
+
|
349
|
+
# Invalid property names
|
350
|
+
INVALID_NAMES = (Resource.instance_methods +
|
351
|
+
Resource.private_instance_methods +
|
352
|
+
Query::OPTIONS.to_a
|
353
|
+
).map { |name| name.to_s }
|
354
|
+
|
355
|
+
attr_reader :primitive, :model, :name, :instance_variable_name,
|
356
|
+
:reader_visibility, :writer_visibility, :options,
|
357
|
+
:default, :repository_name, :allow_nil, :allow_blank, :required
|
358
|
+
|
359
|
+
class << self
|
360
|
+
extend Deprecate
|
361
|
+
|
362
|
+
deprecate :all_descendants, :descendants
|
363
|
+
|
364
|
+
# @api semipublic
|
365
|
+
def determine_class(type)
|
366
|
+
return type if type < DataMapper::Property::Object
|
367
|
+
find_class(DataMapper::Inflector.demodulize(type.name))
|
368
|
+
end
|
369
|
+
|
370
|
+
# @api private
|
371
|
+
def demodulized_names
|
372
|
+
@demodulized_names ||= {}
|
373
|
+
end
|
374
|
+
|
375
|
+
# @api semipublic
|
376
|
+
def find_class(name)
|
377
|
+
klass = demodulized_names[name]
|
378
|
+
klass ||= const_get(name) if const_defined?(name)
|
379
|
+
klass
|
380
|
+
end
|
381
|
+
|
382
|
+
# @api public
|
383
|
+
def descendants
|
384
|
+
@descendants ||= DescendantSet.new
|
385
|
+
end
|
386
|
+
|
387
|
+
# @api private
|
388
|
+
def inherited(descendant)
|
389
|
+
# Descendants is a tree rooted in DataMapper::Property that tracks
|
390
|
+
# inheritance. We pre-calculate each comparison value (demodulized
|
391
|
+
# class name) to achieve a Hash[]-time lookup, rather than walk the
|
392
|
+
# entire descendant tree and calculate names on-demand (expensive,
|
393
|
+
# redundant).
|
394
|
+
#
|
395
|
+
# Since the algorithm relegates property class name lookups to a flat
|
396
|
+
# namespace, we need to ensure properties defined outside of DM don't
|
397
|
+
# override built-ins (Serial, String, etc) by merely defining a property
|
398
|
+
# of a same name. We avoid this by only ever adding to the lookup
|
399
|
+
# table. Given that DM loads its own property classes first, we can
|
400
|
+
# assume that their names are "reserved" when added to the table.
|
401
|
+
#
|
402
|
+
# External property authors who want to provide "replacements" for
|
403
|
+
# builtins (e.g. in a non-DM-supported adapter) should follow the
|
404
|
+
# convention of wrapping those properties in a module, and include'ing
|
405
|
+
# the module on the model class directly. This bypasses the DM-hooked
|
406
|
+
# const_missing lookup that would normally check this table.
|
407
|
+
descendants << descendant
|
408
|
+
|
409
|
+
Property.demodulized_names[DataMapper::Inflector.demodulize(descendant.name)] ||= descendant
|
410
|
+
|
411
|
+
# inherit accepted options
|
412
|
+
descendant.accepted_options.concat(accepted_options)
|
413
|
+
|
414
|
+
# inherit the option values
|
415
|
+
options.each { |key, value| descendant.send(key, value) }
|
416
|
+
end
|
417
|
+
|
418
|
+
# @api public
|
419
|
+
def accepted_options
|
420
|
+
@accepted_options ||= []
|
421
|
+
end
|
422
|
+
|
423
|
+
# @api public
|
424
|
+
def accept_options(*args)
|
425
|
+
accepted_options.concat(args)
|
426
|
+
|
427
|
+
# create methods for each new option
|
428
|
+
args.each do |property_option|
|
429
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
430
|
+
def self.#{property_option}(value = Undefined) # def self.unique(value = Undefined)
|
431
|
+
return @#{property_option} if value.equal?(Undefined) # return @unique if value.equal?(Undefined)
|
432
|
+
descendants.each do |descendant| # descendants.each do |descendant|
|
433
|
+
unless descendant.instance_variable_defined?(:@#{property_option}) # unless descendant.instance_variable_defined?(:@unique)
|
434
|
+
descendant.#{property_option}(value) # descendant.unique(value)
|
435
|
+
end # end
|
436
|
+
end # end
|
437
|
+
@#{property_option} = value # @unique = value
|
438
|
+
end # end
|
439
|
+
RUBY
|
440
|
+
end
|
441
|
+
|
442
|
+
descendants.each { |descendant| descendant.accepted_options.concat(args) }
|
443
|
+
end
|
444
|
+
|
445
|
+
# @api private
|
446
|
+
def nullable(*args)
|
447
|
+
# :required is preferable to :allow_nil, but :nullable maps precisely to :allow_nil
|
448
|
+
raise "#nullable is deprecated, use #required instead (#{caller.first})"
|
449
|
+
end
|
450
|
+
|
451
|
+
# Gives all the options set on this property
|
452
|
+
#
|
453
|
+
# @return [Hash] with all options and their values set on this property
|
454
|
+
#
|
455
|
+
# @api public
|
456
|
+
def options
|
457
|
+
options = {}
|
458
|
+
accepted_options.each do |name|
|
459
|
+
options[name] = send(name) if instance_variable_defined?("@#{name}")
|
460
|
+
end
|
461
|
+
options
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
accept_options :primitive, *Property::OPTIONS
|
466
|
+
|
467
|
+
# A hook to allow properties to extend or modify the model it's bound to.
|
468
|
+
# Implementations are not supposed to modify the state of the property
|
469
|
+
# class, and should produce no side-effects on the property instance.
|
470
|
+
def bind
|
471
|
+
# no op
|
472
|
+
end
|
473
|
+
|
474
|
+
# Supplies the field in the data-store which the property corresponds to
|
475
|
+
#
|
476
|
+
# @return [String] name of field in data-store
|
477
|
+
#
|
478
|
+
# @api semipublic
|
479
|
+
def field(repository_name = nil)
|
480
|
+
if repository_name
|
481
|
+
raise "Passing in +repository_name+ to #{self.class}#field is deprecated (#{caller.first})"
|
482
|
+
end
|
483
|
+
|
484
|
+
# defer setting the field with the adapter specific naming
|
485
|
+
# conventions until after the adapter has been setup
|
486
|
+
@field ||= model.field_naming_convention(self.repository_name).call(self).freeze
|
487
|
+
end
|
488
|
+
|
489
|
+
# Returns true if property is unique. Serial properties and keys
|
490
|
+
# are unique by default.
|
491
|
+
#
|
492
|
+
# @return [Boolean]
|
493
|
+
# true if property has uniq index defined, false otherwise
|
494
|
+
#
|
495
|
+
# @api public
|
496
|
+
def unique?
|
497
|
+
!!@unique
|
498
|
+
end
|
499
|
+
|
500
|
+
# Returns index name if property has index.
|
501
|
+
#
|
502
|
+
# @return [Boolean, Symbol, Array]
|
503
|
+
# returns true if property is indexed by itself
|
504
|
+
# returns a Symbol if the property is indexed with other properties
|
505
|
+
# returns an Array if the property belongs to multiple indexes
|
506
|
+
# returns false if the property does not belong to any indexes
|
507
|
+
#
|
508
|
+
# @api public
|
509
|
+
attr_reader :index
|
510
|
+
|
511
|
+
# Returns true if property has unique index. Serial properties and
|
512
|
+
# keys are unique by default.
|
513
|
+
#
|
514
|
+
# @return [Boolean, Symbol, Array]
|
515
|
+
# returns true if property is indexed by itself
|
516
|
+
# returns a Symbol if the property is indexed with other properties
|
517
|
+
# returns an Array if the property belongs to multiple indexes
|
518
|
+
# returns false if the property does not belong to any indexes
|
519
|
+
#
|
520
|
+
# @api public
|
521
|
+
attr_reader :unique_index
|
522
|
+
|
523
|
+
# Returns whether or not the property is to be lazy-loaded
|
524
|
+
#
|
525
|
+
# @return [Boolean]
|
526
|
+
# true if the property is to be lazy-loaded
|
527
|
+
#
|
528
|
+
# @api public
|
529
|
+
def lazy?
|
530
|
+
@lazy
|
531
|
+
end
|
532
|
+
|
533
|
+
# Returns whether or not the property is a key or a part of a key
|
534
|
+
#
|
535
|
+
# @return [Boolean]
|
536
|
+
# true if the property is a key or a part of a key
|
537
|
+
#
|
538
|
+
# @api public
|
539
|
+
def key?
|
540
|
+
@key
|
541
|
+
end
|
542
|
+
|
543
|
+
# Returns whether or not the property is "serial" (auto-incrementing)
|
544
|
+
#
|
545
|
+
# @return [Boolean]
|
546
|
+
# whether or not the property is "serial"
|
547
|
+
#
|
548
|
+
# @api public
|
549
|
+
def serial?
|
550
|
+
@serial
|
551
|
+
end
|
552
|
+
|
553
|
+
# Returns whether or not the property must be non-nil and non-blank
|
554
|
+
#
|
555
|
+
# @return [Boolean]
|
556
|
+
# whether or not the property is required
|
557
|
+
#
|
558
|
+
# @api public
|
559
|
+
def required?
|
560
|
+
@required
|
561
|
+
end
|
562
|
+
|
563
|
+
# Returns whether or not the property can accept 'nil' as it's value
|
564
|
+
#
|
565
|
+
# @return [Boolean]
|
566
|
+
# whether or not the property can accept 'nil'
|
567
|
+
#
|
568
|
+
# @api public
|
569
|
+
def allow_nil?
|
570
|
+
@allow_nil
|
571
|
+
end
|
572
|
+
|
573
|
+
# Returns whether or not the property can be a blank value
|
574
|
+
#
|
575
|
+
# @return [Boolean]
|
576
|
+
# whether or not the property can be blank
|
577
|
+
#
|
578
|
+
# @api public
|
579
|
+
def allow_blank?
|
580
|
+
@allow_blank
|
581
|
+
end
|
582
|
+
|
583
|
+
# Standardized reader method for the property
|
584
|
+
#
|
585
|
+
# @param [Resource] resource
|
586
|
+
# model instance for which this property is to be loaded
|
587
|
+
#
|
588
|
+
# @return [Object]
|
589
|
+
# the value of this property for the provided instance
|
590
|
+
#
|
591
|
+
# @raise [ArgumentError] "+resource+ should be a Resource, but was ...."
|
592
|
+
#
|
593
|
+
# @api private
|
594
|
+
def get(resource)
|
595
|
+
get!(resource)
|
596
|
+
end
|
597
|
+
|
598
|
+
# Fetch the ivar value in the resource
|
599
|
+
#
|
600
|
+
# @param [Resource] resource
|
601
|
+
# model instance for which this property is to be unsafely loaded
|
602
|
+
#
|
603
|
+
# @return [Object]
|
604
|
+
# current @ivar value of this property in +resource+
|
605
|
+
#
|
606
|
+
# @api private
|
607
|
+
def get!(resource)
|
608
|
+
resource.instance_variable_get(instance_variable_name)
|
609
|
+
end
|
610
|
+
|
611
|
+
# Provides a standardized setter method for the property
|
612
|
+
#
|
613
|
+
# @param [Resource] resource
|
614
|
+
# the resource to get the value from
|
615
|
+
# @param [Object] value
|
616
|
+
# the value to set in the resource
|
617
|
+
#
|
618
|
+
# @return [Object]
|
619
|
+
# +value+ after being typecasted according to this property's primitive
|
620
|
+
#
|
621
|
+
# @raise [ArgumentError] "+resource+ should be a Resource, but was ...."
|
622
|
+
#
|
623
|
+
# @api private
|
624
|
+
def set(resource, value)
|
625
|
+
set!(resource, typecast(value))
|
626
|
+
end
|
627
|
+
|
628
|
+
# Set the ivar value in the resource
|
629
|
+
#
|
630
|
+
# @param [Resource] resource
|
631
|
+
# the resource to set
|
632
|
+
# @param [Object] value
|
633
|
+
# the value to set in the resource
|
634
|
+
#
|
635
|
+
# @return [Object]
|
636
|
+
# the value set in the resource
|
637
|
+
#
|
638
|
+
# @api private
|
639
|
+
def set!(resource, value)
|
640
|
+
resource.instance_variable_set(instance_variable_name, value)
|
641
|
+
end
|
642
|
+
|
643
|
+
# Check if the attribute corresponding to the property is loaded
|
644
|
+
#
|
645
|
+
# @param [Resource] resource
|
646
|
+
# model instance for which the attribute is to be tested
|
647
|
+
#
|
648
|
+
# @return [Boolean]
|
649
|
+
# true if the attribute is loaded in the resource
|
650
|
+
#
|
651
|
+
# @api private
|
652
|
+
def loaded?(resource)
|
653
|
+
resource.instance_variable_defined?(instance_variable_name)
|
654
|
+
end
|
655
|
+
|
656
|
+
# Loads lazy columns when get or set is called.
|
657
|
+
#
|
658
|
+
# @param [Resource] resource
|
659
|
+
# model instance for which lazy loaded attribute are loaded
|
660
|
+
#
|
661
|
+
# @api private
|
662
|
+
def lazy_load(resource)
|
663
|
+
return if loaded?(resource)
|
664
|
+
resource.__send__(:lazy_load, lazy_load_properties)
|
665
|
+
end
|
666
|
+
|
667
|
+
# @api private
|
668
|
+
def lazy_load_properties
|
669
|
+
@lazy_load_properties ||=
|
670
|
+
begin
|
671
|
+
properties = self.properties
|
672
|
+
properties.in_context(lazy? ? [ self ] : properties.defaults)
|
673
|
+
end
|
674
|
+
end
|
675
|
+
|
676
|
+
# @api private
|
677
|
+
def properties
|
678
|
+
@properties ||= model.properties(repository_name)
|
679
|
+
end
|
680
|
+
|
681
|
+
# @api semipublic
|
682
|
+
def typecast(value)
|
683
|
+
if value.nil? || primitive?(value)
|
684
|
+
value
|
685
|
+
elsif respond_to?(:typecast_to_primitive)
|
686
|
+
typecast_to_primitive(value)
|
687
|
+
end
|
688
|
+
end
|
689
|
+
|
690
|
+
# Test the value to see if it is a valid value for this Property
|
691
|
+
#
|
692
|
+
# @param [Object] loaded_value
|
693
|
+
# the value to be tested
|
694
|
+
#
|
695
|
+
# @return [Boolean]
|
696
|
+
# true if the value is valid
|
697
|
+
#
|
698
|
+
# @api semipulic
|
699
|
+
def valid?(value, negated = false)
|
700
|
+
dumped_value = dump(value)
|
701
|
+
|
702
|
+
if required? && dumped_value.nil?
|
703
|
+
negated || false
|
704
|
+
else
|
705
|
+
primitive?(dumped_value) || (dumped_value.nil? && (allow_nil? || negated))
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
709
|
+
# Returns a concise string representation of the property instance.
|
710
|
+
#
|
711
|
+
# @return [String]
|
712
|
+
# Concise string representation of the property instance.
|
713
|
+
#
|
714
|
+
# @api public
|
715
|
+
def inspect
|
716
|
+
"#<#{self.class.name} @model=#{model.inspect} @name=#{name.inspect}>"
|
717
|
+
end
|
718
|
+
|
719
|
+
# Test a value to see if it matches the primitive type
|
720
|
+
#
|
721
|
+
# @param [Object] value
|
722
|
+
# value to test
|
723
|
+
#
|
724
|
+
# @return [Boolean]
|
725
|
+
# true if the value is the correct type
|
726
|
+
#
|
727
|
+
# @api semipublic
|
728
|
+
def primitive?(value)
|
729
|
+
value.kind_of?(primitive)
|
730
|
+
end
|
731
|
+
|
732
|
+
protected
|
733
|
+
|
734
|
+
# @api semipublic
|
735
|
+
def initialize(model, name, options = {})
|
736
|
+
options = options.to_hash.dup
|
737
|
+
|
738
|
+
if INVALID_NAMES.include?(name.to_s) || (kind_of?(Boolean) && INVALID_NAMES.include?("#{name}?"))
|
739
|
+
raise ArgumentError,
|
740
|
+
"+name+ was #{name.inspect}, which cannot be used as a property name since it collides with an existing method or a query option"
|
741
|
+
end
|
742
|
+
|
743
|
+
assert_valid_options(options)
|
744
|
+
|
745
|
+
predefined_options = self.class.options
|
746
|
+
|
747
|
+
@repository_name = model.repository_name
|
748
|
+
@model = model
|
749
|
+
@name = name.to_s.chomp('?').to_sym
|
750
|
+
@options = predefined_options.merge(options).freeze
|
751
|
+
@instance_variable_name = "@#{@name}".freeze
|
752
|
+
|
753
|
+
@primitive = self.class.primitive
|
754
|
+
@field = @options[:field].freeze unless @options[:field].nil?
|
755
|
+
@default = @options[:default]
|
756
|
+
|
757
|
+
@serial = @options.fetch(:serial, false)
|
758
|
+
@key = @options.fetch(:key, @serial)
|
759
|
+
@unique = @options.fetch(:unique, @key ? :key : false)
|
760
|
+
@required = @options.fetch(:required, @key)
|
761
|
+
@allow_nil = @options.fetch(:allow_nil, !@required)
|
762
|
+
@allow_blank = @options.fetch(:allow_blank, !@required)
|
763
|
+
@index = @options.fetch(:index, false)
|
764
|
+
@unique_index = @options.fetch(:unique_index, @unique)
|
765
|
+
@lazy = @options.fetch(:lazy, false) && !@key
|
766
|
+
|
767
|
+
determine_visibility
|
768
|
+
|
769
|
+
bind
|
770
|
+
end
|
771
|
+
|
772
|
+
# @api private
|
773
|
+
def assert_valid_options(options)
|
774
|
+
keys = options.keys
|
775
|
+
|
776
|
+
if (unknown_keys = keys - self.class.accepted_options).any?
|
777
|
+
raise ArgumentError, "options #{unknown_keys.map { |key| key.inspect }.join(' and ')} are unknown"
|
778
|
+
end
|
779
|
+
|
780
|
+
options.each do |key, value|
|
781
|
+
boolean_value = value == true || value == false
|
782
|
+
|
783
|
+
case key
|
784
|
+
when :field
|
785
|
+
assert_kind_of "options[:#{key}]", value, ::String
|
786
|
+
|
787
|
+
when :default
|
788
|
+
if value.nil?
|
789
|
+
raise ArgumentError, "options[:#{key}] must not be nil"
|
790
|
+
end
|
791
|
+
|
792
|
+
when :serial, :key, :allow_nil, :allow_blank, :required, :auto_validation
|
793
|
+
unless boolean_value
|
794
|
+
raise ArgumentError, "options[:#{key}] must be either true or false"
|
795
|
+
end
|
796
|
+
|
797
|
+
if key == :required && (keys.include?(:allow_nil) || keys.include?(:allow_blank))
|
798
|
+
raise ArgumentError, 'options[:required] cannot be mixed with :allow_nil or :allow_blank'
|
799
|
+
end
|
800
|
+
|
801
|
+
when :index, :unique_index, :unique, :lazy
|
802
|
+
unless boolean_value || value.kind_of?(Symbol) || (value.kind_of?(Array) && value.any? && value.all? { |val| val.kind_of?(Symbol) })
|
803
|
+
raise ArgumentError, "options[:#{key}] must be either true, false, a Symbol or an Array of Symbols"
|
804
|
+
end
|
805
|
+
|
806
|
+
when :length
|
807
|
+
assert_kind_of "options[:#{key}]", value, Range, ::Integer
|
808
|
+
|
809
|
+
when :size, :precision, :scale
|
810
|
+
assert_kind_of "options[:#{key}]", value, ::Integer
|
811
|
+
|
812
|
+
when :reader, :writer, :accessor
|
813
|
+
assert_kind_of "options[:#{key}]", value, Symbol
|
814
|
+
|
815
|
+
unless VISIBILITY_OPTIONS.include?(value)
|
816
|
+
raise ArgumentError, "options[:#{key}] must be #{VISIBILITY_OPTIONS.join(' or ')}"
|
817
|
+
end
|
818
|
+
end
|
819
|
+
end
|
820
|
+
end
|
821
|
+
|
822
|
+
# Assert given visibility value is supported.
|
823
|
+
#
|
824
|
+
# Will raise ArgumentError if this Property's reader and writer
|
825
|
+
# visibilities are not included in VISIBILITY_OPTIONS.
|
826
|
+
#
|
827
|
+
# @return [undefined]
|
828
|
+
#
|
829
|
+
# @raise [ArgumentError] "property visibility must be :public, :protected, or :private"
|
830
|
+
#
|
831
|
+
# @api private
|
832
|
+
def determine_visibility
|
833
|
+
default_accessor = @options.fetch(:accessor, :public)
|
834
|
+
|
835
|
+
@reader_visibility = @options.fetch(:reader, default_accessor)
|
836
|
+
@writer_visibility = @options.fetch(:writer, default_accessor)
|
837
|
+
end
|
838
|
+
end # class Property
|
839
|
+
end
|