simple_mapper 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.
- data/README.rdoc +165 -0
- data/Rakefile.rb +11 -0
- data/lib/simple_mapper.rb +8 -0
- data/lib/simple_mapper/attribute.rb +119 -0
- data/lib/simple_mapper/attribute/collection.rb +130 -0
- data/lib/simple_mapper/attribute/pattern.rb +19 -0
- data/lib/simple_mapper/attributes.rb +196 -0
- data/lib/simple_mapper/attributes/types.rb +224 -0
- data/lib/simple_mapper/change_hash.rb +14 -0
- data/lib/simple_mapper/collection.rb +169 -0
- data/lib/simple_mapper/exceptions.rb +10 -0
- data/test/integration/attribute_change_tracking_test.rb +181 -0
- data/test/integration/attribute_meta_interaction_test.rb +169 -0
- data/test/integration/attribute_pattern_test.rb +77 -0
- data/test/integration/to_simple_test.rb +128 -0
- data/test/test_helper.rb +11 -0
- data/test/unit/attribute_collection_test.rb +379 -0
- data/test/unit/attribute_pattern_test.rb +55 -0
- data/test/unit/attribute_test.rb +419 -0
- data/test/unit/attributes_test.rb +561 -0
- data/test/unit/collection_array_test.rb +194 -0
- data/test/unit/collection_hash_test.rb +139 -0
- data/test/unit/types_test.rb +314 -0
- metadata +140 -0
@@ -0,0 +1,196 @@
|
|
1
|
+
module SimpleMapper
|
2
|
+
module Attributes
|
3
|
+
self.instance_eval do
|
4
|
+
def types
|
5
|
+
@types ||= {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def type_for(name)
|
9
|
+
types[name]
|
10
|
+
end
|
11
|
+
|
12
|
+
def register_type(name, expected_type, converter)
|
13
|
+
types[name] = {:name => name, :expected_type => expected_type, :converter => converter}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.included(klass)
|
18
|
+
klass.extend ClassMethods
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
def simple_mapper
|
23
|
+
@simple_mapper ||= SimpleMapper::Attributes::Manager.new(self)
|
24
|
+
end
|
25
|
+
|
26
|
+
def maps(attr, *args, &block)
|
27
|
+
if block_given?
|
28
|
+
hash = args.last
|
29
|
+
args << (hash = {}) unless hash.instance_of? Hash
|
30
|
+
mapper = simple_mapper.create_anonymous_mapper(&block)
|
31
|
+
hash[:type] ||= mapper
|
32
|
+
hash[:mapper] = mapper
|
33
|
+
end
|
34
|
+
attribute = simple_mapper.create_attribute(attr, *args)
|
35
|
+
simple_mapper.install_attribute attr, attribute
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def attribute_object_for(attr)
|
40
|
+
self.class.simple_mapper.attributes[attr]
|
41
|
+
end
|
42
|
+
|
43
|
+
def key_for(attr)
|
44
|
+
attribute_object_for(attr).key
|
45
|
+
end
|
46
|
+
|
47
|
+
def reset_attribute(attr)
|
48
|
+
@simple_mapper_init.delete attr
|
49
|
+
attribute_changed!(attr, false)
|
50
|
+
remove_instance_variable(:"@#{attr}")
|
51
|
+
end
|
52
|
+
|
53
|
+
def write_attribute(attr, value)
|
54
|
+
raise(RuntimeError, "can't modify frozen object") if frozen?
|
55
|
+
instance_variable_set(:"@#{attr}", value)
|
56
|
+
@simple_mapper_init[attr] = true
|
57
|
+
attribute_changed! attr
|
58
|
+
value
|
59
|
+
end
|
60
|
+
|
61
|
+
def transform_source_attribute(attr)
|
62
|
+
attribute_object_for(attr).transformed_source_value(self)
|
63
|
+
end
|
64
|
+
|
65
|
+
def read_source_attribute(attr)
|
66
|
+
attribute_object_for(attr).source_value(self)
|
67
|
+
end
|
68
|
+
|
69
|
+
def read_attribute(attr)
|
70
|
+
if @simple_mapper_init[attr]
|
71
|
+
instance_variable_get(:"@#{attr}")
|
72
|
+
else
|
73
|
+
result = instance_variable_set(:"@#{attr}", transform_source_attribute(attr))
|
74
|
+
@simple_mapper_init[attr] = true
|
75
|
+
result
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_attribute_default(attr)
|
80
|
+
attribute_object_for(attr).default_value(self)
|
81
|
+
end
|
82
|
+
|
83
|
+
def simple_mapper_changes
|
84
|
+
@simple_mapper_changes ||= SimpleMapper::ChangeHash.new
|
85
|
+
end
|
86
|
+
|
87
|
+
def attribute_changed!(attr, flag=true)
|
88
|
+
attribute_object_for(attr).changed!(self, flag)
|
89
|
+
end
|
90
|
+
|
91
|
+
def attribute_changed?(attr)
|
92
|
+
attribute_object_for(attr).changed?(self)
|
93
|
+
end
|
94
|
+
|
95
|
+
def all_changed!
|
96
|
+
simple_mapper_changes.all_changed!
|
97
|
+
end
|
98
|
+
|
99
|
+
def all_changed?
|
100
|
+
simple_mapper_changes.all
|
101
|
+
end
|
102
|
+
|
103
|
+
def changed_attributes
|
104
|
+
attribs = self.class.simple_mapper.attributes
|
105
|
+
if simple_mapper_changes.all
|
106
|
+
attribs.keys
|
107
|
+
else
|
108
|
+
attribs.inject([]) do |list, keyval|
|
109
|
+
list << keyval[0] if keyval[1].changed?(self)
|
110
|
+
list
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def changed?
|
116
|
+
changed_attributes.size > 0
|
117
|
+
end
|
118
|
+
|
119
|
+
def freeze
|
120
|
+
self.class.simple_mapper.attributes.values.each {|attribute| attribute.freeze_for(self)}
|
121
|
+
simple_mapper_changes.freeze
|
122
|
+
end
|
123
|
+
|
124
|
+
def frozen?
|
125
|
+
simple_mapper_changes.frozen?
|
126
|
+
end
|
127
|
+
|
128
|
+
attr_reader :simple_mapper_source
|
129
|
+
|
130
|
+
def initialize(values = {})
|
131
|
+
@simple_mapper_source = values
|
132
|
+
@simple_mapper_init = {}
|
133
|
+
@simple_mapper_changes = SimpleMapper::ChangeHash.new
|
134
|
+
end
|
135
|
+
|
136
|
+
def to_simple(options = {})
|
137
|
+
clean_opt = options.clone
|
138
|
+
# if all_changed? true, we disregard changed flag entirely, so the entire graph
|
139
|
+
# appears to be changed in result set. We
|
140
|
+
# also propagate the :all flag, which tells
|
141
|
+
# objects that support it to include information
|
142
|
+
# about members that were removed.
|
143
|
+
if all_changed?
|
144
|
+
clean_opt.delete(:changed)
|
145
|
+
clean_opt[:all] = true
|
146
|
+
end
|
147
|
+
changes = (clean_opt[:changed] && true) || false
|
148
|
+
self.class.simple_mapper.attributes.values.inject({}) do |container, attrib|
|
149
|
+
attrib.to_simple(self, container, clean_opt) if !changes or attrib.changed?(self)
|
150
|
+
container
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class Manager
|
155
|
+
attr_accessor :applies_to
|
156
|
+
|
157
|
+
def initialize(apply_to = nil)
|
158
|
+
self.applies_to = apply_to if apply_to
|
159
|
+
end
|
160
|
+
|
161
|
+
def attributes
|
162
|
+
@attributes ||= {}
|
163
|
+
end
|
164
|
+
|
165
|
+
def create_attribute(name, options = {})
|
166
|
+
attrib_class = options[:attribute_class] || SimpleMapper::Attribute
|
167
|
+
attrib_class.new(name, options)
|
168
|
+
end
|
169
|
+
|
170
|
+
def install_attribute(attr, object)
|
171
|
+
read_body = Proc.new { read_attribute(attr) }
|
172
|
+
write_body = Proc.new {|value| write_attribute(attr, value)}
|
173
|
+
applies_to.module_eval do
|
174
|
+
define_method(attr, &read_body)
|
175
|
+
define_method(:"#{attr}=", &write_body)
|
176
|
+
end
|
177
|
+
attributes[attr] = object
|
178
|
+
end
|
179
|
+
|
180
|
+
def create_anonymous_mapper(&block)
|
181
|
+
mapper = Class.new do
|
182
|
+
include SimpleMapper::Attributes
|
183
|
+
def self.decode(*arg)
|
184
|
+
new(*arg)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
mapper.module_eval &block if block_given?
|
188
|
+
mapper
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
require 'simple_mapper/exceptions'
|
195
|
+
require 'simple_mapper/attributes/types'
|
196
|
+
|
@@ -0,0 +1,224 @@
|
|
1
|
+
module SimpleMapper::Attributes::Types
|
2
|
+
# Provides basic support for floating point numbers, closely tied to
|
3
|
+
# the core Ruby :Float:.
|
4
|
+
#
|
5
|
+
# Registered as type <tt>:float</tt>
|
6
|
+
#
|
7
|
+
# This is intended to be reasonably flexible and work with inputs
|
8
|
+
# that look numeric whether or not they literally appear to be "floats".
|
9
|
+
module Float
|
10
|
+
PATTERN = /^([0-9]+)(?:(\.)([0-9]*))?$/
|
11
|
+
|
12
|
+
# Decode a numeric-looking +value+ (whether a string or otherwise) into
|
13
|
+
# a +Float+.
|
14
|
+
#
|
15
|
+
# String inputs for +value+ should be numeric and may or may not have
|
16
|
+
# a decimal point, with or without digits following the decimal point.
|
17
|
+
# Therefore, the following values would all decode nicely:
|
18
|
+
# * +"14"+ to +14.0+
|
19
|
+
# * +"0"+ to +0.0+
|
20
|
+
# * +"123."+ to +123.0+
|
21
|
+
# * +"100.22022"+ to +100.22022+
|
22
|
+
# * +"0.0001"+ to +0.0001+
|
23
|
+
# * +14+ to +14.0+
|
24
|
+
# * +0+ to +0.0+
|
25
|
+
#
|
26
|
+
# The empty string will result in a value of +nil+
|
27
|
+
def self.decode(value)
|
28
|
+
return nil if (str = value.to_s).length == 0
|
29
|
+
return value if Float === value
|
30
|
+
match = str.match(PATTERN)
|
31
|
+
raise(SimpleMapper::TypeConversionException, "Cannot decode '#{value}' to Float.") unless match
|
32
|
+
value = match[1]
|
33
|
+
value += match[2] + match[3] if match[3].to_s.length > 0
|
34
|
+
value.to_s.to_f
|
35
|
+
end
|
36
|
+
|
37
|
+
# Encodes a float-like +value+ as a string, conforming to the basic
|
38
|
+
# syntax used for +decode+.
|
39
|
+
def self.encode(value)
|
40
|
+
return nil if value.nil?
|
41
|
+
if ! value.respond_to?(:to_f) or value.respond_to?(:match) && ! value.match(PATTERN)
|
42
|
+
raise(SimpleMapper::TypeConversionException, "Cannot encode '#{value}' as Float.")
|
43
|
+
end
|
44
|
+
value.to_f.to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the type default value of +nil+.
|
48
|
+
def self.default
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
SimpleMapper::Attributes.register_type(:float, ::Float, Float)
|
53
|
+
|
54
|
+
# Provides simple string type support.
|
55
|
+
#
|
56
|
+
# Registered as <tt>:string</tt>.
|
57
|
+
#
|
58
|
+
# This is intended to be quite flexible and rely purely on duck typing.
|
59
|
+
# It should work with any input that supports <tt>:to_s</tt>, and is
|
60
|
+
# not strictly limited to actual +String+ instances.
|
61
|
+
module String
|
62
|
+
# Decodes +value+ into a +String+ object via the <tt>:to_s</tt> of
|
63
|
+
# +value+. Passes nils through unchanged. For the majority use case,
|
64
|
+
# most of the time the result and the input will be equal.
|
65
|
+
def self.decode(value)
|
66
|
+
return nil if value.nil?
|
67
|
+
value.to_s
|
68
|
+
end
|
69
|
+
|
70
|
+
# Encodes +value+ as a string via its <tt>:to_s</tt> method. Passes nils
|
71
|
+
# through unchanged.
|
72
|
+
def self.encode(value)
|
73
|
+
return nil if value.nil?
|
74
|
+
value.to_s
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the empty string for a "default".
|
78
|
+
def self.default
|
79
|
+
''
|
80
|
+
end
|
81
|
+
end
|
82
|
+
SimpleMapper::Attributes.register_type(:string, ::String, String)
|
83
|
+
|
84
|
+
# Provides basic UUID type support derived from the +simple_uuid+ gem.
|
85
|
+
#
|
86
|
+
# Passes nils through unchanged, but otherwise expects to work with
|
87
|
+
# instances of <tt>SimpleUUID::UUID</tt> or GUID strings. Attributes
|
88
|
+
# using this type will store the data simply as a GUID-conforming string
|
89
|
+
# as implemented by +SimpleUUID::UUID#to_guid+. For both decoding and
|
90
|
+
# encoding, a GUID string or an actual +SimpleUUID::UUID+ instance may
|
91
|
+
# be provided, but the result is always the corresponding GUID string.
|
92
|
+
#
|
93
|
+
# Registered as <tt>:simple_uuid</tt>.
|
94
|
+
module SimpleUUID
|
95
|
+
require 'simple_uuid'
|
96
|
+
EXPECTED_CLASS = ::SimpleUUID::UUID
|
97
|
+
|
98
|
+
# Encoded a <tt>SimpleUUID::UUID</tt> instance, or a GUID string,
|
99
|
+
# as a GUID string. GUID strings for +value+ will be validated by
|
100
|
+
# +SimpleUUID::UUID+ prior to passing through as the result.
|
101
|
+
#
|
102
|
+
# Passes nils through unchanged.
|
103
|
+
def self.encode(value)
|
104
|
+
normalize(value)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Decode a <tt>SimpleUUID::UUID</tt> instance or GUID string into
|
108
|
+
# validated GUID string; strings will be validated by +SimpleUUID::UUID+
|
109
|
+
# prior to passing through as the result.
|
110
|
+
#
|
111
|
+
# Passes nils through unchanged.
|
112
|
+
def self.decode(value)
|
113
|
+
normalize(value)
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.normalize(value)
|
117
|
+
value.nil? ? nil : EXPECTED_CLASS.new(value).to_guid
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns a new GUID string value.
|
121
|
+
def self.default
|
122
|
+
EXPECTED_CLASS.new.to_guid
|
123
|
+
end
|
124
|
+
end
|
125
|
+
SimpleMapper::Attributes.register_type(:simple_uuid, nil, SimpleUUID)
|
126
|
+
|
127
|
+
# Provides timezone-aware second-resolution timestamp support for
|
128
|
+
# basic attributes.
|
129
|
+
#
|
130
|
+
# Attributes of this type will have values that are instances of +DateTime+.
|
131
|
+
# These +DateTime+ values will be reduced to strings of format +'%Y-%m-%d %H:%M:%S%z'+
|
132
|
+
# when converting to a simple structure.
|
133
|
+
#
|
134
|
+
# On input, a +DateTime+ instance or a string matching the above format will be
|
135
|
+
# accepted and map to the proper +DateTime+.
|
136
|
+
#
|
137
|
+
# Decoding or encoding nils will simply pass nil through.
|
138
|
+
#
|
139
|
+
# Registered as type +:timestamp+
|
140
|
+
module Timestamp
|
141
|
+
require 'date'
|
142
|
+
|
143
|
+
FORMAT = '%Y-%m-%d %H:%M:%S%z'
|
144
|
+
|
145
|
+
# Encode a +DateTime+ _value_ as a string of format +'%Y-%m-%d %H:%M:%S%z'+.
|
146
|
+
# Given +nil+ for _value, +nil+ will be returned.
|
147
|
+
def self.encode(value)
|
148
|
+
return nil if value.nil?
|
149
|
+
value.strftime FORMAT
|
150
|
+
end
|
151
|
+
|
152
|
+
# Decode a _value_ string of format +'%Y-%m-%d %H:%M:%S%z' into a +DateTime+ instance.
|
153
|
+
# If given a +DateTime+ instance for _value_, that instance will be returned.
|
154
|
+
# Given +nil+ for _value_, +nil+ will be returned.
|
155
|
+
def self.decode(value)
|
156
|
+
return nil if value.nil?
|
157
|
+
return value if value.instance_of? DateTime
|
158
|
+
DateTime.strptime(value, FORMAT)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Return a new +DateTime+ instance representing the current time. Note that this
|
162
|
+
# will include fractional seconds; this precision is lost when encoded to string,
|
163
|
+
# as the string representation only has a precision to the second.
|
164
|
+
def self.default
|
165
|
+
DateTime.now
|
166
|
+
end
|
167
|
+
end
|
168
|
+
SimpleMapper::Attributes.register_type(:timestamp, DateTime, Timestamp)
|
169
|
+
|
170
|
+
module Integer
|
171
|
+
def self.convert(value)
|
172
|
+
converted = value.to_i
|
173
|
+
unless value == converted or value.to_s == converted.to_s or converted.to_f.to_s == value.to_s
|
174
|
+
raise SimpleMapper::TypeConversionException, "cannot convert #{value} to Integer"
|
175
|
+
end
|
176
|
+
converted
|
177
|
+
end
|
178
|
+
|
179
|
+
# raise a TypeConversionException if not a valid Integer
|
180
|
+
# otherwise, convert to a string
|
181
|
+
def self.encode(value)
|
182
|
+
convert(value).to_s
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.decode(value)
|
186
|
+
convert(value)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
SimpleMapper::Attributes.register_type(:integer, ::Integer, Integer)
|
190
|
+
|
191
|
+
module TimestampHighRes
|
192
|
+
require 'bigdecimal'
|
193
|
+
unless BigDecimal.method_defined?(:to_r)
|
194
|
+
require 'bigdecimal/util'
|
195
|
+
end
|
196
|
+
|
197
|
+
SECOND_FRACTION = Rational(1, 24 * 60 * 60)
|
198
|
+
PATTERN = /^([^.]+)\.(\d+)([-+]\d{4})$/
|
199
|
+
OUT_FORMAT = '%Y-%m-%d %H:%M:%S.%N%z'
|
200
|
+
IN_FORMAT = '%Y-%m-%d %H:%M:%S%z'
|
201
|
+
|
202
|
+
def self.encode(value)
|
203
|
+
return nil if value.nil?
|
204
|
+
value.strftime(OUT_FORMAT)
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.decode(value)
|
208
|
+
if value.kind_of?(DateTime)
|
209
|
+
value
|
210
|
+
elsif value.nil?
|
211
|
+
nil
|
212
|
+
else
|
213
|
+
if match = PATTERN.match(value.to_s)
|
214
|
+
stamp, second_fraction, zone = match.captures
|
215
|
+
subseconds = BigDecimal('0.' + second_fraction).to_r
|
216
|
+
DateTime.strptime(stamp + zone, IN_FORMAT) + (subseconds * SECOND_FRACTION)
|
217
|
+
else
|
218
|
+
raise SimpleMapper::TypeConversionException, "cannot transform '#{value}' into hi-res DateTime"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
SimpleMapper::Attributes.register_type(:timestamp_high_res, ::DateTime, TimestampHighRes)
|
224
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
module SimpleMapper
|
3
|
+
module Collection
|
4
|
+
module CommonMethods
|
5
|
+
attr_accessor :attribute
|
6
|
+
attr_accessor :change_tracking
|
7
|
+
|
8
|
+
def simple_mapper_changes
|
9
|
+
@simple_mapper_changes ||= SimpleMapper::ChangeHash.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def changed_members
|
13
|
+
simple_mapper_changes.keys
|
14
|
+
end
|
15
|
+
|
16
|
+
def member_changed!(key, value)
|
17
|
+
return nil unless change_tracking
|
18
|
+
# If the key is new to the collection, we're fine.
|
19
|
+
# If the key is already in the collection, then we have to consider
|
20
|
+
# whether or not value is itself a mapper. If it is, we want it to consider
|
21
|
+
# all of its attributes changed, since they are all replacing whatever was
|
22
|
+
# previously associated with +key+ in the collection.
|
23
|
+
if ! value.nil? and value.respond_to?(:all_changed!)
|
24
|
+
value.all_changed!
|
25
|
+
end
|
26
|
+
simple_mapper_changes[key] = true
|
27
|
+
end
|
28
|
+
|
29
|
+
# Predicate that returns +true+ if _key_ is present in the collection.
|
30
|
+
# Returns +nil+ by default; this must be implemented appropriately per
|
31
|
+
# class that uses this module.
|
32
|
+
def is_member?(key)
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def []=(key, value)
|
37
|
+
member_changed!(key, value)
|
38
|
+
super(key, value)
|
39
|
+
end
|
40
|
+
|
41
|
+
def build(*args)
|
42
|
+
attribute.mapper.new(*args)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Hash < DelegateClass(::Hash)
|
47
|
+
include CommonMethods
|
48
|
+
|
49
|
+
def is_member?(key)
|
50
|
+
key? key
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize(hash = {})
|
54
|
+
super(hash)
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete(key)
|
58
|
+
member_changed!(key, nil)
|
59
|
+
super(key)
|
60
|
+
end
|
61
|
+
|
62
|
+
def reject!
|
63
|
+
changed = false
|
64
|
+
each do |key, val|
|
65
|
+
if yield(key, val)
|
66
|
+
changed = true
|
67
|
+
delete(key)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
changed ? self : nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def delete_if
|
74
|
+
reject! {|k, v| yield(k, v)}
|
75
|
+
self
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Array < DelegateClass(::Array)
|
80
|
+
include CommonMethods
|
81
|
+
|
82
|
+
def initialize(array=[])
|
83
|
+
super(array)
|
84
|
+
end
|
85
|
+
|
86
|
+
def is_member?(key)
|
87
|
+
key = key.to_i
|
88
|
+
key >= 0 and key < size
|
89
|
+
end
|
90
|
+
|
91
|
+
def keys
|
92
|
+
(0..size - 1).to_a
|
93
|
+
end
|
94
|
+
|
95
|
+
def inject(*args)
|
96
|
+
(0..size - 1).inject(*args) {|accum, key| yield(accum, [key, self[key]])}
|
97
|
+
end
|
98
|
+
|
99
|
+
def <<(value)
|
100
|
+
member_changed!(size, value)
|
101
|
+
super(value)
|
102
|
+
end
|
103
|
+
|
104
|
+
def push(*values)
|
105
|
+
values.each {|val| self << val }
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
def slice!(start_or_range, length=1)
|
110
|
+
result = nil
|
111
|
+
original_size = size
|
112
|
+
case start_or_range
|
113
|
+
when Range
|
114
|
+
result = super(start_or_range)
|
115
|
+
if result
|
116
|
+
change_min = start_or_range.min
|
117
|
+
end
|
118
|
+
else
|
119
|
+
result = super(start_or_range, length)
|
120
|
+
if result
|
121
|
+
change_min = start_or_range < 0 ? original_size + start_or_range : start_or_range
|
122
|
+
end
|
123
|
+
end
|
124
|
+
if result
|
125
|
+
change_min = 0 if change_min < 0
|
126
|
+
(change_min..original_size - 1).each {|index| member_changed!(index, self[index]) }
|
127
|
+
end
|
128
|
+
result
|
129
|
+
end
|
130
|
+
|
131
|
+
alias_method :_delete, :delete_at
|
132
|
+
private :_delete
|
133
|
+
|
134
|
+
def delete_at(index)
|
135
|
+
original_size = size
|
136
|
+
result = _delete(index)
|
137
|
+
if size != original_size
|
138
|
+
(index..original_size - 1).each {|idx| member_changed!(idx, self[idx])}
|
139
|
+
end
|
140
|
+
result
|
141
|
+
end
|
142
|
+
|
143
|
+
def reject!
|
144
|
+
first = nil
|
145
|
+
last = size - 1
|
146
|
+
index = 0
|
147
|
+
while index < size
|
148
|
+
if yield(self[index])
|
149
|
+
first ||= index
|
150
|
+
_delete(index)
|
151
|
+
else
|
152
|
+
index += 1
|
153
|
+
end
|
154
|
+
end
|
155
|
+
if first
|
156
|
+
(first..last).each {|idx| member_changed!(idx, self[idx])}
|
157
|
+
self
|
158
|
+
else
|
159
|
+
nil
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def delete_if
|
164
|
+
reject! {|x| yield(x)}
|
165
|
+
self
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|