active_force 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +9 -2
- data/CHANGELOG.md +6 -0
- data/README.md +43 -41
- data/active_force.gemspec +1 -1
- data/lib/active_attr/dirty.rb +0 -5
- data/lib/active_force/active_query.rb +66 -7
- data/lib/active_force/association/association.rb +4 -0
- data/lib/active_force/association/belongs_to_association.rb +11 -3
- data/lib/active_force/association/has_many_association.rb +6 -4
- data/lib/active_force/query.rb +5 -0
- data/lib/active_force/sobject.rb +45 -32
- data/lib/active_force/standard_types.rb +357 -0
- data/lib/active_force/table.rb +33 -0
- data/lib/active_force/version.rb +1 -1
- data/lib/generators/active_force/{active_force_model → model}/USAGE +0 -0
- data/lib/generators/active_force/model/model_generator.rb +61 -0
- data/lib/generators/active_force/model/templates/model.rb.erb +7 -0
- data/spec/active_force/active_query_spec.rb +79 -12
- data/spec/active_force/association_spec.rb +94 -16
- data/spec/active_force/callbacks_spec.rb +42 -0
- data/spec/active_force/query_spec.rb +10 -0
- data/spec/active_force/sobject/table_name_spec.rb +15 -4
- data/spec/active_force/sobject_spec.rb +72 -3
- data/spec/active_force/table_spec.rb +28 -0
- metadata +13 -8
- data/lib/generators/active_force/active_force_model/active_force_model_generator.rb +0 -47
- data/lib/generators/active_force/active_force_model/templates/model.rb.erb +0 -11
@@ -2,18 +2,15 @@ require 'spec_helper'
|
|
2
2
|
require 'active_force/active_query'
|
3
3
|
|
4
4
|
describe ActiveForce::ActiveQuery do
|
5
|
-
let(:sobject)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
let(:client){
|
14
|
-
double("client")
|
15
|
-
}
|
16
|
-
|
5
|
+
let(:sobject) do
|
6
|
+
double("sobject", {
|
7
|
+
table_name: "table_name",
|
8
|
+
fields: [],
|
9
|
+
mappings: mappings
|
10
|
+
})
|
11
|
+
end
|
12
|
+
let(:mappings){ { field: "Field__c", other_field: "Other_Field" } }
|
13
|
+
let(:client){ double("client") }
|
17
14
|
let(:active_query){ ActiveForce::ActiveQuery.new(sobject) }
|
18
15
|
|
19
16
|
before do
|
@@ -37,6 +34,13 @@ describe ActiveForce::ActiveQuery do
|
|
37
34
|
end
|
38
35
|
end
|
39
36
|
|
37
|
+
describe "select only some field using mappings" do
|
38
|
+
it "should return a query only with selected field" do
|
39
|
+
active_query.select(:field)
|
40
|
+
expect(active_query.to_s).to eq("SELECT Field__c FROM table_name")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
40
44
|
describe "condition mapping" do
|
41
45
|
it "maps conditions for a .where" do
|
42
46
|
active_query.where(field: 123)
|
@@ -47,6 +51,69 @@ describe ActiveForce::ActiveQuery do
|
|
47
51
|
active_query.where field: "hello"
|
48
52
|
expect(active_query.to_s).to end_with("Field__c = 'hello'")
|
49
53
|
end
|
54
|
+
|
55
|
+
it "puts NULL when a field is set as nil" do
|
56
|
+
active_query.where field: nil
|
57
|
+
expect(active_query.to_s).to end_with("Field__c = NULL")
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'bind parameters' do
|
61
|
+
let(:mappings) do
|
62
|
+
super().merge({
|
63
|
+
other_field: 'Other_Field__c',
|
64
|
+
name: 'Name'
|
65
|
+
})
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'accepts bind parameters' do
|
69
|
+
active_query.where('Field__c = ?', 123)
|
70
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE Field__c = 123")
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'accepts nil bind parameters' do
|
74
|
+
active_query.where('Field__c = ?', nil)
|
75
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE Field__c = NULL")
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'accepts multiple bind parameters' do
|
79
|
+
active_query.where('Field__c = ? AND Other_Field__c = ? AND Name = ?', 123, 321, 'Bob')
|
80
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE Field__c = 123 AND Other_Field__c = 321 AND Name = 'Bob'")
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'complains when there given an incorrect number of bind parameters' do
|
84
|
+
expect{
|
85
|
+
active_query.where('Field__c = ? AND Other_Field__c = ? AND Name = ?', 123, 321)
|
86
|
+
}.to raise_error(ActiveForce::PreparedStatementInvalid, 'wrong number of bind variables (2 for 3)')
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'named bind parameters' do
|
90
|
+
it 'accepts bind parameters' do
|
91
|
+
active_query.where('Field__c = :field', field: 123)
|
92
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE Field__c = 123")
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'accepts nil bind parameters' do
|
96
|
+
active_query.where('Field__c = :field', field: nil)
|
97
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE Field__c = NULL")
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'accepts multiple bind parameters' do
|
101
|
+
active_query.where('Field__c = :field AND Other_Field__c = :other_field AND Name = :name', field: 123, other_field: 321, name: 'Bob')
|
102
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE Field__c = 123 AND Other_Field__c = 321 AND Name = 'Bob'")
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'accepts multiple bind parameters orderless' do
|
106
|
+
active_query.where('Field__c = :field AND Other_Field__c = :other_field AND Name = :name', name: 'Bob', other_field: 321, field: 123)
|
107
|
+
expect(active_query.to_s).to eq("SELECT Id FROM table_name WHERE Field__c = 123 AND Other_Field__c = 321 AND Name = 'Bob'")
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'complains when there given an incorrect number of bind parameters' do
|
111
|
+
expect{
|
112
|
+
active_query.where('Field__c = :field AND Other_Field__c = :other_field AND Name = :name', field: 123, other_field: 321)
|
113
|
+
}.to raise_error(ActiveForce::PreparedStatementInvalid, 'missing value for :name in Field__c = :field AND Other_Field__c = :other_field AND Name = :name')
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
50
117
|
end
|
51
118
|
|
52
119
|
describe "#find_by" do
|
@@ -4,20 +4,15 @@ require 'active_force/association'
|
|
4
4
|
describe ActiveForce::SObject do
|
5
5
|
|
6
6
|
let :post do
|
7
|
-
|
8
|
-
allow(post).to receive(:id).and_return "1"
|
9
|
-
post
|
7
|
+
Post.new(id: "1")
|
10
8
|
end
|
11
9
|
|
12
10
|
let :comment do
|
13
|
-
|
14
|
-
allow(comment).to receive(:id).and_return "1"
|
15
|
-
allow(comment).to receive(:post_id).and_return "1"
|
16
|
-
comment
|
11
|
+
Comment.new(id: "1", post_id: "1")
|
17
12
|
end
|
18
13
|
|
19
14
|
let :client do
|
20
|
-
double("sfdc_client")
|
15
|
+
double("sfdc_client", query: [Restforce::Mash.new(id: 1)])
|
21
16
|
end
|
22
17
|
|
23
18
|
before do
|
@@ -34,7 +29,6 @@ describe ActiveForce::SObject do
|
|
34
29
|
end
|
35
30
|
|
36
31
|
describe "has_many_query" do
|
37
|
-
|
38
32
|
before do
|
39
33
|
class Post < ActiveForce::SObject
|
40
34
|
has_many :comments
|
@@ -49,13 +43,46 @@ describe ActiveForce::SObject do
|
|
49
43
|
expect(post.comments).to be_a ActiveForce::ActiveQuery
|
50
44
|
end
|
51
45
|
|
46
|
+
it 'makes only one API call to fetch the associated object' do
|
47
|
+
expect(client).to receive(:query).once
|
48
|
+
post.comments.to_a
|
49
|
+
post.comments.to_a
|
50
|
+
end
|
51
|
+
|
52
52
|
describe 'to_s' do
|
53
|
-
it "should
|
53
|
+
it "should return a SOQL statment" do
|
54
54
|
soql = "SELECT Id, PostId FROM Comment__c WHERE PostId = '1'"
|
55
|
-
|
55
|
+
expect(post.comments.to_s).to eq soql
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
+
context 'when the SObject is namespaced' do
|
60
|
+
let(:account){ Foo::Account.new(id: '1') }
|
61
|
+
|
62
|
+
before do
|
63
|
+
module Foo
|
64
|
+
class Opportunity < ActiveForce::SObject
|
65
|
+
field :account_id, from: 'AccountId'
|
66
|
+
end
|
67
|
+
|
68
|
+
class Account < ActiveForce::SObject
|
69
|
+
has_many :opportunities, model: Foo::Opportunity
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'correctly infers the foreign key and forms the correct query' do
|
75
|
+
soql = "SELECT Id, AccountId FROM Opportunity WHERE AccountId = '1'"
|
76
|
+
expect(account.opportunities.to_s).to eq soql
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'uses an explicit foreign key if it is supplied' do
|
80
|
+
Foo::Opportunity.field :partner_account_id, from: 'Partner_Account_Id__c'
|
81
|
+
Foo::Account.has_many :opportunities, foreign_key: :partner_account_id, model: Foo::Opportunity
|
82
|
+
soql = "SELECT Id, AccountId, Partner_Account_Id__c FROM Opportunity WHERE Partner_Account_Id__c = '1'"
|
83
|
+
expect(account.opportunities.to_s).to eq soql
|
84
|
+
end
|
85
|
+
end
|
59
86
|
end
|
60
87
|
|
61
88
|
describe 'has_many(options)' do
|
@@ -70,8 +97,8 @@ describe ActiveForce::SObject do
|
|
70
97
|
end
|
71
98
|
|
72
99
|
it 'should allow to change the foreign key' do
|
73
|
-
Post.has_many :comments, { foreign_key: :
|
74
|
-
Comment.field :
|
100
|
+
Post.has_many :comments, { foreign_key: :poster }
|
101
|
+
Comment.field :poster, from: 'PostId'
|
75
102
|
soql = "SELECT Id, PostId FROM Comment__c WHERE PostId = '1'"
|
76
103
|
expect(post.comments.to_s).to eq soql
|
77
104
|
end
|
@@ -90,13 +117,11 @@ describe ActiveForce::SObject do
|
|
90
117
|
end
|
91
118
|
|
92
119
|
describe "belongs_to" do
|
93
|
-
|
94
120
|
before do
|
95
|
-
|
121
|
+
Comment.belongs_to :post
|
96
122
|
end
|
97
123
|
|
98
124
|
it "should get the resource it belongs to" do
|
99
|
-
Comment.belongs_to :post
|
100
125
|
expect(comment.post).to be_instance_of(Post)
|
101
126
|
end
|
102
127
|
|
@@ -110,5 +135,58 @@ describe ActiveForce::SObject do
|
|
110
135
|
comment.post
|
111
136
|
end
|
112
137
|
|
138
|
+
it 'makes only one API call to fetch the associated object' do
|
139
|
+
expect(client).to receive(:query).once
|
140
|
+
comment.post
|
141
|
+
comment.post
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'accepts assignment of an existing object as an association' do
|
145
|
+
expect(client).to_not receive(:query)
|
146
|
+
other_post = Post.new(id: "2")
|
147
|
+
comment.post = other_post
|
148
|
+
expect(comment.post_id).to eq other_post.id
|
149
|
+
expect(comment.post).to eq other_post
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'when the SObject is namespaced' do
|
153
|
+
let(:attachment){ Foo::Attachment.new(id: '1', lead_id: '2') }
|
154
|
+
before do
|
155
|
+
module Foo
|
156
|
+
class Lead < ActiveForce::SObject; end
|
157
|
+
|
158
|
+
class Attachment < ActiveForce::SObject
|
159
|
+
field :lead_id, from: 'Lead_Id__c'
|
160
|
+
belongs_to :lead, model: Foo::Lead
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'generates the correct query' do
|
166
|
+
expect(client).to receive(:query).with("SELECT Id FROM Lead WHERE Id = '2' LIMIT 1")
|
167
|
+
attachment.lead
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'instantiates the correct object' do
|
171
|
+
expect(attachment.lead).to be_instance_of(Foo::Lead)
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'when given a foreign key' do
|
175
|
+
let(:attachment){ Foo::Attachment.new(id: '1', fancy_lead_id: '2') }
|
176
|
+
before do
|
177
|
+
module Foo
|
178
|
+
class Attachment < ActiveForce::SObject
|
179
|
+
field :fancy_lead_id, from: 'LeadId'
|
180
|
+
belongs_to :lead, model: Foo::Lead, foreign_key: :fancy_lead_id
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'generates the correct query' do
|
186
|
+
expect(client).to receive(:query).with("SELECT Id FROM Lead WHERE Id = '2' LIMIT 1")
|
187
|
+
attachment.lead
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
113
191
|
end
|
114
192
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_force/sobject'
|
3
|
+
|
4
|
+
describe ActiveForce::SObject do
|
5
|
+
let(:client) { double 'Client', create!: 'id' }
|
6
|
+
|
7
|
+
before do
|
8
|
+
allow(ActiveForce::SObject).to receive(:sfdc_client).and_return client
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "save" do
|
12
|
+
|
13
|
+
it 'call action callback when save a record' do
|
14
|
+
class Whizbanged < ActiveForce::SObject
|
15
|
+
|
16
|
+
field :updated_from
|
17
|
+
field :dirty_attribute
|
18
|
+
|
19
|
+
before_save :set_as_updated_from_rails
|
20
|
+
after_save :mark_dirty
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def set_as_updated_from_rails
|
25
|
+
self.updated_from = 'Rails'
|
26
|
+
end
|
27
|
+
|
28
|
+
def mark_dirty
|
29
|
+
self.dirty_attribute = true
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
whizbanged = Whizbanged.new
|
35
|
+
whizbanged.save
|
36
|
+
expect(whizbanged.updated_from).to eq 'Rails'
|
37
|
+
expect(whizbanged.dirty_attribute).to eq true
|
38
|
+
expect(whizbanged.changed.include? 'dirty_attribute').to eq true
|
39
|
+
expect(whizbanged.changed.include? 'updated_from').to eq false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -11,6 +11,16 @@ describe ActiveForce::Query do
|
|
11
11
|
after do
|
12
12
|
end
|
13
13
|
|
14
|
+
describe '.select' do
|
15
|
+
it 'use column sent on the select method' do
|
16
|
+
expect(@query.select('name').all.to_s).to eq "SELECT name FROM table_name"
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'use columns sent on the select method' do
|
20
|
+
expect(@query.select(['id','name']).all.to_s).to eq "SELECT id, name FROM table_name"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
14
24
|
describe ".all" do
|
15
25
|
it "table should return table name" do
|
16
26
|
expect(@query.all.table).to eq(@query.table)
|
@@ -25,13 +25,24 @@ describe ActiveForce::SObject do
|
|
25
25
|
expect(EnforcedTableName.table_name).to eq('Forced__c')
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
context 'with a namespace' do
|
29
|
+
it "the namespace is not included" do
|
30
|
+
module Foo
|
31
|
+
class Bar < ActiveForce::SObject
|
32
|
+
end
|
31
33
|
end
|
34
|
+
|
35
|
+
expect(Foo::Bar.table_name).to eq('Bar__c')
|
32
36
|
end
|
33
37
|
|
34
|
-
|
38
|
+
it 'standard types are inferred correctly' do
|
39
|
+
module Foo
|
40
|
+
class Account < ActiveForce::SObject
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
expect(Foo::Account.table_name).to eq('Account')
|
45
|
+
end
|
35
46
|
end
|
36
47
|
end
|
37
48
|
end
|
@@ -55,16 +55,46 @@ describe ActiveForce::SObject do
|
|
55
55
|
expect(Quota.mappings[:id]).to eq 'Bar_Id__c'
|
56
56
|
end
|
57
57
|
end
|
58
|
+
|
59
|
+
context 'as: :multi_picklist' do
|
60
|
+
before do
|
61
|
+
class IceCream < ActiveForce::SObject
|
62
|
+
field :flavors, as: :multi_picklist
|
63
|
+
end
|
64
|
+
sundae.flavors = %w(chocolate vanilla strawberry)
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'on create' do
|
68
|
+
let(:sundae) { IceCream.new }
|
69
|
+
it 'formats the picklist values' do
|
70
|
+
expect(client).to receive(:create!).with('IceCream__c', {'Flavors__c' => 'chocolate;vanilla;strawberry'})
|
71
|
+
sundae.save
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'on update' do
|
76
|
+
let(:sundae) { IceCream.new(id: '1') }
|
77
|
+
it 'formats the picklist values' do
|
78
|
+
expect(client).to receive(:update!).with('IceCream__c', {'Flavors__c' => 'chocolate;vanilla;strawberry', 'Id' => '1'})
|
79
|
+
sundae.save
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
58
84
|
end
|
59
85
|
|
60
86
|
describe '#update' do
|
61
87
|
|
62
88
|
subject do
|
63
|
-
Whizbang.new
|
89
|
+
Whizbang.new(id: '1')
|
64
90
|
end
|
65
91
|
|
66
92
|
before do
|
67
|
-
|
93
|
+
expected_args = [
|
94
|
+
Whizbang.table_name,
|
95
|
+
{'Text_Label' => 'some text', 'Id' => '1'}
|
96
|
+
]
|
97
|
+
expect(client).to receive(:update!).with(*expected_args).and_return('id')
|
68
98
|
end
|
69
99
|
|
70
100
|
it 'delegates to the Client with create!' do
|
@@ -97,7 +127,7 @@ describe ActiveForce::SObject do
|
|
97
127
|
describe 'self.create' do
|
98
128
|
|
99
129
|
before do
|
100
|
-
expect(client).to receive(:create!).and_return('id')
|
130
|
+
expect(client).to receive(:create!).with(Whizbang.table_name, 'Text_Label' => 'some text').and_return('id')
|
101
131
|
end
|
102
132
|
|
103
133
|
it 'should create a new instance' do
|
@@ -126,4 +156,43 @@ describe ActiveForce::SObject do
|
|
126
156
|
Whizbang.find_by id: 123, text: "foo"
|
127
157
|
end
|
128
158
|
end
|
159
|
+
|
160
|
+
describe '#reload' do
|
161
|
+
let(:client) do
|
162
|
+
double("sfdc_client", query: [Restforce::Mash.new(Id: 1, Name: 'Jeff')])
|
163
|
+
end
|
164
|
+
let(:quota){ Quota.new(id: '1') }
|
165
|
+
let(:territory){ Territory.new(id: '1', quota_id: '1') }
|
166
|
+
|
167
|
+
before do
|
168
|
+
Territory.belongs_to :quota, model: Quota
|
169
|
+
Territory.field :quota_id, from: 'Quota_Id'
|
170
|
+
allow(ActiveForce::SObject).to receive(:sfdc_client).and_return client
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'clears cached associations' do
|
174
|
+
soql = "SELECT Id, Bar_Id__c FROM Quota__c WHERE Id = '1' LIMIT 1"
|
175
|
+
expect(client).to receive(:query).twice.with soql
|
176
|
+
allow(Territory).to receive(:find){ territory }
|
177
|
+
territory.quota
|
178
|
+
territory.quota
|
179
|
+
territory.reload
|
180
|
+
territory.quota
|
181
|
+
end
|
182
|
+
|
183
|
+
it "refreshes the object's attributes" do
|
184
|
+
Territory.field :name, from: 'Name'
|
185
|
+
territory.name = 'Walter'
|
186
|
+
expect(territory.name).to eq 'Walter'
|
187
|
+
territory.reload
|
188
|
+
expect(territory.name).to eq 'Jeff'
|
189
|
+
expect(territory.changed_attributes).to be_empty
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'returns the same object' do
|
193
|
+
allow(Territory).to receive(:find){ Territory.new }
|
194
|
+
expected = territory
|
195
|
+
expect(territory.reload).to eql expected
|
196
|
+
end
|
197
|
+
end
|
129
198
|
end
|