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