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,65 @@
|
|
1
|
+
module Virtus
|
2
|
+
|
3
|
+
# A Configuration instance
|
4
|
+
class Configuration
|
5
|
+
|
6
|
+
# Access the coerce setting for this instance
|
7
|
+
attr_accessor :coerce
|
8
|
+
|
9
|
+
# Build new configuration instance using the passed block
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# Configuration.build do |config|
|
13
|
+
# config.coerce = false
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# @return [Configuration]
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
def self.build(&block)
|
20
|
+
new.call(&block)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Initialized a configuration instance
|
24
|
+
#
|
25
|
+
# @return [undefined]
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
def initialize
|
29
|
+
@coerce = true
|
30
|
+
@coercer = Coercible::Coercer.new
|
31
|
+
end
|
32
|
+
|
33
|
+
# Provide access to the attributes and methods via the passed block
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# configuration.call do |config|
|
37
|
+
# config.coerce = false
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# @return [self]
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
def call(&block)
|
44
|
+
block.call(self) if block_given?
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
# Access the coercer for this instance and optional configure a
|
49
|
+
# new coercer with the passed block
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# configuration.coercer do |config|
|
53
|
+
# config.string.boolean_map = { true => '1', false => '0' }
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# @return [Coercer]
|
57
|
+
#
|
58
|
+
# @api private
|
59
|
+
def coercer(&block)
|
60
|
+
@coercer = Coercible::Coercer.new(&block) if block_given?
|
61
|
+
@coercer
|
62
|
+
end
|
63
|
+
|
64
|
+
end # class Configuration
|
65
|
+
end # module Virtus
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Virtus
|
2
|
+
module ConstMissingExtensions
|
3
|
+
|
4
|
+
# Hooks into const missing process to determine types of attributes
|
5
|
+
#
|
6
|
+
# @param [String] name
|
7
|
+
#
|
8
|
+
# @return [Class]
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
def const_missing(name)
|
12
|
+
Attribute.determine_type(name) or super
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Virtus
|
2
|
+
|
3
|
+
# Extensions common for both classes and instances
|
4
|
+
module Extensions
|
5
|
+
WRITER_METHOD_REGEXP = /=\z/.freeze
|
6
|
+
INVALID_WRITER_METHODS = %w[ == != === []= attributes= ].to_set.freeze
|
7
|
+
|
8
|
+
# A hook called when an object is extended with Virtus
|
9
|
+
#
|
10
|
+
# @param [Object] object
|
11
|
+
#
|
12
|
+
# @return [undefined]
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
def self.extended(object)
|
16
|
+
super
|
17
|
+
object.instance_eval do
|
18
|
+
extend InstanceMethods
|
19
|
+
extend attribute_set
|
20
|
+
end
|
21
|
+
end
|
22
|
+
private_class_method :extended
|
23
|
+
|
24
|
+
# Defines an attribute on an object's class
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# class Book
|
28
|
+
# include Virtus
|
29
|
+
#
|
30
|
+
# attribute :title, String
|
31
|
+
# attribute :author, String
|
32
|
+
# attribute :published_at, DateTime
|
33
|
+
# attribute :page_count, Integer
|
34
|
+
# attribute :index # defaults to Object
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# @param [Symbol] name
|
38
|
+
# the name of an attribute
|
39
|
+
#
|
40
|
+
# @param [Class] type
|
41
|
+
# the type class of an attribute
|
42
|
+
#
|
43
|
+
# @param [#to_hash] options
|
44
|
+
# the extra options hash
|
45
|
+
#
|
46
|
+
# @return [self]
|
47
|
+
#
|
48
|
+
# @see Attribute.build
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
def attribute(name, type, options = {})
|
52
|
+
attribute = Attribute.build(name, type, merge_options(options))
|
53
|
+
virtus_add_attribute(attribute)
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
# The list of writer methods that can be mass-assigned to in #attributes=
|
58
|
+
#
|
59
|
+
# @return [Set]
|
60
|
+
#
|
61
|
+
# @api private
|
62
|
+
def allowed_writer_methods
|
63
|
+
@allowed_writer_methods ||=
|
64
|
+
begin
|
65
|
+
allowed_writer_methods = allowed_methods.grep(WRITER_METHOD_REGEXP).to_set
|
66
|
+
allowed_writer_methods -= INVALID_WRITER_METHODS
|
67
|
+
allowed_writer_methods.freeze
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# Return an attribute set for that instance
|
74
|
+
#
|
75
|
+
# @return [AttributeSet]
|
76
|
+
#
|
77
|
+
# @api private
|
78
|
+
def attribute_set
|
79
|
+
@attribute_set ||= AttributeSet.new
|
80
|
+
end
|
81
|
+
|
82
|
+
# Add an attribute to the attribute set
|
83
|
+
#
|
84
|
+
# @return [AttributeSet]
|
85
|
+
#
|
86
|
+
# @api private
|
87
|
+
def virtus_add_attribute(attribute)
|
88
|
+
attribute_set << attribute
|
89
|
+
end
|
90
|
+
|
91
|
+
# Merge default options
|
92
|
+
#
|
93
|
+
# @return [Hash]
|
94
|
+
#
|
95
|
+
# @api private
|
96
|
+
def merge_options(options)
|
97
|
+
{ :coerce => Virtus.coerce }.merge(options)
|
98
|
+
end
|
99
|
+
|
100
|
+
end # module Extensions
|
101
|
+
end # module Virtus
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module Virtus
|
2
|
+
|
3
|
+
# Instance methods that are added when you include Virtus
|
4
|
+
module InstanceMethods
|
5
|
+
|
6
|
+
# Set attributes during initialization of an object
|
7
|
+
#
|
8
|
+
# @param [#to_hash] attributes
|
9
|
+
# the attributes hash to be set
|
10
|
+
#
|
11
|
+
# @return [undefined]
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
def initialize(attributes = nil)
|
15
|
+
attribute_set.set(self, attributes) if attributes
|
16
|
+
set_default_attributes
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns a value of the attribute with the given name
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# class User
|
23
|
+
# include Virtus
|
24
|
+
#
|
25
|
+
# attribute :name, String
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# user = User.new(:name => 'John')
|
29
|
+
# user[:name] # => "John"
|
30
|
+
#
|
31
|
+
# @param [Symbol] name
|
32
|
+
# a name of an attribute
|
33
|
+
#
|
34
|
+
# @return [Object]
|
35
|
+
# a value of an attribute
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def [](name)
|
39
|
+
public_send(name)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Sets a value of the attribute with the given name
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# class User
|
46
|
+
# include Virtus
|
47
|
+
#
|
48
|
+
# attribute :name, String
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# user = User.new
|
52
|
+
# user[:name] = "John" # => "John"
|
53
|
+
# user.name # => "John"
|
54
|
+
#
|
55
|
+
# @param [Symbol] name
|
56
|
+
# a name of an attribute
|
57
|
+
#
|
58
|
+
# @param [Object] value
|
59
|
+
# a value to be set
|
60
|
+
#
|
61
|
+
# @return [Object]
|
62
|
+
# the value set on an object
|
63
|
+
#
|
64
|
+
# @api public
|
65
|
+
def []=(name, value)
|
66
|
+
public_send("#{name}=", value)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns a hash of all publicly accessible attributes
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
# class User
|
73
|
+
# include Virtus
|
74
|
+
#
|
75
|
+
# attribute :name, String
|
76
|
+
# attribute :age, Integer
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# user = User.new(:name => 'John', :age => 28)
|
80
|
+
# user.attributes # => { :name => 'John', :age => 28 }
|
81
|
+
#
|
82
|
+
# @return [Hash]
|
83
|
+
#
|
84
|
+
# @api public
|
85
|
+
def attributes
|
86
|
+
attribute_set.get(self, &:public_reader?)
|
87
|
+
end
|
88
|
+
alias_method :to_hash, :attributes
|
89
|
+
|
90
|
+
# Mass-assign attribute values
|
91
|
+
#
|
92
|
+
# Keys in the +attributes+ param can be symbols or strings.
|
93
|
+
# All referenced Attribute writer methods *will* be called.
|
94
|
+
# Non-attribute setter methods on the receiver *will* be called.
|
95
|
+
#
|
96
|
+
# @example
|
97
|
+
# class User
|
98
|
+
# include Virtus
|
99
|
+
#
|
100
|
+
# attribute :name, String
|
101
|
+
# attribute :age, Integer
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# user = User.new
|
105
|
+
# user.attributes = { :name => 'John', 'age' => 28 }
|
106
|
+
#
|
107
|
+
# @param [#to_hash] attributes
|
108
|
+
# a hash of attribute names and values to set on the receiver
|
109
|
+
#
|
110
|
+
# @return [Hash]
|
111
|
+
#
|
112
|
+
# @api public
|
113
|
+
def attributes=(attributes)
|
114
|
+
attribute_set.set(self, attributes)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Freeze object
|
118
|
+
#
|
119
|
+
# @return [self]
|
120
|
+
#
|
121
|
+
# @api public
|
122
|
+
#
|
123
|
+
# @example
|
124
|
+
#
|
125
|
+
# class User
|
126
|
+
# include Virtus
|
127
|
+
#
|
128
|
+
# attribute :name, String
|
129
|
+
# attribute :age, Integer
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
# user = User.new(:name => 'John', :age => 28)
|
133
|
+
# user.frozen? # => false
|
134
|
+
# user.freeze
|
135
|
+
# user.frozen? # => true
|
136
|
+
#
|
137
|
+
# @api public
|
138
|
+
def freeze
|
139
|
+
set_default_attributes
|
140
|
+
super
|
141
|
+
end
|
142
|
+
|
143
|
+
# Set default attributes
|
144
|
+
#
|
145
|
+
# @return [self]
|
146
|
+
#
|
147
|
+
# @api private
|
148
|
+
def set_default_attributes
|
149
|
+
attribute_set.set_defaults(self)
|
150
|
+
self
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
# The list of allowed public methods
|
156
|
+
#
|
157
|
+
# @return [Array<String>]
|
158
|
+
#
|
159
|
+
# @api private
|
160
|
+
def allowed_methods
|
161
|
+
public_methods.map(&:to_s)
|
162
|
+
end
|
163
|
+
|
164
|
+
end # module InstanceMethods
|
165
|
+
end # module Virtus
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Virtus
|
2
|
+
|
3
|
+
# Class to build a Virtus module with it's own configuration
|
4
|
+
#
|
5
|
+
# This allows for individual Virtus modules to be included in
|
6
|
+
# classes and not impacted by the global Virtus configuration,
|
7
|
+
# which is implemented using Virtus::Configuration.
|
8
|
+
class ModuleBuilder
|
9
|
+
|
10
|
+
# Return module
|
11
|
+
#
|
12
|
+
# @return [Module]
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
attr_reader :module
|
16
|
+
|
17
|
+
# Return configuration
|
18
|
+
#
|
19
|
+
# @return [Configuration]
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
attr_reader :configuration
|
23
|
+
|
24
|
+
# Builds a new Virtus module
|
25
|
+
#
|
26
|
+
# The block is passed to Virtus::Configuration
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# ModuleBuilder.call do |config|
|
30
|
+
# # config settings
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# @return [Module]
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
def self.call(&block)
|
37
|
+
config = Configuration.build(&block)
|
38
|
+
builder = new(config)
|
39
|
+
builder.add_included_hook
|
40
|
+
builder.module
|
41
|
+
end
|
42
|
+
|
43
|
+
# Initializes a new ModuleBuilder
|
44
|
+
#
|
45
|
+
# @param [Configuration] configuration
|
46
|
+
#
|
47
|
+
# @param [Module] mod
|
48
|
+
#
|
49
|
+
# @return [undefined]
|
50
|
+
#
|
51
|
+
# @api private
|
52
|
+
def initialize(configuration, mod = Module.new)
|
53
|
+
@configuration = configuration
|
54
|
+
@module = mod.send(:include, Virtus)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Adds the .included hook to the anonymous module which then defines the
|
58
|
+
# .attribute method to override the default.
|
59
|
+
#
|
60
|
+
# @return [Module]
|
61
|
+
#
|
62
|
+
# @api private
|
63
|
+
def add_included_hook
|
64
|
+
attribute_proc = attribute_method(configuration)
|
65
|
+
|
66
|
+
self.module.define_singleton_method :included do |object|
|
67
|
+
super(object)
|
68
|
+
object.send :define_singleton_method, :attribute, attribute_proc
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Wrapper for the attribute method that is used in .add_included_hook
|
73
|
+
# The coercer is passed in the unused key :configured_coercer to allow the
|
74
|
+
# property encapsulation by Virtus::Attribute::Coercer, where the
|
75
|
+
# coercion method is known.
|
76
|
+
#
|
77
|
+
# @return [Proc(lambda)]
|
78
|
+
#
|
79
|
+
# @api private
|
80
|
+
def attribute_method(configuration)
|
81
|
+
lambda do |name, type, options = {}|
|
82
|
+
module_options = {
|
83
|
+
:coerce => configuration.coerce,
|
84
|
+
:configured_coercer => configuration.coercer
|
85
|
+
}
|
86
|
+
|
87
|
+
super(name, type, module_options.merge(options))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end # class ModuleBuilder
|
92
|
+
end # module Virtus
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Virtus
|
2
|
+
|
3
|
+
# Virtus module that can define attributes for later inclusion
|
4
|
+
#
|
5
|
+
module ModuleExtensions
|
6
|
+
include ConstMissingExtensions
|
7
|
+
|
8
|
+
# Define an attribute in the module
|
9
|
+
#
|
10
|
+
# @see Virtus::Extensions#attribute
|
11
|
+
#
|
12
|
+
# @return [self]
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
def attribute(*args)
|
16
|
+
attribute_definitions << args
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# Extend an object with Virtus methods and define attributes
|
23
|
+
#
|
24
|
+
# @param [Object] object
|
25
|
+
#
|
26
|
+
# @return [undefined]
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
def extended(object)
|
30
|
+
super
|
31
|
+
object.extend(Virtus)
|
32
|
+
define_attributes(object)
|
33
|
+
object.set_default_attributes
|
34
|
+
end
|
35
|
+
|
36
|
+
# Extend a class with Virtus methods and define attributes
|
37
|
+
#
|
38
|
+
# @param [Object] object
|
39
|
+
#
|
40
|
+
# @return [undefined]
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
def included(object)
|
44
|
+
super
|
45
|
+
object.module_eval { include Virtus }
|
46
|
+
define_attributes(object)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return attribute definitions
|
50
|
+
#
|
51
|
+
# @return [Array<Hash>]
|
52
|
+
#
|
53
|
+
# @api private
|
54
|
+
def attribute_definitions
|
55
|
+
@_attribute_definitions ||= []
|
56
|
+
end
|
57
|
+
|
58
|
+
# Define attributes on a class or instance
|
59
|
+
#
|
60
|
+
# @param [Object,Class] object
|
61
|
+
#
|
62
|
+
# @return [undefined]
|
63
|
+
#
|
64
|
+
# @api private
|
65
|
+
def define_attributes(object)
|
66
|
+
attribute_definitions.each do |attribute_args|
|
67
|
+
object.attribute(*attribute_args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end # module ModuleExtensions
|
72
|
+
end # module Virtus
|
@@ -0,0 +1,149 @@
|
|
1
|
+
#
|
2
|
+
# = ostruct.rb: OpenStruct implementation
|
3
|
+
#
|
4
|
+
# Author:: Yukihiro Matsumoto
|
5
|
+
# Documentation:: Gavin Sinclair
|
6
|
+
#
|
7
|
+
# OpenStruct allows the creation of data objects with arbitrary attributes.
|
8
|
+
# See OpenStruct for an example.
|
9
|
+
#
|
10
|
+
|
11
|
+
#
|
12
|
+
# OpenStruct allows you to create data objects and set arbitrary attributes.
|
13
|
+
# For example:
|
14
|
+
#
|
15
|
+
# require 'ostruct'
|
16
|
+
#
|
17
|
+
# record = OpenStruct.new
|
18
|
+
# record.name = "John Smith"
|
19
|
+
# record.age = 70
|
20
|
+
# record.pension = 300
|
21
|
+
#
|
22
|
+
# puts record.name # -> "John Smith"
|
23
|
+
# puts record.address # -> nil
|
24
|
+
#
|
25
|
+
# It is like a hash with a different way to access the data. In fact, it is
|
26
|
+
# implemented with a hash, and you can initialize it with one.
|
27
|
+
#
|
28
|
+
# hash = { "country" => "Australia", :population => 20_000_000 }
|
29
|
+
# data = OpenStruct.new(hash)
|
30
|
+
#
|
31
|
+
# p data # -> <OpenStruct country="Australia" population=20000000>
|
32
|
+
#
|
33
|
+
class OpenStruct
|
34
|
+
#
|
35
|
+
# Create a new OpenStruct object. The optional +hash+, if given, will
|
36
|
+
# generate attributes and values. For example.
|
37
|
+
#
|
38
|
+
# require 'ostruct'
|
39
|
+
# hash = { "country" => "Australia", :population => 20_000_000 }
|
40
|
+
# data = OpenStruct.new(hash)
|
41
|
+
#
|
42
|
+
# p data # -> <OpenStruct country="Australia" population=20000000>
|
43
|
+
#
|
44
|
+
# By default, the resulting OpenStruct object will have no attributes.
|
45
|
+
#
|
46
|
+
def initialize(hash=nil)
|
47
|
+
@table = {}
|
48
|
+
if hash
|
49
|
+
for k,v in hash
|
50
|
+
@table[k.to_sym] = v
|
51
|
+
new_ostruct_member(k)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Duplicate an OpenStruct object members.
|
57
|
+
def initialize_copy(orig)
|
58
|
+
super
|
59
|
+
@table = @table.dup
|
60
|
+
end
|
61
|
+
|
62
|
+
def marshal_dump
|
63
|
+
@table
|
64
|
+
end
|
65
|
+
def marshal_load(x)
|
66
|
+
@table = x
|
67
|
+
@table.each_key{|key| new_ostruct_member(key)}
|
68
|
+
end
|
69
|
+
|
70
|
+
def modifiable
|
71
|
+
if self.frozen?
|
72
|
+
raise TypeError, "can't modify frozen #{self.class}", caller(2)
|
73
|
+
end
|
74
|
+
@table
|
75
|
+
end
|
76
|
+
protected :modifiable
|
77
|
+
|
78
|
+
def new_ostruct_member(name)
|
79
|
+
name = name.to_sym
|
80
|
+
unless self.respond_to?(name)
|
81
|
+
self.__singleton_class.class_eval do
|
82
|
+
define_method(name) { @table[name] }
|
83
|
+
define_method("#{name}=") { |x| modifiable[name] = x }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
name
|
87
|
+
end
|
88
|
+
|
89
|
+
def method_missing(mid, *args) # :nodoc:
|
90
|
+
mname = mid.id2name
|
91
|
+
len = args.length
|
92
|
+
if mname.chomp!('=')
|
93
|
+
if len != 1
|
94
|
+
raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
|
95
|
+
end
|
96
|
+
modifiable[new_ostruct_member(mname)] = args[0]
|
97
|
+
elsif len == 0
|
98
|
+
@table[mid]
|
99
|
+
else
|
100
|
+
raise NoMethodError, "undefined method `#{mname}' for #{self}", caller(1)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Remove the named field from the object.
|
106
|
+
#
|
107
|
+
def delete_field(name)
|
108
|
+
@table.delete name.to_sym
|
109
|
+
end
|
110
|
+
|
111
|
+
InspectKey = :__inspect_key__ # :nodoc:
|
112
|
+
|
113
|
+
#
|
114
|
+
# Returns a string containing a detailed summary of the keys and values.
|
115
|
+
#
|
116
|
+
def inspect
|
117
|
+
str = "#<#{self.class}"
|
118
|
+
|
119
|
+
ids = (Thread.current[InspectKey] ||= [])
|
120
|
+
if ids.include?(object_id)
|
121
|
+
return str << ' ...>'
|
122
|
+
end
|
123
|
+
|
124
|
+
ids << object_id
|
125
|
+
begin
|
126
|
+
first = true
|
127
|
+
for k,v in @table
|
128
|
+
str << "," unless first
|
129
|
+
first = false
|
130
|
+
str << " #{k}=#{v.inspect}"
|
131
|
+
end
|
132
|
+
return str << '>'
|
133
|
+
ensure
|
134
|
+
ids.pop
|
135
|
+
end
|
136
|
+
end
|
137
|
+
alias :to_s :inspect
|
138
|
+
|
139
|
+
attr_reader :table # :nodoc:
|
140
|
+
protected :table
|
141
|
+
|
142
|
+
#
|
143
|
+
# Compare this object and +other+ for equality.
|
144
|
+
#
|
145
|
+
def ==(other)
|
146
|
+
return false unless(other.kind_of?(OpenStruct))
|
147
|
+
return @table == other.table
|
148
|
+
end
|
149
|
+
end
|