Skip to content

Commit

Permalink
Merge branch 'whitelist'
Browse files Browse the repository at this point in the history
  • Loading branch information
sumirolabs committed Mar 26, 2012
2 parents 220849d + 28a398a commit 0bf48e4
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 28 deletions.
1 change: 1 addition & 0 deletions lib/databasedotcom.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
require 'databasedotcom/sobject'
require 'databasedotcom/chatter'
require 'databasedotcom/blacklist'
require 'databasedotcom/whitelist'
26 changes: 14 additions & 12 deletions lib/databasedotcom/blacklist.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ module Databasedotcom
# Databasedotcom::Blacklist.blacklist = my_blacklist
class Blacklist

@blacklist = {'classes' => [], 'fields' => {}}

# Specify blacklisted class and field names in a hash. The 'class' keypair should contain an array
# of SObject names and the 'fields' keypair should contain a hash of class name & field array keypairs.
# my.blacklist = {'classes' => ['Account', 'Case'], 'fields' => {'Opportunity' => ['field1', 'field2']}}
Expand Down Expand Up @@ -55,28 +57,28 @@ def self.blacklisted_fields(class_name)
# Wrap the DESCRIBE_SOBJECT and DESCRIBE_SOBJECTS methods with the blacklist filter so
# we never know about classes and/or fields that are blacklisted.
class Client
def describe_sobjects_with_filter
describe_sobjects_without_filter.collect do |sobject|
def describe_blacklist_sobjects_with_filter
describe_blacklist_sobjects_without_filter.collect do |sobject|
Databasedotcom::Blacklist.filter_description!(sobject['description'], sobject['name'])
sobject
end
end
alias_method :describe_sobjects_without_filter, :describe_sobjects
alias_method :describe_sobjects, :describe_sobjects_with_filter
alias_method :describe_blacklist_sobjects_without_filter, :describe_sobjects
alias_method :describe_sobjects, :describe_blacklist_sobjects_with_filter

def describe_sobject_with_filter(class_name)
description = describe_sobject_without_filter(class_name)
def describe_blacklist_sobject_with_filter(class_name)
description = describe_blacklist_sobject_without_filter(class_name)
Databasedotcom::Blacklist.filter_description!(description, class_name)
description
end
alias_method :describe_sobject_without_filter, :describe_sobject
alias_method :describe_sobject, :describe_sobject_with_filter
alias_method :describe_blacklist_sobject_without_filter, :describe_sobject
alias_method :describe_sobject, :describe_blacklist_sobject_with_filter

def list_sobjects_with_filter
Databasedotcom::Blacklist.filter_sobjects(list_sobjects_without_filter)
def list_blacklist_sobjects_with_filter
Databasedotcom::Blacklist.filter_sobjects(list_blacklist_sobjects_without_filter)
end
alias_method :list_sobjects_without_filter, :list_sobjects
alias_method :list_sobjects, :list_sobjects_with_filter
alias_method :list_blacklist_sobjects_without_filter, :list_sobjects
alias_method :list_sobjects, :list_blacklist_sobjects_with_filter

end

Expand Down
9 changes: 8 additions & 1 deletion lib/databasedotcom/databasedotcom_error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,12 @@ def initialize(path=nil)
super(message)
end
end


class NoFieldsError < StandardError
def initialize(class_name)
message = "SObject '#{class_name}' has no fields in its description, probably because the blacklist and / or whitelist are configured incorrectly"
super(message)
end
end

end
88 changes: 88 additions & 0 deletions lib/databasedotcom/whitelist.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
require 'databasedotcom/blacklist'

module Databasedotcom

# Sometimes you don't want all of the SObjects or SObject fields. One vexing situation is an
# SObject that has a huge number of fields because a query on that object can exceed the
# maximum number of characters allowed in the URL path (8192). This produces a 500 error that
# is quite difficult to understand. The solution is to remove some of the fields from the
# SObject so the query URL is less than the 8192 character limit.
#
# You can effectively hide entire SObjects or individual fields via the whitelist. Create a hash
# with keys 'classes' and/or 'fields' and specify the classes (as an array) and fields (as a hash of
# class / fieldname array pairs) to be excluded.
#
# my_whitelist = {'classes' => ['Account', 'Case'], 'fields' => {'Opportunity' => ['name']}}
# Databasedotcom::Whitelist.whitelist = my_whitelist
class Whitelist

