From c22f879d6f18727468c9a849b2a23efe36c1b55d Mon Sep 17 00:00:00 2001 From: adamcreekroad Date: Thu, 15 Apr 2021 11:24:57 -0400 Subject: [PATCH] Implements ReactiveRecord::Collection#create --- .../reactive_record/collection.rb | 59 +++++++++----- .../batch1/reactive_record/collection_spec.rb | 79 +++++++++++++++++++ 2 files changed, 120 insertions(+), 18 deletions(-) create mode 100644 ruby/hyper-model/spec/batch1/reactive_record/collection_spec.rb diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb index 7ba7ed714..495ea5dea 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb @@ -116,32 +116,28 @@ def to_s end class << self + attr_accessor :broadcast_updated_at -=begin -sync_scopes takes a newly broadcasted record change and updates all relevant currently active scopes -This is particularly hard when the client proc is specified. For example consider this scope: - -class TestModel < ApplicationRecord - scope :quicker, -> { where(completed: true) }, client: -> { completed } -end - -and this slice of reactive code: + # sync_scopes takes a newly broadcasted record change and updates all relevant currently active scopes + # This is particularly hard when the client proc is specified. For example consider this scope: - DIV { "quicker.count = #{TestModel.quicker.count}" } + # class TestModel < ApplicationRecord + # scope :quicker, -> { where(completed: true) }, client: -> { completed } + # end -then on the server this code is executed: + # and this slice of reactive code: - TestModel.last.update(completed: false) + # DIV { "quicker.count = #{TestModel.quicker.count}" } -This will result in the changes being broadcast to the client, which may cauase the value of -TestModel.quicker.count to increase or decrease. Of course we may not actually have the all the records, -perhaps we just have the aggregate count. + # then on the server this code is executed: -To determine this sync_scopes first asks if the record being changed is in the scope given its value + # TestModel.last.update(completed: false) + # This will result in the changes being broadcast to the client, which may cauase the value of + # TestModel.quicker.count to increase or decrease. Of course we may not actually have the all the records, + # perhaps we just have the aggregate count. -=end - attr_accessor :broadcast_updated_at + # To determine this sync_scopes first asks if the record being changed is in the scope given its value def sync_scopes(broadcast) self.broadcast_updated_at = broadcast.updated_at @@ -503,6 +499,33 @@ def force_push(item) notify_of_change self end + def create(attributes = nil) + raise "You cannot call create unless the parent is saved" if @owner.new_record? + + attributes = [attributes] unless attributes.is_a?(Array) + + records = attributes.map { |a| build_record(a) } + + Promise.new.tap do |promise| + Promise.when(*records.map(&:save)).then do + promise.resolve(records) + + yield(*records) if block_given? + end + end + end + + def build_record(attributes = {}) + if (through_association = @association&.through_association) + through_association.klass.new( + @association.inverse_of => @owner, + @association.source => @association.klass.new(attributes) + ) + else + @association.klass.new(attributes).tap { |r| _internal_push(r) } + end + end + # [:first, :last].each do |method| # define_method method do |*args| # if args.count == 0 diff --git a/ruby/hyper-model/spec/batch1/reactive_record/collection_spec.rb b/ruby/hyper-model/spec/batch1/reactive_record/collection_spec.rb new file mode 100644 index 000000000..1861fd2e5 --- /dev/null +++ b/ruby/hyper-model/spec/batch1/reactive_record/collection_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "ReactiveRecord::Collection", js: true do + context "#create" do + before(:each) { policy_allows_all } + + it "should not create any records if the parent is not saved" do + expect_promise do + error = nil + + user = User.new(first_name: "foo") + + begin + user.comments.create(comment: "foobar") + rescue StandardError => e + error = e + end + + error + end.to eq("You cannot call create unless the parent is saved") + + expect(Comment.count).to eq(0) + end + + it "should create a new record in a has many association" do + user = FactoryBot.create(:user, first_name: "foo") + + expect_promise do + user = User.find_by_first_name("foo") + + user.comments.create(comment: "foobar") + end + + expect(user.comments.count).to eq(1) + end + + it "should create multiple new records in a has many association" do + user = FactoryBot.create(:user, first_name: "foo") + + expect_promise do + user = User.find_by_first_name("foo") + + user.comments.create([{ comment: "foobar" }, { comment: "barfoo" }]) + end + + expect(user.comments.count).to eq(2) + end + + it "should create a new record and a join record in a has many through association" do + user = FactoryBot.create(:user, first_name: "foo") + + expect_promise do + user = User.find_by_first_name("foo") + + user.commented_on_items.create(title: "foo", description: "bar") + end + + expect(user.commented_on_items.count).to eq(1) + expect(user.comments.count).to eq(1) + end + + it "should create a multiple new records and join records in a has many through association" do + user = FactoryBot.create(:user, first_name: "foo") + + expect_promise do + user = User.find_by_first_name("foo") + + user.commented_on_items.create( + [{ title: "foo", description: "bar" }, { title: "foo", description: "bar" }] + ) + end + + expect(user.commented_on_items.count).to eq(2) + expect(user.comments.count).to eq(2) + end + end +end