openstax_active_force 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.mailmap +3 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +3 -0
  6. data/CHANGELOG.md +98 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +174 -0
  10. data/Rakefile +6 -0
  11. data/active_force.gemspec +30 -0
  12. data/lib/active_attr/dirty.rb +33 -0
  13. data/lib/active_force/active_query.rb +155 -0
  14. data/lib/active_force/association/association.rb +50 -0
  15. data/lib/active_force/association/belongs_to_association.rb +26 -0
  16. data/lib/active_force/association/eager_load_projection_builder.rb +60 -0
  17. data/lib/active_force/association/has_many_association.rb +33 -0
  18. data/lib/active_force/association/relation_model_builder.rb +70 -0
  19. data/lib/active_force/association.rb +28 -0
  20. data/lib/active_force/attribute.rb +30 -0
  21. data/lib/active_force/mapping.rb +78 -0
  22. data/lib/active_force/query.rb +110 -0
  23. data/lib/active_force/sobject.rb +210 -0
  24. data/lib/active_force/standard_types.rb +357 -0
  25. data/lib/active_force/table.rb +37 -0
  26. data/lib/active_force/version.rb +3 -0
  27. data/lib/active_force.rb +13 -0
  28. data/lib/generators/active_force/model/USAGE +8 -0
  29. data/lib/generators/active_force/model/model_generator.rb +62 -0
  30. data/lib/generators/active_force/model/templates/model.rb.erb +5 -0
  31. data/spec/active_force/active_query_spec.rb +178 -0
  32. data/spec/active_force/association/relation_model_builder_spec.rb +62 -0
  33. data/spec/active_force/association_spec.rb +157 -0
  34. data/spec/active_force/attribute_spec.rb +27 -0
  35. data/spec/active_force/callbacks_spec.rb +20 -0
  36. data/spec/active_force/mapping_spec.rb +18 -0
  37. data/spec/active_force/query_spec.rb +126 -0
  38. data/spec/active_force/sobject/includes_spec.rb +290 -0
  39. data/spec/active_force/sobject/table_name_spec.rb +27 -0
  40. data/spec/active_force/sobject_spec.rb +398 -0
  41. data/spec/active_force/table_spec.rb +25 -0
  42. data/spec/active_force_spec.rb +7 -0
  43. data/spec/fixtures/sobject/single_sobject_hash.yml +26 -0
  44. data/spec/spec_helper.rb +16 -0
  45. data/spec/support/fixture_helpers.rb +45 -0
  46. data/spec/support/restforce_factories.rb +9 -0
  47. data/spec/support/sobjects.rb +97 -0
  48. data/spec/support/whizbang.rb +30 -0
  49. 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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.mailmap ADDED
@@ -0,0 +1,3 @@
1
+ Armando Andini <armando.andini@hotmail.com>
2
+ Jose Piccioni <josepiccioni@gmail.com> <jackson@jackPc.(none)>
3
+ Pablo Oldani <oldani.pablo@gmail.com>
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.6.1
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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in active_force.gemspec
4
+ gemspec
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,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -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