activesalesforce 0.0.2

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.
data/README ADDED
@@ -0,0 +1,23 @@
1
+ == Welcome to Active Salesforce
2
+
3
+ ActiveSalesforce is an extension to the Rails Framework that allows for the dynamic creation and management of
4
+ ActiveRecord objects through the use of Salesforce meta-data and uses a Salesforce.com organization as the backing store.
5
+
6
+
7
+ == Getting started
8
+
9
+ 1. TBD Add in info on editing scripts/generate and scripts/server + database.yml
10
+
11
+
12
+ == Description of contents
13
+
14
+ lib
15
+ Application specific libraries. Basically, any kind of custom code that doesn't
16
+ belong under controllers, models, or helpers. This directory is in the load path.
17
+
18
+ script
19
+ Helper scripts for automation and generation.
20
+
21
+ test
22
+ Unit and functional tests along with fixtures.
23
+
@@ -0,0 +1,82 @@
1
+ =begin
2
+ ActiveSalesforce
3
+ Copyright (c) 2006 Doug Chasman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+ =end
23
+
24
+ require 'rubygems'
25
+ require_gem 'rails', ">= 1.0.0"
26
+
27
+ #require 'active_record/connection_adapters/abstract_adapter'
28
+ #require 'active_record/connection_adapters/abstract/schema_definitions'
29
+
30
+ module ActiveRecord
31
+ module ConnectionAdapters
32
+ class SalesforceColumn < Column
33
+ attr_reader :label, :readonly, :reference_to
34
+
35
+ def initialize(field)
36
+ @name = field[:name]
37
+ @type = get_type(field[:type])
38
+ @limit = field[:length]
39
+ @label = field[:label]
40
+
41
+ @text = [:string, :text].include? @type
42
+ @number = [:float, :integer].include? @type
43
+
44
+ @readonly = (field[:updateable] != "true" or field[:createable] != "true")
45
+
46
+ if field[:type] =~ /reference/i
47
+ @reference_to = field[:referenceTo]
48
+ end
49
+ end
50
+
51
+ def get_type(field_type)
52
+ case field_type
53
+ when /int/i
54
+ :integer
55
+ when /currency|percent/i
56
+ :float
57
+ when /datetime/i
58
+ :datetime
59
+ when /date/i
60
+ :date
61
+ when /id|string|textarea/i
62
+ :text
63
+ when /phone|fax|email|url/i
64
+ :string
65
+ when /blob|binary/i
66
+ :binary
67
+ when /boolean/i
68
+ :boolean
69
+ when /picklist/i
70
+ :text
71
+ when /reference/i
72
+ :text
73
+ end
74
+ end
75
+
76
+ def human_name
77
+ @label
78
+ end
79
+
80
+ end
81
+ end
82
+ end
data/lib/rforce.rb ADDED
@@ -0,0 +1,226 @@
1
+ require 'net/https'
2
+ require 'uri'
3
+ require 'rexml/document'
4
+ require 'rexml/xpath'
5
+ require 'rubygems'
6
+ require_gem 'builder'
7
+
8
+ =begin
9
+ RForce v0.1
10
+ Copyright (c) 2005 Ian Dees
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in
20
+ all copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ =end
30
+
31
+ # RForce is a simple Ruby binding to the SalesForce CRM system.
32
+ # Rather than enforcing adherence to the sforce.com schema,
33
+ # RForce assumes you are familiar with the API. Ruby method names
34
+ # become SOAP method names. Nested Ruby hashes become nested
35
+ # XML elements.
36
+ #
37
+ # Example:
38
+ #
39
+ # binding = RForce::Binding.new 'na1-api.salesforce.com'
40
+ # binding.login 'username', 'password'
41
+ # answer = binding.search(
42
+ # :searchString =>
43
+ # 'find {Some Account Name} in name fields returning account(id)')
44
+ # account_id = answer.searchResponse.result.searchRecords.record.Id
45
+ #
46
+ # opportunity = {
47
+ # :accountId => account_id,
48
+ # :amount => "10.00",
49
+ # :name => "New sale",
50
+ # :closeDate => "2005-09-01",
51
+ # :stageName => "Closed Won"
52
+ # }
53
+ #
54
+ # binding.create 'sObject {"xsi:type" => "Opportunity"}' => opportunity
55
+ #
56
+ module RForce
57
+
58
+ #Allows indexing hashes like method calls: hash.key
59
+ #to supplement the traditional way of indexing: hash[key]
60
+ module FlashHash
61
+ def method_missing(method, *args)
62
+ self[method]
63
+ end
64
+ end
65
+
66
+ #Turns an XML response from the server into a Ruby
67
+ #object whose methods correspond to nested XML elements.
68
+ class SoapResponse
69
+ include FlashHash
70
+
71
+ #Parses an XML string into structured data.
72
+ def initialize(content)
73
+ document = REXML::Document.new content
74
+ node = REXML::XPath.first document, '//soapenv:Body'
75
+ @parsed = SoapResponse.parse node
76
+ end
77
+
78
+ #Allows this object to act like a hash (and therefore
79
+ #as a FlashHash via the include above).
80
+ def [](symbol)
81
+ @parsed[symbol]
82
+ end
83
+
84
+ #Digests an XML DOM node into nested Ruby types.
85
+ def SoapResponse.parse(node)
86
+ #Convert text nodes into simple strings.
87
+ return node.text unless node.has_elements?
88
+
89
+ #Convert nodes with children into FlashHashes.
90
+ elements = {}
91
+ class << elements
92
+ include FlashHash
93
+ end
94
+
95
+ #Add all the element's children to the hash.
96
+ node.each_element do |e|
97
+ name = e.name.to_sym
98
+
99
+ case elements[name]
100
+ #The most common case: unique child element tags.
101
+ when NilClass: elements[name] = parse(e)
102
+
103
+ #Non-unique child elements become arrays:
104
+
105
+ #We've already created the array: just
106
+ #add the element.
107
+ when Array: elements[name] << parse(e)
108
+
109
+ #We haven't created the array yet: do so,
110
+ #then put the existing element in, followed
111
+ #by the new one.
112
+ else
113
+ elements[name] = [elements[name]]
114
+ elements[name] << parse(e)
115
+ end
116
+ end
117
+
118
+ return elements
119
+ end
120
+ end
121
+
122
+ #Implements the connection to the SalesForce server.
123
+ class Binding
124
+ #Fill in the guts of this typical SOAP envelope
125
+ #with the session ID and the body of the SOAP request.
126
+ Envelope = <<-HERE
127
+ <?xml version="1.0" encoding="utf-8" ?>
128
+ <env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
129
+ xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
130
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
131
+ <env:Header>
132
+ <SessionHeader>
133
+ <sessionId>%s</sessionId>
134
+ </SessionHeader>
135
+ </env:Header>
136
+ <env:Body>
137
+ %s
138
+ </env:Body>
139
+ </env:Envelope>
140
+ HERE
141
+
142
+ #Connect to the server securely.
143
+ def initialize(url)
144
+ @url = URI.parse(url)
145
+ @server = Net::HTTP.new(@url.host, @url.port)
146
+ @server.use_ssl = @url.scheme == 'https'
147
+
148
+ # run ruby with -d to see SOAP wiredumps.
149
+ @server.set_debug_output $stderr if $DEBUG
150
+
151
+ @session_id = ''
152
+ end
153
+
154
+ #Log in to the server and remember the session ID
155
+ #returned to us by SalesForce.
156
+ def login(user, pass)
157
+ response = call_remote(:login, [:username, user, :password, pass])
158
+
159
+ raise "Incorrect user name / password [#{response.fault}]" unless response.loginResponse
160
+ @session_id = response.loginResponse.result.sessionId
161
+ response
162
+ end
163
+
164
+ #Call a method on the remote server. Arguments can be
165
+ #a hash or (if order is important) an array of alternating
166
+ #keys and values.
167
+ def call_remote(method, args)
168
+ #Create XML text from the arguments.
169
+ expanded = ''
170
+ @builder = Builder::XmlMarkup.new(:target => expanded)
171
+ expand({method => args}, 'urn:partner.soap.sforce.com')
172
+
173
+ #Fill in the blanks of the SOAP envelope with our
174
+ #session ID and the expanded XML of our request.
175
+ request = (Envelope % [@session_id, expanded])
176
+
177
+ #Send the request to the server and read the response.
178
+ response = @server.post2(@url.path, request, {'SOAPAction' => method.to_s, 'content-type' => 'text/xml'})
179
+ SoapResponse.new(response.body)
180
+ end
181
+
182
+ #Turns method calls on this object into remote SOAP calls.
183
+ def method_missing(method, *args)
184
+ unless args.size == 1 && [Hash, Array].include?(args[0].class)
185
+ raise 'Expected 1 Hash or Array argument'
186
+ end
187
+
188
+ call_remote method, args[0]
189
+ end
190
+
191
+ #Expand Ruby data structures into XML.
192
+ def expand(args, xmlns = nil)
193
+ #Nest arrays: [:a, 1, :b, 2] => [[:a, 1], [:b, 2]]
194
+ if (args.class == Array)
195
+ args.each_index{|i| args[i, 2] = [args[i, 2]]}
196
+ end
197
+
198
+ args.each do |key, value|
199
+ attributes = xmlns ? {:xmlns => xmlns} : {}
200
+
201
+ #If the XML tag requires attributes,
202
+ #the tag name will contain a space
203
+ #followed by a string representation
204
+ #of a hash of attributes.
205
+ #
206
+ #e.g. 'sObject {"xsi:type" => "Opportunity"}'
207
+ #becomes <sObject xsi:type="Opportunity>...</sObject>
208
+ if key.is_a? String
209
+ key, modifier = key.split(' ', 2)
210
+
211
+ attributes.merge!(eval(modifier)) if modifier
212
+ end
213
+
214
+ #Create an XML element and fill it with this
215
+ #value's sub-items.
216
+ case value
217
+ when Hash, Array
218
+ @builder.tag!(key, attributes) do expand value; end
219
+
220
+ when String
221
+ @builder.tag!(key, attributes) { @builder.text! value }
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,131 @@
1
+ =begin
2
+ ActiveSalesforce
3
+ Copyright (c) 2006 Doug Chasman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+ =end
23
+
24
+ require 'xsd/datatypes'
25
+ require 'soap/soap'
26
+
27
+ require File.dirname(__FILE__) + '/sobject_attributes'
28
+
29
+
30
+ module ActiveRecord
31
+ # Active Records will automatically record creation and/or update timestamps of database objects
32
+ # if fields of the names created_at/created_on or updated_at/updated_on are present. This module is
33
+ # automatically included, so you don't need to do that manually.
34
+ #
35
+ # This behavior can be turned off by setting <tt>ActiveRecord::Base.record_timestamps = false</tt>.
36
+ # This behavior can use GMT by setting <tt>ActiveRecord::Base.timestamps_gmt = true</tt>
37
+ module SalesforceRecord
38
+ include SOAP, XSD
39
+
40
+ NS1 = 'urn:partner.soap.sforce.com'
41
+ NS2 = "urn:sobject.partner.soap.sforce.com"
42
+
43
+ def self.append_features(base) # :nodoc:
44
+ super
45
+
46
+ base.class_eval do
47
+ alias_method :create, :create_with_sforce_api
48
+ alias_method :update, :update_with_sforce_api
49
+ end
50
+ end
51
+
52
+ def create_with_sforce_api
53
+ return if not @attributes.changed?
54
+ puts "create_with_sforce_api creating #{self.class}"
55
+ connection.create(:sObjects => create_sobject())
56
+ end
57
+
58
+ def update_with_sforce_api
59
+ return if not @attributes.changed?
60
+ puts "update_with_sforce_api updating #{self.class}('#{self.Id}')"
61
+ connection.update(:sObjects => create_sobject())
62
+ end
63
+
64
+ def create_sobject()
65
+ fields = @attributes.changed_fields
66
+
67
+ sobj = [ 'type { :xmlns => "urn:sobject.partner.soap.sforce.com" }', self.class.name ]
68
+ sobj << 'Id { :xmlns => "urn:sobject.partner.soap.sforce.com" }' << self.Id if self.Id
69
+
70
+ # now add any changed fields
71
+ fieldValues = {}
72
+ fields.each do |fieldName|
73
+ value = @attributes[fieldName]
74
+ sobj << fieldName.to_sym << value if value
75
+ end
76
+
77
+ sobj
78
+ end
79
+
80
+ end
81
+
82
+ class Base
83
+ set_inheritance_column nil
84
+ lock_optimistically = false
85
+ record_timestamps = false
86
+ default_timezone = :utc
87
+
88
+ def after_initialize()
89
+ if not @attributes.is_a?(Salesforce::SObjectAttributes)
90
+ # Insure that SObjectAttributes is always used for our atttributes
91
+ originalAttributes = @attributes
92
+
93
+ @attributes = Salesforce::SObjectAttributes.new(connection.columns_map(self.class.table_name))
94
+
95
+ originalAttributes.each { |name, value| self[name] = value }
96
+ end
97
+ end
98
+
99
+ def self.table_name
100
+ class_name_of_active_record_descendant(self)
101
+ end
102
+
103
+ def self.primary_key
104
+ "Id"
105
+ end
106
+
107
+ def self.construct_finder_sql(options)
108
+ soql = "SELECT #{column_names.join(', ')} FROM #{table_name} "
109
+ add_conditions!(soql, options[:conditions])
110
+ soql
111
+ end
112
+
113
+ def self.construct_conditions_from_arguments(attribute_names, arguments)
114
+ conditions = []
115
+ attribute_names.each_with_index { |name, idx| conditions << "#{name} #{attribute_condition(arguments[idx])} " }
116
+ [ conditions.join(" AND "), *arguments[0...attribute_names.length] ]
117
+ end
118
+
119
+ def self.count(conditions = nil, joins = nil)
120
+ soql = "SELECT Id FROM #{table_name} "
121
+ add_conditions!(soql, conditions)
122
+
123
+ count_by_sql(soql)
124
+ end
125
+
126
+ def self.count_by_sql(soql)
127
+ connection.select_all(soql, "#{name} Count").length
128
+ end
129
+
130
+ end
131
+ end
@@ -0,0 +1,212 @@
1
+ =begin
2
+ ActiveSalesforce
3
+ Copyright (c) 2006 Doug Chasman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+ =end
23
+
24
+ require 'rubygems'
25
+ require_gem 'rails', ">= 1.0.0"
26
+
27
+ require 'thread'
28
+
29
+ require File.dirname(__FILE__) + '/salesforce_login'
30
+ require File.dirname(__FILE__) + '/sobject_attributes'
31
+ require File.dirname(__FILE__) + '/salesforce_active_record'
32
+ require File.dirname(__FILE__) + '/column_definition'
33
+
34
+ ActiveRecord::Base.class_eval do
35
+ include ActiveRecord::SalesforceRecord
36
+ end
37
+
38
+
39
+ module ActiveRecord
40
+ class Base
41
+ @@cache = {}
42
+
43
+ # Establishes a connection to the database that's used by all Active Record objects.
44
+ def self.salesforce_connection(config) # :nodoc:
45
+ puts "Using Salesforce connection!"
46
+
47
+ config = config.symbolize_keys
48
+
49
+ url = config[:url].to_s
50
+ username = config[:username].to_s
51
+ password = config[:password].to_s
52
+
53
+ connection = @@cache["#{url}.#{username}.#{password}"]
54
+
55
+ if not connection
56
+ connection = SalesforceLogin.new(url, username, password).proxy
57
+ @@cache["#{url}.#{username}.#{password}"] = connection
58
+ puts "Created new connection for [#{url}, #{username}]"
59
+ end
60
+
61
+ #puts "connected to Salesforce as #{connection.getUserInfo(nil).result['userFullName']}"
62
+
63
+ ConnectionAdapters::SalesforceAdapter.new(connection, logger, [url, username, password], config)
64
+ end
65
+ end
66
+
67
+
68
+ module ConnectionAdapters
69
+ class SalesforceError < StandardError
70
+ end
71
+
72
+ class SalesforceAdapter < AbstractAdapter
73
+
74
+ def initialize(connection, logger, connection_options, config)
75
+ super(connection, logger)
76
+
77
+ @connection_options, @config = connection_options, config
78
+ @columns_name_map = {}
79
+ end
80
+
81
+ def adapter_name #:nodoc:
82
+ 'Salesforce'
83
+ end
84
+
85
+ def supports_migrations? #:nodoc:
86
+ false
87
+ end
88
+
89
+
90
+ # QUOTING ==================================================
91
+
92
+ def quote(value, column = nil)
93
+ if value.kind_of?(String) && column && column.type == :binary
94
+ s = column.class.string_to_binary(value).unpack("H*")[0]
95
+ "x'#{s}'"
96
+ else
97
+ super
98
+ end
99
+ end
100
+
101
+ def quote_column_name(name) #:nodoc:
102
+ "`#{name}`"
103
+ end
104
+
105
+ def quote_string(string) #:nodoc:
106
+ string
107
+ end
108
+
109
+ def quoted_true
110
+ "TRUE"
111
+ end
112
+
113
+ def quoted_false
114
+ "FALSE"
115
+ end
116
+
117
+
118
+ # CONNECTION MANAGEMENT ====================================
119
+
120
+ def active?
121
+ true
122
+ end
123
+
124
+ def reconnect!
125
+ connect
126
+ end
127
+
128
+
129
+ # DATABASE STATEMENTS ======================================
130
+
131
+ def select_all(soql, name = nil) #:nodoc:
132
+ log(soql, name)
133
+
134
+ records = @connection.query(:queryString => soql).queryResponse.result.records
135
+
136
+ records = [ records ] unless records.is_a?(Array)
137
+
138
+ result = []
139
+ records.each do |record|
140
+ attributes = Salesforce::SObjectAttributes.new(columns_map(record[:type]), record)
141
+ result << attributes
142
+ end
143
+
144
+ result
145
+ end
146
+
147
+ def select_one(sql, name = nil) #:nodoc:
148
+ result = select_all(sql, name)
149
+ result.nil? ? nil : result.first
150
+ end
151
+
152
+ def create(sobject, name = nil) #:nodoc:
153
+ result = @connection.create(sobject).createResponse.result
154
+
155
+ raise SalesforceError, result.errors.message unless result.success == "true"
156
+
157
+ # @connection.affected_rows
158
+ end
159
+
160
+ def update(sobject, name = nil) #:nodoc:
161
+ result = @connection.update(sobject).updateResponse.result
162
+
163
+ raise SalesforceError, result.errors.message unless result.success == "true"
164
+
165
+ # @connection.affected_rows
166
+ end
167
+
168
+ alias_method :delete, :update
169
+
170
+ def columns(table_name, name = nil)
171
+ columns = []
172
+
173
+ metadata = @connection.describeSObject(:sObjectType => table_name).describeSObjectResponse.result
174
+
175
+ metadata.fields.each do |field|
176
+ columns << SalesforceColumn.new(field)
177
+ end
178
+
179
+ columns
180
+ end
181
+
182
+ def columns_map(table_name, name = nil)
183
+ columns_map = @columns_name_map[table_name]
184
+ return columns_map if columns_map
185
+
186
+ columns_map = {}
187
+ @columns_name_map[table_name] = columns_map
188
+
189
+ columns(table_name).each { |column| columns_map[column.name] = column }
190
+
191
+ columns_map
192
+ end
193
+
194
+ private
195
+
196
+ def select(sql, name = nil)
197
+ puts "select(#{sql}, (#{name}))"
198
+ @connection.query_with_result = true
199
+ result = execute(sql, name)
200
+ rows = []
201
+ if @null_values_in_each_hash
202
+ result.each_hash { |row| rows << row }
203
+ else
204
+ all_fields = result.fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
205
+ result.each_hash { |row| rows << all_fields.dup.update(row) }
206
+ end
207
+ result.free
208
+ rows
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ =begin
4
+ ActiveSalesforce
5
+ Copyright (c) 2006 Doug Chasman
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
24
+ =end
25
+
26
+ require File.dirname(__FILE__) + '/rforce.rb'
27
+
28
+
29
+ class SalesforceLogin
30
+ attr_reader :proxy
31
+
32
+ def initialize(url, username, password)
33
+ puts "SalesforceLogin.initialize()"
34
+
35
+ @proxy = RForce::Binding.new(url)
36
+
37
+ login_result = @proxy.login(username, password).result
38
+ end
39
+ end
@@ -0,0 +1,132 @@
1
+ =begin
2
+ ActiveSalesforce
3
+ Copyright (c) 2006 Doug Chasman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+ =end
23
+
24
+ require 'set'
25
+
26
+
27
+ module Salesforce
28
+
29
+ class SObjectAttributes
30
+ include Enumerable
31
+
32
+ def initialize(columns, record = nil)
33
+ @columns = columns
34
+ @values = {}
35
+
36
+ if record
37
+ record.each do |name, value|
38
+ # Replace nil element with nil
39
+ value = nil if value.respond_to?(:xmlattr_nil) and value.xmlattr_nil
40
+
41
+ # Ids are returned in an array with 2 duplicate entries...
42
+ value = value[0] if name == :Id
43
+
44
+ self[name.to_s] = value
45
+ end
46
+ else
47
+ columns.values.each { |column| self[column.name] = nil }
48
+ end
49
+
50
+ clear_changed!
51
+ end
52
+
53
+ def [](key)
54
+ @values[key].freeze
55
+ end
56
+
57
+ def []=(key, value)
58
+ column = @columns[key]
59
+ return unless column
60
+
61
+ value = nil if value == ""
62
+
63
+ return if @values[key] == value and @values.include?(key)
64
+
65
+ originalClass = value.class
66
+ originalValue = value
67
+
68
+ if value
69
+ # Convert strings representation of dates and datetimes to date and time objects
70
+ case column.type
71
+ when :date
72
+ value = value.is_a?(Date) ? value : Date.parse(value)
73
+ when :datetime
74
+ value = value.is_a?(Time) ? value : Time.parse(value)
75
+ else
76
+ value = column.type_cast(value)
77
+ end
78
+ end
79
+
80
+ @values[key] = value
81
+
82
+ #puts "setting #{key} = #{value} [#{originalValue}] (#{originalClass}, #{value.class})"
83
+
84
+ if not column.readonly
85
+ @changed = Set.new unless @changed
86
+ @changed.add(key)
87
+ end
88
+ end
89
+
90
+ def include?(key)
91
+ @values.include?(key)
92
+ end
93
+
94
+ def has_key?(key)
95
+ @values.has_key?(key)
96
+ end
97
+
98
+ def length
99
+ @values.length
100
+ end
101
+
102
+ def keys
103
+ @values.keys
104
+ end
105
+
106
+ def clear
107
+ @values.clear
108
+ clear_changed
109
+ end
110
+
111
+ def clear_changed!
112
+ @changed = nil
113
+ end
114
+
115
+ def changed?
116
+ @changed != nil
117
+ end
118
+
119
+ def changed_fields
120
+ @changed
121
+ end
122
+
123
+
124
+ # Enumerable support
125
+
126
+ def each(&block)
127
+ @values.each(&block)
128
+ end
129
+
130
+ end
131
+
132
+ end
@@ -0,0 +1,42 @@
1
+ require File.join(File.dirname(__FILE__), '../../config/boot')
2
+ require '~/dev/activesfdc/trunk/ActiveSalesforce/src/salesforce_connection_adapter'
3
+ require File.dirname(__FILE__) + '/../test_helper'
4
+
5
+
6
+
7
+ class AccountTest < Test::Unit::TestCase
8
+
9
+ def test_get_account
10
+ products = Account.find(:all)
11
+ #pp products
12
+
13
+ products.each { |product| puts "#{product.Name}, #{product.Id}, #{product.LastModifiedById}, #{product.Description}" }
14
+
15
+ acme = Account.find(:first, :conditions => ["Name = 'Acme'"])
16
+ puts acme.Name
17
+
18
+ acme = Account.find_by_Id(acme.Id)
19
+ puts acme.Name
20
+
21
+ acme = Account.find_by_Name_and_LastModifiedById('salesforce.com', acme.LastModifiedById)
22
+ puts acme.Name
23
+ end
24
+
25
+ def test_update_account
26
+ #return
27
+
28
+ acme = Account.find_by_Name('Acme')
29
+ puts acme.Name
30
+
31
+ acme.Website = "http://www.dutchforce.com/myImage2.jpg"
32
+
33
+ acme.save
34
+ end
35
+
36
+ def test_xcreate_account
37
+ dutchCo = Account.new
38
+ dutchCo.Name = "DutchCo"
39
+ dutchCo.save
40
+ end
41
+
42
+ end
@@ -0,0 +1,38 @@
1
+ puts "Yahoo"
2
+
3
+ require 'test/unit'
4
+ require File.dirname(__FILE__) + '/../../src/sobject_attributes'
5
+
6
+ class SobjectAttributesTest < Test::Unit::TestCase
7
+
8
+ def setup
9
+ @attributes = Salesforce::SObjectAttributes.new
10
+ end
11
+
12
+ def test_add_values()
13
+ assert((not @attributes.changed?))
14
+
15
+ @attributes['name'] = 'value'
16
+ assert(@attributes.changed?)
17
+
18
+ assert_equal('value', @attributes['name'])
19
+
20
+ assert_equal(Set.new('name'), @attributes.changed_fields)
21
+
22
+ @attributes.clear_changed!
23
+ assert((not @attributes.changed?))
24
+
25
+ assert_equal('value', @attributes['name'])
26
+ end
27
+
28
+ def test_enumeration
29
+ 10.times { |n| @attributes["name_#{n}"] = "value_#{n}" }
30
+
31
+ assert_equal(10, @attributes.length)
32
+
33
+ 5.times { |n| @attributes["name_#{n + 10}"] = "value_#{n + 10}" }
34
+
35
+ @attributes.each { |name, value| assert_equal(name[/_\d/], value[/_\d/]) }
36
+ end
37
+
38
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: activesalesforce
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.2
7
+ date: 2006-01-18 00:00:00 -05:00
8
+ summary: ActiveSalesforce is an extension to the Rails Framework that allows for the dynamic creation and management of ActiveRecord objects through the use of Salesforce meta-data and uses a Salesforce.com organization as the backing store.
9
+ require_paths:
10
+ - lib
11
+ email: dchasman@salesforce.com
12
+ homepage: http://rubyforge.org/projects/activesfdc/
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Doug Chasman
30
+ files:
31
+ - lib/salesforce_login.rb
32
+ - lib/salesforce_active_record.rb
33
+ - lib/column_definition.rb
34
+ - lib/rforce.rb
35
+ - lib/sobject_attributes.rb
36
+ - lib/salesforce_connection_adapter.rb
37
+ - test/unit
38
+ - test/unit/sobject_attributes_test.rb
39
+ - test/unit/account_test.rb
40
+ - README
41
+ test_files: []
42
+
43
+ rdoc_options: []
44
+
45
+ extra_rdoc_files:
46
+ - README
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ requirements: []
52
+
53
+ dependencies:
54
+ - !ruby/object:Gem::Dependency
55
+ name: rails
56
+ version_requirement:
57
+ version_requirements: !ruby/object:Gem::Version::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.0
62
+ version:
63
+ - !ruby/object:Gem::Dependency
64
+ name: builder
65
+ version_requirement:
66
+ version_requirements: !ruby/object:Gem::Version::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 1.2.4
71
+ version: