volt 0.9.6 → 0.9.7.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/CHANGELOG.md +26 -0
- data/Gemfile +6 -1
- data/README.md +2 -0
- data/Rakefile +1 -0
- data/app/volt/models/active_volt_instance.rb +8 -6
- data/app/volt/models/user.rb +11 -2
- data/app/volt/models/volt_app_property.rb +8 -0
- data/app/volt/tasks/query_tasks.rb +23 -31
- data/app/volt/tasks/store_tasks.rb +2 -2
- data/app/volt/tasks/volt_admin_tasks.rb +24 -0
- data/docs/UPGRADE_GUIDE.md +6 -0
- data/lib/volt.rb +19 -12
- data/lib/volt/boot.rb +1 -0
- data/lib/volt/cli.rb +19 -8
- data/lib/volt/cli/console.rb +0 -1
- data/lib/volt/cli/generators.rb +14 -3
- data/lib/volt/cli/migrate.rb +26 -0
- data/lib/volt/config.rb +17 -4
- data/lib/volt/controllers/http_controller.rb +12 -0
- data/lib/volt/data_stores/base_adaptor_client.rb +2 -2
- data/lib/volt/data_stores/base_adaptor_server.rb +2 -0
- data/lib/volt/data_stores/data_store.rb +20 -14
- data/lib/volt/extra_core/class.rb +28 -14
- data/lib/volt/extra_core/hash.rb +5 -0
- data/lib/volt/extra_core/string.rb +3 -1
- data/lib/volt/helpers/time.rb +9 -43
- data/lib/volt/helpers/time/calculations.rb +204 -0
- data/lib/volt/helpers/time/distance.rb +63 -0
- data/lib/volt/helpers/time/duration.rb +71 -0
- data/lib/volt/helpers/time/local_calculations.rb +49 -0
- data/lib/volt/helpers/time/local_volt_time.rb +23 -0
- data/lib/volt/helpers/time/numeric.rb +59 -0
- data/lib/volt/helpers/time/volt_time.rb +170 -0
- data/lib/volt/models.rb +5 -0
- data/lib/volt/models/array_model.rb +33 -6
- data/lib/volt/models/associations.rb +146 -23
- data/lib/volt/models/buffer.rb +38 -41
- data/lib/volt/models/cursor.rb +15 -0
- data/lib/volt/models/errors.rb +11 -0
- data/lib/volt/models/field_helpers.rb +108 -68
- data/lib/volt/models/helpers/array_model.rb +4 -0
- data/lib/volt/models/helpers/base.rb +8 -1
- data/lib/volt/models/helpers/change_helpers.rb +31 -12
- data/lib/volt/models/helpers/defaults.rb +15 -0
- data/lib/volt/models/location.rb +20 -6
- data/lib/volt/models/migrations/migration.rb +23 -0
- data/lib/volt/models/migrations/migration_runner.rb +146 -0
- data/lib/volt/models/model.rb +38 -1
- data/lib/volt/models/permissions.rb +8 -1
- data/lib/volt/models/persistors/array_store.rb +87 -8
- data/lib/volt/models/persistors/base.rb +19 -0
- data/lib/volt/models/persistors/model_store.rb +1 -1
- data/lib/volt/models/persistors/page.rb +4 -1
- data/lib/volt/models/persistors/query/query_identifier.rb +102 -0
- data/lib/volt/models/persistors/query/query_listener.rb +57 -12
- data/lib/volt/models/root_models/root_models.rb +19 -0
- data/lib/volt/models/url.rb +11 -2
- data/lib/volt/models/validations/validations.rb +5 -2
- data/lib/volt/models/validators/type_validator.rb +11 -0
- data/lib/volt/models/validators/unique_validator.rb +2 -2
- data/lib/volt/page/bindings/attribute_binding.rb +23 -1
- data/lib/volt/page/targets/attribute_section.rb +7 -0
- data/lib/volt/page/targets/binding_document/component_node.rb +44 -18
- data/lib/volt/page/targets/binding_document/tag_node.rb +41 -0
- data/lib/volt/page/tasks.rb +16 -8
- data/lib/volt/queries/live_query.rb +109 -0
- data/lib/volt/queries/live_query_pool.rb +58 -0
- data/lib/volt/queries/live_subquery.rb +0 -0
- data/lib/volt/queries/query_association_splitter.rb +31 -0
- data/lib/volt/queries/query_diff.rb +100 -0
- data/lib/volt/queries/query_runner.rb +110 -0
- data/lib/volt/queries/query_subscription.rb +80 -0
- data/lib/volt/queries/query_subscription_pool.rb +37 -0
- data/lib/volt/reactive/eventable.rb +8 -0
- data/lib/volt/reactive/reactive_array.rb +0 -4
- data/lib/volt/router/routes.rb +81 -31
- data/lib/volt/server/message_bus/base_message_bus.rb +9 -3
- data/lib/volt/server/message_bus/peer_to_peer.rb +6 -6
- data/lib/volt/server/message_bus/peer_to_peer/server_tracker.rb +1 -1
- data/lib/volt/server/middleware/default_middleware_stack.rb +12 -8
- data/lib/volt/server/rack/component_paths.rb +31 -4
- data/lib/volt/server/rack/http_content_types.rb +62 -0
- data/lib/volt/server/rack/http_resource.rb +1 -1
- data/lib/volt/server/rack/index_files.rb +8 -1
- data/lib/volt/server/rack/opal_files.rb +16 -1
- data/lib/volt/server/rack/sprockets_helpers_setup.rb +32 -1
- data/lib/volt/server/socket_connection_handler.rb +16 -7
- data/lib/volt/server/template_handlers/sprockets_component_handler.rb +5 -3
- data/lib/volt/spec/capybara.rb +4 -3
- data/lib/volt/spec/setup.rb +5 -0
- data/lib/volt/tasks/dispatcher.rb +3 -1
- data/lib/volt/utils/data_transformer.rb +4 -4
- data/lib/volt/utils/ejson.rb +19 -6
- data/lib/volt/utils/promise_extensions.rb +1 -1
- data/lib/volt/utils/time_opal_patch.rb +749 -0
- data/lib/volt/utils/time_patch.rb +11 -4
- data/lib/volt/version.rb +1 -1
- data/lib/volt/volt/app.rb +19 -11
- data/lib/volt/volt/properties.rb +24 -0
- data/lib/volt/volt/server_setup/app.rb +30 -7
- data/lib/volt/volt/users.rb +15 -3
- data/spec/apps/kitchen_sink/Gemfile +5 -1
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +1 -0
- data/spec/apps/kitchen_sink/app/main/controllers/save_controller.rb +1 -1
- data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +4 -0
- data/spec/apps/kitchen_sink/app/main/controllers/todos_controller.rb +4 -2
- data/spec/apps/kitchen_sink/app/main/models/post.rb +0 -1
- data/spec/apps/kitchen_sink/app/main/models/todo.rb +4 -0
- data/spec/apps/kitchen_sink/app/main/views/mailers/reset_password.html +10 -0
- data/spec/apps/kitchen_sink/app/main/views/todos/index.html +2 -0
- data/spec/apps/kitchen_sink/config/app.rb +2 -0
- data/spec/apps/migrations/config/db/migrations/1445111704_migration1.rb +7 -0
- data/spec/apps/migrations/config/db/migrations/1445113517_migration2.rb +7 -0
- data/spec/apps/migrations/config/db/migrations/1445115200_migration3.rb +7 -0
- data/spec/extra_core/class_spec.rb +10 -0
- data/spec/helpers/distance_spec.rb +35 -0
- data/spec/helpers/duration_spec.rb +160 -0
- data/spec/helpers/volt_time_spec.rb +275 -0
- data/spec/integration/callbacks_spec.rb +2 -1
- data/spec/integration/http_endpoints_spec.rb +4 -0
- data/spec/integration/save_spec.rb +1 -1
- data/spec/integration/todos_spec.rb +7 -5
- data/spec/models/array_model_spec.rb +17 -3
- data/spec/models/associations_spec.rb +48 -1
- data/spec/models/field_helpers_spec.rb +7 -3
- data/spec/models/migrations/migration_runner_spec.rb +69 -0
- data/spec/models/model_spec.rb +42 -8
- data/spec/models/permissions_spec.rb +20 -8
- data/spec/models/persistors/array_store_spec.rb +18 -0
- data/spec/models/persistors/page_spec.rb +15 -10
- data/spec/models/persistors/store_spec.rb +13 -3
- data/spec/models/url_spec.rb +4 -3
- data/spec/models/user_spec.rb +6 -3
- data/spec/models/user_validation_spec.rb +3 -3
- data/spec/models/validations_spec.rb +4 -0
- data/spec/models/validators/block_validations_spec.rb +9 -5
- data/spec/models/validators/email_validator_spec.rb +2 -0
- data/spec/models/validators/lifecycle_callbacks_spec.rb +86 -0
- data/spec/models/validators/unique_validator_spec.rb +1 -0
- data/spec/page/path_string_renderer_spec.rb +5 -0
- data/spec/queries/live_query_spec.rb +16 -0
- data/spec/queries/query_association_splitter_spec.rb +14 -0
- data/spec/queries/query_diff_spec.rb +132 -0
- data/spec/queries/query_identifier_spec.rb +98 -0
- data/spec/queries/query_runner_spec.rb +63 -0
- data/spec/queries/query_tracker_spec.rb +141 -0
- data/spec/router/routes_spec.rb +52 -21
- data/spec/server/middleware/rack_content_types_spec.rb +78 -0
- data/spec/server/rack/asset_files_spec.rb +38 -30
- data/spec/spec_helper.rb +8 -0
- data/spec/utils/ejson_spec.rb +9 -8
- data/spec/utils/ejson_volt_time_spec.rb +65 -0
- data/templates/migration/migration.rb.tt +9 -0
- data/templates/newgem/gitignore.tt +1 -0
- data/templates/project/Gemfile.tt +19 -2
- data/templates/project/README.md.tt +6 -1
- data/templates/project/app/main/config/dependencies.rb +6 -0
- data/templates/project/config/app.rb.tt +18 -4
- data/volt.gemspec +2 -2
- metadata +73 -16
- data/app/volt/tasks/live_query/live_query_pool.rb +0 -48
- data/app/volt/tasks/live_query/query_tracker.rb +0 -92
- data/spec/tasks/live_query_spec.rb +0 -18
- data/spec/tasks/query_tasks.rb +0 -7
- data/spec/tasks/query_tracker_spec.rb +0 -145
@@ -1,93 +1,133 @@
|
|
1
1
|
# Provides a method to setup a field on a model.
|
2
|
-
module
|
3
|
-
|
2
|
+
module Volt
|
3
|
+
module FieldHelpers
|
4
|
+
class InvalidFieldClass < RuntimeError; end
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
6
|
+
NUMERIC_CAST = lambda do |convert_method, val|
|
7
|
+
begin
|
8
|
+
orig = val
|
8
9
|
|
9
|
-
|
10
|
+
val = send(convert_method, val)
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
if RUBY_PLATFORM == 'opal'
|
13
|
+
# Opal has a bug in 0.7.2 that gives us back NaN without an
|
14
|
+
# error sometimes.
|
15
|
+
val = orig if val.nan?
|
16
|
+
end
|
17
|
+
rescue TypeError, ArgumentError => e
|
18
|
+
# ignore, unmatched types will be caught below.
|
19
|
+
val = orig
|
15
20
|
end
|
16
|
-
|
17
|
-
|
18
|
-
val = orig
|
21
|
+
|
22
|
+
return val
|
19
23
|
end
|
20
24
|
|
21
|
-
|
22
|
-
|
25
|
+
FIELD_CASTS = {
|
26
|
+
String => :to_s.to_proc,
|
27
|
+
Fixnum => lambda {|val| NUMERIC_CAST[:Integer, val] },
|
28
|
+
Numeric => lambda {|val| NUMERIC_CAST[:Float, val] },
|
29
|
+
Float => lambda {|val| NUMERIC_CAST[:Float, val] },
|
30
|
+
TrueClass => nil,
|
31
|
+
FalseClass => nil,
|
32
|
+
NilClass => nil,
|
33
|
+
Volt::Boolean => nil
|
34
|
+
}
|
23
35
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# field lets you declare your fields instead of using the underscore syntax.
|
39
|
-
# An optional class restriction can be passed in.
|
40
|
-
def field(name, klasses = nil, options = {})
|
41
|
-
if klasses
|
42
|
-
klasses = [klasses].flatten
|
43
|
-
|
44
|
-
unless klasses.any? {|kl| FIELD_CASTS.key?(kl) }
|
45
|
-
klass_names = FIELD_CASTS.keys.map(&:to_s).join(', ')
|
46
|
-
msg = "valid field types is currently limited to #{klass_names}, you passed: #{klasses.inspect}"
|
47
|
-
fail FieldHelpers::InvalidFieldClass, msg
|
48
|
-
end
|
36
|
+
# If VoltTime has already been required by the time we load this class, we
|
37
|
+
# add it to the field casts:
|
38
|
+
if defined?(VoltTime)
|
39
|
+
FIELD_CASTS[VoltTime] = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
# field lets you declare your fields instead of using the underscore syntax.
|
44
|
+
# An optional class restriction can be passed in.
|
45
|
+
|
46
|
+
def field(name, klasses = nil, options = {})
|
47
|
+
name = name.to_sym
|
48
|
+
if klasses
|
49
|
+
klasses = [klasses].flatten
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
51
|
+
unless klasses.any? {|kl| FIELD_CASTS.key?(kl) }
|
52
|
+
klass_names = FIELD_CASTS.keys.map(&:to_s).join(', ')
|
53
|
+
msg = "valid field types is currently limited to #{klass_names}, you passed: #{klasses.inspect}"
|
54
|
+
fail FieldHelpers::InvalidFieldClass, msg
|
55
|
+
end
|
56
|
+
|
57
|
+
# defined in associations.rb
|
58
|
+
check_name_in_use(name)
|
59
|
+
|
60
|
+
# Add NilClass as an allowed type unless nil: false was passed.
|
61
|
+
unless options[:nil] == false
|
62
|
+
options.delete(:nil)
|
63
|
+
klasses << NilClass
|
64
|
+
end
|
53
65
|
end
|
54
|
-
end
|
55
66
|
|
56
|
-
|
57
|
-
|
67
|
+
# Normalize default
|
68
|
+
options.delete(:default) if options[:default] == nil
|
58
69
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
unless klasses.include?(String)
|
63
|
-
validate name, type: klasses
|
70
|
+
# Store the field defaults
|
71
|
+
if (default_val = options[:default])
|
72
|
+
self.defaults[name] = default_val
|
64
73
|
end
|
65
|
-
end
|
66
74
|
|
67
|
-
|
68
|
-
get(name)
|
69
|
-
end
|
75
|
+
self.fields[name] = [klasses, options]
|
70
76
|
|
71
|
-
define_method(:"#{name}=") do |val|
|
72
|
-
# Check if the value assigned matches the class restriction
|
73
77
|
if klasses
|
74
|
-
#
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
78
|
+
# Add type validation, execpt for String, since anything can be cast to
|
79
|
+
# a string.
|
80
|
+
unless klasses.include?(String)
|
81
|
+
validate name, type: klasses
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# define the fields getter
|
87
|
+
define_method(name) do
|
88
|
+
get(name)
|
89
|
+
end
|
90
|
+
|
91
|
+
define_method(:"#{name}=") do |val|
|
92
|
+
# Check if the value assigned matches the class restriction
|
93
|
+
if klasses
|
94
|
+
# Cast to the right type
|
95
|
+
klasses.each do |kl|
|
96
|
+
if (func = FIELD_CASTS[kl])
|
97
|
+
# Cast on the first available caster
|
98
|
+
val = func[val]
|
99
|
+
break
|
100
|
+
end
|
80
101
|
end
|
81
102
|
end
|
103
|
+
|
104
|
+
set(name, val)
|
82
105
|
end
|
106
|
+
end
|
83
107
|
|
84
|
-
|
108
|
+
def index(columns, options={})
|
109
|
+
# Columns is stored in an array
|
110
|
+
columns = [columns].flatten.map {|c| c.to_sym }
|
111
|
+
|
112
|
+
options[:columns] = columns
|
113
|
+
|
114
|
+
# Add in default name
|
115
|
+
name = (options.delete(:name) || "#{collection_name}_#{columns.join('_')}_index").to_sym
|
116
|
+
self.indexes[name] = options
|
85
117
|
end
|
86
118
|
end
|
87
|
-
end
|
88
119
|
|
89
|
-
|
90
|
-
|
91
|
-
|
120
|
+
def self.included(base)
|
121
|
+
base.class_attribute :fields
|
122
|
+
base.fields = {}
|
123
|
+
|
124
|
+
base.class_attribute :indexes
|
125
|
+
base.indexes = {}
|
126
|
+
|
127
|
+
base.class_attribute :defaults
|
128
|
+
base.defaults = {}
|
129
|
+
|
130
|
+
base.send :extend, ClassMethods
|
131
|
+
end
|
92
132
|
end
|
93
133
|
end
|
@@ -84,7 +84,6 @@ module Volt
|
|
84
84
|
# process_class_name is defined by Model/ArrayModel as
|
85
85
|
# singularize/pluralize
|
86
86
|
klass_name = process_class_name(klass_name = path[index]).camelize
|
87
|
-
|
88
87
|
begin
|
89
88
|
# Lookup the class
|
90
89
|
klass = Object.const_get(klass_name)
|
@@ -121,6 +120,14 @@ module Volt
|
|
121
120
|
|
122
121
|
def self.included(base)
|
123
122
|
base.send :extend, ClassMethods
|
123
|
+
# Used for testing
|
124
|
+
def base.temporary
|
125
|
+
@temporary = true
|
126
|
+
end
|
127
|
+
|
128
|
+
def base.is_temporary
|
129
|
+
@temporary
|
130
|
+
end
|
124
131
|
end
|
125
132
|
|
126
133
|
end
|
@@ -5,7 +5,9 @@ module Volt
|
|
5
5
|
module Helpers
|
6
6
|
module ChangeHelpers
|
7
7
|
def self.included(base)
|
8
|
-
base.setup_action_helpers_in_class(
|
8
|
+
base.setup_action_helpers_in_class(
|
9
|
+
:before_create, :before_update, :before_save, :before_validate
|
10
|
+
)
|
9
11
|
end
|
10
12
|
|
11
13
|
# Called when something in the model changes. Saves
|
@@ -18,10 +20,23 @@ module Volt
|
|
18
20
|
# no_validate mode should only be used internally. no_validate mode is a
|
19
21
|
# performance optimization that prevents validation from running after each
|
20
22
|
# change when assigning multile attributes.
|
21
|
-
|
23
|
+
if Volt.in_mode?(:no_validate)
|
24
|
+
# Didn't run validations, return the model
|
25
|
+
self.then
|
26
|
+
else
|
22
27
|
# Run the validations for all fields
|
23
28
|
result = nil
|
24
29
|
return validate!.then do
|
30
|
+
# Buffers only persist on .save!
|
31
|
+
if buffer?
|
32
|
+
nil
|
33
|
+
else
|
34
|
+
# valid model. Save, then return the model
|
35
|
+
persist_changes(attribute_name)
|
36
|
+
end.then { self }
|
37
|
+
# ^ return the model
|
38
|
+
end.fail do |errors|
|
39
|
+
# invalid model
|
25
40
|
# Buffers are allowed to be in an invalid state
|
26
41
|
unless buffer?
|
27
42
|
# First check that all local validations pass. Any time any
|
@@ -29,27 +44,24 @@ module Volt
|
|
29
44
|
# persist. However, we want to be able to move the model
|
30
45
|
# towards a valid state one change at a time.
|
31
46
|
if error_in_changed_attributes?
|
32
|
-
# Some errors are present
|
47
|
+
# Some errors are present in something that we changed.
|
33
48
|
revert_changes!
|
34
49
|
|
35
|
-
# After we revert, we need to validate again to get the error
|
50
|
+
# After we revert, we need to validate again to get the error
|
51
|
+
# messages back.
|
36
52
|
# TODO: Could probably cache the previous errors.
|
37
53
|
result = validate!.then do
|
38
54
|
# Reject the promise with the errors
|
39
55
|
Promise.new.reject(errs)
|
40
56
|
end
|
41
57
|
else
|
42
|
-
result = persist_changes(attribute_name)
|
58
|
+
result = persist_changes(attribute_name).then
|
43
59
|
end
|
44
60
|
end
|
45
61
|
|
46
|
-
|
47
|
-
result.then { self }
|
62
|
+
result
|
48
63
|
end
|
49
64
|
end
|
50
|
-
|
51
|
-
# Didn't run validations
|
52
|
-
self.then
|
53
65
|
end
|
54
66
|
|
55
67
|
|
@@ -66,8 +78,15 @@ module Volt
|
|
66
78
|
# skip validations when running before_save, this prevents n+1, and allows
|
67
79
|
# the before_save to put the model into an invalid state, which you want
|
68
80
|
# sometimes.
|
69
|
-
|
70
|
-
|
81
|
+
unless buffer?
|
82
|
+
Volt::Model.no_validate do
|
83
|
+
if new?
|
84
|
+
run_callbacks(:before_create)
|
85
|
+
else
|
86
|
+
run_callbacks(:before_update)
|
87
|
+
end
|
88
|
+
run_callbacks(:before_save)
|
89
|
+
end
|
71
90
|
end
|
72
91
|
|
73
92
|
# the changed method on a persistor should return a promise that will
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Volt
|
2
|
+
module Models
|
3
|
+
module Helpers
|
4
|
+
module Defaults
|
5
|
+
def setup_defaults
|
6
|
+
self.class.defaults.each_pair do |field_name, default_value|
|
7
|
+
unless @attributes.has_key?(field_name)
|
8
|
+
@attributes[field_name] = default_value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/volt/models/location.rb
CHANGED
@@ -1,9 +1,23 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
module Volt
|
2
|
+
class Location
|
3
|
+
def host
|
4
|
+
if RUBY_PLATFORM == 'opal'
|
5
|
+
`document.location.host`
|
6
|
+
else
|
7
|
+
Volt.config.domain
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def protocol
|
12
|
+
scheme + ':'
|
13
|
+
end
|
5
14
|
|
6
|
-
|
7
|
-
|
15
|
+
def scheme
|
16
|
+
if RUBY_PLATFORM == 'opal'
|
17
|
+
`document.location.protocol`[0..-2]
|
18
|
+
else
|
19
|
+
Volt.config.scheme || 'http'
|
20
|
+
end
|
21
|
+
end
|
8
22
|
end
|
9
23
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# This is the base class for migrations.
|
2
|
+
require 'volt/reactive/eventable'
|
3
|
+
|
4
|
+
module Volt
|
5
|
+
class Migration
|
6
|
+
extend Eventable
|
7
|
+
def self.inherited(klass)
|
8
|
+
trigger!('inherited', klass)
|
9
|
+
end
|
10
|
+
|
11
|
+
def store
|
12
|
+
Volt.current_app.store
|
13
|
+
end
|
14
|
+
|
15
|
+
def up
|
16
|
+
raise "An up migration was not provided"
|
17
|
+
end
|
18
|
+
|
19
|
+
def down
|
20
|
+
raise "A down migration was not provided"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# The migration runner runs the migrations and tracks the versions that have
|
2
|
+
# been run.
|
3
|
+
require 'volt/models/migrations/migration'
|
4
|
+
|
5
|
+
module Volt
|
6
|
+
class DuplicateMigrationTimestamp < RuntimeError ; end
|
7
|
+
|
8
|
+
class MigrationRunner
|
9
|
+
# The args are passed to each Volt::Migration, (typically just the @db)
|
10
|
+
def initialize(*args)
|
11
|
+
@args = args
|
12
|
+
ensure_migration_versions_table
|
13
|
+
end
|
14
|
+
|
15
|
+
# Runs all migrations up
|
16
|
+
def run(direction=:up, util_version=nil)
|
17
|
+
# Get the disk versions
|
18
|
+
disk_version_paths = self.disk_versions
|
19
|
+
disk_versions = disk_version_paths.map {|v| v[0] }
|
20
|
+
|
21
|
+
# Get the db versions
|
22
|
+
ran_versions = self.all_versions
|
23
|
+
|
24
|
+
if direction == :up
|
25
|
+
# Run all that are on disk, but haven't been run (from the db)
|
26
|
+
need_to_run_versions = disk_versions - ran_versions
|
27
|
+
|
28
|
+
if util_version
|
29
|
+
# remove any versions > the util_version, since the user is saying run
|
30
|
+
# "up" migrations until we hit version X
|
31
|
+
need_to_run_versions.reject! {|version| version > util_version }
|
32
|
+
end
|
33
|
+
else
|
34
|
+
# Run down on all versions that are in the db. If the file doesn't exist
|
35
|
+
need_to_run_versions = ran_versions
|
36
|
+
|
37
|
+
if util_version
|
38
|
+
# remove any versions < the util_version, since the user is saying run
|
39
|
+
# "up" migrations until we hit version X
|
40
|
+
need_to_run_versions.reject! {|version| version < util_version }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
need_to_run_versions.each do |version|
|
45
|
+
path = disk_version_paths[version]
|
46
|
+
|
47
|
+
unless path
|
48
|
+
raise "The database has a migration version of #{version}, but no matching migration file could be found for the down migration"
|
49
|
+
end
|
50
|
+
|
51
|
+
run_migration(path, direction)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Grab the version numbers and files for each migration on disk. Raises an
|
56
|
+
# exception if two migrations have the same number
|
57
|
+
def disk_versions
|
58
|
+
versions = {}
|
59
|
+
Dir["#{Volt.root}/config/db/migrations/*.rb"].map do |path|
|
60
|
+
version = version_for_path(path)
|
61
|
+
|
62
|
+
if (path2 = versions[version])
|
63
|
+
raise DuplicateMigrationTimestamp, "Two migrations have the same version number: #{path} and #{path2}"
|
64
|
+
end
|
65
|
+
|
66
|
+
versions[version] = path
|
67
|
+
end
|
68
|
+
|
69
|
+
versions
|
70
|
+
end
|
71
|
+
|
72
|
+
def version_for_path(path)
|
73
|
+
File.basename(path)[/^[0-9]+/].to_i
|
74
|
+
end
|
75
|
+
|
76
|
+
# Get the number for all versions that have been run
|
77
|
+
def ran_versions
|
78
|
+
migration_versions.all.sync.map {|v| v.version }
|
79
|
+
end
|
80
|
+
|
81
|
+
def run_migration(path, direction=:up)
|
82
|
+
Volt.logger.info("Run #{direction} migration #{File.basename(path)}")
|
83
|
+
version = version_for_path(path)
|
84
|
+
|
85
|
+
# When we require, we use the inherited callback to figure out what class
|
86
|
+
# was loaded.
|
87
|
+
migration_klass = nil
|
88
|
+
listener = Volt::Migration.on('inherited') do |klass|
|
89
|
+
migration_klass = klass
|
90
|
+
end
|
91
|
+
|
92
|
+
require(path)
|
93
|
+
|
94
|
+
# Remove the inherited listener
|
95
|
+
listener.remove
|
96
|
+
|
97
|
+
unless migration_klass
|
98
|
+
raise "No class inheriting from Volt::Migration was defined in #{path}"
|
99
|
+
end
|
100
|
+
|
101
|
+
unless [:up, :down].include?(direction)
|
102
|
+
raise "Only up and down migrations are supported"
|
103
|
+
end
|
104
|
+
|
105
|
+
# Run the up migration
|
106
|
+
migration_klass.new(*@args).send(direction)
|
107
|
+
|
108
|
+
# Remove the object
|
109
|
+
Object.send(:remove_const, migration_klass.name.to_sym)
|
110
|
+
|
111
|
+
# Remove the require
|
112
|
+
$LOADED_FEATURES.reject! {|p| p == path }
|
113
|
+
|
114
|
+
if direction == :up
|
115
|
+
# Track that it ran
|
116
|
+
add_version(version)
|
117
|
+
else
|
118
|
+
# Remove that it ran
|
119
|
+
remove_version(version)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Implement the following in your data provider to allow migrations. We
|
124
|
+
# can't use Volt::Model until after the reconcile step has happened, so
|
125
|
+
# these methods need to work directly with the database.
|
126
|
+
def ensure_migration_versions_table
|
127
|
+
raise "not implemented"
|
128
|
+
end
|
129
|
+
|
130
|
+
def add_version(version)
|
131
|
+
raise "not implemented"
|
132
|
+
end
|
133
|
+
|
134
|
+
def has_version?(version)
|
135
|
+
raise "not implemented"
|
136
|
+
end
|
137
|
+
|
138
|
+
def remove_version(version)
|
139
|
+
raise "not implemented"
|
140
|
+
end
|
141
|
+
|
142
|
+
def all_versions
|
143
|
+
raise "not implemented"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|