motion_virtus 1.0.0.beta0
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/README.md +445 -0
- data/lib/motion_virtus.rb +13 -0
- data/lib/project/attribute/accessor/builder.rb +69 -0
- data/lib/project/attribute/accessor/lazy_accessor.rb +39 -0
- data/lib/project/attribute/accessor.rb +100 -0
- data/lib/project/attribute/accessor_method.rb +73 -0
- data/lib/project/attribute/array.rb +24 -0
- data/lib/project/attribute/boolean.rb +52 -0
- data/lib/project/attribute/class.rb +23 -0
- data/lib/project/attribute/coercer.rb +43 -0
- data/lib/project/attribute/collection/coercible_writer.rb +83 -0
- data/lib/project/attribute/collection.rb +56 -0
- data/lib/project/attribute/date.rb +36 -0
- data/lib/project/attribute/date_time.rb +38 -0
- data/lib/project/attribute/decimal.rb +23 -0
- data/lib/project/attribute/default_value/from_callable.rb +37 -0
- data/lib/project/attribute/default_value/from_clonable.rb +37 -0
- data/lib/project/attribute/default_value/from_symbol.rb +37 -0
- data/lib/project/attribute/default_value.rb +49 -0
- data/lib/project/attribute/embedded_value/open_struct_coercer.rb +43 -0
- data/lib/project/attribute/embedded_value/struct_coercer.rb +42 -0
- data/lib/project/attribute/embedded_value.rb +69 -0
- data/lib/project/attribute/float.rb +30 -0
- data/lib/project/attribute/hash/coercible_writer.rb +78 -0
- data/lib/project/attribute/hash.rb +66 -0
- data/lib/project/attribute/integer.rb +27 -0
- data/lib/project/attribute/numeric.rb +25 -0
- data/lib/project/attribute/object.rb +13 -0
- data/lib/project/attribute/reader.rb +39 -0
- data/lib/project/attribute/set.rb +22 -0
- data/lib/project/attribute/string.rb +24 -0
- data/lib/project/attribute/symbol.rb +23 -0
- data/lib/project/attribute/time.rb +36 -0
- data/lib/project/attribute/writer/coercible.rb +45 -0
- data/lib/project/attribute/writer.rb +73 -0
- data/lib/project/attribute.rb +292 -0
- data/lib/project/attribute_set.rb +260 -0
- data/lib/project/class_inclusions.rb +41 -0
- data/lib/project/class_methods.rb +102 -0
- data/lib/project/configuration.rb +65 -0
- data/lib/project/const_missing_extensions.rb +16 -0
- data/lib/project/extensions.rb +101 -0
- data/lib/project/instance_methods.rb +165 -0
- data/lib/project/module_builder.rb +92 -0
- data/lib/project/module_extensions.rb +72 -0
- data/lib/project/stubs/date.rb +2 -0
- data/lib/project/stubs/date_time.rb +2 -0
- data/lib/project/stubs/decimal.rb +2 -0
- data/lib/project/stubs/ostruct.rb +149 -0
- data/lib/project/stubs/set.rb +767 -0
- data/lib/project/stubs.rb +5 -0
- data/lib/project/support/equalizer.rb +147 -0
- data/lib/project/support/options.rb +114 -0
- data/lib/project/support/type_lookup.rb +109 -0
- data/lib/project/value_object.rb +139 -0
- data/lib/project/version.rb +3 -0
- data/lib/project/virtus.rb +128 -0
- metadata +158 -0
@@ -0,0 +1,292 @@
|
|
1
|
+
motion_require 'virtus'
|
2
|
+
|
3
|
+
module Virtus
|
4
|
+
|
5
|
+
# Abstract class implementing base API for attribute types
|
6
|
+
#
|
7
|
+
# @abstract
|
8
|
+
class Attribute
|
9
|
+
extend DescendantsTracker, TypeLookup, Options
|
10
|
+
|
11
|
+
#include Adamantium::Flat
|
12
|
+
include Equalizer.new(inspect) << :name
|
13
|
+
|
14
|
+
accept_options :primitive, :accessor, :reader,
|
15
|
+
:writer, :coercion_method, :default, :lazy
|
16
|
+
|
17
|
+
accessor :public
|
18
|
+
|
19
|
+
# @see Virtus.coerce
|
20
|
+
#
|
21
|
+
# @deprecated
|
22
|
+
#
|
23
|
+
# @api public
|
24
|
+
def self.coerce(value = Undefined)
|
25
|
+
warn "#{self}.coerce is deprecated and will be removed in a future version. Use Virtus.coerce instead: ##{caller.first}"
|
26
|
+
return Virtus.coerce if value.equal?(Undefined)
|
27
|
+
Virtus.coerce = value
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns name of the attribute
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# User.attributes[:age].name # => :age
|
35
|
+
#
|
36
|
+
# @return [Symbol]
|
37
|
+
#
|
38
|
+
# @api public
|
39
|
+
attr_reader :name
|
40
|
+
|
41
|
+
# Return accessor object
|
42
|
+
#
|
43
|
+
# @return [Accessor]
|
44
|
+
#
|
45
|
+
# @api private
|
46
|
+
attr_reader :accessor
|
47
|
+
|
48
|
+
# Builds an attribute instance
|
49
|
+
#
|
50
|
+
# @param [Symbol] name
|
51
|
+
# the name of an attribute
|
52
|
+
#
|
53
|
+
# @param [Class] type
|
54
|
+
# optional type class of an attribute
|
55
|
+
#
|
56
|
+
# @param [#to_hash] options
|
57
|
+
# optional extra options hash
|
58
|
+
#
|
59
|
+
# @return [Attribute]
|
60
|
+
#
|
61
|
+
# @api private
|
62
|
+
def self.build(name, type = Object, options = {})
|
63
|
+
klass = determine_type(type) or raise(
|
64
|
+
ArgumentError, "#{type.inspect} does not map to an attribute type"
|
65
|
+
)
|
66
|
+
|
67
|
+
attribute_options = klass.merge_options(type, options)
|
68
|
+
accessor = Accessor.build(name, klass, attribute_options)
|
69
|
+
|
70
|
+
klass.new(name, accessor)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Build coercer wrapper
|
74
|
+
#
|
75
|
+
# @example
|
76
|
+
#
|
77
|
+
# Virtus::Attribute.coercer # => #<Virtus::Attribute::Coercer ...>
|
78
|
+
#
|
79
|
+
# @return [Coercer]
|
80
|
+
#
|
81
|
+
# @api public
|
82
|
+
def self.coercer(type = nil, options = {})
|
83
|
+
coercer = options.fetch(:configured_coercer){ Virtus.coercer }
|
84
|
+
Coercer.new(coercer, coercion_method)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Return default reader class
|
88
|
+
#
|
89
|
+
# @return [::Class]
|
90
|
+
#
|
91
|
+
# @api private
|
92
|
+
def self.reader_class(*)
|
93
|
+
Reader
|
94
|
+
end
|
95
|
+
|
96
|
+
# Return default writer class
|
97
|
+
#
|
98
|
+
# @param [::Class] attribute type
|
99
|
+
# @param [::Hash] attribute options
|
100
|
+
#
|
101
|
+
# @return [::Class]
|
102
|
+
#
|
103
|
+
# @api private
|
104
|
+
def self.writer_class(type, options)
|
105
|
+
coerce = options.fetch(:coerce){ Virtus.coerce }
|
106
|
+
coerce ? coercible_writer_class(type, options) : Writer
|
107
|
+
end
|
108
|
+
|
109
|
+
# Return default coercible writer class
|
110
|
+
#
|
111
|
+
# @param [::Class] attribute type
|
112
|
+
# @param [::Hash] attribute options
|
113
|
+
#
|
114
|
+
# @return [::Class]
|
115
|
+
#
|
116
|
+
# @api private
|
117
|
+
def self.coercible_writer_class(_type, _options)
|
118
|
+
Writer::Coercible
|
119
|
+
end
|
120
|
+
|
121
|
+
# Return default options for writer class
|
122
|
+
#
|
123
|
+
# @return [::Hash]
|
124
|
+
#
|
125
|
+
# @api private
|
126
|
+
def self.reader_options(*)
|
127
|
+
{}
|
128
|
+
end
|
129
|
+
|
130
|
+
# Return options accepted by writer class
|
131
|
+
#
|
132
|
+
# @return [Array<Symbol>]
|
133
|
+
#
|
134
|
+
# @api private
|
135
|
+
def self.writer_options(attribute_options)
|
136
|
+
::Hash[writer_option_names.zip(attribute_options.values_at(*writer_option_names))]
|
137
|
+
end
|
138
|
+
|
139
|
+
# Return acceptable option names for write class
|
140
|
+
#
|
141
|
+
# @return [Array<Symbol>]
|
142
|
+
#
|
143
|
+
# @api private
|
144
|
+
def self.writer_option_names
|
145
|
+
[ :coercer, :primitive, :default ]
|
146
|
+
end
|
147
|
+
|
148
|
+
# Determine attribute type based on class or name
|
149
|
+
#
|
150
|
+
# Returns Attribute::EmbeddedValue if a virtus class is passed
|
151
|
+
#
|
152
|
+
# @example
|
153
|
+
# address_class = Class.new { include Virtus }
|
154
|
+
# Virtus::Attribute.determine_type(address_class) # => Virtus::Attribute::EmbeddedValue
|
155
|
+
#
|
156
|
+
# @see Virtus::Support::TypeLookup.determine_type
|
157
|
+
#
|
158
|
+
# @return [Class]
|
159
|
+
#
|
160
|
+
# @api public
|
161
|
+
def self.determine_type(class_or_name)
|
162
|
+
case class_or_name
|
163
|
+
when ::Class
|
164
|
+
EmbeddedValue.determine_type(class_or_name) or super
|
165
|
+
when ::String
|
166
|
+
super
|
167
|
+
when ::Enumerable
|
168
|
+
super(class_or_name.class)
|
169
|
+
else
|
170
|
+
super
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# A hook for Attributes to update options based on the type from the caller
|
175
|
+
#
|
176
|
+
# @param [Object] type
|
177
|
+
# The raw type, typically given by the caller of ClassMethods#attribute
|
178
|
+
# @param [Hash] options
|
179
|
+
# Attribute configuration options
|
180
|
+
#
|
181
|
+
# @return [Hash]
|
182
|
+
# New Hash instance, potentially updated with information from the args
|
183
|
+
#
|
184
|
+
# @api private
|
185
|
+
def self.merge_options(type, options)
|
186
|
+
merged_options = self.options.merge(options)
|
187
|
+
|
188
|
+
if merged_options[:coerce]
|
189
|
+
merged_options.update(
|
190
|
+
:coercer => merged_options.fetch(:coercer) { coercer(type, options) }
|
191
|
+
)
|
192
|
+
end
|
193
|
+
|
194
|
+
merged_options
|
195
|
+
end
|
196
|
+
|
197
|
+
# Initializes an attribute instance
|
198
|
+
#
|
199
|
+
# @param [#to_sym] name
|
200
|
+
# the name of an attribute
|
201
|
+
#
|
202
|
+
# @param [#to_hash] options
|
203
|
+
# hash of extra options which overrides defaults set on an attribute class
|
204
|
+
#
|
205
|
+
# @return [undefined]
|
206
|
+
#
|
207
|
+
# @api private
|
208
|
+
def initialize(name, accessor)
|
209
|
+
@name = name.to_sym
|
210
|
+
@accessor = accessor
|
211
|
+
end
|
212
|
+
|
213
|
+
# Return reader object
|
214
|
+
#
|
215
|
+
# @example
|
216
|
+
#
|
217
|
+
# attribute.reader # => #<Virtus::Attribute::Reader ...>
|
218
|
+
#
|
219
|
+
# @return [Reader]
|
220
|
+
#
|
221
|
+
# @api public
|
222
|
+
def reader
|
223
|
+
accessor.reader
|
224
|
+
end
|
225
|
+
|
226
|
+
# Return writer object
|
227
|
+
#
|
228
|
+
# @example
|
229
|
+
#
|
230
|
+
# attribute.writer # => #<Virtus::Attribute::Writer ...>
|
231
|
+
#
|
232
|
+
# @return [Writer]
|
233
|
+
#
|
234
|
+
# @api public
|
235
|
+
def writer
|
236
|
+
accessor.writer
|
237
|
+
end
|
238
|
+
|
239
|
+
# Define reader and writer methods for an Attribute
|
240
|
+
#
|
241
|
+
# @param [AttributeSet] mod
|
242
|
+
#
|
243
|
+
# @return [self]
|
244
|
+
#
|
245
|
+
# @api private
|
246
|
+
def define_accessor_methods(attribute_set)
|
247
|
+
reader.define_method(accessor, attribute_set)
|
248
|
+
writer.define_method(accessor, attribute_set)
|
249
|
+
self
|
250
|
+
end
|
251
|
+
|
252
|
+
# Returns a Boolean indicating whether the reader method is public
|
253
|
+
#
|
254
|
+
# @return [Boolean]
|
255
|
+
#
|
256
|
+
# @api private
|
257
|
+
def public_reader?
|
258
|
+
accessor.public_reader?
|
259
|
+
end
|
260
|
+
|
261
|
+
# Returns a Boolean indicating whether the writer method is public
|
262
|
+
#
|
263
|
+
# @return [Boolean]
|
264
|
+
#
|
265
|
+
# @api private
|
266
|
+
def public_writer?
|
267
|
+
accessor.public_writer?
|
268
|
+
end
|
269
|
+
|
270
|
+
# Returns if the given value is coerced into the target type
|
271
|
+
#
|
272
|
+
# @return [Boolean]
|
273
|
+
#
|
274
|
+
# @api private
|
275
|
+
def value_coerced?(value)
|
276
|
+
coercer.coerced?(value)
|
277
|
+
end
|
278
|
+
|
279
|
+
private
|
280
|
+
|
281
|
+
# Return coercer for this attribute
|
282
|
+
#
|
283
|
+
# @return [Object]
|
284
|
+
#
|
285
|
+
# @api private
|
286
|
+
def coercer
|
287
|
+
writer.coercer[self.class.primitive]
|
288
|
+
end
|
289
|
+
|
290
|
+
end # class Attribute
|
291
|
+
|
292
|
+
end # module Virtus
|
@@ -0,0 +1,260 @@
|
|
1
|
+
module Virtus
|
2
|
+
|
3
|
+
class AS
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize(parent = nil, attributes = [])
|
7
|
+
@parent = parent
|
8
|
+
@attributes = attributes
|
9
|
+
@index = {}
|
10
|
+
reset
|
11
|
+
end
|
12
|
+
|
13
|
+
def each
|
14
|
+
return to_enum unless block_given?
|
15
|
+
@index.values.uniq.each { |attribute| yield attribute }
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
module AttributeSet
|
22
|
+
|
23
|
+
def self.new(parent = nil, attributes = [])
|
24
|
+
Module.new.tap do |m|
|
25
|
+
m.module_eval do
|
26
|
+
extend Implementation
|
27
|
+
@parent = parent
|
28
|
+
@attributes = attributes
|
29
|
+
@index = {}
|
30
|
+
reset
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module Implementation
|
36
|
+
include Enumerable
|
37
|
+
|
38
|
+
def inspect
|
39
|
+
"#<#{AttributeSet.name}:0x#{ '%x' % (object_id << 1) }>"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Initialize an AttributeSet
|
43
|
+
#
|
44
|
+
# @param [AttributeSet] parent
|
45
|
+
# @param [Array] attributes
|
46
|
+
#
|
47
|
+
# @return [undefined]
|
48
|
+
#
|
49
|
+
# @api private
|
50
|
+
def initialize(parent = nil, attributes = [])
|
51
|
+
@parent = parent
|
52
|
+
@attributes = attributes.dup
|
53
|
+
@index = {}
|
54
|
+
reset
|
55
|
+
end
|
56
|
+
|
57
|
+
# Iterate over each attribute in the set
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
# attribute_set = AttributeSet.new(attributes, parent)
|
61
|
+
# attribute_set.each { |attribute| ... }
|
62
|
+
#
|
63
|
+
# @yield [attribute]
|
64
|
+
#
|
65
|
+
# @yieldparam [Attribute] attribute
|
66
|
+
# each attribute in the set
|
67
|
+
#
|
68
|
+
# @return [self]
|
69
|
+
#
|
70
|
+
# @api public
|
71
|
+
def each
|
72
|
+
return to_enum unless block_given?
|
73
|
+
@index.values.uniq.each { |attribute| yield attribute }
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
# Adds the attributes to the set
|
78
|
+
#
|
79
|
+
# @example
|
80
|
+
# attribute_set.merge(attributes)
|
81
|
+
#
|
82
|
+
# @param [Array<Attribute>] attributes
|
83
|
+
#
|
84
|
+
# @return [self]
|
85
|
+
#
|
86
|
+
# @api public
|
87
|
+
def merge(attributes)
|
88
|
+
attributes.each { |attribute| self << attribute }
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
# Adds an attribute to the set
|
93
|
+
#
|
94
|
+
# @example
|
95
|
+
# attribute_set << attribute
|
96
|
+
#
|
97
|
+
# @param [Attribute] attribute
|
98
|
+
#
|
99
|
+
# @return [self]
|
100
|
+
#
|
101
|
+
# @api public
|
102
|
+
def <<(attribute)
|
103
|
+
self[attribute.name] = attribute
|
104
|
+
attribute.define_accessor_methods(self)
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
# Get an attribute by name
|
109
|
+
#
|
110
|
+
# @example
|
111
|
+
# attribute_set[:name] # => Attribute object
|
112
|
+
#
|
113
|
+
# @param [Symbol] name
|
114
|
+
#
|
115
|
+
# @return [Attribute]
|
116
|
+
#
|
117
|
+
# @api public
|
118
|
+
def [](name)
|
119
|
+
@index[name]
|
120
|
+
end
|
121
|
+
|
122
|
+
# Set an attribute by name
|
123
|
+
#
|
124
|
+
# @example
|
125
|
+
# attribute_set[:name] = attribute
|
126
|
+
#
|
127
|
+
# @param [Symbol] name
|
128
|
+
# @param [Attribute] attribute
|
129
|
+
#
|
130
|
+
# @return [Attribute]
|
131
|
+
#
|
132
|
+
# @api public
|
133
|
+
def []=(name, attribute)
|
134
|
+
@attributes << attribute
|
135
|
+
update_index(name, attribute)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Reset the index when the parent is updated
|
139
|
+
#
|
140
|
+
# @return [self]
|
141
|
+
#
|
142
|
+
# @api private
|
143
|
+
def reset
|
144
|
+
merge_attributes(@parent) if @parent
|
145
|
+
merge_attributes(@attributes)
|
146
|
+
self
|
147
|
+
end
|
148
|
+
|
149
|
+
# Defines an attribute reader method
|
150
|
+
#
|
151
|
+
# @param [Attribute] attribute
|
152
|
+
# @param [Symbol] method_name
|
153
|
+
# @param [Symbol] visibility
|
154
|
+
#
|
155
|
+
# @return [self]
|
156
|
+
#
|
157
|
+
# @api private
|
158
|
+
def define_reader_method(attribute, method_name, visibility)
|
159
|
+
define_method(method_name) { attribute.get(self) }
|
160
|
+
send(visibility, method_name)
|
161
|
+
self
|
162
|
+
end
|
163
|
+
|
164
|
+
# Defines an attribute writer method
|
165
|
+
#
|
166
|
+
# @param [Attribute] attribute
|
167
|
+
# @param [Symbol] method_name
|
168
|
+
# @param [Symbol] visibility
|
169
|
+
#
|
170
|
+
# @return [self]
|
171
|
+
#
|
172
|
+
# @api private
|
173
|
+
def define_writer_method(attribute, method_name, visibility)
|
174
|
+
define_method(method_name) { |value| attribute.set(self, value) }
|
175
|
+
send(visibility, method_name)
|
176
|
+
self
|
177
|
+
end
|
178
|
+
|
179
|
+
# Get values of all attributes defined for this class, ignoring privacy
|
180
|
+
#
|
181
|
+
# @return [Hash]
|
182
|
+
#
|
183
|
+
# @api private
|
184
|
+
def get(object, &block)
|
185
|
+
each_with_object({}) do |attribute, attributes|
|
186
|
+
name = attribute.name
|
187
|
+
attributes[name] = object.__send__(name) if yield(attribute)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Mass-assign attribute values
|
192
|
+
#
|
193
|
+
# @see Virtus::InstanceMethods#attributes=
|
194
|
+
#
|
195
|
+
# @return [Hash]
|
196
|
+
#
|
197
|
+
# @api private
|
198
|
+
def set(object, attributes)
|
199
|
+
coerce(attributes).each do |name, value|
|
200
|
+
writer_name = "#{name}="
|
201
|
+
if object.allowed_writer_methods.include?(writer_name)
|
202
|
+
object.__send__(writer_name, value)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Set default attributes
|
208
|
+
#
|
209
|
+
# @return [self]
|
210
|
+
#
|
211
|
+
# @api private
|
212
|
+
def set_defaults(object)
|
213
|
+
each do |attribute|
|
214
|
+
if object.instance_variable_defined?(attribute.reader.instance_variable_name) || attribute.accessor.lazy?
|
215
|
+
next
|
216
|
+
end
|
217
|
+
attribute.writer.set_default_value(object, attribute)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Coerce attributes received to a hash
|
222
|
+
#
|
223
|
+
# @return [Hash]
|
224
|
+
#
|
225
|
+
# @api private
|
226
|
+
def coerce(attributes)
|
227
|
+
::Hash.try_convert(attributes) or raise(
|
228
|
+
NoMethodError, "Expected #{attributes.inspect} to respond to #to_hash"
|
229
|
+
)
|
230
|
+
end
|
231
|
+
|
232
|
+
private
|
233
|
+
|
234
|
+
# Merge the attributes into the index
|
235
|
+
#
|
236
|
+
# @param [Array<Attribute>] attributes
|
237
|
+
#
|
238
|
+
# @return [undefined]
|
239
|
+
#
|
240
|
+
# @api private
|
241
|
+
def merge_attributes(attributes)
|
242
|
+
attributes.each { |attribute| update_index(attribute.name, attribute) }
|
243
|
+
end
|
244
|
+
|
245
|
+
# Update the symbol and string indexes with the attribute
|
246
|
+
#
|
247
|
+
# @param [Symbol] name
|
248
|
+
#
|
249
|
+
# @param [Attribute] attribute
|
250
|
+
#
|
251
|
+
# @return [undefined]
|
252
|
+
#
|
253
|
+
# @api private
|
254
|
+
def update_index(name, attribute)
|
255
|
+
@index[name] = @index[name.to_s.freeze] = attribute
|
256
|
+
end
|
257
|
+
|
258
|
+
end # Implementation
|
259
|
+
end # AttributeSet
|
260
|
+
end # module Virtus
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Virtus
|
2
|
+
|
3
|
+
# Class-level extensions
|
4
|
+
module ClassInclusions
|
5
|
+
|
6
|
+
# Extends a descendant with class and instance methods
|
7
|
+
#
|
8
|
+
# @param [Class] descendant
|
9
|
+
#
|
10
|
+
# @return [undefined]
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
def self.included(descendant)
|
14
|
+
super
|
15
|
+
descendant.extend(ClassMethods)
|
16
|
+
descendant.class_eval { include InstanceMethods }
|
17
|
+
end
|
18
|
+
private_class_method :included
|
19
|
+
|
20
|
+
# Return a list of allowed writer method names
|
21
|
+
#
|
22
|
+
# @return [Set]
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
def allowed_writer_methods
|
26
|
+
self.class.allowed_writer_methods
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Return class' attribute set
|
32
|
+
#
|
33
|
+
# @return [Virtus::AttributeSet]
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
def attribute_set
|
37
|
+
self.class.send(:attribute_set)
|
38
|
+
end
|
39
|
+
|
40
|
+
end # module ClassInclusions
|
41
|
+
end # module Virtus
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Virtus
|
2
|
+
|
3
|
+
# Class methods that are added when you include Virtus
|
4
|
+
module ClassMethods
|
5
|
+
include Extensions
|
6
|
+
include ConstMissingExtensions
|
7
|
+
|
8
|
+
# Hook called when module is extended
|
9
|
+
#
|
10
|
+
# @param [Class] descendant
|
11
|
+
#
|
12
|
+
# @return [undefined]
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
def self.extended(descendant)
|
16
|
+
super
|
17
|
+
descendant.module_eval do
|
18
|
+
extend DescendantsTracker
|
19
|
+
include attribute_set
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private_class_method :extended
|
24
|
+
|
25
|
+
# Returns all the attributes defined on a Class
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# class User
|
29
|
+
# include Virtus
|
30
|
+
#
|
31
|
+
# attribute :name, String
|
32
|
+
# attribute :age, Integer
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# User.attribute_set # =>
|
36
|
+
#
|
37
|
+
# TODO: implement inspect so the output is not cluttered - solnic
|
38
|
+
#
|
39
|
+
# @return [AttributeSet]
|
40
|
+
#
|
41
|
+
# @api public
|
42
|
+
def attribute_set
|
43
|
+
return @attribute_set if defined?(@attribute_set)
|
44
|
+
superclass = self.superclass
|
45
|
+
method = __method__
|
46
|
+
parent = superclass.public_send(method) if superclass.respond_to?(method)
|
47
|
+
@attribute_set = AttributeSet.new(parent)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @see Virtus::ClassMethods.attribute_set
|
51
|
+
#
|
52
|
+
# @deprecated
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
def attributes
|
56
|
+
warn "#{self}.attributes is deprecated. Use #{self}.attribute_set instead: #{caller.first}"
|
57
|
+
attribute_set
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# Setup descendants' own Attribute-accessor-method-hosting modules
|
63
|
+
#
|
64
|
+
# Descendants inherit Attribute accessor methods via Ruby's inheritance
|
65
|
+
# mechanism: Attribute accessor methods are defined in a module included
|
66
|
+
# in a superclass. Attributes defined on descendants add methods to the
|
67
|
+
# descendant's Attributes accessor module, leaving the superclass's method
|
68
|
+
# table unaffected.
|
69
|
+
#
|
70
|
+
# @param [Class] descendant
|
71
|
+
#
|
72
|
+
# @return [undefined]
|
73
|
+
#
|
74
|
+
# @api private
|
75
|
+
def inherited(descendant)
|
76
|
+
super
|
77
|
+
descendant.module_eval { include attribute_set }
|
78
|
+
end
|
79
|
+
|
80
|
+
# Add the attribute to the class' and descendants' attributes
|
81
|
+
#
|
82
|
+
# @param [Attribute] attribute
|
83
|
+
#
|
84
|
+
# @return [undefined]
|
85
|
+
#
|
86
|
+
# @api private
|
87
|
+
def virtus_add_attribute(attribute)
|
88
|
+
super
|
89
|
+
descendants.each { |descendant| descendant.attribute_set.reset }
|
90
|
+
end
|
91
|
+
|
92
|
+
# The list of allowed public methods
|
93
|
+
#
|
94
|
+
# @return [Array<String>]
|
95
|
+
#
|
96
|
+
# @api private
|
97
|
+
def allowed_methods
|
98
|
+
public_instance_methods.map(&:to_s)
|
99
|
+
end
|
100
|
+
|
101
|
+
end # module ClassMethods
|
102
|
+
end # module Virtus
|