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

<<(item)
Alias for: push
==(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
length()
Alias for: count
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