GitHub Sale: sign up for any paid plan this week and pay nothing until January 1, 2009!  [ hide ]

public
Description: DataMapper Adapters
Homepage: http://www.yehudakatz.com
Clone URL: git://github.com/wycats/dm-adapters.git
commit  91df8fd2f8b0f4ca57becb6cc4361bdda1917a3f
tree    516229ac3ca0ba9b3490f62e62ac77bec5f58a72
parent  c28d2f2e001d7df2ae087edbb988c38a5b9ecd20
dm-adapters / salesforce / lib / dm-salesforce.rb
100644 242 lines (197 sloc) 8.391 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
$:.push File.expand_path(File.dirname(__FILE__))
require "fileutils"
 
module SalesforceAPI
  class CreateError < StandardError; end
  class ReadError < StandardError; end
  class DeleteError < StandardError; end
  class UpdateError < StandardError; end
end
 
module DataMapper
  module Adapters
    
    module SQL
      
      class << self
      
        def from_condition(condition, repository)
          op, prop, value = condition
          operator = case op
            when String then operator
            when :eql, :in then equality_operator(value)
            when :not then inequality_operator(value)
            when :like then "LIKE #{quote_value(value)}"
            when :gt then "> #{quote_value(value)}"
            when :gte then ">= #{quote_value(value)}"
            when :lt then "< #{quote_value(value)}"
            when :lte then "<= #{quote_value(value)}"
            else raise "CAN HAS CRASH?"
          end
          case prop
          when Property
            "#{prop.field} #{operator}"
          when Query::Path
            names = prop.relationships.map {|r| r.parent_model.storage_name(repository.name)}.join(".")
            names << ".#{prop.field}"
            "#{names} #{operator}"
          end
        end
        
        def order(direction)
          "#{direction.property.field} #{direction.direction.upcase}"
        end
        
        private
        def equality_operator(value)
          case value
          when Array then "IN #{quote_value(value)}"
          else "= #{quote_value(value)}"
          end
        end
        
        def inequality_operator(value)
          case value
          when Array then "NOT IN #{quote_value(value)}"
          else "!= #{quote_value(value)}"
          end
        end
        
        def quote_value(value)
          case value
          when Array then "(#{value.map {|v| quote_value(v)}.join(", ")})"
          when NilClass then "NULL"
          when String then "'#{value.gsub(/'/, "\\'").gsub(/\\/, %{\\\\})}'"
else "#{value}"
end
end
end
end
class SalesforceAdapter < AbstractAdapter
def initialize(name, uri_or_options)
super
@resource_naming_convention = proc {|value| value.split("::").last}
@field_naming_convention = proc {|value| Extlib::Inflection.camelize(value)}
connect!
end
def connect!
if !@uri.host.empty? && !@uri.path.empty?
path = File.join(Dir.pwd, @uri.host, @uri.path)
elsif !@uri.host.empty?
path = File.join(Dir.pwd, @uri.host)
else
path = @uri.path
end
 
basename = File.basename(path)
 
