databasedotcom-ejholmes 1.3.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.
@@ -0,0 +1,369 @@
1
+ module Databasedotcom
2
+ module Sobject
3
+ # Parent class of dynamically created sobject types. Interacts with Force.com through a Client object that is passed in during materialization.
4
+ class Sobject
5
+ cattr_accessor :client
6
+ extend ActiveModel::Naming if defined?(ActiveModel::Naming)
7
+
8
+ def ==(other)
9
+ return false unless other.is_a?(self.class)
10
+ self.Id == other.Id
11
+ end
12
+
13
+ # Returns a new Sobject. The default values for all attributes are set based on its description.
14
+ def initialize(attrs = {})
15
+ super()
16
+ self.class.description["fields"].each do |field|
17
+ self.send("#{field["name"]}=", field["defaultValueFormula"])
18
+ end
19
+ self.attributes=(attrs)
20
+ end
21
+
22
+ # Returns a hash representing the state of this object
23
+ def attributes
24
+ self.class.attributes.inject({}) do |hash, attr|
25
+ hash[attr] = self.send(attr.to_sym)
26
+ hash
27
+ end
28
+ end
29
+
30
+ # Set attributes of this object, from a hash, in bulk
31
+ def attributes=(attrs)
32
+ attrs.each do |key, value|
33
+ self.send("#{key}=", value)
34
+ end
35
+ end
36
+
37
+ # Returns true if the object has been persisted in the Force.com database.
38
+ def persisted?
39
+ !self.Id.nil?
40
+ end
41
+
42
+ # Returns true if this record has not been persisted in the Force.com database.
43
+ def new_record?
44
+ !self.persisted?
45
+ end
46
+
47
+ # Returns self.
48
+ def to_model
49
+ self
50
+ end
51
+
52
+ # Returns a unique object id for self.
53
+ def to_key
54
+ [object_id]
55
+ end
56
+
57
+ # Returns the Force.com Id for this instance.
58
+ def to_param
59
+ self.Id
60
+ end
61
+
62
+ # Updates the corresponding record on Force.com by setting the attribute +attr_name+ to +attr_value+.
63
+ #
64
+ # client.materialize("Car")
65
+ # c = Car.new
66
+ # c.update_attribute("Color", "Blue")
67
+ def update_attribute(attr_name, attr_value)
68
+ update_attributes(attr_name => attr_value)
69
+ end
70
+
71
+ # Updates the corresponding record on Force.com with the attributes specified by the +new_attrs+ hash.
72
+ #
73
+ # client.materialize("Car")
74
+ # c = Car.new
75
+ # c.update_attributes {"Color" => "Blue", "Year" => "2012"}
76
+ def update_attributes(new_attrs)
77
+ if self.client.update(self.class, self.Id, new_attrs)
78
+ new_attrs = new_attrs.is_a?(Hash) ? new_attrs : JSON.parse(new_attrs)
79
+ new_attrs.each do |attr, value|
80
+ self.send("#{attr}=", value)
81
+ end
82
+ end
83
+ self
84
+ end
85
+
86
+ # Updates the corresponding record on Force.com with the attributes of self.
87
+ #
88
+ # client.materialize("Car")
89
+ # c = Car.find_by_Color("Yellow")
90
+ # c.Color = "Green"
91
+ # c.save
92
+ #
93
+ # _options_ can contain the following keys:
94
+ #
95
+ # exclusions # an array of field names (case sensitive) to exclude from save
96
+ def save(options={})
97
+ attr_hash = {}
98
+ selection_attr = self.Id.nil? ? "createable" : "updateable"
99
+ self.class.description["fields"].select { |f| f[selection_attr] }.collect { |f| f["name"] }.each { |attr| attr_hash[attr] = self.send(attr) }
100
+
101
+ # allow fields to be removed on a case by case basis as some data is not allowed to be saved
102
+ # (e.g. Name field on Account with record type of Person Account) despite the API listing
103
+ # some fields as editable
104
+ if options[:exclusions] and options[:exclusions].respond_to?(:include?) then
105
+ attr_hash.delete_if { |key, value| options[:exclusions].include?(key.to_s) }
106
+ end
107
+
108
+ if self.Id.nil?
109
+ self.Id = self.client.create(self.class, attr_hash).Id
110
+ else
111
+ self.client.update(self.class, self.Id, attr_hash)
112
+ end
113
+ end
114
+
115
+ # Deletes the corresponding record from the Force.com database. Returns self.
116
+ #
117
+ # client.materialize("Car")
118
+ # c = Car.find_by_Color("Yellow")
119
+ # c.delete
120
+ def delete
121
+ if self.client.delete(self.class, self.Id)
122
+ self
123
+ end
124
+ end
125
+
126
+ # Reloads the record from the Force.com database. Returns self.
127
+ #
128
+ # client.materialize("Car")
129
+ # c = Car.find_by_Color("Yellow")
130
+ # c.reload
131
+ def reload
132
+ self.attributes = self.class.find(self.Id).attributes
133
+ self
134
+ end
135
+
136
+ # Get a named attribute on this object
137
+ def [](attr_name)
138
+ self.send(attr_name) rescue nil
139
+ end
140
+
141
+ # Set a named attribute on this object
142
+ def []=(attr_name, value)
143
+ raise ArgumentError.new("No attribute named #{attr_name}") unless self.class.attributes.include?(attr_name)
144
+ self.send("#{attr_name}=", value)
145
+ end
146
+
147
+ # Returns an Array of attribute names that this Sobject has.
148
+ #
149
+ # client.materialize("Car")
150
+ # Car.attributes #=> ["Id", "Name", "Color", "Year"]
151
+ def self.attributes
152
+ self.description["fields"].collect { |f| [f["name"], f["relationshipName"]] }.flatten.compact
153
+ end
154
+
155
+ # Materializes the dynamically created Sobject class by adding all attribute accessors for each field as described in the description of the object on Force.com
156
+ def self.materialize(sobject_name)
157
+ self.cattr_accessor :description
158
+ self.cattr_accessor :type_map
159
+ self.cattr_accessor :sobject_name
160
+
161
+ self.sobject_name = sobject_name
162
+ self.description = self.client.describe_sobject(self.sobject_name)
163
+ self.type_map = {}
164
+
165
+ self.description["fields"].each do |field|
166
+
167
+ # Register normal fields
168
+ name = field["name"]
169
+ register_field( field["name"], field )
170
+
171
+ # Register relationship fields.
172
+ if( field["type"] == "reference" and field["relationshipName"] )
173
+ register_field( field["relationshipName"], field )
174
+ end
175
+
176
+ end
177
+ end
178
+
179
+ # Returns the Force.com type of the attribute +attr_name+. Raises ArgumentError if attribute does not exist.
180
+ #
181
+ # client.materialize("Car")
182
+ # Car.field_type("Color") #=> "string"
183
+ def self.field_type(attr_name)
184
+ self.type_map_attr(attr_name, :type)
185
+ end
186
+
187
+ # Returns the label for the attribute +attr_name+. Raises ArgumentError if attribute does not exist.
188
+ def self.label_for(attr_name)
189
+ self.type_map_attr(attr_name, :label)
190
+ end
191
+
192
+ # Returns the possible picklist options for the attribute +attr_name+. If +attr_name+ is not of type picklist or multipicklist, [] is returned. Raises ArgumentError if attribute does not exist.
193
+ def self.picklist_values(attr_name)
194
+ self.type_map_attr(attr_name, :picklist_values)
195
+ end
196
+
197
+ # Returns true if the attribute +attr_name+ can be updated. Raises ArgumentError if attribute does not exist.
198
+ def self.updateable?(attr_name)
199
+ self.type_map_attr(attr_name, :updateable?)
200
+ end
201
+
202
+ # Returns true if the attribute +attr_name+ can be created. Raises ArgumentError if attribute does not exist.
203
+ def self.createable?(attr_name)
204
+ self.type_map_attr(attr_name, :createable?)
205
+ end
206
+
207
+ # Delegates to Client.find with arguments +record_id+ and self
208
+ #
209
+ # client.materialize("Car")
210
+ # Car.find("rid") #=> #<Car @Id="rid", ...>
211
+ def self.find(record_id)
212
+ self.client.find(self, record_id)
213
+ end
214
+
215
+ # Returns all records of type self as instances.
216
+ #
217
+ # client.materialize("Car")
218
+ # Car.all #=> [#<Car @Id="1", ...>, #<Car @Id="2", ...>, #<Car @Id="3", ...>, ...]
219
+ def self.all
220
+ self.client.query("SELECT #{self.field_list} FROM #{self.sobject_name}")
221
+ end
222
+
223
+ # Returns a collection of instances of self that match the conditional +where_expr+, which is the WHERE part of a SOQL query.
224
+ #
225
+ # client.materialize("Car")
226
+ # Car.query("Color = 'Blue'") #=> [#<Car @Id="1", @Color="Blue", ...>, #<Car @Id="5", @Color="Blue", ...>, ...]
227
+ def self.query(where_expr)
228
+ self.client.query("SELECT #{self.field_list} FROM #{self.sobject_name} WHERE #{where_expr}")
229
+ end
230
+
231
+ # Delegates to Client.search
232
+ def self.search(sosl_expr)
233
+ self.client.search(sosl_expr)
234
+ end
235
+
236
+ # Find the first record. If the +where_expr+ argument is present, it must be the WHERE part of a SOQL query
237
+ def self.first(where_expr=nil)
238
+ where = where_expr ? "WHERE #{where_expr} " : ""
239
+ self.client.query("SELECT #{self.field_list} FROM #{self.sobject_name} #{where}ORDER BY Id ASC LIMIT 1").first
240
+ end
241
+
242
+ # Find the last record. If the +where_expr+ argument is present, it must be the WHERE part of a SOQL query
243
+ def self.last(where_expr=nil)
244
+ where = where_expr ? "WHERE #{where_expr} " : ""
245
+ self.client.query("SELECT #{self.field_list} FROM #{self.sobject_name} #{where}ORDER BY Id DESC LIMIT 1").first
246
+ end
247
+
248
+ #Delegates to Client.upsert with arguments self, +field+, +values+, and +attrs+
249
+ def self.upsert(field, value, attrs)
250
+ self.client.upsert(self.sobject_name, field, value, attrs)
251
+ end
252
+
253
+ # Delegates to Client.delete with arguments +record_id+ and self
254
+ def self.delete(record_id)
255
+ self.client.delete(self.sobject_name, record_id)
256
+ end
257
+
258
+ # Get the total number of records
259
+ def self.count
260
+ self.client.query("SELECT COUNT() FROM #{self.sobject_name}").total_size
261
+ end
262
+
263
+ # Sobject objects support dynamic finders similar to ActiveRecord.
264
+ #
265
+ # client.materialize("Car")
266
+ # Car.find_by_Color("Blue")
267
+ # Car.find_all_by_Year("2011")
268
+ # Car.find_by_Color_and_Year("Blue", "2011")
269
+ # Car.find_or_create_by_Year("2011")
270
+ # Car.find_or_initialize_by_Name("Foo")
271
+ def self.method_missing(method_name, *args, &block)
272
+ if method_name.to_s =~ /^find_(or_create_|or_initialize_)?by_(.+)$/ || method_name.to_s =~ /^find_(all_)by_(.+)$/
273
+ named_attrs = $2.split('_and_')
274
+ attrs_and_values_for_find = {}
275
+ hash_args = args.length == 1 && args[0].is_a?(Hash)
276
+ attrs_and_values_for_write = hash_args ? args[0] : {}
277
+
278
+ named_attrs.each_with_index do |attr, index|
279
+ value = hash_args ? args[0][attr] : args[index]
280
+ attrs_and_values_for_find[attr] = value
281
+ attrs_and_values_for_write[attr] = value unless hash_args
282
+ end
283
+
284
+ limit_clause = method_name.to_s.include?('_all_by_') ? "" : " LIMIT 1"
285
+
286
+ results = self.client.query("SELECT #{self.field_list} FROM #{self.sobject_name} WHERE #{soql_conditions_for(attrs_and_values_for_find)}#{limit_clause}")
287
+ results = limit_clause == "" ? results : results.first rescue nil
288
+
289
+ if results.nil?
290
+ if method_name.to_s =~ /^find_or_create_by_(.+)$/
291
+ results = self.client.create(self, attrs_and_values_for_write)
292
+ elsif method_name.to_s =~ /^find_or_initialize_by_(.+)$/
293
+ results = self.new
294
+ attrs_and_values_for_write.each { |attr, val| results.send("#{attr}=", val) }
295
+ end
296
+ end
297
+
298
+ results
299
+ else
300
+ super
301
+ end
302
+ end
303
+
304
+ # Delegates to Client.create with arguments +object_attributes+ and self
305
+ def self.create(object_attributes)
306
+ self.client.create(self, object_attributes)
307
+ end
308
+
309
+ # Coerce values submitted from a Rails form to the values expected by the database
310
+ # returns a new hash with updated values
311
+ def self.coerce_params(params)
312
+ params.each do |attr, value|
313
+ case self.field_type(attr)
314
+ when "boolean"
315
+ params[attr] = value.is_a?(String) ? value.to_i != 0 : value
316
+ when "currency", "percent", "double"
317
+ params[attr] = value.to_f
318
+ when "date"
319
+ params[attr] = Date.parse(value) rescue Date.today
320
+ when "datetime"
321
+ params[attr] = DateTime.parse(value) rescue DateTime.now
322
+ end
323
+ end
324
+ end
325
+
326
+ private
327
+
328
+ def self.register_field( name, field )
329
+ public
330
+ attr_accessor name.to_sym
331
+ private
332
+ self.type_map[name] = {
333
+ :type => field["type"],
334
+ :label => field["label"],
335
+ :picklist_values => field["picklistValues"],
336
+ :updateable? => field["updateable"],
337
+ :createable? => field["createable"]
338
+ }
339
+ end
340
+
341
+ def self.field_list
342
+ self.description['fields'].collect { |f| f['name'] }.join(',')
343
+ end
344
+
345
+ def self.type_map_attr(attr_name, key)
346
+ raise ArgumentError.new("No attribute named #{attr_name}") unless self.type_map.has_key?(attr_name)
347
+ self.type_map[attr_name][key]
348
+ end
349
+
350
+ def self.soql_conditions_for(params)
351
+ params.inject([]) do |arr, av|
352
+ case av[1]
353
+ when String
354
+ value_str = "'#{av[1].gsub("'", "\\\\'")}'"
355
+ when DateTime, Time
356
+ value_str = av[1].strftime(RUBY_VERSION.match(/^1.8/) ? "%Y-%m-%dT%H:%M:%S.000%z" : "%Y-%m-%dT%H:%M:%S.%L%z").insert(-3, ":")
357
+ when Date
358
+ value_str = av[1].strftime("%Y-%m-%d")
359
+ else
360
+ value_str = av[1].to_s
361
+ end
362
+
363
+ arr << "#{av[0]} = #{value_str}"
364
+ arr
365
+ end.join(" AND ")
366
+ end
367
+ end
368
+ end
369
+ end
@@ -0,0 +1,2 @@
1
+ require 'databasedotcom/sobject/sobject'
2
+
@@ -0,0 +1,3 @@
1
+ module Databasedotcom
2
+ VERSION = "1.3.2"
3
+ end
@@ -0,0 +1,7 @@
1
+ require 'databasedotcom/version'
2
+ require 'databasedotcom/core_extensions'
3
+ require 'databasedotcom/client'
4
+ require 'databasedotcom/sales_force_error'
5
+ require 'databasedotcom/collection'
6
+ require 'databasedotcom/sobject'
7
+ require 'databasedotcom/chatter'
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: databasedotcom-ejholmes
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.3.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Glenn Gillen, Danny Burkes, Richard Zhao & Eric Holmes
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: multipart-post
16
+ requirement: &70219662377740 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.1'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70219662377740
25
+ - !ruby/object:Gem::Dependency
26
+ name: json
27
+ requirement: &70219662377320 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70219662377320
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &70219662376780 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '2.6'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70219662376780
47
+ - !ruby/object:Gem::Dependency
48
+ name: webmock
49
+ requirement: &70219662376360 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70219662376360
58
+ - !ruby/object:Gem::Dependency
59
+ name: rake
60
+ requirement: &70219662375820 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - =
64
+ - !ruby/object:Gem::Version
65
+ version: 0.8.6
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70219662375820
69
+ description: A ruby wrapper for the Force.com REST API
70
+ email:
71
+ - eric@ejholmes.net
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - README.rdoc
77
+ - MIT-LICENSE
78
+ - lib/databasedotcom/chatter/comment.rb
79
+ - lib/databasedotcom/chatter/conversation.rb
80
+ - lib/databasedotcom/chatter/feed.rb
81
+ - lib/databasedotcom/chatter/feed_item.rb
82
+ - lib/databasedotcom/chatter/feeds.rb
83
+ - lib/databasedotcom/chatter/filter_feed.rb
84
+ - lib/databasedotcom/chatter/group.rb
85
+ - lib/databasedotcom/chatter/group_membership.rb
86
+ - lib/databasedotcom/chatter/like.rb
87
+ - lib/databasedotcom/chatter/message.rb
88
+ - lib/databasedotcom/chatter/photo_methods.rb
89
+ - lib/databasedotcom/chatter/record.rb
90
+ - lib/databasedotcom/chatter/subscription.rb
91
+ - lib/databasedotcom/chatter/user.rb
92
+ - lib/databasedotcom/chatter.rb
93
+ - lib/databasedotcom/client.rb
94
+ - lib/databasedotcom/collection.rb
95
+ - lib/databasedotcom/core_extensions/class_extensions.rb
96
+ - lib/databasedotcom/core_extensions/hash_extensions.rb
97
+ - lib/databasedotcom/core_extensions/string_extensions.rb
98
+ - lib/databasedotcom/core_extensions.rb
99
+ - lib/databasedotcom/sales_force_error.rb
100
+ - lib/databasedotcom/sobject/sobject.rb
101
+ - lib/databasedotcom/sobject.rb
102
+ - lib/databasedotcom/version.rb
103
+ - lib/databasedotcom.rb
104
+ homepage: https://github.com/databasedotcom
105
+ licenses: []
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project: databasedotcom-ejholmes
124
+ rubygems_version: 1.8.11
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: A ruby wrapper for the Force.com REST API
128
+ test_files: []
129
+ has_rdoc: