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
@@ -28,9 +28,9 @@ module Generators
28
28
  method_option :bin, type: :boolean, default: false, aliases: '-b', banner: 'Generate a binary for your library.'
29
29
  method_option :test, type: :string, lazy_default: 'rspec', aliases: '-t', banner: "Generate a test directory for your library: 'rspec' is the default, but 'minitest' is also supported."
30
30
  method_option :edit, type: :string, aliases: '-e',
31
- lazy_default: [ENV['BUNDLER_EDITOR'], ENV['VISUAL'], ENV['EDITOR']].find { |e| !e.nil? && !e.empty? },
32
- required: false, banner: '/path/to/your/editor',
33
- desc: 'Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)'
31
+ lazy_default: [ENV['BUNDLER_EDITOR'], ENV['VISUAL'], ENV['EDITOR']].find { |e| !e.nil? && !e.empty? },
32
+ required: false, banner: '/path/to/your/editor',
33
+ desc: 'Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)'
34
34
  method_option :coc, type: :boolean, desc: "Generate a code of conduct file. Set a default with `bundle config gem.coc true`."
35
35
  method_option :mit, type: :boolean, desc: "Generate an MIT license file"
36
36
 
@@ -100,6 +100,17 @@ module Generators
100
100
  controller(name, component) unless controller_exists?(name, component)
101
101
  end
102
102
 
103
+ desc 'migration NAME', 'creates a migration with the name specified'
104
+ method_option :name, type: :string, banner: 'The name of the migration file'
105
+ def migration(name)
106
+ timestamp = Time.now.to_i
107
+ file_name = "#{timestamp}_#{name.underscore}"
108
+ class_name = name.camelize
109
+ output_file = "#{Dir.pwd}/config/db/migrations/#{file_name}.rb"
110
+
111
+ template('migration/migration.rb.tt', output_file, class_name: class_name)
112
+ end
113
+
103
114
  private
104
115
 
105
116
  def controller_exists?(name, component = 'main')
@@ -0,0 +1,26 @@
1
+ module Volt
2
+ module CliSubclasses
3
+ class Migrate < Thor
4
+ desc 'migrate up', 'migrate up'
5
+ method_option :version, type: :string, banner: 'Migrate up to the specified VERSION'
6
+ def up(version=nil)
7
+ Volt.boot(Dir.pwd)
8
+ require 'volt/models/migrations/migration_runner'
9
+
10
+ Volt::MigrationRunner.new.run(:up, version)
11
+ end
12
+
13
+ desc 'migrate down', 'migrate down'
14
+ method_option :version, type: :string, banner: 'Migrate down to the specified VERSION'
15
+ def down(version=nil)
16
+ Volt.boot(Dir.pwd)
17
+ require 'volt/models/migrations/migration_runner'
18
+
19
+ Volt::MigrationRunner.new.run(:down, version)
20
+ end
21
+
22
+ default_task :up
23
+
24
+ end
25
+ end
26
+ end
data/lib/volt/config.rb CHANGED
@@ -48,12 +48,25 @@ else
48
48
  class << self
49
49
  def defaults
50
50
  app_name = File.basename(Dir.pwd)
