class ReactiveRecord::Collection
Attributes
client_collection[R]
parent[W]
scope_description[W]
vector[R]
todo move following to a separate module related to scope updates ******************
Public Class Methods
apply_to_all_collections(method, record, dont_gather)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 126 def apply_to_all_collections(method, record, dont_gather) related_records = Set.new if dont_gather Base.outer_scopes.each do |collection| unless dont_gather related_records = collection.gather_related_records(record) end collection.send method, related_records, record end end
new(target_klass, owner = nil, association = nil, *vector)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 29 def initialize(target_klass, owner = nil, association = nil, *vector) @owner = owner # can be nil if this is an outer most scope @association = association @target_klass = target_klass if owner and !owner.id and vector.length <= 1 @collection = [] elsif vector.length > 0 @vector = vector elsif owner @vector = owner.backing_record.vector + [association.attribute] else @vector = [target_klass] end @scopes = {} end
sync_scopes(broadcast)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 106 def sync_scopes(broadcast) # record_with_current_values will return nil if data between # the broadcast record and the value on the client is out of sync # not running set_pre_sync_related_records will cause sync scopes # to refresh all related scopes React::State.bulk_update do record = broadcast.record_with_current_values apply_to_all_collections( :set_pre_sync_related_records, record, broadcast.new? ) if record record = broadcast.record_with_new_values apply_to_all_collections( :sync_scopes, record, record.destroyed? ) record.backing_record.sync_unscoped_collection! if record.destroyed? || broadcast.new? end end
Public Instance Methods
==(other_collection)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 82 def ==(other_collection) observed return !@collection unless other_collection.is_a? Collection other_collection.observed my_children = (@collection || []).select { |target| target != @dummy_record } if other_collection other_children = (other_collection.collection || []).select { |target| target != other_collection.dummy_record } return false unless my_children == other_children unsaved_children.to_a == other_collection.unsaved_children.to_a else my_children.empty? && unsaved_children.empty? end end
[](index)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 70 def [](index) observed if (@collection || all).length <= index and @dummy_collection (@collection.length..index).each do |i| new_dummy_record = ReactiveRecord::Base.new_from_vector(@target_klass, nil, *@vector, "*#{i}") new_dummy_record.backing_record.attributes[@association.inverse_of] = @owner if @association && !@association.through_association? @collection << new_dummy_record end end @collection[index] end
all()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 53 def all observed @dummy_collection.notify if @dummy_collection unless @collection @collection = [] if ids = ReactiveRecord::Base.fetch_from_db([*@vector, "*all"]) ids.each do |id| @collection << @target_klass.find_by(@target_klass.primary_key => id) end else @dummy_collection = ReactiveRecord::Base.load_from_db(nil, *@vector, "*all") @dummy_record = self[0] end end @collection end
apply_scope(name, *vector)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 208 def apply_scope(name, *vector) description = ScopeDescription.find(@target_klass, name) collection = build_child_scope(description, *description.name, *vector) collection.reload_from_db if name == "#{description.name}!" collection end
build_child_scope(scope_description, *scope_vector)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 219 def build_child_scope(scope_description, *scope_vector) child_scopes[scope_vector] ||= begin new_vector = @vector new_vector += [scope_vector] unless new_vector.nil? || scope_vector.empty? child_scope = Collection.new(@target_klass, nil, nil, *new_vector) child_scope.scope_description = scope_description child_scope.parent = self child_scope.extend ScopedCollection child_scope end end
child_scopes()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 215 def child_scopes @child_scopes ||= {} end
collect(*args, &block)
click to toggle source
WHY IS THIS NEEDED? Perhaps it was just for debug
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 307 def collect(*args, &block) all.collect(*args, &block) end
collector?()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 179 def collector? false end
count()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 292 def count observed if @collection @collection.count elsif @count ||= ReactiveRecord::Base.fetch_from_db([*@vector, "*count"]) @count else ReactiveRecord::Base.load_from_db(nil, *@vector, "*count") @count = 1 end end
Also aliased as: length
delete(item)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 444 def delete(item) unsaved_children.delete(item) notify_of_change( if @owner && @association && !@association.through_association? inverse_of = @association.inverse_of if (backing_record = item.backing_record) && backing_record.attributes[inverse_of] == @owner # the if prevents double update if delete is being called from << (see << above) backing_record.update_attribute(inverse_of, nil) end # forces a check if association contents have changed from synced values delete_internal(item) { @owner.backing_record.update_attribute(@association.attribute) } else delete_internal(item) end ) end
delete_internal(item) { |item| ... }
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 461 def delete_internal(item) if collection all.delete(item) elsif !@count.nil? @count -= 1 end yield item if block_given? item end
dup_for_sync()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 45 def dup_for_sync self.dup.instance_eval do @collection = @collection.dup if @collection @scopes = @scopes.dup self end end
empty?()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 476 def empty? # should be handled by method missing below, but opal-rspec does not deal well with method missing, so to test... all.empty? end
filter?()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 152 def filter? true end
filter_records(related_records)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 183 def filter_records(related_records) # possibly we should never get here??? scope_args = @vector.last.is_a?(Array) ? @vector.last[1..-1] : [] @scope_description.filter_records(related_records, scope_args) end
force_push(item)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 384 def force_push(item) return delete(item) if item.destroyed? # pushing a destroyed item is the same as removing it all << item unless all.include? item # does this use == if so we are okay... update_child(item) if item.id and @dummy_record @dummy_record.id = item.id # we cant use == because that just means the objects are referencing # the same backing record. @collection.reject { |i| i.object_id == @dummy_record.object_id } @dummy_record = @collection.detect { |r| r.backing_record.vector.last =~ /^\*[0-9]+$/ } @dummy_collection = nil end notify_of_change self end
internal_replace(new_array)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 417 def internal_replace(new_array) # not tested if you do all[n] where n > 0... this will create additional dummy items, that this will not sync up. # probably just moving things around so the @dummy_collection and @dummy_record are updated AFTER the new items are pushed # should work. if @dummy_collection @dummy_collection.notify array = new_array.is_a?(Collection) ? new_array.collection : new_array @collection.each_with_index do |r, i| r.id = new_array[i].id if array[i] and array[i].id and !r.new? and r.backing_record.vector.last =~ /^\*[0-9]+$/ end end @collection.dup.each { |item| delete(item) } if @collection # this line is a big nop I think @collection = [] if new_array.is_a? Collection @dummy_collection = new_array.dummy_collection @dummy_record = new_array.dummy_record new_array.collection.each { |item| self << item } if new_array.collection else @dummy_collection = @dummy_record = nil new_array.each { |item| self << item } end notify_of_change new_array end
joins_with?(record)
click to toggle source
is it necessary to check @association in the next 2 methods???
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 158 def joins_with?(record) if @association && @association.through_association @association.through_association.klass == record.class else @target_klass == record.class end end
klass()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 319 def klass @target_klass end
link_child(child)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 243 def link_child(child) live_scopes << child link_to_parent end
link_to_parent()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 231 def link_to_parent return if @linked @linked = true if @parent @parent.link_child self sync_collection_with_parent unless collection else ReactiveRecord::Base.add_to_outer_scopes self end all if collector? # force fetch all so the collector can do its job end
live_scopes()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 189 def live_scopes @live_scopes ||= Set.new end
loading?()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 471 def loading? all # need to force initialization at this point @dummy_collection.loading? end
method_missing(method, *args, &block)
click to toggle source
Calls superclass method
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 480 def method_missing(method, *args, &block) if [].respond_to? method all.send(method, *args, &block) elsif ScopeDescription.find(@target_klass, method) || (args.count == 1 && method =~ /^find_by_/) apply_scope(method, *args) elsif @target_klass.respond_to?(method) && ScopeDescription.find(@target_klass, "_#{method}") apply_scope("_#{method}", *args).first else super end end
observed()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 273 def observed return if @observing || ReactiveRecord::Base.data_loading? begin @observing = true link_to_parent reload_from_db(true) if @out_of_date React::State.get_state(self, :collection) ensure @observing = false end end
proxy_association()
click to toggle source
def each_known_child
[*collection, *client_pushes].each { |i| yield i }
end
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 315 def proxy_association @association || self # returning self allows this to work with things like Model.all end
push(item)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 362 def push(item) item.itself # force get of at least the id if collection self.force_push item else unsaved_children << item update_child(item) @owner.backing_record.update_attribute(@association.attribute) if @owner && @association if !@count.nil? @count += item.destroyed? ? -1 : 1 notify_of_change self end end self end
Also aliased as: <<
push_and_update_belongs_to(id)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 323 def push_and_update_belongs_to(id) # example collection vector: TestModel.find(1).child_models.harrybarry # harrybarry << child means that # child.test_model = 1 # so... we go back starting at this collection and look for the first # collection with an owner... that is our guy child = proxy_association.klass.find(id) push child set_belongs_to child end
reload_from_db(force = nil)
click to toggle source
end of stuff to move
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 262 def reload_from_db(force = nil) if force || React::State.has_observers?(self, :collection) @out_of_date = false ReactiveRecord::Base.load_from_db(nil, *@vector, '*all') if @collection ReactiveRecord::Base.load_from_db(nil, *@vector, '*count') else @out_of_date = true end self end
replace(new_array)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 409 def replace(new_array) unsaved_children.clear new_array = new_array.to_a return self if new_array == @collection Base.load_data { internal_replace(new_array) } notify_of_change new_array end
set_belongs_to(child)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 334 def set_belongs_to(child) if @owner child.send("#{@association.inverse_of}=", @owner) if @association elsif @parent @parent.set_belongs_to(child) end child end
set_count_state(val)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 285 def set_count_state(val) unless ReactiveRecord::WhileLoading.has_observers? React::State.set_state(self, :collection, collection, true) end @count = val end
sort!(*args, &block)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 380 def sort!(*args, &block) replace(sort(*args, &block)) end
sync_collection_with_parent()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 248 def sync_collection_with_parent if @parent.collection if @parent.collection.empty? @collection = [] elsif filter? @collection = filter_records(@parent.collection) end elsif @parent.count.zero? @count = 0 end end
sync_scopes(related_records, record, filtering = true)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 199 def sync_scopes(related_records, record, filtering = true) #related_records = related_records.intersection([*@collection]) #related_records = in_this_collection related_records live_scopes.each { |scope| scope.sync_scopes(related_records, record, filtering) } notify_of_change unless related_records.empty? ensure @pre_sync_related_records = nil end
to_s()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 101 def to_s "<Coll-#{object_id} - #{vector}>" end
unsaved_children()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 13 def unsaved_children old_uc_already_being_called = @uc_already_being_called if @owner && @association @unsaved_children ||= Set.new unless @uc_already_being_called @uc_already_being_called = true #@owner.backing_record.update_attribute(@association.attribute) end else @unsaved_children ||= DummySet.new end @unsaved_children ensure @uc_already_being_called = old_uc_already_being_called end
update_child(item)
click to toggle source
appointment.doctor = doctor_value (i.e. through association is changing) means appointment.doctor_value.patients << appointment.patient and we have to appointment.doctor(current value).patients.delete(appointment.patient)
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 350 def update_child(item) backing_record = item.backing_record if backing_record && @owner && @association && !@association.through_association? && item.attributes[@association.inverse_of] != @owner inverse_of = @association.inverse_of current_association = item.attributes[inverse_of] backing_record.virgin = false unless backing_record.data_loading? backing_record.update_attribute(inverse_of, @owner) current_association.attributes[@association.attribute].delete(item) if current_association and current_association.attributes[@association.attribute] @owner.backing_record.update_attribute(@association.attribute) # forces a check if association contents have changed from synced values end end
Protected Instance Methods
collection()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 498 def collection @collection end
dummy_collection()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 502 def dummy_collection @dummy_collection end
dummy_record()
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 494 def dummy_record @dummy_record end
notify_of_change(value = nil)
click to toggle source
# File lib/reactive_record/active_record/reactive_record/collection.rb, line 506 def notify_of_change(value = nil) React::State.set_state(self, "collection", collection) unless ReactiveRecord::Base.data_loading? value end