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
wycats (author)
Wed Jun 18 02:24:08 -0700 2008
commit  65ae4746caae18d41c9fd7cf9aa88ffebef34818
tree    9970d1aa9b3b8de760df734e39b70f19a4d996f1
parent  f81e43f0e68f07f8d0161f0a234913b7df24b9f1
dm-adapters / salesforce / lib / dm-salesforce.rb
100644 223 lines (185 sloc) 7.497 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
$:.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.to_s.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)
        Collection.new(query) do |set|
          read(query, set, true)
        end
      end
      
      def read_one(query)
        read(query, query.model, false)
      end
      
      private
      def read(query, set, arr = true)
        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
 
        return nil unless results.records
        
        # This is the core logic that handles the difference between all/first
        (results.records || []).each do |result|
          props = props_from_result(properties_with_indexes, result, repository)
          arr ? set.load(props) : (break set.load(props, query))
        end
        
      end
      
      def props_from_result(properties_with_indexes, result, repo)
        properties_with_indexes.inject([]) do |accum, (prop, idx)|
          accum[idx] = result.send(soap_attr(prop, repo))
          accum
        end
      end
      
      public
      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