@whitelist = {'classes' => [], 'fields' => {}}

# Specify whitelisted class and field names in a hash. The 'class' keypair should contain an array
# of SObject names and the 'fields' keypair should contain a hash of class name & field array keypairs.
# my.whitelist = {'classes' => ['Account', 'Case'], 'fields' => {'Opportunity' => ['field1', 'field2']}}
def self.whitelist=(whitelist_hash)
@whitelist = whitelist_hash || {}
@whitelist['fields'] ||= {}
@whitelist['classes'] ||= []
end

# Remove whitelisted fields from the description provided by Salesforce. Once the
# fields are removed, Databasedotcom will not know about them or use them.
def self.filter_description!(description, class_name)
if description && description['fields']
description['fields'] = description['fields'].select{|f| allow_field?(class_name, f['name'])}
raise Databasedotcom::NoFieldsError.new(class_name) unless description['fields'].length > 0
end
end

def self.allow_field?(class_name, field)
allowed_fields = whitelisted_fields(class_name)
allowed_fields.length == 0 || allowed_fields.include?(field)
end

def self.filter_sobjects(sobjects)
whitelisted_classes.length > 0 ? whitelisted_classes : sobjects
end

private

def self.whitelisted_classes
@whitelist['classes'] || []
end

def self.whitelisted_fields(class_name)
@whitelist['fields'][class_name] || []
end

end

# Wrap the DESCRIBE_SOBJECT and DESCRIBE_SOBJECTS methods with the whitelist filter so
# we never know about classes and/or fields that are whitelisted.
class Client
def describe_whitelist_sobjects_with_filter
describe_whitelist_sobjects_without_filter.collect do |sobject|
Databasedotcom::Whitelist.filter_description!(sobject['description'], sobject['name'])
sobject
end
end
alias_method :describe_whitelist_sobjects_without_filter, :describe_sobjects
alias_method :describe_sobjects, :describe_whitelist_sobjects_with_filter

def describe_whitelist_sobject_with_filter(class_name)
description = describe_whitelist_sobject_without_filter(class_name)
Databasedotcom::Whitelist.filter_description!(description, class_name)
description
end
alias_method :describe_whitelist_sobject_without_filter, :describe_sobject
alias_method :describe_sobject, :describe_whitelist_sobject_with_filter

def list_whitelist_sobjects_with_filter
Databasedotcom::Whitelist.filter_sobjects(list_whitelist_sobjects_without_filter)
end
alias_method :list_whitelist_sobjects_without_filter, :list_sobjects
alias_method :list_sobjects, :list_whitelist_sobjects_with_filter

end

end
51 changes: 36 additions & 15 deletions spec/lib/blacklist_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,30 @@

describe Databasedotcom::Blacklist do

describe '#blacklist=' do
before do
@bl = Databasedotcom::Blacklist
end
it 'should assign classes' do
@bl.blacklist = {'classes' => [1]}
@bl.instance_variable_get(:@blacklist)['classes'].should == [1]
end
it 'should assign fields' do
@bl.blacklist = {'fields' => {'class_name' => [:fields]}}
@bl.instance_variable_get(:@blacklist)['fields'].should == {'class_name' => [:fields]}
end
it 'should initialize classes' do
raw_class = Databasedotcom::Blacklist.dup
raw_class.blacklist = nil
raw_class.instance_variable_get(:@blacklist).keys.should include('classes')
end
it 'should initialize fields' do
raw_class = Databasedotcom::Blacklist.dup
raw_class.blacklist = nil
raw_class.instance_variable_get(:@blacklist).keys.should include('fields')
end
end

describe '#allow_field?(field)' do
it 'should indicate if a field is allowed/included' do
fake_class_name = 'FakeClassName'
Expand All @@ -18,7 +42,7 @@
Databasedotcom::Blacklist.allow_field?(fake_class_name, :foo).should be_true

