volt 0.9.3.pre2 → 0.9.3.pre3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/app/volt/tasks/query_tasks.rb +0 -7
  4. data/app/volt/tasks/store_tasks.rb +0 -6
  5. data/docs/UPGRADE_GUIDE.md +2 -0
  6. data/lib/volt/cli/asset_compile.rb +2 -2
  7. data/lib/volt/cli/console.rb +21 -0
  8. data/lib/volt/config.rb +0 -10
  9. data/lib/volt/controllers/collection_helpers.rb +18 -0
  10. data/lib/volt/controllers/model_controller.rb +2 -12
  11. data/lib/volt/extra_core/object.rb +19 -0
  12. data/lib/volt/models.rb +14 -9
  13. data/lib/volt/models/array_model.rb +62 -22
  14. data/lib/volt/models/associations.rb +16 -1
  15. data/lib/volt/models/model.rb +27 -15
  16. data/lib/volt/models/model_helpers/model_helpers.rb +29 -0
  17. data/lib/volt/models/permissions.rb +15 -4
  18. data/lib/volt/models/persistors/array_store.rb +40 -0
  19. data/lib/volt/models/persistors/model_store.rb +2 -2
  20. data/lib/volt/models/persistors/query/query_listener.rb +3 -1
  21. data/lib/volt/models/persistors/store.rb +2 -1
  22. data/lib/volt/models/root_models/root_models.rb +31 -0
  23. data/lib/volt/models/root_models/store_root.rb +36 -0
  24. data/lib/volt/models/validators/unique_validator.rb +1 -1
  25. data/lib/volt/page/bindings/each_binding.rb +56 -47
  26. data/lib/volt/page/page.rb +5 -5
  27. data/lib/volt/reactive/reactive_array.rb +9 -6
  28. data/lib/volt/server.rb +2 -2
  29. data/lib/volt/server/component_templates.rb +7 -4
  30. data/lib/volt/server/message_bus/message_encoder.rb +9 -1
  31. data/lib/volt/server/rack/component_code.rb +8 -1
  32. data/lib/volt/server/rack/index_files.rb +5 -2
  33. data/lib/volt/tasks/{task_handler.rb → task.rb} +6 -6
  34. data/lib/volt/utils/promise.rb +429 -0
  35. data/lib/volt/utils/promise_extensions.rb +79 -0
  36. data/lib/volt/version.rb +1 -1
  37. data/lib/volt/volt/app.rb +5 -2
  38. data/lib/volt/volt/server_setup/app.rb +28 -7
  39. data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +1 -1
  40. data/spec/apps/kitchen_sink/app/main/views/main/store.html +3 -0
  41. data/spec/extra_core/object_spec.rb +13 -0
  42. data/spec/integration/store_spec.rb +10 -0
  43. data/spec/models/associations_spec.rb +48 -26
  44. data/spec/models/model_spec.rb +23 -7
  45. data/spec/models/persistors/store_spec.rb +28 -0
  46. data/spec/models/validators/unique_validator_spec.rb +1 -1
  47. data/spec/spec_helper.rb +4 -1
  48. data/spec/utils/promise_extensions_spec.rb +42 -0
  49. data/templates/component/config/initializers/boot.rb +10 -0
  50. data/templates/{project/app → component/config/initializers/client}/.empty_directory +0 -0
  51. data/templates/component/config/initializers/server/.empty_directory +0 -0
  52. data/templates/newgem/app/newgem/config/initializers/client/.empty_directory +0 -0
  53. data/templates/newgem/app/newgem/config/initializers/server/.empty_directory +0 -0
  54. data/templates/project/Gemfile.tt +6 -2
  55. data/templates/project/app/main/config/initializers/boot.rb +10 -0
  56. data/templates/project/app/main/config/initializers/client/.empty_directory +0 -0
  57. data/templates/project/app/main/config/initializers/server/.empty_directory +0 -0
  58. data/templates/project/config/app.rb.tt +3 -0
  59. data/templates/project/config/initializers/client/.empty_directory +0 -0
  60. data/templates/project/config/initializers/server/.empty_directory +0 -0
  61. metadata +22 -5
  62. data/lib/volt/utils/promise_patch.rb +0 -70
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 424f310e39b6096bf3372ff3dd5b90e6936718ef
4
- data.tar.gz: fa76365d2cc587f7985e39008f13825f02c6515d
3
+ metadata.gz: f07b58ba0668779997cda13fafd289b7c91451b6
4
+ data.tar.gz: b5b4d4a31d7b2ea898c6c1ce57ad6fa39373b03a
5
5
  SHA512:
