ryandotsmith-asf-soap-adapter 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (178) hide show
  1. data/.document +5 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +125 -0
  4. data/Rakefile +63 -0
  5. data/VERSION +1 -0
  6. data/asf-soap-adapter.gemspec +300 -0
  7. data/asf-soap-adapter.pptx +0 -0
  8. data/deploy +6 -0
  9. data/lib/active_record/connection_adapters/activesalesforce.rb +36 -0
  10. data/lib/active_record/connection_adapters/activesalesforce_adapter.rb +919 -0
  11. data/lib/active_record/connection_adapters/asf_active_record.rb +40 -0
  12. data/lib/active_record/connection_adapters/boxcar_command.rb +66 -0
  13. data/lib/active_record/connection_adapters/column_definition.rb +95 -0
  14. data/lib/active_record/connection_adapters/entity_definition.rb +59 -0
  15. data/lib/active_record/connection_adapters/id_resolver.rb +84 -0
  16. data/lib/active_record/connection_adapters/recording_binding.rb +93 -0
  17. data/lib/active_record/connection_adapters/relationship_definition.rb +81 -0
  18. data/lib/active_record/connection_adapters/result_array.rb +31 -0
  19. data/lib/active_record/connection_adapters/sid_authentication_filter.rb +57 -0
  20. data/lib/activerecord-activesalesforce-adapter.rb +1 -0
  21. data/lib/asf-soap-adapter.rb +34 -0
  22. data/lib/salesforce/account.rb +28 -0
  23. data/lib/salesforce/account_feed.rb +27 -0
  24. data/lib/salesforce/apex_log.rb +28 -0
  25. data/lib/salesforce/asset.rb +27 -0
  26. data/lib/salesforce/asset_feed.rb +27 -0
  27. data/lib/salesforce/campaign.rb +27 -0
  28. data/lib/salesforce/campaign_feed.rb +27 -0
  29. data/lib/salesforce/case.rb +27 -0
  30. data/lib/salesforce/case_feed.rb +27 -0
  31. data/lib/salesforce/case_team_member.rb +28 -0
  32. data/lib/salesforce/case_team_role.rb +28 -0
  33. data/lib/salesforce/chatter_feed.rb +291 -0
  34. data/lib/salesforce/contact.rb +27 -0
  35. data/lib/salesforce/contact_feed.rb +29 -0
  36. data/lib/salesforce/contract.rb +27 -0
  37. data/lib/salesforce/contract_feed.rb +27 -0
  38. data/lib/salesforce/entity_subscription.rb +27 -0
  39. data/lib/salesforce/feed_comment.rb +27 -0
  40. data/lib/salesforce/feed_post.rb +27 -0
  41. data/lib/salesforce/feed_tracked_change.rb +27 -0
  42. data/lib/salesforce/file_writer.rb +61 -0
  43. data/lib/salesforce/group.rb +28 -0
  44. data/lib/salesforce/group_member.rb +28 -0
  45. data/lib/salesforce/lead.rb +27 -0
  46. data/lib/salesforce/lead_feed.rb +27 -0
  47. data/lib/salesforce/news_feed.rb +27 -0
  48. data/lib/salesforce/opportunity.rb +27 -0
  49. data/lib/salesforce/opportunity_feed.rb +27 -0
  50. data/lib/salesforce/organization.rb +27 -0
  51. data/lib/salesforce/product2.rb +27 -0
  52. data/lib/salesforce/product2_feed.rb +27 -0
  53. data/lib/salesforce/sf_base.rb +94 -0
  54. data/lib/salesforce/sf_utility.rb +214 -0
  55. data/lib/salesforce/solution.rb +27 -0
  56. data/lib/salesforce/solution_feed.rb +27 -0
  57. data/lib/salesforce/solution_history.rb +28 -0
  58. data/lib/salesforce/task.rb +28 -0
  59. data/lib/salesforce/task_feed.rb +28 -0
  60. data/lib/salesforce/user.rb +27 -0
  61. data/lib/salesforce/user_feed.rb +27 -0
  62. data/lib/salesforce/user_profile_feed.rb +27 -0
  63. data/lib/salesforce/user_role.rb +28 -0
  64. data/ryandotsmith-asf-soap-adapter.gemspec +301 -0
  65. data/test/asf-soap-adapter-rails-app/README +243 -0
  66. data/test/asf-soap-adapter-rails-app/Rakefile +10 -0
  67. data/test/asf-soap-adapter-rails-app/app/controllers/adapter_homes_controller.rb +83 -0
  68. data/test/asf-soap-adapter-rails-app/app/controllers/application_controller.rb +10 -0
  69. data/test/asf-soap-adapter-rails-app/app/helpers/adapter_homes_helper.rb +2 -0
  70. data/test/asf-soap-adapter-rails-app/app/helpers/application_helper.rb +3 -0
  71. data/test/asf-soap-adapter-rails-app/app/models/adapter_home.rb +2 -0
  72. data/test/asf-soap-adapter-rails-app/app/views/adapter_homes/edit.html.erb +24 -0
  73. data/test/asf-soap-adapter-rails-app/app/views/adapter_homes/index.html.erb +24 -0
  74. data/test/asf-soap-adapter-rails-app/app/views/adapter_homes/new.html.erb +23 -0
  75. data/test/asf-soap-adapter-rails-app/app/views/adapter_homes/show.html.erb +18 -0
  76. data/test/asf-soap-adapter-rails-app/app/views/layouts/adapter_homes.html.erb +17 -0
  77. data/test/asf-soap-adapter-rails-app/config/boot.rb +110 -0
  78. data/test/asf-soap-adapter-rails-app/config/database.yml +30 -0
  79. data/test/asf-soap-adapter-rails-app/config/environment.rb +44 -0
  80. data/test/asf-soap-adapter-rails-app/config/environments/development.rb +17 -0
  81. data/test/asf-soap-adapter-rails-app/config/environments/production.rb +28 -0
  82. data/test/asf-soap-adapter-rails-app/config/environments/salesforce-default-realm.rb +30 -0
  83. data/test/asf-soap-adapter-rails-app/config/environments/test.rb +28 -0
  84. data/test/asf-soap-adapter-rails-app/config/initializers/backtrace_silencers.rb +7 -0
  85. data/test/asf-soap-adapter-rails-app/config/initializers/cookie_verification_secret.rb +7 -0
  86. data/test/asf-soap-adapter-rails-app/config/initializers/inflections.rb +10 -0
  87. data/test/asf-soap-adapter-rails-app/config/initializers/mime_types.rb +5 -0
  88. data/test/asf-soap-adapter-rails-app/config/initializers/new_rails_defaults.rb +21 -0
  89. data/test/asf-soap-adapter-rails-app/config/initializers/session_store.rb +15 -0
  90. data/test/asf-soap-adapter-rails-app/config/locales/en.yml +5 -0
  91. data/test/asf-soap-adapter-rails-app/config/routes.rb +46 -0
  92. data/test/asf-soap-adapter-rails-app/db/development.sqlite3 +0 -0
  93. data/test/asf-soap-adapter-rails-app/db/migrate/20101107202112_create_adapter_homes.rb +15 -0
  94. data/test/asf-soap-adapter-rails-app/db/schema.rb +22 -0
  95. data/test/asf-soap-adapter-rails-app/db/seeds.rb +7 -0
  96. data/test/asf-soap-adapter-rails-app/db/test.sqlite3 +0 -0
  97. data/test/asf-soap-adapter-rails-app/log/development.log +51 -0
  98. data/test/asf-soap-adapter-rails-app/log/salesforce-default-realm.log +928 -0
  99. data/test/asf-soap-adapter-rails-app/nbproject/private/config.properties +0 -0
  100. data/test/asf-soap-adapter-rails-app/nbproject/private/private.properties +3 -0
  101. data/test/asf-soap-adapter-rails-app/nbproject/private/private.xml +4 -0
  102. data/test/asf-soap-adapter-rails-app/nbproject/private/rake-d.txt +94 -0
  103. data/test/asf-soap-adapter-rails-app/nbproject/project.properties +5 -0
  104. data/test/asf-soap-adapter-rails-app/nbproject/project.xml +9 -0
  105. data/test/asf-soap-adapter-rails-app/public/404.html +30 -0
  106. data/test/asf-soap-adapter-rails-app/public/422.html +30 -0
  107. data/test/asf-soap-adapter-rails-app/public/500.html +30 -0
  108. data/test/asf-soap-adapter-rails-app/public/favicon.ico +0 -0
  109. data/test/asf-soap-adapter-rails-app/public/images/rails.png +0 -0
  110. data/test/asf-soap-adapter-rails-app/public/javascripts/application.js +2 -0
  111. data/test/asf-soap-adapter-rails-app/public/javascripts/controls.js +963 -0
  112. data/test/asf-soap-adapter-rails-app/public/javascripts/dragdrop.js +973 -0
  113. data/test/asf-soap-adapter-rails-app/public/javascripts/effects.js +1128 -0
  114. data/test/asf-soap-adapter-rails-app/public/javascripts/prototype.js +4320 -0
  115. data/test/asf-soap-adapter-rails-app/public/robots.txt +5 -0
  116. data/test/asf-soap-adapter-rails-app/public/stylesheets/scaffold.css +54 -0
  117. data/test/asf-soap-adapter-rails-app/script/about +4 -0
  118. data/test/asf-soap-adapter-rails-app/script/console +3 -0
  119. data/test/asf-soap-adapter-rails-app/script/dbconsole +3 -0
  120. data/test/asf-soap-adapter-rails-app/script/destroy +3 -0
  121. data/test/asf-soap-adapter-rails-app/script/generate +3 -0
  122. data/test/asf-soap-adapter-rails-app/script/performance/benchmarker +3 -0
  123. data/test/asf-soap-adapter-rails-app/script/performance/profiler +3 -0
  124. data/test/asf-soap-adapter-rails-app/script/plugin +3 -0
  125. data/test/asf-soap-adapter-rails-app/script/runner +3 -0
  126. data/test/asf-soap-adapter-rails-app/script/server +3 -0
  127. data/test/asf-soap-adapter-rails-app/test/performance/browsing_test.rb +9 -0
  128. data/test/asf-soap-adapter-rails-app/test/test_helper.rb +44 -0
  129. data/test/asf-soap-adapter-rails-app/test/test_keys.rb +4 -0
  130. data/test/asf-soap-adapter-rails-app/test/unit/adapter_home_test.rb +8 -0
  131. data/test/asf-soap-adapter-rails-app/test/unit/helpers/adapter_homes_helper_test.rb +4 -0
  132. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/account_feed_test.rb +16 -0
  133. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/account_test.rb +66 -0
  134. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/apex_log_test.rb +10 -0
  135. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/asset_feed_test.rb +14 -0
  136. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/asset_test.rb +13 -0
  137. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/campaign_feed_test.rb +13 -0
  138. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/campaign_test.rb +13 -0
  139. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/case_feed_test.rb +13 -0
  140. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/case_team_member_test.rb +10 -0
  141. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/case_team_role.rb +10 -0
  142. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/case_test.rb +13 -0
  143. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/chatter_feed_test.rb +53 -0
  144. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/contact_feed_test.rb +13 -0
  145. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/contact_test.rb +14 -0
  146. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/contract_feed_test.rb +13 -0
  147. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/contract_test.rb +13 -0
  148. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/entity_subscription_test.rb +13 -0
  149. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/feed_comment_test.rb +9 -0
  150. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/feed_post_test.rb +10 -0
  151. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/feed_tracked_change_test.rb +9 -0
  152. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/group_member_test.rb +10 -0
  153. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/group_test.rb +24 -0
  154. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/lead_feed_test.rb +13 -0
  155. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/lead_test.rb +40 -0
  156. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/news_feed_test.rb +13 -0
  157. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/opportunity_feed_test.rb +13 -0
  158. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/opportunity_test.rb +13 -0
  159. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/organization_test.rb +8 -0
  160. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/product2_feed_test.rb +13 -0
  161. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/product2_test.rb +13 -0
  162. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/salesforce_base_test.rb +10 -0
  163. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/solution_feed_test.rb +13 -0
  164. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/solution_history_test.rb +10 -0
  165. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/solution_test.rb +14 -0
  166. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/task_feed_test.rb +10 -0
  167. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/task_test.rb +10 -0
  168. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/user_feed_test.rb +13 -0
  169. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/user_profile_feed_test.rb +11 -0
  170. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/user_role_test.rb +10 -0
  171. data/test/asf-soap-adapter-rails-app/test/unit/salesforce/user_test.rb +48 -0
  172. data/test/basic_test.rb +204 -0
  173. data/test/config.yml +5 -0
  174. data/test/helper.rb +10 -0
  175. data/test/recorded_test_case.rb +87 -0
  176. data/test/simple_test.rb +96 -0
  177. data/test/tests_without_rail.rb +65 -0
  178. metadata +401 -0
