openstax_active_force 1.0.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 +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
|
+
[](http://badge.fury.io/rb/active_force)
|
2
|
+
[](https://travis-ci.org/ionia-corporation/active_force)
|
3
|
+
[](https://codeclimate.com/github/ionia-corporation/active_force)
|
4
|
+
[](http://inch-ci.org/github/ionia-corporation/active_force)
|
5
|
+
[](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
|