6
- metadata.gz: c6371586b5afc5278951bf89478153b6e854edb444503ca8ea312071f74d350c2807aa67eede9f8d64a97bc8c8208649dafd46ae723f58c1cb4308566b3a0ce5
7
- data.tar.gz: 86c5ef5a2b1da23d537103884ee7cdd840be23dae4a59ae22e0c2be0b3ac56652541fcbd5dd808b79c474cda6846e4847ef2839c9a91c5009da931952937c563
6
+ metadata.gz: f8a220c588390c7c6a1f37199b8be4a56129339e8d6450ab18fd142bb9ff7b892465290e6efdd79524c538af793e3f5ef80226fbd1e009704fe656585c8722bf
7
+ data.tar.gz: 5e89589d70e40122664207102c1e4abafb33fe58fcb6aa26827c2ef706a302ce83a1bca8d75f4e8989208d67a0690f2a2f3a4eac6d45cb73afbe1763e88e6a0f
data/CHANGELOG.md CHANGED
@@ -7,8 +7,19 @@
7
7
  - models without an assigned persistor now use the page persistor (which now can provide basic querying)
8
8
  - Volt now pushes updates between mulitple app instances. Updates are pushed between any servers, clients, runners, etc.. that are connected to the same database via the MessageBus (see next)
9
9
  - Volt now comes with a "MessageBus" built in. The message bus provides a pub/sub interface for the app "cluster". Volt provides a default message bus implementation using peer to peer sockets that are automatically managed via the database.
