volt 0.5.18 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/Readme.md +14 -0
  3. data/VERSION +1 -1
  4. data/app/volt/controllers/notices_controller.rb +9 -0
  5. data/app/volt/tasks/live_query/data_store.rb +12 -0
  6. data/app/volt/tasks/live_query/live_query.rb +86 -0
  7. data/app/volt/tasks/live_query/live_query_pool.rb +36 -0
  8. data/app/volt/tasks/live_query/query_tracker.rb +95 -0
  9. data/app/volt/tasks/query_tasks.rb +57 -0
  10. data/app/volt/tasks/store_tasks.rb +4 -17
  11. data/lib/volt.rb +2 -0
  12. data/lib/volt/console.rb +1 -1
  13. data/lib/volt/controllers/model_controller.rb +4 -0
  14. data/lib/volt/extra_core/array.rb +9 -0
  15. data/lib/volt/extra_core/extra_core.rb +1 -0
  16. data/lib/volt/extra_core/hash.rb +11 -0
  17. data/lib/volt/extra_core/object.rb +4 -0
  18. data/lib/volt/models/array_model.rb +56 -0
  19. data/lib/volt/models/model.rb +6 -11
  20. data/lib/volt/models/model_helpers.rb +12 -0
  21. data/lib/volt/models/persistors/array_store.rb +120 -21
  22. data/lib/volt/models/persistors/model_identity_map.rb +12 -0
  23. data/lib/volt/models/persistors/model_store.rb +20 -60
  24. data/lib/volt/models/persistors/query/query_listener.rb +87 -0
  25. data/lib/volt/models/persistors/query/query_listener_pool.rb +9 -0
  26. data/lib/volt/models/persistors/store.rb +11 -13
  27. data/lib/volt/models/url.rb +1 -1
  28. data/lib/volt/page/bindings/attribute_binding.rb +2 -2
  29. data/lib/volt/page/bindings/base_binding.rb +13 -1
  30. data/lib/volt/page/bindings/component_binding.rb +1 -1
  31. data/lib/volt/page/bindings/content_binding.rb +2 -2
  32. data/lib/volt/page/bindings/each_binding.rb +25 -21
  33. data/lib/volt/page/bindings/event_binding.rb +4 -6
  34. data/lib/volt/page/bindings/if_binding.rb +4 -5
  35. data/lib/volt/page/bindings/template_binding.rb +4 -4
  36. data/lib/volt/page/channel.rb +0 -1
  37. data/lib/volt/page/document.rb +7 -0
  38. data/lib/volt/page/page.rb +4 -4
  39. data/lib/volt/page/reactive_template.rb +2 -2
  40. data/lib/volt/page/targets/dom_section.rb +5 -0
  41. data/lib/volt/page/tasks.rb +10 -40
  42. data/lib/volt/page/template_renderer.rb +4 -4
  43. data/lib/volt/reactive/events.rb +14 -0
  44. data/lib/volt/reactive/reactive_array.rb +17 -7
  45. data/lib/volt/reactive/reactive_value.rb +65 -1
  46. data/lib/volt/server.rb +1 -1
  47. data/lib/volt/server/if_binding_setup.rb +3 -1
  48. data/lib/volt/server/socket_connection_handler.rb +7 -5
  49. data/lib/volt/server/template_parser.rb +7 -7
  50. data/lib/volt/tasks/dispatcher.rb +3 -0
  51. data/lib/volt/utils/ejson.rb +9 -0
  52. data/lib/volt/utils/generic_counting_pool.rb +44 -0
  53. data/lib/volt/utils/generic_pool.rb +88 -0
  54. data/spec/models/reactive_array_spec.rb +43 -0
  55. data/spec/models/reactive_generator_spec.rb +58 -0
  56. data/spec/models/reactive_value_spec.rb +6 -0
  57. data/spec/page/bindings/content_binding_spec.rb +36 -0
  58. data/spec/spec_helper.rb +13 -12
  59. data/spec/tasks/live_query_spec.rb +20 -0
  60. data/spec/tasks/query_tasks.rb +10 -0
  61. data/spec/tasks/query_tracker_spec.rb +120 -0
  62. data/spec/templates/template_binding_spec.rb +16 -10
  63. data/spec/utils/generic_counting_pool_spec.rb +36 -0
  64. data/spec/utils/generic_pool_spec.rb +50 -0
  65. metadata +29 -5
  66. data/app/volt/tasks/channel_tasks.rb +0 -55
  67. data/spec/tasks/channel_tasks_spec.rb +0 -74
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 772c4d0aaa812cfcf06f57870866fb11a7048e5f
4
- data.tar.gz: 2acf6521c5efa480cb06857becb68133cce953c5
3
+ metadata.gz: b53ad931d8386ec350af55adf688c865a96021aa
4
+ data.tar.gz: db65b8addb0e41e518cc1494a0a75fdee4afc84a
5
5
  SHA512:
