volt 0.9.6 → 0.9.7.pre2

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.
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