Databasedotcom::Blacklist.blacklist = {'fields' => {fake_class_name => [:foo]}}
Databasedotcom::Blacklist.allow_field?(fake_class_name, :foo).should_not be_true
Databasedotcom::Blacklist.allow_field?(fake_class_name, :foo).should be_false
end
end

Expand Down Expand Up @@ -62,37 +86,34 @@
before do
@client = Databasedotcom::Client.new
end
describe '#describe_sobjects_with_filter' do
describe '#describe_blacklist_sobjects_with_filter' do
it 'should filter SObjects descriptions using the blacklist' do
@client.should_receive(:describe_sobjects_without_filter) { [{'description'=>1, 'name'=>2}] }
Databasedotcom::Blacklist.should_receive(:filter_description!).with(1,2)
@client.should_receive(:describe_blacklist_sobjects_without_filter) { [{'description'=>'1', 'name'=>'2'}] }
Databasedotcom::Blacklist.should_receive(:filter_description!).with('1','2')
@client.describe_sobjects
end
it 'should return the sobjects' do
sobjects = [
{'description'=>1, 'name'=>2},
{'description'=>3, 'name'=>4},
{'description'=>5, 'name'=>6},
]
@client.should_receive(:describe_sobjects_without_filter) { sobjects }
sobjects = [{'description'=>'1', 'name'=>'2'}]
@client.should_receive(:describe_blacklist_sobjects_without_filter) { sobjects }
Databasedotcom::Blacklist.stub(:filter_description!)
@client.describe_sobjects.should == sobjects
end
end

it 'should filter an SObject description fields using the blacklist' do
description = mock()
description = {'fields' => ['fake_field']}
class_name = 'fake_class_name'
@client.should_receive(:describe_sobject_without_filter).with(class_name) { description }
@client.should_receive(:describe_blacklist_sobject_without_filter).with(class_name) { description }
Databasedotcom::Blacklist.should_receive(:filter_description!).with(description, class_name)
@client.describe_sobject(class_name).should == description
end

it 'should filter #list_sobjects using the blacklist' do
sobjects = [1,2,3]
@client.should_receive(:list_sobjects_without_filter) { sobjects }
Databasedotcom::Blacklist.should_receive(:filter_sobjects).once { sobjects } #.with(:list_sobjects_without_filter)
@client.list_sobjects.should == sobjects
filtered_sobjects = [5,6]
@client.should_receive(:list_blacklist_sobjects_without_filter) { sobjects }
Databasedotcom::Blacklist.should_receive(:filter_sobjects).once { filtered_sobjects } #.with(:list_sobjects_without_filter)
@client.list_sobjects.should == filtered_sobjects
end

end
126 changes: 126 additions & 0 deletions spec/lib/whitelist_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
require 'rspec'
require 'spec_helper'
require 'databasedotcom'
require 'databasedotcom/whitelist'

describe Databasedotcom::Whitelist do
before do
Databasedotcom::Blacklist.instance_variable_set(:@blacklist, {'classes' => [], 'fields' => {}})
end

describe '#whitelist=' do
before do
@wl = Databasedotcom::Whitelist
end
it 'should assign classes' do
@wl.whitelist = {'classes' => [1]}
@wl.instance_variable_get(:@whitelist)['classes'].should == [1]
end
it 'should assign fields' do
@wl.whitelist = {'fields' => {'class_name' => [:fields]}}
@wl.instance_variable_get(:@whitelist)['fields'].should == {'class_name' => [:fields]}
end
it 'should initialize classes' do
raw_class = Databasedotcom::Whitelist.dup
raw_class.whitelist = nil
raw_class.instance_variable_get(:@whitelist).keys.should include('classes')
end
it 'should initialize fields' do
raw_class = Databasedotcom::Whitelist.dup
raw_class.whitelist = nil
raw_class.instance_variable_get(:@whitelist).keys.should include('fields')
end
end