6
- metadata.gz: 34531249996f8ef4b435c60b70d08fdccc1558049f669ea19c738ce5bf05b8c7d6ea9452b1ca0e135d5d472be3d5cd7bdc45a550c044a54f6597d8e2f0fbc80f
7
- data.tar.gz: b6d154d43b4226306965eed1aa8b04b4b15891b5c4c133dac70a0565e838d058203b9bd15a6b77810b73b1d1d55aa6b4c20e6e862b96c48256352cc5d7fc47ab
6
+ metadata.gz: 6b32dffb18e5e0e0052c71c15f7d3540fd1d7bcabb0d3c04d6b8187d4ec40044b4746fdb87a53a271c91bffad9a25cf49fc8bd1e3928bf4916ce839823b34594
7
+ data.tar.gz: 97d1d0c43d453482904cff905109192dccdfb18a388fddc5fa285e1958241802da9b64aeb6ed652d801d1d5aac6c4f1ad2d1c2ec6e932eb7dfd0de7117d61373
data/Readme.md CHANGED
@@ -196,6 +196,8 @@ If you want these to be used reactively, see the section on [with](#with)
196
196
 
197
197
  Also, due to a small limitation in ruby, ReactiveValue's always are truthy. See the [truthy checks](#truthy-checks-true-false-or-and-and) section on how to check for truth.
198
198
 
199
+ When passing something that may contain reactive values to a JS function, you can call ```.deep_cur``` on any object to get back a copy that will have all reactive value's turned into their current value.
200
+
199
201
  ### Current Status
200
202
 
201
203
  NOTE: currently ReactiveValue's are not complete. At the moment, they do not handle methods that are passed blocks (or procs, lambda's). This is planned, but not complete. At the moment you can use [with](#with) to accomplish similar things.
@@ -689,3 +691,15 @@ Controllers provide a .channel method, that you can use to get the status of the
689
691
  ## Accessing DOM section in a controller
690
692
 
691
693
  TODO
694
+
695
+
696
+ # Data Store
697
+
698
+ **Work in process**
699
+
700
+ | state | events bound | description |
701
+ |-------------|--------------|--------------------------------------------------------------|
702
+ | not_loaded | no | no events and no one has accessed the data in the model |
703
+ | loading | maybe | someone either accessed the data or bound an event |
704
+ | loaded | yes | data is loaded and there is an event bound |
705
+ | dirty | no | data was either accessed without binding an event, or an event was bound, but later unbound. |
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.18
1
+ 0.6.0
@@ -1,4 +1,13 @@
1
1
  class Volt
2
2
  class NoticesController < ModelController
3
+ model :page
4
+
5
+ def hey
6
+ "yep"
7
+ end
8
+
9
+ def page
10
+ $page.page
11
+ end
3
12
  end
4
13
  end
@@ -0,0 +1,12 @@
1
+ require 'mongo'
2
+
3
+ class DataStore
4
+ def initialize
5
+ @@mongo_db ||= Mongo::MongoClient.new("localhost", 27017)
6
+ @@db ||= @@mongo_db.db("development")
7
+ end
8
+
9
+ def query(collection, query)
10
+ @@db[collection].find(query).to_a
11
+ end
12
+ end
@@ -0,0 +1,86 @@
1
+ require_relative 'query_tracker'
2
+
3
+ # Tracks a channel and a query on a collection. Alerts
4
+ # the listener when the data in the query changes.
5
+ class LiveQuery
6
+ attr_reader :current_ids, :collection, :query
7
+
8
+ def initialize(pool, data_store, collection, query)
9
+ @pool = pool
10
+ @collection = collection
11
+ @query = query
12
+
13
+ @channels = []
14
+ @data_store = data_store
15
+
16
+ @query_tracker = QueryTracker.new(self, @data_store)
17
+
18
+ run
19
+ end
20
+
21
+ def run(skip_channel=nil)
22
+ @query_tracker.run(skip_channel)
23
+ end
24
+
25
+ def notify_removed(ids, skip_channel)
26
+ notify! do |channel|
27
+ # puts "Removed: #{ids.inspect} to #{channel.inspect}"
28
+ channel.send_message("removed", nil, @collection, @query, ids)
29
+ end
30
+ end
31
+
32
+ def notify_added(index, data, skip_channel)
33
+ notify! do |channel|
34
+ # puts "Added: #{index} - #{data.inspect} to #{channel.inspect}"
35
+ channel.send_message("added", nil, @collection, @query, index, data)
36
+ end
37
+ end
38
+
39
+ def notify_moved(id, new_position, skip_channel)
40
+ notify! do |channel|
41
+ # puts "Moved: #{id}, #{new_position} to #{channel.inspect}"
42
+ channel.send_message("moved", nil, @collection, @query, id, new_position)
43
+ end
44
+ end
45
+
46
+ def notify_changed(id, data, skip_channel)
47
+ notify!(skip_channel) do |channel|
48
+ # puts "Changed: #{id}, #{data} to #{channel.inspect}"
49
+ channel.send_message("changed", nil, @collection, @query, id, data)
50
+ end
51
+ end
52
+
53
+
54
+ # return the query results the first time a channel connects
55
+ def initial_data
56
+ @query_tracker.results.map.with_index {|data, index| [index, data] }
57
+ end
58
+
59
+ def add_channel(channel)
60
+ @channels << channel
61
+ end
62
+
63
+ def remove_channel(channel)
64
+ @channels.delete(channel)
65
+
66
+ if @channels.size == 0
67
+ # remove this query, no one is listening anymore
68
+ @pool.remove(@collection, @query)
69
+ end
70
+ end
71
+
72
+ def notify!(skip_channel=nil, only_channel=nil)
73
+ if only_channel
74
+ channels = [only_channel]
75
+ else
76
+ channels = @channels
77
+ end
78
+
79
+ channels = channels.reject {|c| c == skip_channel }
80
+
81
+ channels.each do |channel|
82
+ yield(channel)
83
+ end
84
+ end
85
+
86
+ end
@@ -0,0 +1,36 @@
1
+ require_relative 'live_query'
2
+ require 'volt/utils/generic_pool'
3
+
4
+ class LiveQueryPool < GenericPool
5
+ def initialize(data_store)
6
+ super()
7
+ @data_store = data_store
8
+ end
9
+
10
+ def lookup(collection, query)
11
+ query = normalize_query(query)
12
+
13
+ return super(collection, query)
14
+ end
15
+
16
+ def updated_collection(collection, skip_channel)
17
+ lookup_all(collection).each do |live_query|
18
+ # puts "RUN ON: #{live_query} with #{live_query.instance_variable_get('@channels').inspect}"
19
+ live_query.run(skip_channel)
20
+ end
21
+ end
22
+
23
+ private
24
+ # Creates the live query if it doesn't exist, and stores it so it
25
+ # can be found later.
26
+ def create(collection, query)
27
+ # If not already setup, create a new one for this collection/query
28
+ return LiveQuery.new(self, @data_store, collection, query)
29
+ end
30
+
31
+ def normalize_query(query)
32
+ # TODO: add something to sort query properties so the queries are
33
+ # always compared the same.
34
+ return query
35
+ end
36
+ end
@@ -0,0 +1,95 @@
1
+ # The query tracker runs queries and then tracks the changes
2
+ # that take place.
3
+ class QueryTracker
4
+ attr_accessor :results
5
+ def initialize(live_query, data_store)
6
+ @live_query = live_query
7
+ @data_store = data_store
8
+
9
+ # Stores the list of id's currently associated with this query
10
+ @current_ids = []
11
+ @results = []
12
+ @results_hash = {}
13
+ end
14
+
15
+ # Runs the query, stores the results and updates the current_ids
16
+ def run(skip_channel=nil)
17
+ @previous_results = @results
18
+ @previous_results_hash = @results_hash
19
+ @previous_ids = @current_ids
20
+
21
+ # Run the query again
22
+ @results = @data_store.query(@live_query.collection, @live_query.query)
23
+
24
+ # Update the current_ids
25
+ @current_ids = @results.map {|r| r['_id'] }
26
+ @results_hash = Hash[@results.map {|r| [r['_id'], r] }]
27
+
28
+ process_changes(skip_channel)
29
+ end
30
+
31
+ # Looks at the changes in the last run and sends out notices
32
+ # all changes.
33
+ def process_changes(skip_channel)
34
+ if @previous_ids
35
+ detect_removed(skip_channel)
36
+
37
+ detect_added_and_moved(skip_channel)
38
+
39
+ detect_changed(skip_channel)
40
+ end
41
+ end
42
+
43
+ def detect_removed(skip_channel)
44
+ # Removed models
45
+ removed_ids = @previous_ids - @current_ids
46
+ if removed_ids.size > 0
47
+ @live_query.notify_removed(removed_ids, skip_channel)
48
+ end
49
+
50
+ # Update @previous_ids to relect the removed
51
+ @previous_ids = @previous_ids & @current_ids
52
+ end
53
+
54
+ # Loop through the new list, tracking in the old, notifies of any that
55
+ # have been added or moved.
56
+ def detect_added_and_moved(skip_channel)
57
+ previous_index = 0
58
+ @current_ids.each_with_index do |id,index|
59
+ if (cur_previous = @previous_ids[previous_index]) && cur_previous == id
60
+ # Same in both previous and new
61
+ previous_index += 1
62
+ next
63
+ end
64
+
65
+ # We have an item that didn't match the current position's previous
66
+ # TODO: make a hash so we don't have to do include?
67
+ if @previous_ids.include?(id)
68
+ # The location from the previous has changed, move to correct location.
69
+
70
+ # Remove from previous_ids, since it will be moved and we will be past it.
71
+ @previous_ids.delete(id)
72
+
73
+ @live_query.notify_moved(id, index, skip_channel)
74
+ else
75
+ # TODO: Faster lookup
76
+ data = @results_hash[id]
77
+ @live_query.notify_added(index, data, skip_channel)
78
+ end
79
+ end
80
+ end
81
+
82
+ # Finds all items in the previous results that have new values, and alerts
83
+ # of changes.
84
+ def detect_changed(skip_channel)
85
+ not_added_or_removed = @previous_ids & @current_ids
86
+
87
+ not_added_or_removed.each do |id|
88
+ if @previous_results_hash[id] != (data = @results_hash[id])
89
+ # Data hash changed
90
+ @live_query.notify_changed(id, data, skip_channel)
91
+ end
92
+ end
93
+ end
94
+
95
+ end
@@ -0,0 +1,57 @@
1
+ require_relative 'live_query/data_store'
2
+ require_relative 'live_query/live_query_pool'
3
+
4
+ class QueryTasks
5
+ @@live_query_pool = LiveQueryPool.new(DataStore.new)
6
+ @@channel_live_queries = {}
7
+
8
+ def self.live_query_pool
9
+ @@live_query_pool
10
+ end
11
+
12
+ # The dispatcher passes its self in
13
+ def initialize(channel, dispatcher=nil)
14
+ @channel = channel
15
+ @dispatcher = dispatcher
16
+ end
17
+
18
+ def add_listener(collection, query)
19
+ live_query = @@live_query_pool.lookup(collection, query)
20
+ track_channel_in_live_query(live_query)
21
+
22
+ live_query.add_channel(@channel)
23
+
24
+ # Return the initial data
25
+ return live_query.initial_data
26
+ end
27
+
28
+ # Remove a listening channel, the LiveQuery will automatically remove
29
+ # itsself from the pool when there are no channels.
30
+ def remove_listener(collection, query)
31
+ live_query = @@live_query_pool.lookup(collection, query)
32
+ live_query.remove_channel(@channel)
33
+ end
34
+
35
+
36
+ # Removes a channel from all associated live queries
37
+ def close!
38
+ live_queries = @@channel_live_queries[@channel]
39
+
40
+ if live_queries
41
+ live_queries.each do |live_query|
42
+ live_query.remove_channel(@channel)
43
+ end
44
+ end
45
+
46
+ @@channel_live_queries.delete(@channel)
47
+ end
48
+
49
+ private
50
+ # Tracks that this channel will be notified from the live query.
51
+ def track_channel_in_live_query(live_query)
52
+ @@channel_live_queries[@channel] ||= []
53
+ @@channel_live_queries[@channel] << live_query
54
+ end
55
+
56
+
57
+ end
@@ -1,5 +1,4 @@
1
1
  require 'mongo'
2
- require_relative 'channel_tasks'
3
2
 
4
3
  class StoreTasks
5
4
  def initialize(channel=nil, dispatcher=nil)
@@ -15,7 +14,7 @@ class StoreTasks
15
14
  end
16
15
 
17
16
  def save(collection, data)
18
- puts "Insert: #{data.inspect} on #{collection.inspect}"
17
+ # puts "Insert: #{data.inspect} on #{collection.inspect}"
19
18
 
20
19
  data = data.symbolize_keys
21
20
  id = data[:_id]
@@ -24,10 +23,6 @@ class StoreTasks
24
23
  # TODO: Seems mongo is dumb and doesn't let you upsert with custom id's
25
24
  begin
26
25
  @@db[collection].insert(data)
27
-
28
- # Message that we inserted a new item
29
- puts "SENDING DATA: #{data.inspect}"
30
- ChannelTasks.send_message_to_channel("#{collection}-added", ['added', nil, collection, data], @channel)
31
26
  rescue Mongo::OperationFailure => error
32
27
  # Really mongo client?
33
28
  if error.message[/^11000[:]/]
@@ -40,21 +35,13 @@ class StoreTasks
40
35
  end
41
36
  end
42
37
 
43
-
44
- ChannelTasks.send_message_to_channel("#{collection}##{id}", ['changed', nil, id, data], @channel)
45
- end
46
-
47
- def find(collection, scope, query=nil)
48
- results = @@db[collection].find(scope).to_a.map {|item| item.symbolize_keys }
49
- puts "FIND: #{collection.inspect} - #{scope} - #{results.inspect}"
50
-
51
- return results
38
+ QueryTasks.live_query_pool.updated_collection(collection, @channel)
52
39
  end
53
-
40
+
54
41
  def delete(collection, id)
55
42
  puts "DELETE: #{collection.inspect} - #{id.inspect}"
56
43
  @@db[collection].remove('_id' => id)
57
44
 
58
- ChannelTasks.send_message_to_channel("#{collection}-removed", ['removed', nil, id], @channel)
45
+ QueryTasks.live_query_pool.updated_collection(collection, @channel)
59
46
  end
60
47
  end
data/lib/volt.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  require 'volt/volt/environment'
2
+ require 'volt/extra_core/extra_core'
3
+ require 'volt/reactive/reactive_value'
2
4
 
3
5
  class Volt
4
6
  def self.root
data/lib/volt/console.rb CHANGED
@@ -5,7 +5,7 @@ class Console
5
5
  $LOAD_PATH << 'lib'
6
6
  ENV['SERVER'] = 'true'
7
7
 
8
- require 'volt/extra_core/extra_core'
8
+ require 'volt'
9
9
  require 'volt/models'
10
10
  require 'volt/server/template_parser'
11
11
  require 'volt'
@@ -33,6 +33,10 @@ class ModelController
33
33
  def page
34
34
  $page.page
35
35
  end
36
+
37
+ def paged
38
+ $page.page
39
+ end
36
40
 
37
41
  def store
38
42
  $page.store
@@ -7,4 +7,13 @@ class Array
7
7
 
8
8
  return total
9
9
  end
10
+
11
+ def deep_cur
12
+ new_array = []
13
+ each do |item|
14
+ new_array << item.deep_cur
15
+ end
16
+
17
+ return new_array
18
+ end
10
19
  end