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