From b646f507cb188a0dec01605ea464f2d37af8ecaa Mon Sep 17 00:00:00 2001 From: sbelknap-bf <114414270+sbelknap-bf@users.noreply.github.com> Date: Tue, 8 Aug 2023 16:36:17 -0400 Subject: [PATCH] Feature/fix chaining methods v2 (#10) * fix chaining on where clauses, but still need to fix other chainable methods * create clone_and_set_instance_variables method and write specs * remove pry statement * fix associations * reset records and decorated records on on cloned instance * (WIP) fix none so that specs pass * add tests to check for has_many mutation * fix count, sum, not, and or * add tests for active query not method * update version an changelog * fix sum ArgumentError message for invalid field --------- Co-authored-by: rferg --- CHANGELOG.md | 2 + lib/active_force/active_query.rb | 36 +++--- lib/active_force/query.rb | 54 +++++---- spec/active_force/active_query_spec.rb | 160 ++++++++++++++++--------- spec/active_force/association_spec.rb | 10 ++ spec/active_force/query_spec.rb | 65 +++++++++- spec/active_force/sobject_spec.rb | 61 +++++++++- 7 files changed, 272 insertions(+), 116 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86613e5..51c7d36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Not released +- Fix bug with has_many queries due to query method chaining mutating in-place (https://github.com/Beyond-Finance/active_force/pull/10) + ## 0.16.0 - Fix `default` in models when default value is overridden by the same value, it is still sent to salesforce (https://github.com/Beyond-Finance/active_force/pull/61) diff --git a/lib/active_force/active_query.rb b/lib/active_force/active_query.rb index b321f1d..c8dffd0 100644 --- a/lib/active_force/active_query.rb +++ b/lib/active_force/active_query.rb @@ -42,36 +42,34 @@ def to_a alias_method :all, :to_a def count - super - sfdc_client.query(to_s).first.expr0 + sfdc_client.query(super.to_s).first.expr0 end def sum field - super(mappings[field]) - sfdc_client.query(to_s).first.expr0 + raise ArgumentError, 'field is required' if field.blank? + raise ArgumentError, "field '#{field}' does not exist on #{sobject}" unless mappings.key?(field.to_sym) + + sfdc_client.query(super(mappings.fetch(field.to_sym)).to_s).first.expr0 end def limit limit - super - limit == 1 ? to_a.first : self + limit == 1 ? super.to_a.first : super end def not args=nil, *rest return self if args.nil? + super build_condition args, rest - self end def where args=nil, *rest return self if args.nil? - return clone_self_and_clear_cache.where(args, *rest) if @decorated_records.present? super build_condition args, rest - self end - def select *fields - fields.map! { |field| mappings[field] } - super *fields + def select *selected_fields + selected_fields.map! { |field| mappings[field] } + super *selected_fields end def find!(id) @@ -100,8 +98,10 @@ def includes(*relations) end def none - @records = [] - where(id: '1'*18).where(id: '0'*18) + clone_and_set_instance_variables( + records: [], + conditions: [build_condition(id: '1' * 18), build_condition(id: '0' * 18)] + ) end def loaded? @@ -205,13 +205,6 @@ def result sfdc_client.query(self.to_s) end - def clone_self_and_clear_cache - new_query = self.clone - new_query.instance_variable_set(:@decorated_records, nil) - new_query.instance_variable_set(:@records, nil) - new_query - end - def build_order_by(args) args.map do |arg| case arg @@ -228,6 +221,5 @@ def build_order_by(args) def order_type(type) type == :desc ? 'DESC' : 'ASC' end - end end diff --git a/lib/active_force/query.rb b/lib/active_force/query.rb index 0bec64d..7493585 100644 --- a/lib/active_force/query.rb +++ b/lib/active_force/query.rb @@ -31,33 +31,34 @@ def to_s end def select *columns - @query_fields = columns - self + clone_and_set_instance_variables(query_fields: columns) + end + + def where condition = nil + new_conditions = @conditions | [condition] + if new_conditions != @conditions + clone_and_set_instance_variables({conditions: new_conditions}) + else + self + end end def not condition - @conditions << "NOT ((#{ condition.join(') AND (') }))" - self + condition ? where("NOT ((#{condition.join(') AND (')}))") : self end def or query - @conditions = ["(#{ and_conditions }) OR (#{ query.and_conditions })"] - self - end + return self unless query - def where condition = nil - @conditions << condition if condition - self + clone_and_set_instance_variables(conditions: ["(#{and_conditions}) OR (#{query.and_conditions})"]) end def order order - @order = order if order - self + order ? clone_and_set_instance_variables(order: order) : self end def limit size - @size = size if size - self + size ? clone_and_set_instance_variables(size: size) : self end def limit_value @@ -65,8 +66,7 @@ def limit_value end def offset offset - @offset = offset - self + clone_and_set_instance_variables(offset: offset) end def offset_value @@ -74,8 +74,7 @@ def offset_value end def find id - where "#{ @table_id } = '#{ id }'" - limit 1 + where("#{ @table_id } = '#{ id }'").limit 1 end def first @@ -87,18 +86,17 @@ def last end def join object_query - fields ["(#{ object_query.to_s })"] - self + chained_query = self.clone + chained_query.fields ["(#{ object_query.to_s })"] + chained_query end def count - @query_fields = ["count(Id)"] - self + clone_and_set_instance_variables(query_fields: ["count(Id)"]) end def sum field - @query_fields = ["sum(#{field})"] - self + clone_and_set_instance_variables(query_fields: ["sum(#{field})"]) end protected @@ -125,5 +123,13 @@ def build_order def build_offset "OFFSET #{ @offset }" if @offset end + + def clone_and_set_instance_variables instance_variable_hash={} + clone = self.clone + clone.instance_variable_set(:@decorated_records, nil) + clone.instance_variable_set(:@records, nil) + instance_variable_hash.each { |k,v| clone.instance_variable_set("@#{k.to_s}", v) } + clone + end end end diff --git a/spec/active_force/active_query_spec.rb b/spec/active_force/active_query_spec.rb index c990743..60d72c6 100644 --- a/spec/active_force/active_query_spec.rb +++ b/spec/active_force/active_query_spec.rb @@ -9,7 +9,7 @@ }) end let(:mappings){ { id: "Id", field: "Field__c", other_field: "Other_Field" } } - let(:client){ double("client") } + let(:client) { double('client', query: nil) } let(:active_query){ described_class.new(sobject) } let(:api_result) do [ @@ -42,48 +42,45 @@ describe "select only some field using mappings" do it "should return a query only with selected field" do - active_query.select(:field) - expect(active_query.to_s).to eq("SELECT Field__c FROM table_name") + new_query = active_query.select(:field) + expect(new_query.to_s).to eq("SELECT Field__c FROM table_name") end end describe "condition mapping" do it "maps conditions for a .where" do - active_query.where(field: 123) - expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123)") + new_query = active_query.where(field: 123) + expect(new_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123)") end it 'transforms an array to a WHERE/IN clause' do - active_query.where(field: ['foo', 'bar']) - expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c IN ('foo','bar'))") + new_query = active_query.where(field: ['foo', 'bar']) + expect(new_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c IN ('foo','bar'))") end it "encloses the value in quotes if it's a string" do - active_query.where field: "hello" - expect(active_query.to_s).to end_with("(Field__c = 'hello')") + new_query = active_query.where field: "hello" + expect(new_query.to_s).to end_with("(Field__c = 'hello')") end it "formats as YYYY-MM-DDThh:mm:ss-hh:mm and does not enclose in quotes if it's a DateTime" do value = DateTime.now - active_query.where(field: value) - expect(active_query.to_s).to end_with("(Field__c = #{value.iso8601})") + expect(active_query.where(field: value).to_s).to end_with("(Field__c = #{value.iso8601})") end it "formats as YYYY-MM-DDThh:mm:ss-hh:mm and does not enclose in quotes if it's a Time" do value = Time.now - active_query.where(field: value) - expect(active_query.to_s).to end_with("(Field__c = #{value.iso8601})") + expect(active_query.where(field: value).to_s).to end_with("(Field__c = #{value.iso8601})") end it "formats as YYYY-MM-DD and does not enclose in quotes if it's a Date" do value = Date.today - active_query.where(field: value) - expect(active_query.to_s).to end_with("(Field__c = #{value.iso8601})") + expect(active_query.where(field: value).to_s).to end_with("(Field__c = #{value.iso8601})") end it "puts NULL when a field is set as nil" do - active_query.where field: nil - expect(active_query.to_s).to end_with("(Field__c = NULL)") + new_query = active_query.where field: nil + expect(new_query.to_s).to end_with("(Field__c = NULL)") end describe 'bind parameters' do @@ -95,36 +92,33 @@ end it 'accepts bind parameters' do - active_query.where('Field__c = ?', 123) - expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123)") + new_query = active_query.where('Field__c = ?', 123) + expect(new_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123)") end it 'accepts nil bind parameters' do - active_query.where('Field__c = ?', nil) - expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = NULL)") + new_query = active_query.where('Field__c = ?', nil) + expect(new_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = NULL)") end it 'accepts multiple bind parameters' do - active_query.where('Field__c = ? AND Other_Field__c = ? AND Name = ?', 123, 321, 'Bob') - expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123 AND Other_Field__c = 321 AND Name = 'Bob')") + new_query = active_query.where('Field__c = ? AND Other_Field__c = ? AND Name = ?', 123, 321, 'Bob') + expect(new_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123 AND Other_Field__c = 321 AND Name = 'Bob')") end it 'formats as YYYY-MM-DDThh:mm:ss-hh:mm and does not enclose in quotes if value is a DateTime' do value = DateTime.now - active_query.where('Field__c > ?', value) - expect(active_query.to_s).to end_with("(Field__c > #{value.iso8601})") + expect(active_query.where('Field__c > ?', value).to_s).to end_with("(Field__c > #{value.iso8601})") end it 'formats as YYYY-MM-DDThh:mm:ss-hh:mm and does not enclose in quotes if value is a Time' do value = Time.now - active_query.where('Field__c > ?', value) - expect(active_query.to_s).to end_with("(Field__c > #{value.iso8601})") + expect(active_query.where('Field__c > ?', value).to_s).to end_with("(Field__c > #{value.iso8601})") end it 'formats as YYYY-MM-DD and does not enclose in quotes if value is a Date' do value = Date.today - active_query.where('Field__c > ?', value) - expect(active_query.to_s).to end_with("(Field__c > #{value.iso8601})") + expect(active_query.where('Field__c > ?', value).to_s).to end_with("(Field__c > #{value.iso8601})") end it 'complains when there given an incorrect number of bind parameters' do @@ -135,41 +129,41 @@ context 'named bind parameters' do it 'accepts bind parameters' do - active_query.where('Field__c = :field', field: 123) - expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123)") + new_query = active_query.where('Field__c = :field', field: 123) + expect(new_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123)") end it 'accepts nil bind parameters' do - active_query.where('Field__c = :field', field: nil) - expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = NULL)") + new_query = active_query.where('Field__c = :field', field: nil) + expect(new_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = NULL)") end it 'formats as YYYY-MM-DDThh:mm:ss-hh:mm and does not enclose in quotes if value is a DateTime' do value = DateTime.now - active_query.where('Field__c < :field', field: value) - expect(active_query.to_s).to end_with("(Field__c < #{value.iso8601})") + new_query = active_query.where('Field__c < :field', field: value) + expect(new_query.to_s).to end_with("(Field__c < #{value.iso8601})") end it 'formats as YYYY-MM-DDThh:mm:ss-hh:mm and does not enclose in quotes if value is a Time' do value = Time.now - active_query.where('Field__c < :field', field: value) - expect(active_query.to_s).to end_with("(Field__c < #{value.iso8601})") + new_query = active_query.where('Field__c < :field', field: value) + expect(new_query.to_s).to end_with("(Field__c < #{value.iso8601})") end it 'formats as YYYY-MM-DD and does not enclose in quotes if value is a Date' do value = Date.today - active_query.where('Field__c < :field', field: value) - expect(active_query.to_s).to end_with("(Field__c < #{value.iso8601})") + new_query = active_query.where('Field__c < :field', field: value) + expect(new_query.to_s).to end_with("(Field__c < #{value.iso8601})") end it 'accepts multiple bind parameters' do - active_query.where('Field__c = :field AND Other_Field__c = :other_field AND Name = :name', field: 123, other_field: 321, name: 'Bob') - expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123 AND Other_Field__c = 321 AND Name = 'Bob')") + new_query = active_query.where('Field__c = :field AND Other_Field__c = :other_field AND Name = :name', field: 123, other_field: 321, name: 'Bob') + expect(new_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123 AND Other_Field__c = 321 AND Name = 'Bob')") end it 'accepts multiple bind parameters orderless' do - active_query.where('Field__c = :field AND Other_Field__c = :other_field AND Name = :name', name: 'Bob', other_field: 321, field: 123) - expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123 AND Other_Field__c = 321 AND Name = 'Bob')") + new_query = active_query.where('Field__c = :field AND Other_Field__c = :other_field AND Name = :name', name: 'Bob', other_field: 321, field: 123) + expect(new_query.to_s).to eq("SELECT Id FROM table_name WHERE (Field__c = 123 AND Other_Field__c = 321 AND Name = 'Bob')") end it 'complains when there given an incorrect number of bind parameters' do @@ -198,29 +192,77 @@ {"Id" => "0000000000EEEEEFFF"} ] end + it 'allows method chaining' do result = active_query.where("Text_Label = 'foo'").where("Checkbox_Label = true") expect(result).to be_a described_class end + it 'does not execute a query' do + active_query.where('x') + expect(client).not_to have_received(:query) + end + + context 'when calling `where` on an ActiveQuery object that already has records' do + context 'after the query result has been decorated' do + it 'returns a new ActiveQuery object' do + first_active_query = active_query.where("Text_Label = 'foo'") + first_active_query.to_a # decorates the results + second_active_query = first_active_query.where("Checkbox_Label = true") + second_active_query.to_a + expect(second_active_query).to be_a described_class + expect(second_active_query).not_to eq first_active_query + expect(second_active_query.to_s).not_to eq first_active_query.to_s + expect(second_active_query.to_a.size).to eq(1) + end + end + end + context 'when calling `where` on an ActiveQuery object that already has records' do - it 'returns a new ActiveQuery object' do - first_active_query = active_query.where("Text_Label = 'foo'") - first_active_query.inspect # so the query is executed - second_active_query = first_active_query.where("Checkbox_Label = true") - second_active_query.inspect - expect(second_active_query).to be_a described_class - expect(second_active_query).not_to eq first_active_query + context 'without the query result being decorated' do + + it 'returns a new ActiveQuery object' do + first_active_query = active_query.where("Text_Label = 'foo'") + second_active_query = first_active_query.where("Checkbox_Label = true") + expect(second_active_query).to be_a described_class + expect(second_active_query).not_to eq first_active_query + expect(second_active_query.to_s).not_to eq first_active_query.to_s + expect(second_active_query.to_a.size).to eq(1) + end end end + end + describe '#not' do + it 'adds a not condition' do + expect(active_query.not(field: 'x').to_s).to end_with("WHERE (NOT ((Field__c = 'x')))") + end + + it 'allows chaining' do + expect(active_query.where(field: 'x').not(field: 'y').where(field: 'z')).to be_a(described_class) + end + + it 'does not mutate the original query' do + original = active_query.to_s + active_query.not(field: 'x') + expect(active_query.to_s).to eq(original) + end + + it 'returns the original query if not given a condition' do + expect(active_query.not).to be(active_query) + end + + it 'does not execute a query' do + active_query.not(field: 'x') + expect(client).not_to have_received(:query) + end end describe "#find_by" do it "should query the client, with the SFDC field names and correctly enclosed values" do - expect(client).to receive :query - active_query.find_by field: 123 - expect(active_query.to_s).to eq "SELECT Id FROM table_name WHERE (Field__c = 123) LIMIT 1" + expect(client).to receive(:query).with("SELECT Id FROM table_name WHERE (Field__c = 123) LIMIT 1") + new_query = active_query.find_by field: 123 + expect(new_query).to be_nil end end @@ -290,18 +332,18 @@ let(:expected_query){ "SELECT Id FROM table_name WHERE (Backslash_Field__c = '\\\\' AND NumberField = 123 AND QuoteField = '\\' OR Id!=NULL OR Id=\\'')" } it 'escapes quotes and backslashes in bind parameters' do - active_query.where('Backslash_Field__c = :backslash_field AND NumberField = :number_field AND QuoteField = :quote_field', number_field: number_input, backslash_field: backslash_input, quote_field: quote_input) - expect(active_query.to_s).to eq(expected_query) + new_query = active_query.where('Backslash_Field__c = :backslash_field AND NumberField = :number_field AND QuoteField = :quote_field', number_field: number_input, backslash_field: backslash_input, quote_field: quote_input) + expect(new_query.to_s).to eq(expected_query) end it 'escapes quotes and backslashes in named bind parameters' do - active_query.where('Backslash_Field__c = ? AND NumberField = ? AND QuoteField = ?', backslash_input, number_input, quote_input) - expect(active_query.to_s).to eq(expected_query) + new_query = active_query.where('Backslash_Field__c = ? AND NumberField = ? AND QuoteField = ?', backslash_input, number_input, quote_input) + expect(new_query.to_s).to eq(expected_query) end it 'escapes quotes and backslashes in hash conditions' do - active_query.where(backslash_field: backslash_input, number_field: number_input, quote_field: quote_input) - expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE (Backslash_Field__c = '\\\\') AND (NumberField = 123) AND (QuoteField = '\\' OR Id!=NULL OR Id=\\'')") + new_query = active_query.where(backslash_field: backslash_input, number_field: number_input, quote_field: quote_input) + expect(new_query.to_s).to eq("SELECT Id FROM table_name WHERE (Backslash_Field__c = '\\\\') AND (NumberField = 123) AND (QuoteField = '\\' OR Id!=NULL OR Id=\\'')") end end diff --git a/spec/active_force/association_spec.rb b/spec/active_force/association_spec.rb index 3eb6747..fe5eb09 100644 --- a/spec/active_force/association_spec.rb +++ b/spec/active_force/association_spec.rb @@ -40,6 +40,16 @@ post.comments.to_a end + it 'is not mutated by #where' do + post.comments.where(body: 'test').to_a + expect(post.comments.to_s).to end_with("FROM Comment__c WHERE (PostId = '1')") + end + + it 'is not mutated by #none' do + post.comments.none.to_a + expect(post.comments.to_s).to end_with("FROM Comment__c WHERE (PostId = '1')") + end + describe 'to_s' do it "should return a SOQL statment" do soql = "SELECT Id, PostId, PosterId__c, FancyPostId, Body__c FROM Comment__c WHERE (PostId = '1')" diff --git a/spec/active_force/query_spec.rb b/spec/active_force/query_spec.rb index ae47ee8..d7a68a8 100644 --- a/spec/active_force/query_spec.rb +++ b/spec/active_force/query_spec.rb @@ -32,7 +32,7 @@ expect(query.all.to_s).to eq "SELECT Id, name, etc FROM table_name" end - it "should ignore dupicated attributes in select statment" do + it "should ignore duplicated attributes in select statment" do query.fields ['Id', 'name', 'etc'] expect(query.all.to_s).to eq "SELECT Id, name, etc FROM table_name" end @@ -46,6 +46,19 @@ it "should add multiples conditions to a query with parentheses" do expect(query.where("condition1 = 1").where("condition2 = 2 OR condition3 = 3").to_s).to eq "SELECT Id, name, etc FROM table_name WHERE (condition1 = 1) AND (condition2 = 2 OR condition3 = 3)" end + + it "should not duplicate conditions" do + first_query = query.where("name = 'cool'").where("foo = 'baz'") + second_query = first_query.where("name = 'cool'") + expect(first_query.to_s).to eq(second_query.to_s) + expect(first_query.object_id).to eq(second_query.object_id) + end + + it "should not update the original query" do + new_query = query.where("name = 'cool'") + expect(query.to_s).to eq "SELECT Id, name, etc FROM table_name" + expect(new_query.to_s).to eq "SELECT Id, name, etc FROM table_name WHERE (name = 'cool')" + end end describe ".not" do @@ -68,12 +81,18 @@ it "should add a limit to a query" do expect(query.limit("25").to_s).to eq "SELECT Id, name, etc FROM table_name LIMIT 25" end + + it "should not update the original query" do + new_query = query.limit("25") + expect(query.to_s).to eq "SELECT Id, name, etc FROM table_name" + expect(new_query.to_s).to eq "SELECT Id, name, etc FROM table_name LIMIT 25" + end end describe ".limit_value" do it "should return the limit value" do - query.limit(4) - expect(query.limit_value).to eq 4 + new_query = query.limit(4) + expect(new_query.limit_value).to eq 4 end end @@ -81,12 +100,18 @@ it "should add an offset to a query" do expect(query.offset(4).to_s).to eq "SELECT Id, name, etc FROM table_name OFFSET 4" end + + it "should not update the original query" do + new_query = query.offset(4) + expect(query.to_s).to eq "SELECT Id, name, etc FROM table_name" + expect(new_query.to_s).to eq "SELECT Id, name, etc FROM table_name OFFSET 4" + end end describe ".offset_value" do it "should return the offset value" do - query.offset(4) - expect(query.offset_value).to eq 4 + new_query = query.offset(4) + expect(new_query.offset_value).to eq 4 end end @@ -104,6 +129,12 @@ it "should add a order condition in the statment with WHERE and LIMIT" do expect(query.where("condition1 = 1").order("name desc").limit(1).to_s).to eq "SELECT Id, name, etc FROM table_name WHERE (condition1 = 1) ORDER BY name desc LIMIT 1" end + + it "should not update the original query" do + ordered_query = query.order("name desc") + expect(query.to_s).to eq "SELECT Id, name, etc FROM table_name" + expect(ordered_query.to_s).to eq "SELECT Id, name, etc FROM table_name ORDER BY name desc" + end end describe '.join' do @@ -116,18 +147,36 @@ it 'should add another select statment on the current select' do expect(query.join(join_query).to_s).to eq 'SELECT Id, name, etc, (SELECT Id, name, etc FROM join_table_name) FROM table_name' end + + it "should not update the original query" do + new_query = query.join(join_query) + expect(query.to_s).to eq "SELECT Id, name, etc FROM table_name" + expect(new_query.to_s).to eq 'SELECT Id, name, etc, (SELECT Id, name, etc FROM join_table_name) FROM table_name' + end end describe '.first' do it 'should return the query for the first record' do expect(query.first.to_s).to eq 'SELECT Id, name, etc FROM table_name LIMIT 1' end + + it "should not update the original query" do + new_query = query.first + expect(query.to_s).to eq "SELECT Id, name, etc FROM table_name" + expect(new_query.to_s).to eq 'SELECT Id, name, etc FROM table_name LIMIT 1' + end end describe '.last' do it 'should return the query for the last record' do expect(query.last.to_s).to eq 'SELECT Id, name, etc FROM table_name ORDER BY Id DESC LIMIT 1' end + + it "should not update the original query" do + new_query = query.last + expect(query.to_s).to eq "SELECT Id, name, etc FROM table_name" + expect(new_query.to_s).to eq 'SELECT Id, name, etc FROM table_name ORDER BY Id DESC LIMIT 1' + end end describe ".count" do @@ -138,6 +187,12 @@ it "should work with a condition" do expect(query.where("name = 'cool'").count.to_s).to eq "SELECT count(Id) FROM table_name WHERE (name = 'cool')" end + + it "should not update the original query" do + query_with_count = query.where("name = 'cool'").count + expect(query.to_s).to eq "SELECT Id, name, etc FROM table_name" + expect(query_with_count.to_s).to eq "SELECT count(Id) FROM table_name WHERE (name = 'cool')" + end end describe ".sum" do diff --git a/spec/active_force/sobject_spec.rb b/spec/active_force/sobject_spec.rb index 4c40443..f54ab12 100644 --- a/spec/active_force/sobject_spec.rb +++ b/spec/active_force/sobject_spec.rb @@ -315,18 +315,67 @@ class IceCream < ActiveForce::SObject end end - describe "#count" do - let(:count_response){ [Restforce::Mash.new(expr0: 1)] } + describe '.count' do + let(:response) { [Restforce::Mash.new(expr0: 1)] } - it "responds to count" do - expect(Whizbang).to respond_to(:count) + before do + allow(client).to receive(:query).and_return(response) + end + + it 'sends the correct query to the client' do + expected = 'SELECT count(Id) FROM Whizbang__c' + Whizbang.count + expect(client).to have_received(:query).with(expected) end - it "sends the query to the client" do - expect(client).to receive(:query).and_return(count_response) + it 'returns the result from the response' do expect(Whizbang.count).to eq(1) end + it 'works with .where' do + expected = 'SELECT count(Id) FROM Whizbang__c WHERE (Boolean_Label = true)' + Whizbang.where(boolean: true).count + expect(client).to have_received(:query).with(expected) + end + end + + describe '.sum' do + let(:response) { [Restforce::Mash.new(expr0: 22)] } + + before do + allow(client).to receive(:query).and_return(response) + end + + it 'raises ArgumentError if given blank' do + expect { Whizbang.sum(nil) }.to raise_error(ArgumentError, 'field is required') + end + + it 'raises ArgumentError if given invalid field' do + expect { Whizbang.sum(:invalid) } + .to raise_error(ArgumentError, /field 'invalid' does not exist on Whizbang/i) + end + + it 'sends the correct query to the client' do + expected = 'SELECT sum(Percent_Label) FROM Whizbang__c' + Whizbang.sum(:percent) + expect(client).to have_received(:query).with(expected) + end + + it 'works when given a string field' do + expected = 'SELECT sum(Percent_Label) FROM Whizbang__c' + Whizbang.sum('percent') + expect(client).to have_received(:query).with(expected) + end + + it 'returns the result from the response' do + expect(Whizbang.sum(:percent)).to eq(22) + end + + it 'works with .where' do + expected = 'SELECT sum(Percent_Label) FROM Whizbang__c WHERE (Boolean_Label = true)' + Whizbang.where(boolean: true).sum(:percent) + expect(client).to have_received(:query).with(expected) + end end describe "#find_by" do