openstax_active_force 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.mailmap +3 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.md +98 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +174 -0
- data/Rakefile +6 -0
- data/active_force.gemspec +30 -0
- data/lib/active_attr/dirty.rb +33 -0
- data/lib/active_force/active_query.rb +155 -0
- data/lib/active_force/association/association.rb +50 -0
- data/lib/active_force/association/belongs_to_association.rb +26 -0
- data/lib/active_force/association/eager_load_projection_builder.rb +60 -0
- data/lib/active_force/association/has_many_association.rb +33 -0
- data/lib/active_force/association/relation_model_builder.rb +70 -0
- data/lib/active_force/association.rb +28 -0
- data/lib/active_force/attribute.rb +30 -0
- data/lib/active_force/mapping.rb +78 -0
- data/lib/active_force/query.rb +110 -0
- data/lib/active_force/sobject.rb +210 -0
- data/lib/active_force/standard_types.rb +357 -0
- data/lib/active_force/table.rb +37 -0
- data/lib/active_force/version.rb +3 -0
- data/lib/active_force.rb +13 -0
- data/lib/generators/active_force/model/USAGE +8 -0
- data/lib/generators/active_force/model/model_generator.rb +62 -0
- data/lib/generators/active_force/model/templates/model.rb.erb +5 -0
- data/spec/active_force/active_query_spec.rb +178 -0
- data/spec/active_force/association/relation_model_builder_spec.rb +62 -0
- data/spec/active_force/association_spec.rb +157 -0
- data/spec/active_force/attribute_spec.rb +27 -0
- data/spec/active_force/callbacks_spec.rb +20 -0
- data/spec/active_force/mapping_spec.rb +18 -0
- data/spec/active_force/query_spec.rb +126 -0
- data/spec/active_force/sobject/includes_spec.rb +290 -0
- data/spec/active_force/sobject/table_name_spec.rb +27 -0
- data/spec/active_force/sobject_spec.rb +398 -0
- data/spec/active_force/table_spec.rb +25 -0
- data/spec/active_force_spec.rb +7 -0
- data/spec/fixtures/sobject/single_sobject_hash.yml +26 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/fixture_helpers.rb +45 -0
- data/spec/support/restforce_factories.rb +9 -0
- data/spec/support/sobjects.rb +97 -0
- data/spec/support/whizbang.rb +30 -0
- metadata +196 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c62816549592114aa2162d2a9de73c80fe873d947fefe47da1d41e2dfcd4a8ea
|
4
|
+
data.tar.gz: 4054c4a6ec4d7a29635ec83c7b1b52e360d6ad107e6a1852227bf560002f789d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cf067372afe347c1548d39dbfbf62f7f9788e7c8085239e98d9ed85b95ff3b0ec582f46bfb081cf17a79ebb1046ed0963b7074a1aa00513ebe20ef38444fa776
|
7
|
+
data.tar.gz: 64b71c9c2218426c1bde27eb04721482a1aeb7233e10d9e0deca9db5c3424f02acf615ecd5d548f5ee08d2ab3a943be74476ac209d8c460042ad8eb301c84541
|
data/.gitignore
ADDED
data/.mailmap
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## Not released
|
4
|
+
|
5
|
+
## 0.7.1
|
6
|
+
|
7
|
+
* Allow sfdc_client to be set. ([#92][])
|
8
|
+
|
9
|
+
## 0.7.0
|
10
|
+
|
11
|
+
* Rails4-style conditional has_many associations ([Dan Olson][])
|
12
|
+
* Add `#includes` query method to eager load has_many association. ([Dan Olson][])
|
13
|
+
* Add `#includes` query method to eager load belongs_to association. ([#65][])
|
14
|
+
* SObject#destroy method.
|
15
|
+
|
16
|
+
## 0.6.1
|
17
|
+
|
18
|
+
* Fix missing require of 'restforce'. Now clients don't need to add an initializer.
|
19
|
+
|
20
|
+
## 0.6.0
|
21
|
+
|
22
|
+
* Add select statement functionality. ([Pablo Oldani][], [#33][])
|
23
|
+
* Add callback functionality ([Pablo Oldani][], [#20][])
|
24
|
+
* Support bind parameters. ([Dan Olson][], [#29][])
|
25
|
+
* Fix when passing nil value in a :where condition. ([Armando Andini][])
|
26
|
+
* Model generator complete ([Armando Andini][], [#19][])
|
27
|
+
|
28
|
+
## 0.5.0
|
29
|
+
|
30
|
+
* Provide a default id field for all SObject subclassees ([Dan Olson][], [#30][])
|
31
|
+
* Fix Ruby 2.0 compatibility issue ([Dan Olson][], [Pablo Oldani][], [#28][])
|
32
|
+
* Normalize rspec syntax to remove deprecation warnings ([Dan Olson][], [#26][])
|
33
|
+
* Remove namespace when inferring default SObject.table_name ([Dan Olson][], [#24][])
|
34
|
+
* Add create! and save! methods. ([Pablo Oldani][], [#21][])
|
35
|
+
* Refactor update and create methods. ([Pablo Oldani][], [#21][])
|
36
|
+
* Add a generator. ([José Piccioni][], [#19][])
|
37
|
+
* ActiveQuery now provides :each, :map and :inspect. ([Armando Andini][])
|
38
|
+
* Add SObject.create class mehtod. ([Pablo Oldani][], [#10][])
|
39
|
+
* SObject.field default mapping value follows SFDC API naming convention.
|
40
|
+
([Dan Olson][], [#14][] [#15][])
|
41
|
+
|
42
|
+
## 0.4.2
|
43
|
+
|
44
|
+
* Use ActiveQuery instead of Query. ([Armando Andini][])
|
45
|
+
* Add instructions to use validations ([José Piccioni][])
|
46
|
+
* Lots of refactoring.
|
47
|
+
|
48
|
+
## 0.3.2
|
49
|
+
|
50
|
+
* Fixed gemspec.
|
51
|
+
|
52
|
+
## 0.3.1
|
53
|
+
|
54
|
+
* Create different classes for associations. ([#4][])
|
55
|
+
* Big refactor on has_many association. ([Armando Andini][])
|
56
|
+
* Add a lot of specs and refactors. ([Armando Andini][])
|
57
|
+
* Add a Finders module. ([Armando Andini][])
|
58
|
+
* Add fist and last method to SObject.
|
59
|
+
|
60
|
+
## 0.2.0
|
61
|
+
|
62
|
+
* Add belogns_to and has_many associations.
|
63
|
+
* Changed when the SOQL query is sent to the client.
|
64
|
+
* Add join method to query to use associtations.
|
65
|
+
|
66
|
+
## 0.1.0
|
67
|
+
|
68
|
+
* Add query builder object to chain conditions.
|
69
|
+
* Update update and create methods.
|
70
|
+
* Add Campaing standard table name.
|
71
|
+
|
72
|
+
## 0.0.6.alfa
|
73
|
+
|
74
|
+
* ActiveForce::SObject#table_name is auto populated using the class
|
75
|
+
name. It adds "__c" to all non standard types.
|
76
|
+
|
77
|
+
<!--- The following link definition list is generated by PimpMyChangelog --->
|
78
|
+
|
79
|
+
[#4]: https://github.com/ionia-corporation/active_force/issues/4
|
80
|
+
[#9]: https://github.com/ionia-corporation/active_force/issues/9
|
81
|
+
[#10]: https://github.com/ionia-corporation/active_force/issues/10
|
82
|
+
[#14]: https://github.com/ionia-corporation/active_force/issues/14
|
83
|
+
[#15]: https://github.com/ionia-corporation/active_force/issues/15
|
84
|
+
[#19]: https://github.com/ionia-corporation/active_force/issues/19
|
85
|
+
[#20]: https://github.com/ionia-corporation/active_force/issues/20
|
86
|
+
[#21]: https://github.com/ionia-corporation/active_force/issues/21
|
87
|
+
[#24]: https://github.com/ionia-corporation/active_force/issues/24
|
88
|
+
[#26]: https://github.com/ionia-corporation/active_force/issues/26
|
89
|
+
[#28]: https://github.com/ionia-corporation/active_force/issues/28
|
90
|
+
[#29]: https://github.com/ionia-corporation/active_force/issues/29
|
91
|
+
[#30]: https://github.com/ionia-corporation/active_force/issues/30
|
92
|
+
[#33]: https://github.com/ionia-corporation/active_force/issues/33
|
93
|
+
[#65]: https://github.com/ionia-corporation/active_force/issues/65
|
94
|
+
[#92]: https://github.com/ionia-corporation/active_force/issues/92
|
95
|
+
[Pablo Oldani]: https://github.com/olvap
|
96
|
+
[Armando Andini]: https://github.com/antico5
|
97
|
+
[José Piccioni]: https://github.com/lmhsjackson
|
98
|
+
[Dan Olson]: https://github.com/DanOlson
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Eloy Espinaco
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
[![Gem Version](http://img.shields.io/gem/v/active_force.svg)](http://badge.fury.io/rb/active_force)
|
2
|
+
[![Build Status](http://img.shields.io/travis/ionia-corporation/active_force.svg)](https://travis-ci.org/ionia-corporation/active_force)
|
3
|
+
[![Test Coverage](https://codeclimate.com/github/ionia-corporation/active_force/badges/coverage.svg)](https://codeclimate.com/github/ionia-corporation/active_force)
|
4
|
+
[![Inline docs](http://inch-ci.org/github/ionia-corporation/active_force.png?branch=master)](http://inch-ci.org/github/ionia-corporation/active_force)
|
5
|
+
[![Chat](http://img.shields.io/badge/chat-gitter-brightgreen.svg)](https://gitter.im/ionia-corporation/active_force)
|
6
|
+
|
7
|
+
# ActiveForce
|
8
|
+
|
9
|
+
A ruby gem to interact with [SalesForce][1] as if it were Active Record. It
|
10
|
+
uses [Restforce][2] to interact with the API, so it is fast and stable.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
gem 'active_force'
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install active_force
|
25
|
+
|
26
|
+
## Setup credentials
|
27
|
+
|
28
|
+
[Restforce][2] is used to interact with the API, so you will need to setup
|
29
|
+
environment variables to set up credentials.
|
30
|
+
|
31
|
+
SALESFORCE_USERNAME = your-email@gmail.com
|
32
|
+
SALESFORCE_PASSWORD = your-sfdc-password
|
33
|
+
SALESFORCE_SECURITY_TOKEN = security-token
|
34
|
+
SALESFORCE_CLIENT_ID = your-client-id
|
35
|
+
SALESFORCE_CLIENT_SECRET = your-client-secret
|
36
|
+
|
37
|
+
You might be interested in [dotenv-rails][3] to set up those in development.
|
38
|
+
|
39
|
+
Also, you may specify which client to use as a configuration option, which is useful
|
40
|
+
when having to reauthenticate utilizing oauth.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
ActiveForce.sfdc_client = Restforce.new(
|
44
|
+
oauth_token: current_user.oauth_token,
|
45
|
+
refresh_token: current_user.refresh_token,
|
46
|
+
instance_url: current_user.instance_url,
|
47
|
+
client_id: SALESFORCE_CLIENT_ID,
|
48
|
+
client_secret: SALESFORCE_CLIENT_SECRET
|
49
|
+
)
|
50
|
+
```
|
51
|
+
|
52
|
+
## Usage
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class Medication < ActiveForce::SObject
|
56
|
+
|
57
|
+
field :name, from: 'Name'
|
58
|
+
|
59
|
+
field :max_dossage # defaults to "Max_Dossage__c"
|
60
|
+
field :updated_from
|
61
|
+
|
62
|
+
##
|
63
|
+
# You can cast field value using `as`
|
64
|
+
# field :address_primary_active, from: 's360a__AddressPrimaryActive__c', as: :boolean
|
65
|
+
#
|
66
|
+
# Available options are :boolean, :int, :double, :percent, :date, :datetime, :string, :base64,
|
67
|
+
# :byte, :ID, :reference, :currency, :textarea, :phone, :url, :email, :combobox, :picklist,
|
68
|
+
# :multipicklist, :anyType, :location, :compound
|
69
|
+
|
70
|
+
##
|
71
|
+
# Table name is inferred from class name.
|
72
|
+
#
|
73
|
+
# self.table_name = 'Medication__c' # default one.
|
74
|
+
|
75
|
+
##
|
76
|
+
# Validations
|
77
|
+
#
|
78
|
+
validates :name, :login, :email, presence: true
|
79
|
+
|
80
|
+
# Use any validation from active record.
|
81
|
+
# validates :text, length: { minimum: 2 }
|
82
|
+
# validates :text, format: { with: /\A[a-zA-Z]+\z/, message: "only allows letters" }
|
83
|
+
# validates :size, inclusion: { in: %w(small medium large),
|
84
|
+
# message: "%{value} is not a valid size" }
|
85
|
+
|
86
|
+
##
|
87
|
+
# Callbacks
|
88
|
+
#
|
89
|
+
before_save :set_as_updated_from_rails
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def set_as_updated_from_rails
|
94
|
+
self.updated_from = 'Rails'
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
Altenative you can try the generator. (requires setting up the connection)
|
101
|
+
|
102
|
+
rails generate active_force_model Medication__c
|
103
|
+
|
104
|
+
### Associations
|
105
|
+
|
106
|
+
#### Has Many
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
class Account < ActiveForce::SObject
|
110
|
+
has_many :pages
|
111
|
+
|
112
|
+
# Use optional parameters in the declaration.
|
113
|
+
|
114
|
+
has_many :medications,
|
115
|
+
scoped_as: ->{ where("Discontinued__c > ? OR Discontinued__c = ?", Date.today.strftime("%Y-%m-%d"), nil) }
|
116
|
+
|
117
|
+
has_many :today_log_entries,
|
118
|
+
model: DailyLogEntry,
|
119
|
+
scoped_as: ->{ where(date: Time.now.in_time_zone.strftime("%Y-%m-%d")) }
|
120
|
+
|
121
|
+
has_many :labs,
|
122
|
+
scoped_as: ->{ where("Category__c = 'EMR' AND Date__c <> NULL").order('Date__c DESC') }
|
123
|
+
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
#### Belongs to
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
class Page < ActiveForce::SObject
|
131
|
+
field :account_id, from: 'Account__c'
|
132
|
+
|
133
|
+
belongs_to :account
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
### Querying
|
138
|
+
|
139
|
+
You can retrieve SObject from the database using chained conditions to build
|
140
|
+
the query.
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
Account.where(web_enable: 1, contact_by: ['web', 'email']).limit(2)
|
144
|
+
#=> this will query "SELECT Id, Name, WebEnable__c
|
145
|
+
# FROM Account
|
146
|
+
# WHERE WebEnable__C = 1 AND ContactBy__c IN ('web','email')
|
147
|
+
# LIMIT 2
|
148
|
+
```
|
149
|
+
|
150
|
+
It is also possible to eager load associations:
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
Comment.includes(:post)
|
154
|
+
```
|
155
|
+
|
156
|
+
### Model generator
|
157
|
+
|
158
|
+
When using rails, you can generate a model with all the fields you have on your SFDC table by running:
|
159
|
+
|
160
|
+
rails g active_force:model <table name>
|
161
|
+
|
162
|
+
## Contributing
|
163
|
+
|
164
|
+
1. Fork it
|
165
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
166
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
167
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
168
|
+
5. Create new pull request so we can talk about it.
|
169
|
+
6. Once accepted, please add an entry in the CHANGELOG and rebase your changes
|
170
|
+
to squash typos or corrections.
|
171
|
+
|
172
|
+
[1]: https://www.salesforce.com
|
173
|
+
[2]: https://github.com/ejholmes/restforce
|
174
|
+
[3]: https://github.com/bkeepers/dotenv
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'active_force/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "openstax_active_force"
|
8
|
+
spec.version = ActiveForce::VERSION
|
9
|
+
spec.authors = ["Eloy Espinaco", "Pablo Oldani", "Armando Andini", "José Piccioni", "JP Slavinsky", "Dante Soares"]
|
10
|
+
spec.email = "dante.m.soares@rice.edu"
|
11
|
+
spec.description = %q{Use SalesForce as an ActiveModel}
|
12
|
+
spec.summary = %q{Help you implement models persisting on Sales Force within Rails using RESTForce}
|
13
|
+
spec.homepage = "https://github.com/openstax/active_force"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.required_ruby_version = '>= 1.9.3'
|
22
|
+
|
23
|
+
spec.add_dependency 'rails', '< 6.0'
|
24
|
+
spec.add_dependency 'active_attr'
|
25
|
+
spec.add_dependency 'restforce'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'rake'
|
28
|
+
spec.add_development_dependency 'rspec'
|
29
|
+
spec.add_development_dependency 'pry'
|
30
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_model/dirty'
|
3
|
+
require "active_model/attribute_mutation_tracker"
|
4
|
+
require 'active_attr'
|
5
|
+
|
6
|
+
module ActiveAttr
|
7
|
+
module Dirty
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
include ActiveModel::Dirty
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def attribute!(name, options={})
|
13
|
+
super(name, options)
|
14
|
+
define_method("#{name}=") do |value|
|
15
|
+
send("#{name}_will_change!") unless value == read_attribute(name)
|
16
|
+
super(value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def attributes_and_changes
|
22
|
+
attributes.select { |attr, key| changed.include? attr }
|
23
|
+
end
|
24
|
+
|
25
|
+
def mutations_from_database
|
26
|
+
@mutations_from_database ||= ActiveModel::NullMutationTracker.instance
|
27
|
+
end
|
28
|
+
|
29
|
+
def forget_attribute_assignments
|
30
|
+
@attributes if defined?(@attributes)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'active_force/query'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module ActiveForce
|
5
|
+
class PreparedStatementInvalid < ArgumentError; end
|
6
|
+
class ActiveQuery < Query
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
attr_reader :sobject
|
10
|
+
|
11
|
+
def_delegators :sobject, :sfdc_client, :build, :table_name, :mappings
|
12
|
+
def_delegators :to_a, :each, :map, :inspect
|
13
|
+
|
14
|
+
def initialize sobject
|
15
|
+
@sobject = sobject
|
16
|
+
super table_name
|
17
|
+
fields sobject.fields
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_a
|
21
|
+
@records ||= result.to_a.map { |mash| build mash }
|
22
|
+
end
|
23
|
+
|
24
|
+
alias_method :all, :to_a
|
25
|
+
|
26
|
+
def count
|
27
|
+
super
|
28
|
+
sfdc_client.query(to_s).first.expr0
|
29
|
+
end
|
30
|
+
|
31
|
+
def limit limit
|
32
|
+
super
|
33
|
+
limit == 1 ? to_a.first : self
|
34
|
+
end
|
35
|
+
|
36
|
+
def where args=nil, *rest
|
37
|
+
return self if args.nil?
|
38
|
+
super build_condition args, rest
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def select *fields
|
43
|
+
fields.map! { |field| mappings[field] }
|
44
|
+
super *fields
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_by conditions
|
48
|
+
where(conditions).limit 1
|
49
|
+
end
|
50
|
+
|
51
|
+
def includes(*relations)
|
52
|
+
relations.each do |relation|
|
53
|
+
association = sobject.associations[relation]
|
54
|
+
fields Association::EagerLoadProjectionBuilder.build association
|
55
|
+
end
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def none
|
60
|
+
@records = []
|
61
|
+
where(id: '1'*18).where(id: '0'*18)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def build_condition(args, other=[])
|
67
|
+
case args
|
68
|
+
when String, Array
|
69
|
+
build_condition_from_array other.empty? ? args : ([args] + other)
|
70
|
+
when Hash
|
71
|
+
build_conditions_from_hash args
|
72
|
+
else
|
73
|
+
args
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_condition_from_array(ary)
|
78
|
+
statement, *bind_parameters = ary
|
79
|
+
return statement if bind_parameters.empty?
|
80
|
+
if bind_parameters.first.is_a? Hash
|
81
|
+
replace_named_bind_parameters statement, bind_parameters.first
|
82
|
+
else
|
83
|
+
replace_bind_parameters statement, bind_parameters
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def replace_named_bind_parameters(statement, bind_parameters)
|
88
|
+
statement.gsub(/(:?):([a-zA-Z]\w*)/) do
|
89
|
+
key = $2.to_sym
|
90
|
+
if bind_parameters.has_key? key
|
91
|
+
enclose_value bind_parameters[key]
|
92
|
+
else
|
93
|
+
raise PreparedStatementInvalid, "missing value for :#{key} in #{statement}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def replace_bind_parameters(statement, values)
|
99
|
+
raise_if_bind_arity_mismatch statement.count('?'), values.size
|
100
|
+
bound = values.dup
|
101
|
+
statement.gsub('?') do
|
102
|
+
enclose_value bound.shift
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def raise_if_bind_arity_mismatch(expected_var_count, actual_var_count)
|
107
|
+
if expected_var_count != actual_var_count
|
108
|
+
raise PreparedStatementInvalid, "wrong number of bind variables (#{actual_var_count} for #{expected_var_count})"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_conditions_from_hash(hash)
|
113
|
+
hash.map do |key, value|
|
114
|
+
applicable_predicate mappings[key], value
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def applicable_predicate(attribute, value)
|
119
|
+
if value.is_a? Array
|
120
|
+
in_predicate attribute, value
|
121
|
+
else
|
122
|
+
eq_predicate attribute, value
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def in_predicate(attribute, values)
|
127
|
+
escaped_values = values.map &method(:enclose_value)
|
128
|
+
"#{attribute} IN (#{escaped_values.join(',')})"
|
129
|
+
end
|
130
|
+
|
131
|
+
def eq_predicate(attribute, value)
|
132
|
+
"#{attribute} = #{enclose_value value}"
|
133
|
+
end
|
134
|
+
|
135
|
+
def enclose_value value
|
136
|
+
case value
|
137
|
+
when String
|
138
|
+
"'#{quote_string(value)}'"
|
139
|
+
when NilClass
|
140
|
+
'NULL'
|
141
|
+
else
|
142
|
+
value.to_s
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def quote_string(s)
|
147
|
+
# From activerecord/lib/active_record/connection_adapters/abstract/quoting.rb, version 4.1.5, line 82
|
148
|
+
s.gsub(/\\/, '\&\&').gsub(/'/, "''")
|
149
|
+
end
|
150
|
+
|
151
|
+
def result
|
152
|
+
sfdc_client.query(self.to_s)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ActiveForce
|
2
|
+
module Association
|
3
|
+
class Association
|
4
|
+
extend Forwardable
|
5
|
+
def_delegators :relation_model, :build
|
6
|
+
|
7
|
+
attr_accessor :options, :relation_name
|
8
|
+
|
9
|
+
def initialize parent, relation_name, options = {}
|
10
|
+
@parent = parent
|
11
|
+
@relation_name = relation_name
|
12
|
+
@options = options
|
13
|
+
define_relation_method
|
14
|
+
end
|
15
|
+
|
16
|
+
def relation_model
|
17
|
+
options[:model] || relation_name.to_s.singularize.camelcase.constantize
|
18
|
+
end
|
19
|
+
|
20
|
+
def foreign_key
|
21
|
+
options[:foreign_key] || default_foreign_key
|
22
|
+
end
|
23
|
+
|
24
|
+
def relationship_name
|
25
|
+
options[:relationship_name] || relation_model.table_name
|
26
|
+
end
|
27
|
+
|
28
|
+
###
|
29
|
+
# Does this association's relation_model represent
|
30
|
+
# +sfdc_table_name+? Examples of +sfdc_table_name+
|
31
|
+
# could be 'Quota__r' or 'Account'.
|
32
|
+
def represents_sfdc_table?(sfdc_table_name)
|
33
|
+
name = sfdc_table_name.sub(/__r\z/, '').singularize
|
34
|
+
relationship_name.sub(/__c\z|__r\z/, '') == name
|
35
|
+
end
|
36
|
+
|
37
|
+
def sfdc_association_field
|
38
|
+
relationship_name.gsub /__c\z/, '__r'
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def infer_foreign_key_from_model(model)
|
44
|
+
name = model.custom_table? ? model.name : model.table_name
|
45
|
+
name.foreign_key.to_sym
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ActiveForce
|
2
|
+
module Association
|
3
|
+
class BelongsToAssociation < Association
|
4
|
+
private
|
5
|
+
|
6
|
+
def default_foreign_key
|
7
|
+
infer_foreign_key_from_model relation_model
|
8
|
+
end
|
9
|
+
|
10
|
+
def define_relation_method
|
11
|
+
association = self
|
12
|
+
_method = @relation_name
|
13
|
+
@parent.send :define_method, _method do
|
14
|
+
association_cache.fetch(_method) do
|
15
|
+
association_cache[_method] = association.relation_model.find(send association.foreign_key)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
@parent.send :define_method, "#{_method}=" do |other|
|
20
|
+
send "#{ association.foreign_key }=", other.nil? ? nil : other.id
|
21
|
+
association_cache[_method] = other
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|