Binary file
data/deploy ADDED
@@ -0,0 +1,6 @@
1
+ git add .
2
+ git commit -m 'Add support for __r and __c for custom object'
3
+ sudo rake version:bump:patch
4
+ sudo gem uninstall asf-soap-adapter
5
+ sudo rm pkg/*gem
6
+ sudo rake install
@@ -0,0 +1,36 @@
1
+ =begin
2
+ ActiveSalesforce
3
+ Copyright 2006 Doug Chasman
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ =end
17
+
18
+ require 'activesalesforce_adapter'
19
+
20
+ module ActionView
21
+ module Helpers
22
+ # Provides a set of methods for making easy links and getting urls that depend on the controller and action. This means that
23
+ # you can use the same format for links in the views that you do in the controller. The different methods are even named
24
+ # synchronously, so link_to uses that same url as is generated by url_for, which again is the same url used for
25
+ # redirection in redirect_to.
26
+ module UrlHelper
27
+ def link_to_asf(active_record, column)
28
+ if column.reference_to
29
+ link_to(column.reference_to, { :action => 'show', :controller => column.reference_to.pluralize, :id => active_record.send(column.name) } )
30
+ else
31
+ active_record.send(column.name)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,919 @@
1
+ =begin
2
+ ActiveSalesforce
3
+ Copyright 2006 Doug Chasman
4
+ 2010 updated by Raymond Gao
5
+
6
+ Licensed under the Apache License, Version 2.0 (the "License");
7
+ you may not use this file except in compliance with the License.
8
+ You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing, software
13
+ distributed under the License is distributed on an "AS IS" BASIS,
14
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ See the License for the specific language governing permissions and
16
+ limitations under the License.
17
+ =end
18
+
19
+ require 'thread'
20
+ require 'benchmark'
21
+
22
+ require 'active_record'
23
+ require 'active_record/connection_adapters/abstract_adapter'
24
+
25
+ # replaced hardlink to 'rforce' gem as a pre-requisite, allowing parallel evolution of both gem.
26
+ require 'rforce'
27
+ #require File.dirname(__FILE__) + '/../../rforce'
28
+ require File.dirname(__FILE__) + '/column_definition'
29
+ require File.dirname(__FILE__) + '/relationship_definition'
30
+ require File.dirname(__FILE__) + '/boxcar_command'
31
+ require File.dirname(__FILE__) + '/entity_definition'
32
+ require File.dirname(__FILE__) + '/asf_active_record'
33
+ require File.dirname(__FILE__) + '/id_resolver'
34
+ require File.dirname(__FILE__) + '/sid_authentication_filter'
35
+ require File.dirname(__FILE__) + '/recording_binding'
36
+ require File.dirname(__FILE__) + '/result_array'
37
+
38
+ # See http://www.rubular.com/ for Ruby Regular Expression
39
+ # See http://api.rubyonrails.org/classes/ActiveRecord/Base.html for documentation
40
+ # ActiveRecord
41
+ # To Understand how to write an adapter, see:
42
+ # http://ar.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/AbstractAdapter.html
43
+ # and
44
+ # http://rails.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/AbstractAdapter.html
45
+
46
+ module ActiveRecord
47
+ # Overrides the ActiveRecord::Base class to provide Salesforce Web Services services
48
+ # Particularly important are 1) getting a 'binding' and 2) 'api_version'
49
+ class Base
50
+ @@cache = {}
51
+
52
+ def self.debug(msg)
53
+ logger.debug(msg) if logger
54
+ end
55
+
56
+ def self.flush_connections()
57
+ @@cache = {}
58
+ end
59
+
60
+ # Establishes a connection to the database that's used by all Active Record objects.
61
+ def self.activesalesforce_connection(config) # :nodoc:
62
+ debug("\nUsing ActiveSalesforce connection\n")
63
+
64
+ database_com_url = ENV["DATABASE_COM_URL"]
65
+ raise "Set DATABASE_COM_URL" if database_com_url.nil?
66
+ database_com_uri = URI.parse(database_com_url)
67
+
68
+ # Default to production system using 20.0 API
69
+ #url = config[:url]
70
+ #url = database_com_uri.path
71
+ url = "https://www.salesforce.com" #unless url
72
+
73
+ #Take the API version from the Config file e.g. 'database.yml' -> 'salesforce-default-realm'
74
+ api_version = config[:api_version] ? config[:api_version] : "20.0"
75
+ uri = URI.parse(url)
76
+ #uri.path = "/services/Soap/u/20.0"
77
+ uri.path = "/services/Soap/u/" + (api_version).to_s
78
+ url = uri.to_s
79
+
80
+ sid = config[:sid]
81
+ client_id = config[:client_id]
82
+ #username = config[:username].to_s
83
+ #password = config[:password].to_s
84
+ username = URI.unescape(database_com_uri.user)
85
+ password = URI.unescape(database_com_uri.password)
86
+
87
+ # Recording/playback support
88
+ recording_source = config[:recording_source]
89
+ recording = config[:recording]
90
+
91
+ if recording_source
92
+ recording_source = File.open(recording_source, recording ? "w" : "r")
93
+ binding = ActiveSalesforce::RecordingBinding.new(url, nil, recording != nil, recording_source, logger)
94
+ binding.client_id = client_id if client_id
95
+ binding.login(username, password) unless sid
96
+ end
97
+
98
+ # Check to insure that the second to last path component is a 'u' for Partner API
99
+ raise ActiveSalesforce::ASFError.new(logger, "Invalid salesforce server url '#{url}', must be a valid Parter API URL") unless url.match(/\/u\//mi)
100
+
101
+ if sid
102
+ binding = @@cache["sid=#{sid}"] unless binding
103
+
104
+ unless binding
105
+ debug("Establishing new connection for [sid='#{sid}']")
106
+
107
+ binding = RForce::Binding.new(url, sid)
108
+ @@cache["sid=#{sid}"] = binding
109
+
110
+ debug("Created new connection for [sid='#{sid}']")
111
+ else
112
+ debug("Reused existing connection for [sid='#{sid}']")
113
+ end
114
+ else
115
+ binding = @@cache["#{url}.#{username}.#{password}.#{client_id}"] unless binding
116
+
117
+ unless binding
118
+ debug("Establishing new connection for ['#{url}', '#{username}, '#{client_id}'")
119
+
120
+ seconds = Benchmark.realtime {
121
+ binding = RForce::Binding.new(url, sid)
122
+ binding.login(username, password)
123
+
124
+ @@cache["#{url}.#{username}.#{password}.#{client_id}"] = binding
125
+ }
126
+
127
+ debug("Created new connection for ['#{url}', '#{username}', '#{client_id}'] in #{seconds} seconds")
128
+ end
129
+ end
130
+
131
+ ConnectionAdapters::SalesforceAdapter.new(binding, logger, config)
132
+
133
+ end
134
+ end
135
+
136
+
137
+ module ConnectionAdapters
138
+ # Inherit from <em>ConnectionAdapters::AbstractAdapter</em> see:
139
+ # http://rails.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/AbstractAdapter.html
140
+ class SalesforceAdapter < AbstractAdapter
141
+ include StringHelper
142
+
143
+ MAX_BOXCAR_SIZE = 200
144
+
145
+ attr_accessor :batch_size
146
+ attr_reader :entity_def_map, :keyprefix_to_entity_def_map, :config, :class_to_entity_map
147
+
148
+ # Create a new instance of the connection adapter
149
+ def initialize(connection, logger, config)
150
+ super(connection, logger)
151
+
152
+ @connection_options = nil
153
+ @config = config
154
+
155
+ @entity_def_map = {}
156
+ @keyprefix_to_entity_def_map = {}
157
+
158
+ @command_boxcar = nil
159
+ @class_to_entity_map = {}
160
+ end
161
+
162
+ def set_class_for_entity(klass, entity_name)
163
+ debug("Setting @class_to_entity_map['#{entity_name.upcase}'] = #{klass} for connection #{self}")
164
+ @class_to_entity_map[entity_name.upcase] = klass
165
+ end
166
+
167
+ # returns the binding associated with the current adapter
168
+ # e.g Salesforce::User.first.connection.binding -> returns the binding.
169
+ def binding
170
+ @connection
171
+ end
172
+
173
+ #Sets the adapter name to "ActiveSalesforce"
174
+ def adapter_name #:nodoc:
175
+ 'ActiveSalesforce'
176
+ end
177
+
178
+ def supports_migrations? #:nodoc:
179
+ false
180
+ end
181
+
182
+ # For Silent-e, added 'tables' method to solve ARel problem
183
+ def tables(name = nil) #:nodoc:
184
+ @connection.describeGlobal({}).describeGlobalResponse.result.types
185
+ end
186
+
187
+ def table_exists?(table_name)
188
+ true
189
+ end
190
+
191
+ #-- QUOTING ==================================================
192
+ def quote(value, column = nil)
193
+ case value
194
+ when NilClass then quoted_value = "NULL"
195
+ when TrueClass then quoted_value = "TRUE"
196
+ when FalseClass then quoted_value = "FALSE"
197
+ when Float, Fixnum, Bignum then quoted_value = "'#{value.to_s}'"
198
+ else quoted_value = super(value, column)
199
+ end
200
+
201
+ quoted_value
202
+ end
203
+
204
+ #-- CONNECTION MANAGEMENT ====================================
205
+ # Set the connection state to active
206
+ def active?
207
+ true
208
+ end
209
+
210
+ # Overrides the basic method for ActiveRecord::ConnectionAdapters::AbstractAdapter
211
+ def reconnect!
212
+ connect
213
+ end
214
+
215
+ # TRANSACTIOn SUPPORT (Boxcarring really because the salesforce.com api does not support transactions)
216
+
217
+ # Override AbstractAdapter's transaction method to implement
218
+ # per-connection support for nested transactions that do not commit until
219
+ # the outermost transaction is finished. ActiveRecord provides support
220
+ # for this, but does not distinguish between database connections, which
221
+ # prevents opening transactions to two different databases at the same
222
+ # time.
223
+ def transaction_with_nesting_support(*args, &block)
224
+ open = Thread.current["open_transactions_for_#{self.class.name.underscore}"] ||= 0
225
+ Thread.current["open_transactions_for_#{self.class.name.underscore}"] = open + 1
226
+
227
+ begin
228
+ transaction_without_nesting_support(&block)
229
+ ensure
230
+ Thread.current["open_transactions_for_#{self.class.name.underscore}"] -= 1
231
+ end
232
+ end
233
+ alias_method_chain :transaction, :nesting_support
234
+
235
+ # Begins the transaction (and turns off auto-committing).
236
+ def begin_db_transaction
237
+ log('Opening boxcar', 'begin_db_transaction()')
238
+ @command_boxcar = []
239
+ end
240
+
241
+ # Calls <em>RForce::Binding</em> -> <em>method_missing</em> -> <em>call_remote method, args[0] methods</em>
242
+ def send_commands(commands)
243
+ # Send the boxcar'ed command set
244
+ verb = commands[0].verb
245
+
246
+ args = []
247
+ commands.each do |command|
248
+ command.args.each { |arg| args << arg }
249
+ end
250
+
251
+ response = @connection.send(verb, args)
252
+
253
+ result = get_result(response, verb)
254
+
255
+ result = [ result ] unless result.is_a?(Array)
256
+
257
+ errors = []
258
+ result.each_with_index do |r, n|
259
+ success = r[:success] == "true"
260
+
261
+ # Give each command a chance to process its own result
262
+ command = commands[n]
263
+ command.after_execute(r)
264
+
265
+ # Handle the set of failures
266
+ errors << r[:errors] unless r[:success] == "true"
267
+ end
268
+
269
+ unless errors.empty?
270
+ message = errors.join("\n")
271
+ fault = (errors.map { |error| error[:message] }).join("\n")
272
+ raise ActiveSalesforce::ASFError.new(@logger, message, fault)
273
+ end
274
+
275
+ result
276
+ end
277
+
278
+ # Commits the transaction (and turns on auto-committing).
279
+ def commit_db_transaction()
280
+ log("Committing boxcar with #{@command_boxcar.length} commands", 'commit_db_transaction()')
281
+
282
+ previous_command = nil
283
+ commands = []
284
+
285
+ @command_boxcar.each do |command|
286
+ if commands.length >= MAX_BOXCAR_SIZE or (previous_command and (command.verb != previous_command.verb))
287
+ send_commands(commands)
288
+
289
+ commands = []
290
+ previous_command = nil
291
+ else
292
+ commands << command
293
+ previous_command = command
294
+ end
295
+ end
296
+
297
+ # Discard the command boxcar
298
+ @command_boxcar = nil
299
+
300
+ # Finish off the partial boxcar
301
+ send_commands(commands) unless commands.empty?
302
+
303
+ end
304
+
305
+ # Rolls back the transaction (and turns on auto-committing). Must be
306
+ # done if the transaction block raises an exception or returns false.
307
+ def rollback_db_transaction()
308
+ log('Rolling back boxcar', 'rollback_db_transaction()')
309
+ @command_boxcar = nil
310
+ end
311
+
312
+
313
+ # DATABASE STATEMENTS ======================================
314
+ # This method is very important. Pretty much all the 'find' methods call
315
+ # it. This is the place to toggle linebreak for debugging purpose.
316
+ # In essence, your finder method calls ActiveRecord, which generates a 'sql'
317
+ # And, this methods turn it into a 'soql' statement, and use Rforce to call
318
+ # Salesforce and gets a result.
319
+ # So, if you are having problems, make sure you check 'soql' and toggle the
320
+ # 'result' object.
321
+ def select_all(sql, name = nil) #:nodoc:
322
+
323
+ # silent-e's solution for single quote escape
324
+ # fix the single quote escape method in WHERE condition expression
325
+ sql = fix_single_quote_in_where(sql)
326
+ # Arel adds the class to the selection - we do not want this i.e...
327
+ # SELECT contacts.* FROM => SELECT * FROM
328
+ sql = sql.gsub(/SELECT\s+[^\(][A-Z]+\./mi," ")
329
+
330
+
331
+ raw_table_name = sql.match(/FROM (\w+)/mi)[1]
332
+
333
+ table_name, columns, entity_def = lookup(raw_table_name)
334
+
335
+ column_names = columns.map { |column| column.api_name }
336
+
337
+ # Check for SELECT COUNT(*) FROM query
338
+
339
+ # Rails 1.1
340
+ selectCountMatch = sql.match(/SELECT\s+COUNT\(\*\)\s+AS\s+count_all\s+FROM/mi)
341
+
342
+ # Rails 1.0
343
+ selectCountMatch = sql.match(/SELECT\s+COUNT\(\*\)\s+FROM/mi) unless selectCountMatch
344
+
345
+ if selectCountMatch
346
+ soql = "SELECT COUNT() FROM#{selectCountMatch.post_match}"
347
+ else
348
+ if sql.match(/SELECT\s+\*\s+FROM/mi)
349
+ # Always convert SELECT * to select all columns (required for the AR attributes mechanism to work correctly)
350
+ soql = sql.sub(/SELECT .+ FROM/mi, "SELECT #{column_names.join(', ')} FROM")
351
+ else
352
+ soql = sql
353
+ end
354
+ end
355
+
356
+ soql.sub!(/\s+FROM\s+\w+/mi, " FROM #{entity_def.api_name}")
357
+
358
+ if selectCountMatch
359
+ query_result = get_result(@connection.query(:queryString => soql), :query)
360
+ return [{ :count => query_result[:size] }]
361
+ end
362
+
363
+ # Look for a LIMIT clause
364
+ limit = extract_sql_modifier(soql, "LIMIT")
365
+ limit = MAX_BOXCAR_SIZE unless limit
366
+
367
+ # Look for an OFFSET clause
368
+ offset = extract_sql_modifier(soql, "OFFSET")
369
+
370
+ # Fixup column references to use api names
371
+ columns = entity_def.column_name_to_column
372
+ soql.gsub!(/((?:\w+\.)?\w+)(?=\s*(?:=|!=|<|>|<=|>=|like)\s*(?:'[^']*'|NULL|TRUE|FALSE))/mi) do |column_name|
373
+ # strip away any table alias
374
+ column_name.sub!(/\w+\./, '')
375
+
376
+ column = columns[column_name]
377
+ raise ActiveSalesforce::ASFError.new(@logger, "Column not found for #{column_name} - in <select_all> method!") unless column
378
+
379
+ column.api_name
380
+ end
381
+
382
+ # Update table name references
383
+ soql.sub!(/#{raw_table_name}\./mi, "#{entity_def.api_name}.")
384
+
385
+ @connection.batch_size = @batch_size if @batch_size
386
+ @batch_size = nil
387
+
388
+ query_result = get_result(@connection.query(:queryString => soql), :query)
389
+ result = ActiveSalesforce::ResultArray.new(query_result[:size].to_i)
390
+ return result unless query_result[:records]
391
+
392
+ add_rows(entity_def, query_result, result, limit)
393
+
394
+ while ((query_result[:done].casecmp("true") != 0) and (result.size < limit or limit == 0))
395
+ # Now queryMore
396
+ locator = query_result[:queryLocator];
397
+ query_result = get_result(@connection.queryMore(:queryLocator => locator), :queryMore)
398
+
399
+ add_rows(entity_def, query_result, result, limit)
400
+ end
401
+
402
+ result
403
+ end
404
+
405
+ # This methods constructs an array of result objects to be returned to your
406
+ # ActiveRecord's finder method.
407
+ def add_rows(entity_def, query_result, result, limit)
408
+ records = query_result[:records]
409
+ records = [ records ] unless records.is_a?(Array)
410
+
411
+ records.each do |record|
412
+ row = {}
413
+
414
+ record.each do |name, value|
415
+ if name != :type
416
+ # Ids may be returned in an array with 2 duplicate entries...
417
+ value = value[0] if name == :Id && value.is_a?(Array)
418
+
419
+ column = entity_def.api_name_to_column[name.to_s]
420
+ attribute_name = column.name
421
+
422
+ if column.type == :boolean
423
+ row[attribute_name] = (value.casecmp("true") == 0)
424
+ else
425
+ row[attribute_name] = value
426
+ end
427
+ end
428
+ end
429
+
430
+ result << row
431
+
432
+ break if result.size >= limit and limit != 0
433
+ end
434
+ end
435
+
436
+ # Calls the <b><em>'select_all'</em></b> method, but limits the result to return only a
437
+ # single row.
438
+ def select_one(sql, name = nil) #:nodoc:
439
+ self.batch_size = 1
440
+
441
+ result = select_all(sql, name)
442
+
443
+ result.nil? ? nil : result.first
444
+ end
445
+
446
+ # Insert object into Salesforce DB
447
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
448
+ log(sql, name) {
449
+ # Convert sql to sobject
450
+ table_name, columns, entity_def = lookup(sql.match(/INSERT\s+INTO\s+(\w+)\s+/mi)[1])
451
+ columns = entity_def.column_name_to_column
452
+
453
+ # Extract array of column names
454
+ names = sql.match(/\((.+)\)\s+VALUES/mi)[1].scan(/\w+/mi)
455
+
456
+ # Extract arrays of values
457
+ values = sql.match(/VALUES\s*\((.+)\)/mi)[1]
458
+ values = values.scan(/(NULL|TRUE|FALSE|'(?:(?:[^']|'')*)'),*/mi).flatten
459
+ values.map! { |v| v.first == "'" ? v.slice(1, v.length - 2) : v == "NULL" ? nil : v }
460
+
461
+ fields = get_fields(columns, names, values, :createable)
462
+
463
+ sobject = create_sobject(entity_def.api_name, nil, fields)
464
+
465
+ # Track the id to be able to update it when the create() is actually executed
466
+ id = String.new
467
+ queue_command ActiveSalesforce::BoxcarCommand::Insert.new(self, sobject, id)
468
+
469
+ id
470
+ }
471
+ end
472
+
473
+ # Updates object(s) via SQL. It is an adapter method to be used by ActiveRecord
474
+ def update(sql, name = nil) #:nodoc:
475
+ # From silent-e, solution for ARel
476
+ sql = sql.gsub(/WHERE\s+\([A-Z]+\./mi,"WHERE ")
477
+
478
+
479
+ #log(sql, name) {
480
+ # Convert sql to sobject
481
+ table_name, columns, entity_def = lookup(sql.match(/UPDATE\s+(\w+)\s+/mi)[1])
482
+ columns = entity_def.column_name_to_column
483
+
484
+ match = sql.match(/SET\s+(.+)\s+WHERE/mi)[1]
485
+ names = match.scan(/(\w+)\s*=\s*(?:'|NULL|TRUE|FALSE)/mi).flatten
486
+
487
+ values = match.scan(/=\s*(NULL|TRUE|FALSE|'(?:(?:[^']|'')*)'),*/mi).flatten
488
+ values.map! { |v| v.first == "'" ? v.slice(1, v.length - 2) : v == "NULL" ? nil : v }
489
+
490
+ fields = get_fields(columns, names, values, :updateable)
491
+ null_fields = get_null_fields(columns, names, values, :updateable)
492
+
493
+ ids = sql.match(/WHERE\s+id\s*=\s*'(\w+)'/mi)
494
+ return if ids.nil?
495
+ id = ids[1]
496
+
497
+ sobject = create_sobject(entity_def.api_name, id, fields, null_fields)
498
+
499
+ queue_command ActiveSalesforce::BoxcarCommand::Update.new(self, sobject)
500
+ #}
501
+ end
502
+
503
+ # Delete object(s) from Salesforce
504
+ def delete(sql, name = nil)
505
+ log(sql, name) {
506
+ # Extract the id
507
+ match = sql.match(/WHERE\s+id\s*=\s*'(\w+)'/mi)
508
+
509
+ if match
510
+ ids = [ match[1] ]
511
+ else
512
+ # Check for the form (id IN ('x', 'y'))
513
+ match = sql.match(/WHERE\s+\(\s*id\s+IN\s*\((.+)\)\)/mi)[1]
514
+ ids = match.scan(/\w+/)
515
+ end
516
+
517
+ ids_element = []
518
+ ids.each { |id| ids_element << :ids << id }
519
+
520
+ queue_command ActiveSalesforce::BoxcarCommand::Delete.new(self, ids_element)
521
+ }
522
+ end
523
+
524
+ # If a SF object is dirty, it is called to get fresh attributes.
525
+ def get_updated(object_type, start_date, end_date, name = nil)
526
+ msg = "get_updated(#{object_type}, #{start_date}, #{end_date})"
527
+ log(msg, name) {
528
+ get_updated_element = []
529
+ get_updated_element << 'type { :xmlns => "urn:sobject.partner.soap.sforce.com" }' << object_type
530
+ get_updated_element << :startDate << start_date
531
+ get_updated_element << :endDate << end_date
532
+
533
+ result = get_result(@connection.getUpdated(get_updated_element), :getUpdated)
534
+
535
+ result[:ids]
536
+ }
537
+ end
538
+
539
+ # Get SF object(s) that have been marked as deleted but not yet permanently removed, like things in a recycle bin.
540
+ def get_deleted(object_type, start_date, end_date, name = nil)
541
+ msg = "get_deleted(#{object_type}, #{start_date}, #{end_date})"
542
+ log(msg, name) {
543
+ get_deleted_element = []
544
+ get_deleted_element << 'type { :xmlns => "urn:sobject.partner.soap.sforce.com" }' << object_type
545
+ get_deleted_element << :startDate << start_date
546
+ get_deleted_element << :endDate << end_date
547
+
548
+ result = get_result(@connection.getDeleted(get_deleted_element), :getDeleted)
549
+
550
+ ids = []
551
+ result[:deletedRecords].each do |v|
552
+ ids << v[:id]
553
+ end
554
+
555
+ ids
556
+ }
557
+ end
558
+
559
+ # Returns information about the user account which is used to connect to salesforce.
560
+ # <b>Salesforce::User.first.connection.get_user_info</b>
561
+ def get_user_info(name = nil)
562
+ msg = "get_user_info()"
563
+ log(msg, name) {
564
+ get_result(@connection.getUserInfo([]), :getUserInfo)
565
+ }
566
+ end
567
+
568
+ # Get value given object type, fields, and Salesforce Object.
569
+ def retrieve_field_values(object_type, fields, ids, name = nil)
570
+ msg = "retrieve(#{object_type}, [#{ids.to_a.join(', ')}])"
571
+ log(msg, name) {
572
+ retrieve_element = []
573
+ retrieve_element << :fieldList << fields.to_a.join(", ")
574
+ retrieve_element << 'type { :xmlns => "urn:sobject.partner.soap.sforce.com" }' << object_type
575
+ ids.to_a.each { |id| retrieve_element << :ids << id }
576
+
577
+ result = get_result(@connection.retrieve(retrieve_element), :retrieve)
578
+
579
+ result = [ result ] unless result.is_a?(Array)
580
+
581
+ # Remove unwanted :type and normalize :Id if required
582
+ field_values = []
583
+ result.each do |v|
584
+ v = v.dup
585
+ v.delete(:type)
586
+ v[:Id] = v[:Id][0] if v[:Id].is_a? Array
587
+
588
+ field_values << v
589
+ end
590
+
591
+ field_values
592
+ }
593
+ end
594
+
595
+
596
+ def get_fields(columns, names, values, access_check)
597
+ fields = {}
598
+ names.each_with_index do | name, n |
599
+ value = values[n]
600
+
601
+ if value
602
+ column = columns[name]
603
+
604
+ raise ActiveSalesforce::ASFError.new(@logger, "Column not found for #{name} - get_fields!") unless column
605
+
606
+ value.gsub!(/''/, "'") if value.is_a? String
607
+
608
+ include_field = ((not value.empty?) and column.send(access_check))
609
+
610
+ if (include_field)
611
+ case column.type
612
+ when :date
613
+ value = Time.parse(value + "Z").utc.strftime("%Y-%m-%d")
614
+ when :datetime
615
+ value = Time.parse(value + "Z").utc.strftime("%Y-%m-%dT%H:%M:%SZ")
616
+ end
617
+
618
+ fields[column.api_name] = value
619
+ end
620
+ end
621
+ end
622
+
623
+ fields
624
+ end
625
+
626
+ def get_null_fields(columns, names, values, access_check)
627
+ fields = {}
628
+ names.each_with_index do | name, n |
629
+ value = values[n]
630
+
631
+ if !value
632
+ column = columns[name]
633
+ fields[column.api_name] = nil if column.send(access_check) && column.api_name.casecmp("ownerid") != 0
634
+ end
635
+ end
636
+
637
+ fields
638
+ end
639
+
640
+ # Removed no valid SQL modifier, right now, only LIMIT is allowed.
641
+ # To use a custom SQL, it is better to interface with RForce, see
642
+ # Salesforce::SfBase query_by_sql(sql) method.
643
+ def extract_sql_modifier(soql, modifier)
644
+ value = soql.match(/\s+#{modifier}\s+(\d+)/mi)
645
+ if value
646
+ value = value[1].to_i
647
+ if !(modifier.upcase == "LIMIT")
648
+ # If it is not the keyword - LIMIT, remove it from the SOQL
649
+ soql.sub!(/\s+#{modifier}\s+\d+/mi, "")
650
+ else
651
+ # If it is the keyword - LIMIT, do not remove it from the SOQL
652
+ end
653
+ # SOQL now supports LIMIT clause. If the user is not an app admin &
654
+ # queries Newsfeed or EntitySubscription & without q limit (e.g. > 1000),
655
+ # it would cause MQL_FORMED_QUERY exception:
656
+ # However, OFFSET is still not supported by SOQL.
657
+ # ***NOTE: needs to change it in the gem to make it effective.
658
+ end
659
+
660
+ value
661
+ end
662
+
663
+ # A clever contraption to construct the key to the object array which is returned
664
+ # by Rforce from SOAP result.
665
+ def get_result(response, method)
666
+ responseName = (method.to_s + "Response").to_sym
667
+ finalResponse = response[responseName]
668
+
669
+ raise ActiveSalesforce::ASFError.new(@logger, response[:Fault][:faultstring], response.fault) unless finalResponse
670
+
671
+ result = finalResponse[:result]
672
+ end
673
+
674
+
675
+ def check_result(result)
676
+ result = [ result ] unless result.is_a?(Array)
677
+
678
+ result.each do |r|
679
+ raise ActiveSalesforce::ASFError.new(@logger, r[:errors], r[:errors][:message]) unless r[:success] == "true"
680
+ end
681
+
682
+ result
683
+ end
684
+
685
+
686
+ # A clever contract to get meta-attribute associated with a Salesforce Object.
687
+ # by Rforce from SOAP result. e.g.
688
+ # <b>pp user.connection.get_entity_def("AccountFeed") </b>
689
+ # <ActiveSalesforce::EntityDefinition:0x1030eee40
690
+ # @api_name_to_column=
691
+ # {"CreatedDate"=> <ActiveRecord::ConnectionAdapters::SalesforceColumn:0x1030f2c20
692
+ # @api_name="CreatedDate",
693
+ # @createable=false,
694
+ # .....
695
+ def get_entity_def(entity_name)
696
+ cached_entity_def = @entity_def_map[entity_name]
697
+
698
+ if cached_entity_def
699
+ # Check for the loss of asf AR setup
700
+ entity_klass = class_from_entity_name(entity_name)
701
+
702
+ configure_active_record(cached_entity_def) unless entity_klass.respond_to?(:asf_augmented?)
703
+
704
+ return cached_entity_def
705
+ end
706
+
707
+ cached_columns = []
708
+ cached_relationships = []
709
+
710
+ begin
711
+ metadata = get_result(@connection.describeSObject(:sObjectType => entity_name), :describeSObject)
712
+ custom = false
713
+ rescue ActiveSalesforce::ASFError
714
+ # Fallback and see if we can find a custom object with this name
715
+ debug(" Unable to find medata for '#{entity_name}', falling back to custom object name #{entity_name + "__c"}")
716
+
717
+ metadata = get_result(@connection.describeSObject(:sObjectType => entity_name + "__c"), :describeSObject)
718
+ custom = true
719
+ end
720
+
721
+ metadata[:fields].each do |field|
722
+ column = SalesforceColumn.new(field)
723
+ cached_columns << column
724
+
725
+ cached_relationships << SalesforceRelationship.new(field, column) if field[:type] =~ /reference/mi
726
+ end
727
+
728
+ relationships = metadata[:childRelationships]
729
+ if relationships
730
+ relationships = [ relationships ] unless relationships.is_a? Array
731
+
732
+ relationships.each do |relationship|
733
+ if relationship[:cascadeDelete] == "true"
734
+ r = SalesforceRelationship.new(relationship)
735
+ cached_relationships << r
736
+ end
737
+ end
738
+ end
739
+
740
+ key_prefix = metadata[:keyPrefix]
741
+
742
+ entity_def = ActiveSalesforce::EntityDefinition.new(self, entity_name, entity_klass,
743
+ cached_columns, cached_relationships, custom, key_prefix)
744
+
745
+ @entity_def_map[entity_name] = entity_def
746
+ @keyprefix_to_entity_def_map[key_prefix] = entity_def
747
+
748
+ configure_active_record(entity_def)
749
+
750
+ entity_def
751
+ end
752
+
753
+
754
+ def configure_active_record(entity_def)
755
+ entity_name = entity_def.name
756
+ klass = class_from_entity_name(entity_name)
757
+
758
+ class << klass
759
+ def asf_augmented?
760
+ true
761
+ end
762
+ end
763
+
764
+ # Add support for SID-based authentication
765
+ ActiveSalesforce::SessionIDAuthenticationFilter.register(klass)
766
+
767
+ klass.set_inheritance_column nil unless entity_def.custom?
768
+ klass.set_primary_key "id"
769
+
770
+ # Create relationships for any reference field
771
+ entity_def.relationships.each do |relationship|
772
+ referenceName = relationship.name
773
+ unless self.respond_to? referenceName.to_sym or relationship.reference_to == "Profile"
774
+ reference_to = relationship.reference_to
775
+ one_to_many = relationship.one_to_many
776
+ foreign_key = relationship.foreign_key
777
+
778
+ # DCHASMAN TODO Figure out how to handle polymorphic refs (e.g. Note.parent can refer to
779
+ # Account, Contact, Opportunity, Contract, Asset, Product2, <CustomObject1> ... <CustomObject(n)>
780
+ if reference_to.is_a? Array
781
+ debug(" Skipping unsupported polymophic one-to-#{one_to_many ? 'many' : 'one' } relationship '#{referenceName}' from #{klass} to [#{relationship.reference_to.join(', ')}] using #{foreign_key}")
782
+ next
783
+ end
784
+
785
+ # Handle references to custom objects
786
+ reference_to = reference_to.chomp("__c").camelize if reference_to.match(/__c$/)
787
+
788
+ begin
789
+ referenced_klass = class_from_entity_name(reference_to)
790
+ rescue NameError => e
791
+ # Automatically create a least a stub for the referenced entity
792
+ debug(" Creating ActiveRecord stub for the referenced entity '#{reference_to}'")
793
+
794
+ referenced_klass = klass.class_eval("Salesforce::#{reference_to} = Class.new(ActiveRecord::Base)")
795
+ referenced_klass.instance_variable_set("@asf_connection", klass.connection)
796
+
797
+ # Automatically inherit the connection from the referencee
798
+ def referenced_klass.connection
799
+ @asf_connection
800
+ end
801
+ end
802
+
803
+ if referenced_klass
804
+ if one_to_many
805
+ assoc_name = reference_to.underscore.pluralize.to_sym
806
+ klass.has_many assoc_name, :class_name => referenced_klass.name, :foreign_key => foreign_key
807
+ else
808
+ assoc_name = reference_to.underscore.singularize.to_sym
809
+ klass.belongs_to assoc_name, :class_name => referenced_klass.name, :foreign_key => foreign_key
810
+ end
811
+
812
+ debug(" Created one-to-#{one_to_many ? 'many' : 'one' } relationship '#{referenceName}' from #{klass} to #{referenced_klass} using #{foreign_key}")
813
+ end
814
+ end
815
+ end
816
+
817
+ end
818
+
819
+
820
+ # Return column names given a table_name, usage:
821
+ # >> pp user.connection.columns('account_feed') or ('AccountFeed')
822
+ def columns(table_name, name = nil)
823
+ table_name, columns, entity_def = lookup(table_name)
824
+ entity_def.columns
825
+ end
826
+
827
+ # Given a entity and show column names,
828
+ # >>pp user.connection.class_from_entity_name("AccountFeed")
829
+ def class_from_entity_name(entity_name)
830
+ entity_klass = @class_to_entity_map[entity_name.upcase]
831
+ debug("Found matching class '#{entity_klass}' for entity '#{entity_name}'") if entity_klass
832
+
833
+ # Constantize entities under the Salesforce namespace.
834
+ entity_klass = ("Salesforce::" + entity_name).constantize unless entity_klass
835
+
836
+ entity_klass
837
+ end
838
+
839
+ # Create a blank Salesforce object
840
+ def create_sobject(entity_name, id, fields, null_fields = [])
841
+ sobj = []
842
+
843
+ sobj << 'type { :xmlns => "urn:sobject.partner.soap.sforce.com" }' << entity_name
844
+ sobj << 'Id { :xmlns => "urn:sobject.partner.soap.sforce.com" }' << id if id
845
+
846
+ # add any changed fields
847
+ fields.each do | name, value |
848
+ sobj << name.to_sym << value if value
849
+ end
850
+
851
+ # add null fields
852
+ null_fields.each do | name, value |
853
+ sobj << 'fieldsToNull { :xmlns => "urn:sobject.partner.soap.sforce.com" }' << name
854
+ end
855
+
856
+ [ :sObjects, sobj ]
857
+ end
858
+
859
+ # Returns column names associated with a Salesforce Object
860
+ def column_names(table_name)
861
+ columns(table_name).map { |column| column.name }
862
+ end
863
+
864
+
865
+ def lookup(raw_table_name)
866
+ table_name = raw_table_name.singularize
867
+
868
+ # See if a table name to AR class mapping was registered
869
+ klass = @class_to_entity_map[table_name.upcase]
870
+
871
+ entity_name = klass ? raw_table_name : table_name.camelize
872
+ entity_def = get_entity_def(entity_name)
873
+
874
+ [table_name, entity_def.columns, entity_def]
875
+ end
876
+
877
+
878
+ def debug(msg)
879
+ @logger.debug(msg) if @logger
880
+ end
881
+
882
+ protected
883
+
884
+ # For Silent-e, added 'tables' method to solve ARel problem
885
+ # fix single-quote escape sequence for WHERE condition expressions. Salesforce enforces a backspace on SELECTs
886
+ # NOTE: this method is only used for SELECT queries. INSERT/UPDATE queries are smart enough to use the primary
887
+ # key for their WHERE statements, or so I've found.
888
+ def fix_single_quote_in_where(sql)
889
+ where_match = sql.match(/WHERE\s*\((.*)\)/mi)
890
+
891
+ return sql unless where_match
892
+ where_conditions = where_match[1]
893
+
894
+ # debug("where_conditions: #{where_conditions}")
895
+ where_conditions.gsub!(/''/, "\\\\'")
896
+
897
+ # debug("updated where_conditions: #{where_conditions.gsub(/''/, "\\\\'")}")
898
+
899
+ sql = "#{where_match.pre_match}WHERE (#{where_conditions})#{where_match.post_match}"
900
+ end
901
+
902
+
903
+ def queue_command(command)
904
+ # If @command_boxcar is not nil, then this is a transaction
905
+ # and commands should be queued in the boxcar
906
+ if @command_boxcar
907
+ @command_boxcar << command
908
+
909
+ # If a command is not executed within a transaction, it should
910
+ # be executed immediately
911
+ else
912
+ send_commands([command])
913
+ end
914
+ end
915
+
916
+ end
917
+
918
+ end
919
+ end