describe '#allow_field?(field)' do
before do
@fake_class_name = 'FakeClassName'
end
it 'should indicate if a field is allowed/included' do
Databasedotcom::Whitelist.whitelist = {'fields' => {@fake_class_name => [:foox]}}
Databasedotcom::Whitelist.allow_field?(@fake_class_name, :foo).should be_false

Databasedotcom::Whitelist.whitelist = {'fields' => {@fake_class_name => [:foo]}}
Databasedotcom::Whitelist.allow_field?(@fake_class_name, :foo).should be_true
end
it 'should be true if there is no whitelist' do
Databasedotcom::Whitelist.whitelist = nil
Databasedotcom::Whitelist.allow_field?(@fake_class_name, :foo).should be_true
end
it 'should be true if the whitelist is blank' do
Databasedotcom::Whitelist.whitelist = {'fields' => {@fake_class_name => []}}
Databasedotcom::Whitelist.allow_field?(@fake_class_name, :foo).should be_true
end
end

describe '#filter_sobjects(sobjects)' do
it 'should only include whitelisted sobjects' do
Databasedotcom::Whitelist.whitelist = {'classes' => [:a, :c]}
Databasedotcom::Whitelist.filter_sobjects([:a, :b, :c, :d]).should == [:a, :c]
end
it 'should include all sobjects if there is no whitelist' do
Databasedotcom::Whitelist.whitelist = nil
Databasedotcom::Whitelist.filter_sobjects([:a, :b, :c, :d]).should == [:a, :b, :c, :d]
end
end

describe '#filter_description!' do
describe 'with a FIELDS keypair' do
before do
@fake_class_name = 'FakeClassName'
@description_hash = {:a => 1, :b => 2, 'fields' => [{'name'=>'one'}, {'name'=>'two'}, {'name'=>'three'}]}
Databasedotcom::Whitelist.stub(:allow_field?).with(@fake_class_name, 'one'){false}
Databasedotcom::Whitelist.stub(:allow_field?).with(@fake_class_name, 'two') {true}
Databasedotcom::Whitelist.stub(:allow_field?).with(@fake_class_name, 'three') {false}
end
it 'should only include allowed fields' do
Databasedotcom::Whitelist.filter_description!(@description_hash, @fake_class_name)
@description_hash['fields'].include?({'name'=>'one'}).should be_false
@description_hash['fields'].include?({'name'=>'three'}).should be_false
end
end
it 'should not change other keypairs' do
description_hash = {:a => 1, :b => 2}
description_hash_clone = description_hash.clone
Databasedotcom::Whitelist.filter_description!(description_hash, @fake_class_name)
description_hash_clone.should == description_hash
end
end

end

describe Databasedotcom::Client do
before do
@client = Databasedotcom::Client.new
end
describe '#describe_whitelist_sobjects_with_filter' do
it 'should filter SObjects descriptions using the whitelist' do
@client.should_receive(:describe_whitelist_sobjects_without_filter) { [{'description'=>1, 'name'=>2}] }
Databasedotcom::Whitelist.should_receive(:filter_description!).with(1,2)
@client.describe_sobjects
end
it 'should return the filtered sobjects' do
sobjects = [ {'description'=>1, 'name'=>2} ]
@client.should_receive(:describe_whitelist_sobjects_without_filter) { sobjects }
Databasedotcom::Whitelist.stub(:filter_description!)
@client.describe_sobjects.should == sobjects
end
end

it 'should filter an SObject description fields using the whitelist' do
description = mock()
class_name = 'fake_class_name'
@client.should_receive(:describe_whitelist_sobject_without_filter).with(class_name) { description }
Databasedotcom::Whitelist.should_receive(:filter_description!).with(description, class_name)
@client.describe_sobject(class_name).should == description
end

it 'should filter #list_sobjects using the whitelist' do
sobjects = [1,2,3]
filtered_sobjects = [1,2,3]
@client.should_receive(:list_whitelist_sobjects_without_filter) { sobjects }
Databasedotcom::Whitelist.should_receive(:filter_sobjects).once { filtered_sobjects } #.with(:list_sobjects_without_filter)
@client.list_sobjects.should == filtered_sobjects
end

end

0 comments on commit 0bf48e4

Please sign in to comment.