10
+ - You can now nest models on store. Previously store was limited to only storing either values or ArrayModels (associations). You can now store directly, in mongo this will be stored as a nested value.
11
+ - Promises got more awesome. Promises in volt can now proxy methods to their future resolved value. Something like: ```promise.then {|v| v.name }``` can now be written simply as: ```promise.name``` It will still return a promise, but to make life easier:
12
+ - All bindings now support promises directly.
13
+ - You can now set/get properties directly on ```store``` and they will be saved to a ```root_stores_models``` table.
14
+ - All code in config/initializers is now run on app startup.
15
+ - All code in any components config/initializers (app/main/config/initializers/*.rb) is now run on the server during app startup. On the client, only the included components initializers will be run.
16
+ - all initializers folders now support a ```client``` and ```server``` folder.
17
+ - has_one is now supported.
18
+ - You can now use .create to make a new item on a collection.
10
19
 
11
20
  ### Changed
21
+ - All methods on ArrayModel's under the store collection now return a Promise.
22
+ - A #create method was added to ArrayModel.
12
23
  - All logic associated with mongo has been moved into the volt-mongo gem. If you are migrating from a previous version, be sure to add ```gem 'volt-mongo'``` to the Gemfile.
13
24
  - models using the page or store persistor now auto-generate an id when created. This simplifies things since models always have an id. It makes association easier as well. (internally that is)
14
25
  - models now use ```.id``` instead of ```._id``` Queries and saves are mapped to _id in the volt-mongo gem
@@ -18,6 +29,7 @@
18
29
  - controllers now take a Volt::App when created directly.
19
30
  - You can now use .each in attribute bindings.
20
31
  - We moved to csso as the css compressor because it does not require libv8, only an execjs runtime.
32
+ - Each bindings now support promises.
21
33
 
22
34
  ## 0.9.2
23
35
  ### Changed
@@ -1,11 +1,4 @@
1
1
  class QueryTasks < Volt::Task
2
- # The dispatcher passes its self in
3
- def initialize(volt_app, channel, dispatcher = nil)
4
- @volt_app = volt_app
5
- @channel = channel
6
- @dispatcher = dispatcher
7
- end
8
-
9
2
  def add_listener(collection, query)
10
3
  live_query = @volt_app.live_query_pool.lookup(collection, query)
11
4
  track_channel_in_live_query(live_query)
@@ -2,12 +2,6 @@ require 'mongo'
2
2
  require 'volt/models'
3
3
 
4
4
  class StoreTasks < Volt::Task
5
- def initialize(volt_app, channel = nil, dispatcher = nil)
6
- @volt_app = volt_app
7
- @channel = channel
8
- @dispatcher = dispatcher
9
- end
10
-
11
5
  def db
12
6
  @@db ||= Volt::DataStore.fetch
13
7
  end
@@ -11,4 +11,6 @@ gem 'rbnacl-libsodium', require: false
11
11
  # Asset compilation gems, they will be required when needed.
12
12
  gem 'csso-rails', '~> 0.3.4', require: false
13
13
  gem 'uglifier', '>= 2.4.0', require: false
14
+
15
+ gem 'volt-mongo'
14
16
  ```
@@ -22,7 +22,7 @@ module Volt
22
22
  @root_path ||= Dir.pwd
23
23
  Volt.root = @root_path
24
24
 
25
- Volt.boot(@root_path)
25
+ volt_app = Volt.boot(@root_path)
26
26
 
27
27
  require 'volt/server/rack/component_paths'
28
28
  require 'volt/server/rack/component_code'
@@ -35,7 +35,7 @@ module Volt
35
35
  @component_paths = ComponentPaths.new(@root_path)
36
36
  @app = Rack::Builder.new
37
37
  @opal_files = OpalFiles.new(@app, @app_path, @component_paths)
38
- @index_files = IndexFiles.new(@app, @component_paths, @opal_files)
38
+ @index_files = IndexFiles.new(@app, volt_app, @component_paths, @opal_files)
39
39
  @component_handler = ComponentHandler.new(@component_paths)
40
40
 
41
41
  puts 'Compile Opal for components'
@@ -25,6 +25,27 @@ class Pry
25
25
  end
26
26
  end
27
27
  end
28
+
29
+ # The following causes promises in the console to auto-resolve, making it
30
+ # easier to see your data, but harder to follow along. Currently off by
31
+ # default.
32
+ if ENV['AUTO_RESOLVE']
33
+ def evaluate_ruby(code)
34
+ inject_sticky_locals!
35
+ exec_hook :before_eval, code, self
36
+
37
+ result = current_binding.eval(code, Pry.eval_path, Pry.current_line)
38
+
39
+ if result.is_a?(Promise)
40
+ result = result.sync
41
+ end
42
+
43
+ set_last_result(result, code)
44
+ ensure
45
+ update_input_history(code)
46
+ exec_hook :after_eval, result, self
47
+ end
48
+ end
28
49
  end
29
50
 
30
51
  module Volt
data/lib/volt/config.rb CHANGED
@@ -71,16 +71,6 @@ else
71
71
  end
72
72
  end
73
73
 
74
- # Load in all .rb files in the config folder
75
- def run_app_and_initializers
76
- files = ["#{Volt.root}/config/app.rb"]
77
- files += Dir[Volt.root + '/config/initializers/*.rb']
78
-
79
- files.each do |config_file|
80
- require(config_file)
81
- end
82
- end
83
-
84
74
  alias_method :setup, :configure
85
75
  alias_method :config, :configuration
86
76
  end
@@ -0,0 +1,18 @@
1
+ # Collection helpers provide methods to access methods of page directly.
2
+ # @page is expected to be defined and a Volt::Page
3
+ module Volt
4
+ module CollectionHelpers
5
+
6
+ def url_for(params)
7
+ @page.url.url_for(params)
8
+ end
9
+
10
+ def url_with(params)
11
+ @page.url.url_with(params)
12
+ end
13
+
14
+ def store
15
+ @page.store
16
+ end
17
+ end
18
+ end
@@ -1,11 +1,13 @@
1
1
  require 'volt/reactive/reactive_accessors'
2
2
  require 'volt/controllers/actions'
3
3
  require 'volt/controllers/template_helpers'
4
+ require 'volt/controllers/collection_helpers'
4
5
 
5
6
  module Volt
6
7
  class ModelController
7
8
  include ReactiveAccessors
8
9
  include Actions
10
+ include CollectionHelpers
9
11
 
10
12
  # A model controller will have its
11
13
  # class VoltTemplates
@@ -149,10 +151,6 @@ module Volt
149
151
  @page.page
150
152
  end
151
153
 
152
- def store
153
- @page.store
154
- end
155
-
156
154
  def flash
157
155
  @page.flash
158
156
  end
@@ -185,14 +183,6 @@ module Volt
185
183
  @controller ||= Model.new
186
184
  end
187
185
 
188
- def url_for(params)
189
- @page.url.url_for(params)
190
- end
191
-
192
- def url_with(params)
193
- @page.url.url_with(params)
194
- end
195
-
196
186
  # loaded? is a quick way to see if the model for the controller is loaded
197
187
  # yet. If the model is there, it asks the model if its loaded. If the model
198
188
  # was set to a promise, it waits for the promise to resolve.
@@ -19,6 +19,16 @@ class Object
19
19
  end
20
20
  end
21
21
 
22
+ # Convert a non-promise value into a resolved promise. Resolve the block if
23
+ # it takes one.
24
+ def then(&block)
25
+ promisify_and_run_method(:then, &block)
26
+ end
27
+
28
+ # def fail(&block)
29
+ # promisify_and_run_method(:fail, &block)
30
+ # end
31
+
22
32
  def try(*a, &b)
23
33
  if a.empty? && block_given?
24
34
  yield self
@@ -26,4 +36,13 @@ class Object
26
36
  public_send(*a, &b) if respond_to?(a.first)
27
37
  end
28
38
  end
39
+
40
+ private
41
+ def promisify_and_run_method(method_name, &block)
42
+ promise = Promise.new.resolve(self)
43
+
44
+ promise = promise.send(method_name, &block) if block
45
+
46
+ promise
47
+ end
29
48
  end
data/lib/volt/models.rb CHANGED
@@ -9,14 +9,19 @@ require 'volt/models/persistors/params'
9
9
  require 'volt/models/persistors/cookies' if RUBY_PLATFORM == 'opal'
10
10
  require 'volt/models/persistors/flash'
11
11
  require 'volt/models/persistors/local_store'
12
- if RUBY_PLATFORM == 'opal'
13
- require 'promise'
14
- else
15
- # Opal doesn't expose its promise library directly
16
- require 'opal'
12
+ require 'volt/models/root_models/root_models'
13
+ require 'volt/models/root_models/store_root'
17
14
 
18
- gem_dir = File.join(Opal.gem_dir, '..')
19
- require(gem_dir + '/stdlib/promise')
20
- end
15
+ # Fow now, we're keeping a volt copy of the promise library from opal 0.8,
16
+ # since opal 0.7.x's version has some bugs.
17
+ # if RUBY_PLATFORM == 'opal'
18
+ # require 'promise'
19
+ # else
20
+ # # Opal doesn't expose its promise library directly
21
+ # require 'opal'
22
+
23
+ # gem_dir = File.join(Opal.gem_dir, '..')
24
+ # require(gem_dir + '/stdlib/promise')
25
+ # end
21
26
  # TODO: remove once https://github.com/opal/opal/pull/725 is released.
22
- require 'volt/utils/promise_patch'
27
+ require 'volt/utils/promise_extensions'
@@ -14,15 +14,28 @@ module Volt
14
14
 
15
15
  attr_reader :parent, :path, :persistor, :options, :array
16
16
 
17
- # For many methods, we want to call load data as soon as the model is interacted
18
- # with, so we proxy the method, then call super.
19
- def self.proxy_with_root_dep(*method_names)
17
+ # For many methods, we want to register a dependency on the root_dep as soon
18
+ # as the method is called, so it can begin loading. Also, some persistors
19
+ # need to have special loading logic (such as returning a promise instead
20
+ # of immediately returning). To accomplish this, we call the
21
+ # #run_once_loaded method on the persistor.
22
+ def self.proxy_with_load(*method_names)
20
23
  method_names.each do |method_name|
21
- define_method(method_name) do |*args|
24
+ old_method_name = :"__old_#{method_name}"
25
+ alias_method(old_method_name, method_name)
26
+
27
+ define_method(method_name) do |*args, &block|
22
28
  # track on the root dep
23
29
  persistor.try(:root_dep).try(:depend)
24
30
 
25
- super(*args)
31
+ if persistor.respond_to?(:run_once_loaded) &&
32
+ !Volt.in_mode?(:no_model_promises)
33
+ persistor.run_once_loaded(block) do
34
+ send(old_method_name, *args)
35
+ end
36
+ else
37
+ send(old_method_name, *args)
38
+ end
26
39
  end
27
40
  end
28
41
  end
@@ -40,7 +53,6 @@ module Volt
40
53
  end
41
54
  end
42
55
 
43
- proxy_with_root_dep :[], :size, :first, :last, :state_for, :reverse
44
56
  proxy_to_persistor :then, :fetch, :fetch_first, :fetch_each
45
57
 
46
58
  def initialize(array = [], options = {})
@@ -64,6 +76,13 @@ module Volt
64
76
  self
65
77
  end
66
78
 
79
+ def state_for(*args)
80
+ # Track on root dep
81
+ persistor.try(:root_dep).try(:depend)
82
+
83
+ super
84
+ end
85
+
67
86
  # Make sure it gets wrapped
68
87
  def <<(model)
69
88
  if model.is_a?(Model)
@@ -119,6 +138,11 @@ module Volt
119
138
  end
120
139
  end
121
140
 
141
+ # Create does append with a default empty model
142
+ def create(model={})
143
+ append(model)
144
+ end
145
+
122
146
  def delete(val)
123
147
  # Check to make sure the models are allowed to be deleted
124
148
  if !val.is_a?(Model) || val.can_delete?
@@ -129,17 +153,36 @@ module Volt
129
153
  end
130
154
  end
131
155
 
132
- # Find one does a query, but only returns the first item or
133
- # nil if there is no match. Unlike #find, #find_one does not
134
- # return another cursor that you can call .then on.
135
- def find_one(*args, &block)
136
- find(*args, &block).limit(1)[0]
137
- end
138
-
139
156
  def first
140
157
  self[0]
141
158
  end
142
159
 
160
+ # Return the first item in the collection, or create one if one does not
161
+ # exist yet.
162
+ def first_or_create
163
+ first.then do |item|
164
+ if item
165
+ item
166
+ else
167
+ create
168
+ end
169
+ end
170
+ end
171
+
172
+ def last
173
+ self[-1]
174
+ end
175
+
176
+ def reverse
177
+ super
178
+ end
179
+
180
+ # Return the model, on store, .all is proxied to wait for load and return
181
+ # a promise.
182
+ def all
183
+ self
184
+ end
185
+
143
186
  # returns a promise to fetch the first instance
144
187
  def fetch_first(&block)
145
188
  persistor = self.persistor
@@ -186,8 +229,10 @@ module Volt
186
229
  def to_a
187
230
  @size_dep.depend
188
231
  array = []
189
- attributes.size.times do |index|
190
- array << deep_unwrap(self[index])
232
+ Volt.run_in_mode(:no_model_promises) do
233
+ attributes.size.times do |index|
234
+ array << deep_unwrap(self[index])
235
+ end
191
236
  end
192
237
  array
193
238
  end
@@ -216,13 +261,8 @@ module Volt
216
261
  model
217
262
  end
218
263
 
219
- private
264
+ # We need to setup the proxy methods below where they are defined.
265
+ proxy_with_load :first, :[], :size, :last, :reverse, :all, :to_a
220
266
 
221
- # Takes the persistor if there is one and
222
- def setup_persistor(persistor)
223
- # Use page as the default persistor
224
- persistor ||= Persistors::Page
225
- @persistor = persistor.new(self)
226
- end
227
267
  end
228
268
  end
@@ -13,7 +13,7 @@ module Volt
13
13
  lookup_key = get(key_name)
14
14
 
15
15
  # Return a promise for the belongs_to
16
- root.get(method_name.pluralize).where(id: lookup_key).fetch_first
16
+ root.get(method_name.pluralize).where(id: lookup_key).first
17
17
  end
18
18
  end
19
19
  end
@@ -23,6 +23,21 @@ module Volt
23
23
  get(method_name.pluralize, true)
24
24
  end
25
25
  end
26
+
27
+ # has_one creates a method on the Volt::Model that returns a promise
28
+ # to get the associated model.
29
+ def has_one(method_name)
30
+ if method_name.plural?
31
+ raise NameError, "has_one takes a singluar association name"
32
+ end
33
+
34
+ define_method(method_name) do
35
+ association_with_root_model('has_one') do |root|
36
+ key = self.class.to_s.underscore + '_id'
37
+ root.send(method_name.pluralize).where(key => id).first
38
+ end
39
+ end
40
+ end
26
41
  end
27
42
 
28
43
  def self.included(base)
@@ -59,6 +59,9 @@ module Volt
59
59
  }
60
60
 
61
61
  def initialize(attributes = {}, options = {}, initial_state = nil)
62
+ # Start off with empty attributes
63
+ @attributes = {}
64
+
62
65
  # The listener event counter keeps track of how many computations are listening on this model
63
66
  @listener_event_counter = EventCounter.new(
64
67
  -> { parent.try(:persistor).try(:listener_added) },
@@ -132,8 +135,6 @@ module Volt
132
135
 
133
136
  # Assign multiple attributes as a hash, directly.
134
137
  def assign_attributes(attrs, initial_setup = false, skip_changes = false)
135
- @attributes ||= {}
136
-
137
138
  attrs = wrap_values(attrs)
138
139
 
139
140
  if attrs
@@ -146,8 +147,8 @@ module Volt
146
147
  assign_all_attributes(attrs)
147
148
  end
148
149
  else
149
- # Assign to nil
150
- @attributes = attrs
150
+ # Assign to empty
151
+ @attributes = {}
151
152
  end
152
153
 
153
154
  # Trigger and change all
@@ -291,8 +292,10 @@ module Volt
291
292
  end
292
293
  end
293
294
 
294
- def new_model(*args)
295
- Volt::Model.class_at_path(options[:path]).new(*args)
295
+ def new_model(attributes = {}, new_options = {}, initial_state = nil)
296
+ new_options = new_options.merge(persistor: @persistor)
297
+
298
+ Volt::Model.class_at_path(new_options[:path]).new(attributes, new_options, initial_state)
296
299
  end
297
300
 
298
301
  def new_array_model(attributes, options)
@@ -314,9 +317,18 @@ module Volt
314
317
  # loaded_state = self.loaded_state
315
318
  # str += " state:#{loaded_state}" if loaded_state
316
319
 
317
- persistor = self.persistor
320
+ # persistor = self.persistor
318
321
  # str += " persistor:#{persistor.inspect}" if persistor
319
- str += " #{attributes.inspect}>"
322
+
323
+ # Show the :id first, then sort the rest of the attributes
324
+ attrs = attributes.dup
325
+
326
+ id = attrs.delete(:id)
327
+
328
+ attrs = attrs.sort
329
+ attrs.insert(0, [:id, id])
330
+
331
+ str += " #{attrs.to_h.inspect}>"
320
332
 
321
333
  str
322
334
  end
@@ -372,13 +384,6 @@ module Volt
372
384
  end
373
385
  end
374
386
 
375
- # Takes the persistor if there is one and
376
- def setup_persistor(persistor)
377
- # Use page as the default persistor
378
- persistor ||= Persistors::Page
379
- @persistor = persistor.new(self)
380
- end
381
-
382
387
  # Used internally from other methods that assign all attributes
383
388
  def assign_all_attributes(attrs, track_changes = false)
384
389
  # Assign each attribute using setters
@@ -403,5 +408,12 @@ module Volt
403
408
  self.id = generate_id
404
409
  end
405
410
  end
411
+
412
+ def self.inherited(subclass)
413
+ if defined?(RootModels)
414
+ RootModels.add_model_class(subclass)
415
+ end
416
+ end
417
+
406
418
  end
407
419
  end