51
+
52
+ db_opts = {
53
+ }
54
+
55
+ # The user can use any DB_KEY to assign a value into the database
56
+ # config.
57
+ ENV.keys.grep(/^DB_/).each do |db_key|
58
+ db_opts[db_key.gsub(/^DB_/, '').downcase] = ENV[db_key]
59
+ end
60
+
51
61
  opts = {
52
62
  app_name: app_name,
53
- db_name: (ENV['DB_NAME'] || (app_name + '_' + Volt.env.to_s)).gsub('.', '_'),
54
- db_host: ENV['DB_HOST'] || 'localhost',
55
- db_port: (ENV['DB_PORT'] || 27_017).to_i,
56
- db_driver: ENV['DB_DRIVER'] || 'mongo',
63
+ db: db_opts,
64
+ # db_name: (ENV['DB_NAME'] || (app_name + '_' + Volt.env.to_s)).gsub('.', '_'),
65
+ # db_host: ENV['DB_HOST'] || 'localhost',
66
+ # db_port: (ENV['DB_PORT'] || 27_017).to_i,
67
+ # db_driver: ENV['DB_DRIVER'] || 'postgres',
68
+ # ## TEMP
69
+ # db_uri: "postgres://ryanstout:@localhost:5432/#{(ENV['DB_NAME'] || (app_name + '_' + Volt.env.to_s)).gsub('.', '_')}",
57
70
 
58
71
  # a list of components which should be included in all components
59
72
  default_components: ['volt'],
@@ -65,6 +65,18 @@ module Volt
65
65
  response_body << body
66
66
  end
67
67
 
68
+ def send_file(path)
69
+ if File.exists?(path)
70
+ head(200, {})
71
+ else
72
+ raise "Invalid SendFile: #{path}"
73
+ end
74
+
75
+ # You can pass back a File to rack and it will stream from it and close it
76
+ # when its done.
77
+ @response_body = File.open(path, 'rb')
78
+ end
79
+
68
80
  def respond
69
81
  unless @response_status
70
82
  # render was not called, show an error
@@ -24,8 +24,8 @@ module Volt
24
24
  Volt::ArrayModel.proxy_to_persistor(*method_names)
25
25
 
26
26
  method_names.each do |method_name|
27
- Volt::Persistors::ArrayStore.send(:define_method, method_name) do |*args|
28
- add_query_part(method_name, *args)
27
+ Volt::Persistors::ArrayStore.send(:define_method, method_name) do |*args, &block|
28
+ add_query_part(method_name, *args, &block)
29
29
  end
30
30
  end
31
31
  end
@@ -1,6 +1,8 @@
1
1
  module Volt
2
2
  class DataStore
3
3
  class BaseAdaptorServer
4
+ def initialize(volt_app)
5
+ end
4
6
  end
5
7
  end
6
8
  end
@@ -1,23 +1,29 @@
1
1
  require 'volt/data_stores/base_adaptor_server'
2
+ require 'thread'
2
3
 
3
4
  module Volt
4
5
  class DataStore
5
- def self.fetch
6
- # Cache the driver
7
- return @adaptor if @adaptor
6
+ @@data_store_mutex = Mutex.new
8
7
 
9
- database_name = Volt.config.db_driver
10
- adaptor_name = database_name.camelize + 'AdaptorServer'
8
+ # TODO: cache based on volt_app
9
+ def self.fetch(volt_app)
10
+ @@data_store_mutex.synchronize do
11
+ # Cache the driver
12
+ return @adaptor if @adaptor
11
13
 
12
- root = Volt::DataStore
13
- if root.const_defined?(adaptor_name)
14
- adaptor_name = root.const_get(adaptor_name)
15
- @adaptor = adaptor_name.new
16
- else
17
- raise "#{database_name} is not a supported database, you might be missing a volt-#{database_name} gem"
18
- end
14
+ database_name = Volt.config.db_driver
15
+ adaptor_name = database_name.camelize + 'AdaptorServer'
16
+
17
+ root = Volt::DataStore
18
+ if root.const_defined?(adaptor_name)
19
+ adaptor_name = root.const_get(adaptor_name)
20
+ @adaptor = adaptor_name.new(volt_app)
21
+ else
22
+ raise "#{database_name} is not a supported database (as configured by Volt.config.db_driver), you might be missing a volt-#{database_name} gem"
23
+ end
19
24
 
20
- @adaptor
25
+ @adaptor
26
+ end
21
27
  end
22
28
 
23
29
  def self.adaptor_client
@@ -26,7 +32,7 @@ module Volt
26
32
  ds_name = Volt.config.public.datastore_name
27
33
  unless ds_name
28
34
  raise "No data store configured, please include volt-mongo or " +
29
- "another similar gem."
35
+ "another similar gem."
30
36
  end
31
37
  adaptor_class_name = ds_name.capitalize + "AdaptorClient"
32
38
  Volt::DataStore.const_get(adaptor_class_name)
@@ -1,27 +1,41 @@
1
1
  class Class
2
2
  # Provides a way to make class attributes that inherit. Pass
3
- # in symbols for attribute names
3
+ # in symbols for attribute names. When the class attribute is accessed from
4
+ # a sublcass, it will be duped. This allows the children to receive the value
5
+ # from their parent, but then change it only in the child.
6
+ #
7
+ # NOTE: This does not do a deep clone, so multi-nested values may be changed.
4
8
  def class_attribute(*attrs)
5
9
  attrs.each do |name|
6
- define_singleton_method(name) { nil }
10
+ name = name.to_sym
11
+ ivar = :"@#{name}"
7
12
 
8
- ivar = "@#{name}"
13
+ assigner = :"#{name}="
14
+ define_singleton_method(assigner) do |val|
15
+ instance_variable_set(ivar, val)
16
+ end
17
+
18
+ define_singleton_method(name) do
19
+ if instance_variable_defined?(ivar)
20
+ # Get the value from the instance variable
21
+ val = instance_variable_get(ivar)
22
+ else
23
+ # Fetch from parent and dup
24
+ if superclass.respond_to?(name)
25
+ val = superclass.send(name)
26
+ else
27
+ val = nil
28
+ end
29
+ # We need the numeric check because of: https://github.com/opal/opal/issues/1122
30
+ unless val.is_a?(Numeric)
31
+ val = val.dup rescue val
32
+ end
9
33
 
10
- define_singleton_method("#{name}=") do |val|
11
- singleton_class.class_eval do
12
- remove_possible_method(name)
13
- define_method(name) { val }
34
+ instance_variable_set(ivar, val)
14
35
  end
15
36
 
16
37
  val
17
38
  end
18
39
  end
19
40
  end
20
-
21
- # Removes a method if it is defined.
22
- def remove_possible_method(method)
23
- if method_defined?(method) || private_method_defined?(method)
24
- undef_method(method)
25
- end
26
- end
27
41
  end
@@ -5,4 +5,9 @@ class Hash
5
5
  keys.include?(key)
6
6
  end
7
7
  end
8
+
9
+ # multifetch - returns an array of values for the arg keys
10
+ def mfetch(*args)
11
+ args.map {|k| self[k] }
12
+ end
8
13
  end
@@ -16,7 +16,9 @@ class String
16
16
  # Returns the underscore version of a string. If it is already underscore, it should
17
17
  # return the same string.
18
18
  def underscore
19
- gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
19
+ gsub('::', '/')
20
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2')
21
+ .downcase
20
22
  end
21
23
 
22
24
  def dasherize
@@ -1,43 +1,9 @@
1
- # Much of this class was borrowed from ActiveSupport:
2
- # https://github.com/rails/rails/blob/ca9736e78ca9348e785a5c78c8cc085c0c2d4731/activesupport/lib/active_support/core_ext/time/calculations.rb
3
-
4
- class Time
5
- # Returns a new Time where one or more of the elements have been changed according
6
- # to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
7
- # <tt>:sec</tt>, <tt>:usec</tt>) reset cascadingly, so if only the hour is passed,
8
- # then minute, sec, and usec is set to 0. If the hour and minute is passed, then
9
- # sec and usec is set to 0. The +options+ parameter takes a hash with any of these
10
- # keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>,
11
- # <tt>:sec</tt>, <tt>:usec</tt>.
12
- #
13
- # Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0)
14
- # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0)
15
- # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => Time.new(1981, 8, 29, 0, 0, 0)
16
- def change(options)
17
- new_year = options.fetch(:year, year)
18
- new_month = options.fetch(:month, month)
19
- new_day = options.fetch(:day, day)
20
- new_hour = options.fetch(:hour, hour)
21
- new_min = options.fetch(:min, options[:hour] ? 0 : min)
22
- new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
23
- # new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
24
-
25
- # TODO: Opal doesn't have rational yet, so usec's don't get added in right yet
26
- ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, utc_offset)
27
- end
28
-
29
- def beginning_of_day
30
- #(self - seconds_since_midnight).change(usec: 0)
31
- change(hour: 0, min: 0, sec: 0)
32
- end
33
-
34
- # Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9)
35
- def end_of_day
36
- change(
37
- hour: 23,
38
- min: 59,
39
- sec: 59,
40
- # usec: Rational(999999999, 1000)
41
- )
42
- end
43
- end
1
+ require 'time'
2
+ require 'date'
3
+ require 'volt/helpers/time/volt_time'
4
+ require 'volt/helpers/time/local_volt_time' if RUBY_PLATFORM == 'opal'
5
+ require 'volt/helpers/time/calculations'
6
+ require 'volt/helpers/time/local_calculations' if RUBY_PLATFORM == 'opal'
7
+ require 'volt/helpers/time/duration'
8
+ require 'volt/helpers/time/numeric'
9
+ require 'volt/helpers/time/distance'
@@ -0,0 +1,204 @@
1
+ # Much of this class was borrowed from ActiveSupport:
2
+ # https://github.com/rails/rails/blob/ca9736e78ca9348e785a5c78c8cc085c0c2d4731/activesupport/lib/active_support/core_ext/time/calculations.rb
3
+
4
+ class VoltTime
5
+
6
+ COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
7
+
8
+ class << self
9
+ # Returns the number of days in a month. If no year is provided assumes
10
+ # the current year
11
+ def days_in_month(month, year = now.year)
12
+ if month == 2 && leap?(year)
13
+ 29
14
+ else
15
+ COMMON_YEAR_DAYS_IN_MONTH[month]
16
+ end
17
+ end
18
+
19
+ # Returns true if the year is a leap year, otherwise false
20
+ def leap?(year)
21
+ if year%400 == 0 || (year%4 == 0 && year%100 != 0)
22
+ true
23
+ else
24
+ false
25
+ end
26
+ end
27
+ end
28
+
29
+ # Returns a new Time where one or more of the elements have been changed according
30
+ # to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
31
+ # <tt>:sec</tt>, <tt>:usec</tt>) reset cascadingly, so if only the hour is passed,
32
+ # then minute, sec, and usec is set to 0. If the hour and minute is passed, then
33
+ # sec and usec is set to 0. The +options+ parameter takes a hash with any of these
34
+ # keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>,
35
+ # <tt>:sec</tt>
36
+ #
37
+ # Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0)
38
+ # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0)
39
+ # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => Time.new(1981, 8, 29, 0, 0, 0)
40
+ def change(options)
41
+ new_year = options.fetch(:year, year)
42
+ new_month = options.fetch(:month, month)
43
+ new_day = options.fetch(:day, day)
44
+ new_hour = options.fetch(:hour, hour)
45
+ new_min = options.fetch(:min, options[:hour] ? 0 : min)
46
+ new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
47
+ VoltTime.new(:utc, new_year, new_month, new_day, new_hour, new_min, new_sec)
48
+ end
49
+
50
+ # Returns a new time that has been advanced according to the +options+
51
+ # parameter. The +options+ parameter is a hash with any of these keys:
52
+ # <tt>:years</tt>, <tt>:months</tt>, <tt>:days</tt>, <tt>:hours</tt>, <tt>:mins</tt>,
53
+ # <tt>:secs</tt>
54
+ def advance(options)
55
+ t = self
56
+ t = advance_years(options[:years]) if options[:years]
57
+ t = advance_months(options[:months]) if options[:months]
58
+ t = advance_days(options[:days]) if options[:days]
59
+ t = advance_hours(options[:hours]) if options[:hours]
60
+ t = advance_minutes(options[:mins]) if options[:mins]
61
+ t = advance_seconds(options[:secs]) if options[:secs]
62
+ t
63
+ end
64
+
65
+ # Compares two VoltTime object to the given accuracy
66
+ # The +accuracy+ parameter can be <tt>:year</tt>, <tt>:month</tt>
67
+ # <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>
68
+ # Returns 0 if the two dates are the same to the required accuracy
69
+ # (e.g. the same year or the same day), 1 if the date called on is later
70
+ # than the parameter, or -1 if the date called on is earlier than the parameter
71
+ def compare(other, accuracy)
72
+ if accuracy == :year
73
+ year <=> other.year
74
+ elsif accuracy == :month
75
+ compare_date_components(other, :month, :year)
76
+ elsif accuracy == :day
77
+ compare_date_components(other, :day, :month)
78
+ elsif [:hour, :min, :sec].include? accuracy
79
+ change(accuracy => send(accuracy)) <=> other.change(accuracy => other.send(accuracy))
80
+ end
81
+ end
82
+
83
+ def compare?(other, accuracy)
84
+ compare(other, accuracy) == 0 ? true : false
85
+ end
86
+
87
+ # Advances time by a number of years
88
+ def advance_years(years)
89
+ advance_to_date(to_date >> (years*12))
90
+ end
91
+
92
+ # Advances time by a number of months
93
+ def advance_months(months)
94
+ advance_to_date(to_date >> months)
95
+ end
96
+
97
+ # Advances time by a number of days
98
+ def advance_days(days)
99
+ advance_to_date(to_date + days)
100
+ end
101
+
102
+ # Advances time by a number of seconds
103
+ def advance_seconds(secs)
104
+ self + secs
105
+ end
106
+
107
+ # Advances time by a number of hours
108
+ def advance_hours(hours)
109
+ self + (hours * 60 * 60)
110
+ end
111
+
112
+ # Advances time by a number of minutes
113
+ def advance_minutes(mins)
114
+ self + (mins * 60)
115
+ end
116
+
117
+ # Converts the time to a date
118
+ def to_date
119
+ Date.new(year, month, day)
120
+ end
121
+
122
+ # Advances the time to the given date
123
+ def advance_to_date(date)
124
+ VoltTime.new(:utc, date.year, date.month, date.day, hour, min, sec + (usec/1.0e6))
125
+ end
126
+
127
+ # Adds a duration to the time
128
+ def plus_with_duration(other)
129
+ if other.is_a?(Volt::Duration)
130
+ end
131
+ end
132
+
133
+ # Returns a new VoltTime representing the beginning of the day, 00:00:00
134
+ def beginning_of_day
135
+ change(hour: 0, min: 0, sec: 0)
136
+ end
137
+
138
+ # Returns a new VoltTime representing the end of the day, 23:59:59.999
139
+ # Only milliseconds are supported in Opal
140
+ def end_of_day
141
+ VoltTime.new(:utc, year, month, day, 23, 59, 59.999)
142
+ end
143
+
144
+ # Returns a new Time for the middle of the day i.e. 12:00:00
145
+ def middle_of_day
146
+ change(hour: 12)
147
+ end
148
+
149
+ # Returns the number of seconds since 00:00:00 of the current day
150
+ def seconds_since_midnight
151
+ to_f - change(hour: 0).to_f
152
+ end
153
+
154
+ # Returns the number of seconds to the 23:59:59 of the current day
155
+ def seconds_until_end_of_day
156
+ end_of_day.to_f - to_f
157
+ end
158
+
159
+ # Returns a new VoltTime for the number of seconds ago
160
+ def ago(seconds)
161
+ since(-seconds)
162
+ end
163
+
164
+ # Returns a new VoltTime for the number of seconds since the current time
165
+ def since(seconds)
166
+ VoltTime.new.set_time(@time + seconds)
167
+ end
168
+
169
+ # Returns a new Time for the beginning of the current hour
170
+ def beginning_of_hour
171
+ change(min: 0)
172
+ end
173
+
174
+ # Returns a new Time for the end of the current hour
175
+ # only milliseconds are supported in Opal
176
+ def end_of_hour
177
+ change(min: 59, sec: 59.999)
178
+ end
179
+
180
+ # Returns a new Time for beginning of the current minute
181
+ def beginning_of_minute
182
+ change(sec: 0)
183
+ end
184
+
185
+ # Returns a new Time for the end of the current minute
186
+ def end_of_minute
187
+ change(sec: 59.999)
188
+ end
189
+
190
+ # Returns a Range for the start to end of day
191
+ def all_day
192
+ beginning_of_day..end_of_day
193
+ end
194
+
195
+ private
196
+ def compare_date_components(other, component, higher_component)
197
+ if compare(other, higher_component) == 0
198
+ send(component) <=> other.send(component)
199
+ else
200
+ compare(other, higher_component)
201
+ end
202
+ end
203
+
204
+ end