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.
Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/CHANGELOG.md +26 -0
  4. data/Gemfile +6 -1
  5. data/README.md +2 -0
  6. data/Rakefile +1 -0
  7. data/app/volt/models/active_volt_instance.rb +8 -6
  8. data/app/volt/models/user.rb +11 -2
  9. data/app/volt/models/volt_app_property.rb +8 -0
  10. data/app/volt/tasks/query_tasks.rb +23 -31
  11. data/app/volt/tasks/store_tasks.rb +2 -2
  12. data/app/volt/tasks/volt_admin_tasks.rb +24 -0
  13. data/docs/UPGRADE_GUIDE.md +6 -0
  14. data/lib/volt.rb +19 -12
  15. data/lib/volt/boot.rb +1 -0
  16. data/lib/volt/cli.rb +19 -8
  17. data/lib/volt/cli/console.rb +0 -1
  18. data/lib/volt/cli/generators.rb +14 -3
  19. data/lib/volt/cli/migrate.rb +26 -0
  20. data/lib/volt/config.rb +17 -4
  21. data/lib/volt/controllers/http_controller.rb +12 -0
  22. data/lib/volt/data_stores/base_adaptor_client.rb +2 -2
  23. data/lib/volt/data_stores/base_adaptor_server.rb +2 -0
  24. data/lib/volt/data_stores/data_store.rb +20 -14
  25. data/lib/volt/extra_core/class.rb +28 -14
  26. data/lib/volt/extra_core/hash.rb +5 -0
  27. data/lib/volt/extra_core/string.rb +3 -1
  28. data/lib/volt/helpers/time.rb +9 -43
  29. data/lib/volt/helpers/time/calculations.rb +204 -0
  30. data/lib/volt/helpers/time/distance.rb +63 -0
  31. data/lib/volt/helpers/time/duration.rb +71 -0
  32. data/lib/volt/helpers/time/local_calculations.rb +49 -0
  33. data/lib/volt/helpers/time/local_volt_time.rb +23 -0
  34. data/lib/volt/helpers/time/numeric.rb +59 -0
  35. data/lib/volt/helpers/time/volt_time.rb +170 -0
  36. data/lib/volt/models.rb +5 -0
  37. data/lib/volt/models/array_model.rb +33 -6
  38. data/lib/volt/models/associations.rb +146 -23
  39. data/lib/volt/models/buffer.rb +38 -41
  40. data/lib/volt/models/cursor.rb +15 -0
  41. data/lib/volt/models/errors.rb +11 -0
  42. data/lib/volt/models/field_helpers.rb +108 -68
  43. data/lib/volt/models/helpers/array_model.rb +4 -0
  44. data/lib/volt/models/helpers/base.rb +8 -1
  45. data/lib/volt/models/helpers/change_helpers.rb +31 -12
  46. data/lib/volt/models/helpers/defaults.rb +15 -0
  47. data/lib/volt/models/location.rb +20 -6
  48. data/lib/volt/models/migrations/migration.rb +23 -0
  49. data/lib/volt/models/migrations/migration_runner.rb +146 -0
  50. data/lib/volt/models/model.rb +38 -1
  51. data/lib/volt/models/permissions.rb +8 -1
  52. data/lib/volt/models/persistors/array_store.rb +87 -8
  53. data/lib/volt/models/persistors/base.rb +19 -0
  54. data/lib/volt/models/persistors/model_store.rb +1 -1
  55. data/lib/volt/models/persistors/page.rb +4 -1
  56. data/lib/volt/models/persistors/query/query_identifier.rb +102 -0
  57. data/lib/volt/models/persistors/query/query_listener.rb +57 -12
  58. data/lib/volt/models/root_models/root_models.rb +19 -0
  59. data/lib/volt/models/url.rb +11 -2
  60. data/lib/volt/models/validations/validations.rb +5 -2
  61. data/lib/volt/models/validators/type_validator.rb +11 -0
  62. data/lib/volt/models/validators/unique_validator.rb +2 -2
  63. data/lib/volt/page/bindings/attribute_binding.rb +23 -1
  64. data/lib/volt/page/targets/attribute_section.rb +7 -0
  65. data/lib/volt/page/targets/binding_document/component_node.rb +44 -18
  66. data/lib/volt/page/targets/binding_document/tag_node.rb +41 -0
  67. data/lib/volt/page/tasks.rb +16 -8
  68. data/lib/volt/queries/live_query.rb +109 -0
  69. data/lib/volt/queries/live_query_pool.rb +58 -0
  70. data/lib/volt/queries/live_subquery.rb +0 -0
  71. data/lib/volt/queries/query_association_splitter.rb +31 -0
  72. data/lib/volt/queries/query_diff.rb +100 -0
  73. data/lib/volt/queries/query_runner.rb +110 -0
  74. data/lib/volt/queries/query_subscription.rb +80 -0
  75. data/lib/volt/queries/query_subscription_pool.rb +37 -0
  76. data/lib/volt/reactive/eventable.rb +8 -0
  77. data/lib/volt/reactive/reactive_array.rb +0 -4
  78. data/lib/volt/router/routes.rb +81 -31
  79. data/lib/volt/server/message_bus/base_message_bus.rb +9 -3
  80. data/lib/volt/server/message_bus/peer_to_peer.rb +6 -6
  81. data/lib/volt/server/message_bus/peer_to_peer/server_tracker.rb +1 -1
  82. data/lib/volt/server/middleware/default_middleware_stack.rb +12 -8
  83. data/lib/volt/server/rack/component_paths.rb +31 -4
  84. data/lib/volt/server/rack/http_content_types.rb +62 -0
  85. data/lib/volt/server/rack/http_resource.rb +1 -1
  86. data/lib/volt/server/rack/index_files.rb +8 -1
  87. data/lib/volt/server/rack/opal_files.rb +16 -1
  88. data/lib/volt/server/rack/sprockets_helpers_setup.rb +32 -1
  89. data/lib/volt/server/socket_connection_handler.rb +16 -7
  90. data/lib/volt/server/template_handlers/sprockets_component_handler.rb +5 -3
  91. data/lib/volt/spec/capybara.rb +4 -3
  92. data/lib/volt/spec/setup.rb +5 -0
  93. data/lib/volt/tasks/dispatcher.rb +3 -1
  94. data/lib/volt/utils/data_transformer.rb +4 -4
  95. data/lib/volt/utils/ejson.rb +19 -6
  96. data/lib/volt/utils/promise_extensions.rb +1 -1
  97. data/lib/volt/utils/time_opal_patch.rb +749 -0
  98. data/lib/volt/utils/time_patch.rb +11 -4
  99. data/lib/volt/version.rb +1 -1
  100. data/lib/volt/volt/app.rb +19 -11
  101. data/lib/volt/volt/properties.rb +24 -0
  102. data/lib/volt/volt/server_setup/app.rb +30 -7
  103. data/lib/volt/volt/users.rb +15 -3
  104. data/spec/apps/kitchen_sink/Gemfile +5 -1
  105. data/spec/apps/kitchen_sink/app/main/config/routes.rb +1 -0
  106. data/spec/apps/kitchen_sink/app/main/controllers/save_controller.rb +1 -1
  107. data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +4 -0
  108. data/spec/apps/kitchen_sink/app/main/controllers/todos_controller.rb +4 -2
  109. data/spec/apps/kitchen_sink/app/main/models/post.rb +0 -1
  110. data/spec/apps/kitchen_sink/app/main/models/todo.rb +4 -0
  111. data/spec/apps/kitchen_sink/app/main/views/mailers/reset_password.html +10 -0
  112. data/spec/apps/kitchen_sink/app/main/views/todos/index.html +2 -0
  113. data/spec/apps/kitchen_sink/config/app.rb +2 -0
  114. data/spec/apps/migrations/config/db/migrations/1445111704_migration1.rb +7 -0
  115. data/spec/apps/migrations/config/db/migrations/1445113517_migration2.rb +7 -0
  116. data/spec/apps/migrations/config/db/migrations/1445115200_migration3.rb +7 -0
  117. data/spec/extra_core/class_spec.rb +10 -0
  118. data/spec/helpers/distance_spec.rb +35 -0
  119. data/spec/helpers/duration_spec.rb +160 -0
  120. data/spec/helpers/volt_time_spec.rb +275 -0
  121. data/spec/integration/callbacks_spec.rb +2 -1
  122. data/spec/integration/http_endpoints_spec.rb +4 -0
  123. data/spec/integration/save_spec.rb +1 -1
  124. data/spec/integration/todos_spec.rb +7 -5
  125. data/spec/models/array_model_spec.rb +17 -3
  126. data/spec/models/associations_spec.rb +48 -1
  127. data/spec/models/field_helpers_spec.rb +7 -3
  128. data/spec/models/migrations/migration_runner_spec.rb +69 -0
  129. data/spec/models/model_spec.rb +42 -8
  130. data/spec/models/permissions_spec.rb +20 -8
  131. data/spec/models/persistors/array_store_spec.rb +18 -0
  132. data/spec/models/persistors/page_spec.rb +15 -10
  133. data/spec/models/persistors/store_spec.rb +13 -3
  134. data/spec/models/url_spec.rb +4 -3
  135. data/spec/models/user_spec.rb +6 -3
  136. data/spec/models/user_validation_spec.rb +3 -3
  137. data/spec/models/validations_spec.rb +4 -0
  138. data/spec/models/validators/block_validations_spec.rb +9 -5
  139. data/spec/models/validators/email_validator_spec.rb +2 -0
  140. data/spec/models/validators/lifecycle_callbacks_spec.rb +86 -0
  141. data/spec/models/validators/unique_validator_spec.rb +1 -0
  142. data/spec/page/path_string_renderer_spec.rb +5 -0
  143. data/spec/queries/live_query_spec.rb +16 -0
  144. data/spec/queries/query_association_splitter_spec.rb +14 -0
  145. data/spec/queries/query_diff_spec.rb +132 -0
  146. data/spec/queries/query_identifier_spec.rb +98 -0
  147. data/spec/queries/query_runner_spec.rb +63 -0
  148. data/spec/queries/query_tracker_spec.rb +141 -0
  149. data/spec/router/routes_spec.rb +52 -21
  150. data/spec/server/middleware/rack_content_types_spec.rb +78 -0
  151. data/spec/server/rack/asset_files_spec.rb +38 -30
  152. data/spec/spec_helper.rb +8 -0
  153. data/spec/utils/ejson_spec.rb +9 -8
  154. data/spec/utils/ejson_volt_time_spec.rb +65 -0
  155. data/templates/migration/migration.rb.tt +9 -0
  156. data/templates/newgem/gitignore.tt +1 -0
  157. data/templates/project/Gemfile.tt +19 -2
  158. data/templates/project/README.md.tt +6 -1
  159. data/templates/project/app/main/config/dependencies.rb +6 -0
  160. data/templates/project/config/app.rb.tt +18 -4
  161. data/volt.gemspec +2 -2
  162. metadata +73 -16
  163. data/app/volt/tasks/live_query/live_query_pool.rb +0 -48
  164. data/app/volt/tasks/live_query/query_tracker.rb +0 -92
  165. data/spec/tasks/live_query_spec.rb +0 -18
  166. data/spec/tasks/query_tasks.rb +0 -7
  167. 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 FieldHelpers
