require 'volt/models/persistors/store' require 'volt/models/persistors/query/query_listener_pool' require 'volt/models/persistors/store_state' module Persistors class ArrayStore < Store include StoreState @@query_pool = QueryListenerPool.new attr_reader :model def self.query_pool @@query_pool end def initialize(model, tasks=nil) super query = @model.options[:query] @query = ReactiveValue.from_hash(query || {}) end def event_added(event, scope_provider, first, first_for_event) # First event, we load the data. load_data if first end def event_removed(event, last, last_for_event) # Remove listener where there are no more events on this model stop_listening if last end # Called when an event is removed and we no longer want to keep in # sync with the database. def stop_listening if @query_changed_listener @query_changed_listener.remove @query_changed_listener = nil end if @query_listener @query_listener.remove_store(self) @query_listener = nil end @state = :dirty end # Called the first time data is requested from this collection def load_data # Don't load data from any queried if @state == :not_loaded || @state == :dirty # puts "Load Data at #{@model.path.inspect} - query: #{@query.inspect}"# on #{@model.inspect}" change_state_to :loading @query_changed_listener.remove if @query_changed_listener if @query.reactive? # puts "SETUP REACTIVE QUERY LISTENER: #{@query.inspect}" # Query might change, change the query when it does @query_changed_listener = @query.on('changed') do stop_listening # Don't load again if all of the listeners are gone load_data if @model.has_listeners? end end run_query(@model, @query.deep_cur) end end # Clear out the models data, since we're not listening anymore. def unload_data change_state_to :not_loaded @model.clear end def run_query(model, query={}) collection = model.path.last # Scope to the parent if model.path.size > 1 parent = model.parent parent.persistor.ensure_setup if parent.persistor if parent && (attrs = parent.attributes) && attrs[:_id].true? query[:"#{model.path[-3].singularize}_id"] = attrs[:_id] end end @query_listener = @@query_pool.lookup(collection, query) do # Create if it does not exist QueryListener.new(@@query_pool, @tasks, collection, query) end @query_listener.add_store(self) end def find(query={}) model = Cursor.new([], @model.options.merge(:query => query)) return ReactiveValue.new(model) end # Returns a promise that is resolved/rejected when the query is complete. Any # passed block will be passed to the promises then. Then will be passed the model. def then(&block) promise = Promise.new promise = promise.then(&block) if block if @state == :loaded promise.resolve(@model) else @fetch_promises ||= [] @fetch_promises << promise load_data end return promise end # Called from backend def add(index, data) $loading_models = true new_options = @model.options.merge(path: @model.path + [:[]], parent: @model) # Don't add if the model is already in the ArrayModel if !@model.cur.array.find {|v| v['_id'] == data['_id'] } # Find the existing model, or create one new_model = @@identity_map.find(data['_id']) { @model.new_model(data.symbolize_keys, new_options, :loaded) } @model.insert(index, new_model) end $loading_models = false end def remove(ids) $loading_models = true ids.each do |id| # TODO: optimize this delete so we don't need to loop @model.each_with_index do |model, index| if model._id == id del = @model.delete_at(index) break end end end $loading_models = false end def channel_name @model.path[-1] end # When a model is added to this collection, we call its "changed" # method. This should trigger a save. def added(model, index) if model.persistor # Tell the persistor it was added model.persistor.add_to_collection end end def removed(model) if model.persistor # Tell the persistor it was removed model.persistor.remove_from_collection end if defined?($loading_models) && $loading_models return else @tasks.call('StoreTasks', 'delete', channel_name, model.attributes[:_id]) end end end end