migratrix 0.0.9 → 0.8.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/lib/migratrix.rb +62 -6
- data/lib/migratrix/exceptions.rb +4 -1
- data/lib/migratrix/{extractors → extractions}/active_record.rb +14 -10
- data/lib/migratrix/{extractors/extractor.rb → extractions/extraction.rb} +21 -20
- data/lib/migratrix/loads/load.rb +43 -0
- data/lib/migratrix/loads/yaml.rb +15 -0
- data/lib/migratrix/migration.rb +115 -27
- data/lib/migratrix/migratrix.rb +43 -84
- data/lib/migratrix/registry.rb +20 -0
- data/lib/migratrix/transforms/map.rb +57 -0
- data/lib/migratrix/transforms/transform.rb +268 -0
- data/lib/migratrix/valid_options.rb +22 -0
- data/lib/patches/object_ext.rb +0 -4
- data/spec/fixtures/migrations/marbles_migration.rb +6 -4
- data/spec/lib/migratrix/{loggable_spec.rb → _loggable_spec.rb} +0 -0
- data/spec/lib/migratrix/extractions/active_record_spec.rb +146 -0
- data/spec/lib/migratrix/extractions/extraction_spec.rb +71 -0
- data/spec/lib/migratrix/loads/load_spec.rb +59 -0
- data/spec/lib/migratrix/loads/yaml_spec.rb +39 -0
- data/spec/lib/migratrix/migration_spec.rb +195 -27
- data/spec/lib/migratrix/migratrix_spec.rb +57 -85
- data/spec/lib/migratrix/registry_spec.rb +28 -0
- data/spec/lib/migratrix/transforms/map_spec.rb +55 -0
- data/spec/lib/migratrix/transforms/transform_spec.rb +134 -0
- data/spec/lib/migratrix_spec.rb +98 -0
- data/spec/lib/patches/object_ext_spec.rb +0 -7
- data/spec/spec_helper.rb +18 -13
- metadata +21 -10
- data/spec/lib/migratrix/extractors/active_record_spec.rb +0 -43
- data/spec/lib/migratrix/extractors/extractor_spec.rb +0 -63
- data/spec/lib/migratrix_module_spec.rb +0 -63
data/lib/migratrix/migratrix.rb
CHANGED
@@ -1,79 +1,12 @@
|
|
1
1
|
# Main "App" or Driver class for Migrating. Responsible for loading
|
2
2
|
# and integrating all the parts of a migration.
|
3
3
|
module Migratrix
|
4
|
-
include ::Migratrix::Loggable
|
5
|
-
|
6
|
-
def self.migrate!(name, options={})
|
7
|
-
::Migratrix::Migratrix.migrate(name, options)
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.reload_migration(name)
|
11
|
-
::Migratrix::Migratrix.reload_migration(name)
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.logger
|
15
|
-
::Migratrix::Migratrix.logger
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.logger=(new_logger)
|
19
|
-
::Migratrix::Migratrix.logger = new_logger
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.log_to(stream)
|
23
|
-
::Migratrix::Migratrix.log_to(stream)
|
24
|
-
end
|
25
|
-
|
26
4
|
class Migratrix
|
27
5
|
include ::Migratrix::Loggable
|
28
6
|
|
29
7
|
def initialize
|
30
8
|
end
|
31
9
|
|
32
|
-
def self.migrate(name, options={})
|
33
|
-
migratrix = self.new()
|
34
|
-
migration = migratrix.create_migration(name, options)
|
35
|
-
migration.migrate
|
36
|
-
migratrix
|
37
|
-
end
|
38
|
-
|
39
|
-
# Loads #{name}_migration.rb from migrations path, instantiates
|
40
|
-
# #{Name}Migration with options, and returns it.
|
41
|
-
def create_migration(name, options={})
|
42
|
-
options = filter_options(options)
|
43
|
-
klass_name = migration_name(name)
|
44
|
-
unless loaded?(klass_name)
|
45
|
-
raise MigrationAlreadyExists.new("Migratrix cannot instantiate class Migratrix::#{klass_name} because it already exists") if ::Migratrix.const_defined?(klass_name)
|
46
|
-
reload_migration name
|
47
|
-
raise MigrationNotDefined.new("Expected migration file #{filename} to define Migratrix::#{klass_name} but it did not") unless ::Migratrix.const_defined?(klass_name)
|
48
|
-
register_migration(klass_name, "Migratrix::#{klass_name}".constantize)
|
49
|
-
end
|
50
|
-
fetch_migration(klass_name).new(options)
|
51
|
-
end
|
52
|
-
|
53
|
-
def migration_name(name)
|
54
|
-
name = name.to_s
|
55
|
-
name = if name.plural?
|
56
|
-
name.classify.pluralize
|
57
|
-
else
|
58
|
-
name.classify
|
59
|
-
end
|
60
|
-
name + "Migration"
|
61
|
-
end
|
62
|
-
|
63
|
-
def filter_options(hash)
|
64
|
-
Hash[valid_options.map {|v| hash.key?(v) ? [v, hash[v]] : nil }.compact]
|
65
|
-
end
|
66
|
-
|
67
|
-
def valid_options
|
68
|
-
%w(limit offset order where)
|
69
|
-
end
|
70
|
-
|
71
|
-
def reload_migration(name)
|
72
|
-
filename = migrations_path + "#{name}_migration.rb"
|
73
|
-
raise MigrationFileNotFound.new("Migratrix cannot find migration file #{filename}") unless File.exists?(filename)
|
74
|
-
load filename
|
75
|
-
end
|
76
|
-
|
77
10
|
# ----------------------------------------------------------------------
|
78
11
|
# Logger singleton; tries to hook into Rails.logger if it exists (it
|
79
12
|
# won't if you log anything during startup because Migratrix is
|
@@ -93,15 +26,15 @@ module Migratrix
|
|
93
26
|
|
94
27
|
def self.init_logger
|
95
28
|
return Rails.logger if Rails.logger
|
96
|
-
|
29
|
+
@logger = create_logger($stdout)
|
97
30
|
end
|
98
31
|
|
99
32
|
def self.logger
|
100
|
-
|
33
|
+
@logger ||= self.init_logger
|
101
34
|
end
|
102
35
|
|
103
36
|
def self.logger=(new_logger)
|
104
|
-
|
37
|
+
@logger = new_logger
|
105
38
|
end
|
106
39
|
# ----------------------------------------------------------------------
|
107
40
|
|
@@ -109,30 +42,56 @@ module Migratrix
|
|
109
42
|
|
110
43
|
# ----------------------------------------------------------------------
|
111
44
|
# Candidate for exract class? MigrationRegistry?
|
112
|
-
def
|
113
|
-
|
45
|
+
def self.registry
|
46
|
+
@registry ||= Hash[[:extractions,:loads,:migrations,:transforms].map {|key| [key, Registry.new]}]
|
114
47
|
end
|
115
48
|
|
116
|
-
|
117
|
-
|
49
|
+
# --------------------
|
50
|
+
# extractions
|
51
|
+
def self.extractions
|
52
|
+
registry[:extractions]
|
118
53
|
end
|
119
54
|
|
120
|
-
def
|
121
|
-
|
55
|
+
def self.register_extraction(class_name, klass, options={})
|
56
|
+
self.extractions.register(class_name, klass, options)
|
122
57
|
end
|
123
58
|
|
124
|
-
def
|
125
|
-
|
59
|
+
def self.extraction(class_name, extraction_name, options={})
|
60
|
+
self.extractions.class_for(class_name).new(extraction_name, options)
|
61
|
+
end
|
62
|
+
# --------------------
|
63
|
+
|
64
|
+
# --------------------
|
65
|
+
# transforms
|
66
|
+
def self.transforms
|
67
|
+
registry[:transforms]
|
126
68
|
end
|
127
|
-
# End MigrationRegistry
|
128
|
-
# ----------------------------------------------------------------------
|
129
69
|
|
130
|
-
def
|
131
|
-
|
70
|
+
def self.register_transform(name, klass, options={})
|
71
|
+
self.transforms.register(name, klass, options)
|
132
72
|
end
|
133
73
|
|
134
|
-
def
|
135
|
-
|
74
|
+
def self.transform(transform_name, class_name, options={})
|
75
|
+
self.transforms.class_for(class_name).new(transform_name, options)
|
136
76
|
end
|
77
|
+
# --------------------
|
78
|
+
|
79
|
+
# --------------------
|
80
|
+
# loads
|
81
|
+
def self.loads
|
82
|
+
registry[:loads]
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.register_load(name, klass, options={})
|
86
|
+
self.loads.register(name, klass, options)
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.load(load_name, class_name, options={})
|
90
|
+
self.loads.class_for(class_name).new(load_name, options)
|
91
|
+
end
|
92
|
+
# --------------------
|
93
|
+
|
94
|
+
# End MigrationRegistry
|
95
|
+
# ----------------------------------------------------------------------
|
137
96
|
end
|
138
97
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Migratrix
|
2
|
+
# Basically a place to store our factories
|
3
|
+
class Registry
|
4
|
+
def register(name, klass, init_options)
|
5
|
+
registry[name] = [klass, init_options]
|
6
|
+
end
|
7
|
+
|
8
|
+
def class_for(name)
|
9
|
+
registry.fetch(name).first
|
10
|
+
end
|
11
|
+
|
12
|
+
def registered?(name)
|
13
|
+
registry.key?(name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def registry
|
17
|
+
@registry ||= {}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Migratrix
|
2
|
+
module Transforms
|
3
|
+
# Map is a transform that maps attributes from the source object
|
4
|
+
# to the target object.
|
5
|
+
#
|
6
|
+
# :transform: a hash with dst => src keys, where dst is an
|
7
|
+
# attribute on the transformed target object and src is either an
|
8
|
+
# attribute on the source object or a Proc that receives the
|
9
|
+
# entire extracted row and returns a value to be set.
|
10
|
+
#
|
11
|
+
# TODO: Right now map makes a lot of hard-coded assumptions as a
|
12
|
+
# result of the primary test case. Notably that target is a Hash,
|
13
|
+
# final class is a Hash keyed by transformed_object[:id], etc.
|
14
|
+
#
|
15
|
+
# TODO: Figure out how to do both of these strategies with Map:
|
16
|
+
#
|
17
|
+
# # Create object and then modify it sequentially
|
18
|
+
# new_object = target.new
|
19
|
+
# map.each do |dst, src|
|
20
|
+
# new_object[dst] = extracted_item[src]
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # Build up creation params and then new the object
|
24
|
+
# hash = Hash.new
|
25
|
+
# map.each do [dst, src]
|
26
|
+
# hash[dst] = extracted_item[src]
|
27
|
+
# end
|
28
|
+
# new_object = target.new(hash)
|
29
|
+
class Map < Transform
|
30
|
+
attr_accessor :map
|
31
|
+
|
32
|
+
def initialize(name, options={})
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_transformed_collection
|
37
|
+
Hash.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_new_object(extracted_row)
|
41
|
+
Hash.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def apply_attribute(object, attribute_or_apply, value)
|
45
|
+
object[attribute_or_apply] = value
|
46
|
+
end
|
47
|
+
|
48
|
+
def extract_attribute(object, attribute_or_extract)
|
49
|
+
object[attribute_or_extract]
|
50
|
+
end
|
51
|
+
|
52
|
+
def store_transformed_object(object, collection)
|
53
|
+
collection[object[:id]] = object
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
module Migratrix
|
2
|
+
module Transforms
|
3
|
+
# Transform base class. A transform takes a collection of
|
4
|
+
# extracted objects and returns a collection of transformed
|
5
|
+
# objects. To do this, it needs to know the following:
|
6
|
+
#
|
7
|
+
# 1. What kind of collection to create. (Array? Hash? Set?
|
8
|
+
# Custom?)
|
9
|
+
# 2. How to transform one extracted object into a transformed
|
10
|
+
# object.
|
11
|
+
# 2.1 How to create the object that will hold the transformed
|
12
|
+
# object's attributes (might be the transformed object's class,
|
13
|
+
# might be a hash of attributes used to create the object itself,
|
14
|
+
# you might even try loading the target object from the
|
15
|
+
# destination db to see if it's already been migrated--and if so,
|
16
|
+
# can it be skipped or does it need to be updated?)
|
17
|
+
# 2.2 What attributes to pull off the extracted object
|
18
|
+
# 2.3 HOW to pull an attribute off the extracted object
|
19
|
+
# 2.4 How to transform each attribute
|
20
|
+
# 2.5 What attributes to store on the target object
|
21
|
+
# 2.6 HOW to store an attribute on the target object
|
22
|
+
# 2.7 How to finalize the transformation
|
23
|
+
# 2.7.1 Did you create the target object by class and have you
|
24
|
+
# been updating it all along? Then yay, this step is a no-op and
|
25
|
+
# you're already done.
|
26
|
+
# 2.7.2 If you're doing a merge transformation, and you haven't
|
27
|
+
# already checked the destination db for an existing record, now's
|
28
|
+
# the time to try to load that sucker and, if successful, to merge
|
29
|
+
# it with your migrated values.
|
30
|
+
# 2.7.3 A common Rails optimization is to create an attributes
|
31
|
+
# hash and new the ActiveRecord object in one call. This is
|
32
|
+
# faster* than creating a blank object and updating its attributes
|
33
|
+
# individually.
|
34
|
+
# 2.8 How to store the finalized object in the collection.
|
35
|
+
# (hash[id]=obj? set << obj?)
|
36
|
+
#
|
37
|
+
# And remember, all of this is just to handle ONE record, albeit
|
38
|
+
# admittedly in the most complicated way possible . For sql->sql
|
39
|
+
# migrations the transformation step is pretty simple. (It doesn't
|
40
|
+
# exist, ha ha. A cross-database INSERT SELECT means that the
|
41
|
+
# extract, transform and load all take place in the extract step.)
|
42
|
+
#
|
43
|
+
# * DANGER DANGER DANGER This is a completely unsubstantiated
|
44
|
+
# optimization claim. I'm *sure* it's faster, though, so that's
|
45
|
+
# good enough, right? TODO: BENCHMARK THIS OR SOMETHING WHATEVER
|
46
|
+
class Transform
|
47
|
+
include ::Migratrix::Loggable
|
48
|
+
include ::Migratrix::ValidOptions
|
49
|
+
attr_accessor :name, :options, :extraction, :transformations
|
50
|
+
|
51
|
+
set_valid_options(
|
52
|
+
:apply_attribute,
|
53
|
+
:extract_attribute,
|
54
|
+
:extraction,
|
55
|
+
:final_class,
|
56
|
+
:finalize_object,
|
57
|
+
:store_transform,
|
58
|
+
:target,
|
59
|
+
:transform,
|
60
|
+
:transform_class,
|
61
|
+
:transform_collection
|
62
|
+
)
|
63
|
+
|
64
|
+
def initialize(name, options={})
|
65
|
+
@name = name
|
66
|
+
@options = options.symbolize_keys
|
67
|
+
@transformations = options[:transform]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Name of the extraction to use. If omitted, returns our name.
|
71
|
+
def extraction
|
72
|
+
options[:extraction] || name
|
73
|
+
end
|
74
|
+
|
75
|
+
# This transform method has strategy magic at every turn. I
|
76
|
+
# expect it to be slow, but we can optimize it later, e.g. by
|
77
|
+
# rolling out a define_method or similar for all of the constant
|
78
|
+
# parts.
|
79
|
+
#
|
80
|
+
# ----------------------------------------------------------------------
|
81
|
+
# Map's strategy, as used by PetsMigration
|
82
|
+
#
|
83
|
+
# create_transformed_collection -> Hash.new
|
84
|
+
# create_new_object -> Hash.new
|
85
|
+
# transformation -> {:id => :id, :name => :name }
|
86
|
+
# extract_attribute -> object[attribute_or_extract]
|
87
|
+
# apply_attribute -> object[attribute] = attribute_or_apply
|
88
|
+
# finalize_object -> no-op
|
89
|
+
# store_transformed_object -> collection[object[:id]] = object
|
90
|
+
# ----------------------------------------------------------------------
|
91
|
+
#
|
92
|
+
# ----------------------------------------------------------------------
|
93
|
+
# Default strategy:
|
94
|
+
#
|
95
|
+
# create_transformed_collection -> Array.new
|
96
|
+
# create_new_object -> @options[:target].new
|
97
|
+
# transformations -> MUST BE SUPPLIED BY CHILD CLASS, e.g. Map's {:dst => :src} hash
|
98
|
+
# extract_attribute -> attribute_or_extract.is_a?(Proc) ? attribute_or_extract.call(object) : object.send(attribute_or_extract)
|
99
|
+
# apply_attribute -> object.send("#{attribute_or_apply}=", value)
|
100
|
+
# finalize_object -> no-op
|
101
|
+
# store_transformed_object -> collection << transformed_object
|
102
|
+
# ----------------------------------------------------------------------
|
103
|
+
#
|
104
|
+
#
|
105
|
+
# Now, can we represent these two strategies as configurations
|
106
|
+
# in the migration? For example, here's one way of representing
|
107
|
+
# PetTypeMigration's strategy, which in the Load step must save
|
108
|
+
# off a YAML dump of a hash of all the pet objects (with just id
|
109
|
+
# and name) keyed
|
110
|
+
#
|
111
|
+
# set_transform :repetition_types, :map,
|
112
|
+
# :map => {:id => :id, :name => :name },
|
113
|
+
# :extract_method => :index,
|
114
|
+
# :apply_method => :index,
|
115
|
+
# :new_class => Hash,
|
116
|
+
# :collection => Hash,
|
117
|
+
# :store_method => lambda {|item, hash| hash[item[:id]] = item },
|
118
|
+
#
|
119
|
+
# So the backing magic is this:
|
120
|
+
#
|
121
|
+
# - We expect :new_class to respond to new() and give us a new,
|
122
|
+
# blank object.
|
123
|
+
#
|
124
|
+
# - Ditto for :collection; it should respond to new()
|
125
|
+
#
|
126
|
+
# - extract_method :index means attr = extracted_object[:name]
|
127
|
+
#
|
128
|
+
# - apply_method :index means the same thing
|
129
|
+
#
|
130
|
+
# - store_method is the only weirdness here, and it might
|
131
|
+
# actually make sense to have names for the most obvious
|
132
|
+
# strategies, like :store_by => { :index => :id } (An array
|
133
|
+
# could use :store_by => :push, Set by :add, etc.)
|
134
|
+
#
|
135
|
+
# - The :final_class option is optional; its presence interacts
|
136
|
+
# with the also-optional :finalize option.
|
137
|
+
#
|
138
|
+
# - The :finalize option is optional; its presence interacts
|
139
|
+
# with the also-optional :final_class option, as follows:
|
140
|
+
#
|
141
|
+
# Both Missing: final_object = new_object
|
142
|
+
# :final_class only: final_obj = FinalClass.new(new_obj)
|
143
|
+
# :finalize only: final_obj = finalize(new_obj)
|
144
|
+
# Both Present: final_obj = FinalClass.new(finalize(obj))
|
145
|
+
#
|
146
|
+
# Examples/Notes:
|
147
|
+
#
|
148
|
+
# - To build ActiveRecord objects quickly, use :new_class =>
|
149
|
+
# Hash, :final_class => ModelClass, :finalize => nil.
|
150
|
+
#
|
151
|
+
# - If your source row is a hash (e.g. select_all or a YAML
|
152
|
+
# load) that needs no transformation, but has more columns
|
153
|
+
# than your ActiveRecord model can accept, you could use a
|
154
|
+
# copy transform (basically a map transform without the map
|
155
|
+
# step; new_object = extracted_object) with something like
|
156
|
+
# :new_class => Hash, :final_class => ModelClass, :finalize
|
157
|
+
# => lambda {|hsh| hsh.dup.keep_if? {|k,v|
|
158
|
+
# k.in?(ModelClass.new.attributes.keys)}}
|
159
|
+
# (That would be HORRIBLY inefficient but there would be
|
160
|
+
# ways to memoize with e.g. before_finalize { @keys =
|
161
|
+
# ModelClass.new.attributes.keys })
|
162
|
+
#
|
163
|
+
# Advanced Magic:
|
164
|
+
# - new_class, collection can be procs returning a new object
|
165
|
+
# - extract_method, apply_method can be procs taking |obj, attr|
|
166
|
+
# and |obj, attr, value|
|
167
|
+
#
|
168
|
+
# Super Advanced Magic (YAGNI):
|
169
|
+
# - instead of procs, take blocks and use define_method on them
|
170
|
+
# so they're faster.
|
171
|
+
def transform(extracted_objects)
|
172
|
+
info "Transform #{name} started transform."
|
173
|
+
transformed_collection = create_transformed_collection
|
174
|
+
extracted_objects.each do |extracted_object|
|
175
|
+
new_object = create_new_object(extracted_object)
|
176
|
+
transformations.each do |attribute_or_apply, attribute_or_extract|
|
177
|
+
apply_attribute(new_object, attribute_or_apply, extract_attribute(extracted_object, attribute_or_extract))
|
178
|
+
end
|
179
|
+
transformed_object = finalize_object(new_object)
|
180
|
+
store_transformed_object(transformed_object, transformed_collection)
|
181
|
+
end
|
182
|
+
info "Transform #{name} finished transform."
|
183
|
+
transformed_collection
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
def create_transformed_collection
|
188
|
+
raise NotImplementedError unless options[:transform_collection]
|
189
|
+
option = options[:transform_collection]
|
190
|
+
case option
|
191
|
+
when Proc
|
192
|
+
option.call
|
193
|
+
when Class
|
194
|
+
option.new
|
195
|
+
else
|
196
|
+
raise TypeError
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def create_new_object(extracted_row)
|
201
|
+
# TODO: this should work like finalize, taking a create and an init.
|
202
|
+
raise NotImplementedError unless options[:transform_class]
|
203
|
+
option = options[:transform_class]
|
204
|
+
case option
|
205
|
+
when Proc
|
206
|
+
option.call(extracted_row)
|
207
|
+
when Class
|
208
|
+
option.new # laaame--should receive extracted_row, see todo above
|
209
|
+
else
|
210
|
+
raise TypeError
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def apply_attribute(object, attribute_or_apply, value)
|
215
|
+
raise NotImplementedError unless options[:apply_attribute]
|
216
|
+
option = options[:apply_attribute]
|
217
|
+
case option
|
218
|
+
when Proc
|
219
|
+
option.call(object, attribute_or_apply, value)
|
220
|
+
when Symbol
|
221
|
+
object.send(option, attribute_or_apply, value)
|
222
|
+
else
|
223
|
+
raise TypeError
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def extract_attribute(object, attribute_or_extract)
|
228
|
+
raise NotImplementedError unless options[:extract_attribute]
|
229
|
+
option = options[:extract_attribute]
|
230
|
+
case option
|
231
|
+
when Proc
|
232
|
+
option.call(object, attribute_or_extract)
|
233
|
+
when Symbol
|
234
|
+
object.send(option, attribute_or_extract)
|
235
|
+
else
|
236
|
+
raise TypeError
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
# Both Missing: final_object = new_object
|
242
|
+
# :final_class only: final_obj = FinalClass.new(new_obj)
|
243
|
+
# :finalize only: final_obj = finalize(new_obj)
|
244
|
+
# Both Present: final_obj = FinalClass.new(finalize(obj))
|
245
|
+
def finalize_object(new_object)
|
246
|
+
return new_object unless options[:final_class] || options[:finalize_object]
|
247
|
+
raise TypeError if options[:finalize_object] && !options[:finalize_object].is_a?(Proc)
|
248
|
+
raise TypeError if options[:final_class] && !options[:final_class].is_a?(Class)
|
249
|
+
new_object = options[:finalize_object].call(new_object) if options[:finalize_object]
|
250
|
+
new_object = options[:final_class].new(new_object) if options[:final_class]
|
251
|
+
new_object
|
252
|
+
end
|
253
|
+
|
254
|
+
def store_transformed_object(object, collection)
|
255
|
+
raise NotImplementedError unless options[:store_transformed_object]
|
256
|
+
option = options[:store_transformed_object]
|
257
|
+
case option
|
258
|
+
when Proc
|
259
|
+
option.call(object, collection)
|
260
|
+
when Symbol
|
261
|
+
collection.send(option, object)
|
262
|
+
else
|
263
|
+
raise TypeError
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|