3
- class InvalidFieldClass < RuntimeError; end
2
+ module Volt
3
+ module FieldHelpers
4
+ class InvalidFieldClass < RuntimeError; end
4
5
 
5
- NUMERIC_CAST = lambda do |convert_method, val|
6
- begin
7
- orig = val
6
+ NUMERIC_CAST = lambda do |convert_method, val|
7
+ begin
8
+ orig = val
8
9
 
9
- val = send(convert_method, val)
10
+ val = send(convert_method, val)
10
11
 
11
- if RUBY_PLATFORM == 'opal'
12
- # Opal has a bug in 0.7.2 that gives us back NaN without an
13
- # error sometimes.
14
- val = orig if val.nan?
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
- rescue TypeError, ArgumentError => e
17
- # ignore, unmatched types will be caught below.
18
- val = orig
21
+
22
+ return val
19
23
  end
20
24
 
21
- return val
22
- end
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
- FIELD_CASTS = {
25
- String => :to_s.to_proc,
26
- Fixnum => lambda {|val| NUMERIC_CAST[:Integer, val] },
27
- Numeric => lambda {|val| NUMERIC_CAST[:Float, val] },
28
- Float => lambda {|val| NUMERIC_CAST[:Float, val] },
29
- Time => nil,
30
- TrueClass => nil,
31
- FalseClass => nil,
32
- NilClass => nil,
33
- Volt::Boolean => nil
34
- }
35
-
36
-
37
- module ClassMethods
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
- # Add NilClass as an allowed type unless allow_nil: false was passed.
51
- unless options[:allow_nil] == false
52
- klasses << NilClass
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
- self.fields_data ||= {}
57
- self.fields_data[name] = [klasses, options]
67
+ # Normalize default
68
+ options.delete(:default) if options[:default] == nil
58
69
 
59
- if klasses
60
- # Add type validation, execpt for String, since anything can be cast to
61
- # a string.
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
- define_method(name) do
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
- # Cast to the right type
75
- klasses.each do |kl|
76
- if (func = FIELD_CASTS[kl])
77
- # Cast on the first available caster
78
- val = func[val]
79
- break
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
- set(name, val)
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
- def self.included(base)
90
- base.class_attribute :fields_data
91
- base.send :extend, ClassMethods
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
@@ -9,6 +9,10 @@ module Volt
9
9
  def loaded?
10
10
  loaded_state == :loaded
11
11
  end
12
+
13
+ def collection_name
14
+ path.last.gsub('/', '_')
15
+ end
12
16
  end
13
17
  end
14
18
  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(:before_save, :before_validate)
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
- unless Volt.in_mode?(:no_validate)
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, revert changes
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 messages back
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
- # Return result inside of the validate! promise
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
- Volt::Model.no_validate do
70
- run_callbacks(:before_save)
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
@@ -1,9 +1,23 @@
1
- class Location
2
- def host
3
- `document.location.host`
4
- end
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
- def protocol
7
- `document.location.protocol`
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