activesalesforce 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: