motion_virtus 1.0.0.beta0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|