# Generate Ruby files and move them into .salesforce for future use
unless File.directory? "#{ENV["HOME"]}/.salesforce/#{basename}"
          old_args = ARGV.dup
          path = path =~ %r{^/} ? path : File.expand_path(path)
          ARGV.replace %W(--wsdl #{path} --module_path SalesforceAPI --classdef SalesforceAPI --type client)
          p ARGV
          load `which wsdl2ruby.rb`.chomp
          FileUtils.mkdir_p "#{ENV["HOME"]}/.salesforce/#{basename}"
          FileUtils.mv Dir["SalesforceAPI*"], "#{ENV["HOME"]}/.salesforce/#{basename}/"
          FileUtils.rm Dir["SforceServiceClient.rb"]
        end
        
        require "salesforce_api"
        @connection = SalesforceAPI::Connection.new(URI.unescape(@uri.user), @uri.password, "#{ENV["HOME"]}/.salesforce/#{basename}").driver
      end
      
      def read_many(query)
        repository = query.repository
        properties = query.fields
        properties_with_indexes = Hash[*properties.zip((0...properties.size).to_a).flatten]
        
        Collection.new(query) do |set|
        
          conditions = query.conditions.map {|c| SQL.from_condition(c, repository)}.compact.join(") AND (")
query_string = "SELECT #{query.fields.map {|f| f.field}.join(", ")} from #{query.model.storage_name(repository.name)}"
          query_string << " WHERE (#{conditions})" unless conditions.empty?
          query_string << " ORDER BY #{SQL.order(query.order[0])}" unless query.order.empty?
          query_string << " LIMIT #{query.limit}" if query.limit
 
          DataMapper.logger.debug query_string
        
          begin
            results = @connection.query(:queryString => query_string).result
          rescue SOAP::FaultError => e
            raise SalesforceAPI::ReadError, e.message
          end
          
          results = results.size > 0 ? results.records : []
        
          results.each do |result|
            props = properties_with_indexes.inject([]) do |accum, (prop, idx)|
              accum[idx] = result.send(soap_attr(prop, repository))
              accum
            end
            set.load(props)
          end
        
        end
      end
      
      def read_one(query)
        repository = query.repository
        properties = query.fields
        properties_with_indexes = Hash[*properties.zip((0...properties.size).to_a).flatten]
        
        conditions = query.conditions.map {|c| SQL.from_condition(c, repository)}.compact.join(") AND (")
      
        query_string = "SELECT #{query.fields.map {|f| f.field}.join(", ")} from #{query.model.storage_name(repository.name)}"
        query_string << " WHERE (#{conditions})" unless conditions.empty?
        query_string << " ORDER BY #{SQL.order(query.order[0])}" unless query.order.empty?
        query_string << " LIMIT #{query.limit}" if query.limit
 
        DataMapper.logger.debug query_string
      
        begin
          results = @connection.query(:queryString => query_string).result
        rescue SOAP::FaultError => e
          raise SalesforceAPI::ReadError, e.message
        end
        
        results = results.size > 0 ? results.records : []
      
        result = results.first
        return nil unless result
        
        props = properties_with_indexes.inject([]) do |accum, (prop, idx)|
          accum[idx] = result.send(soap_attr(prop, repository))
          accum
        end
        return query.model.load(props, query)
        
      end
      
      def update(attributes, query)
        arr = if key_condition = query.conditions.find {|op,prop,val| prop.key?}
          [ make_sforce_obj(query, attributes, key_condition.last) ]
        else
          read_many(query).map do |obj|
            obj = make_salesforce_obj(query, attributes, x.key)
          end
        end
        results = @connection.update(arr)
        results.select {|r| r.success == true}.size
      end
      
      def create(resources)
        map = {}
        arr = resources.map do |resource|
          obj = make_sforce_obj(resource, resource.dirty_attributes, nil)
        end
        
        @connection.create(arr).each_with_index do |result, i|
          if result.success
            resource = resources[i]
            key = resource.class.key(repository.name).first
            resource.instance_variable_set(key.instance_variable_name, result.id)
          else
            raise SalesforceAPI::CreateError,
              results.errors.map {|e| "#{e.statusCode}: #{e.message}"}.join(", ")
          end
        end.size
        
      end
      
      def delete(query)
        keys = if key_condition = query.conditions.find {|op,prop,val| prop.key?}
          [key_condition.last]
        else
          query.read_many.map {|r| r.key}
        end
        
        results = @connection.delete(keys)
        
        if results.all? {|r| r.success}
          results.size
        else
          raise SalesforceAPI::DeleteError,
            results.find {|r| r.success == false}.errors.map {|e| "#{e.statusCode}: #{e.message}"}.join(", ")
        end
      end
      
      private
      def make_sforce_obj(query, attrs, key = nil)
        klass = SalesforceAPI.const_get(query.model.storage_name(query.repository.name))
        obj = klass.new
        obj.id = query.conditions.find {|op,prop,val| prop.key?}.last if key
        attrs.each do |prop,val|
          obj.send("#{soap_attr(prop, query.repository)}=", val)
        end
        obj
      end
      
      def soap_attr(prop, repository)
        prop.field(repository.name).gsub(/^[A-Z]/) {|m| m.downcase}
      end
      
    end
    
  end
end