databasedotcom 1.0.1

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,37 @@
1
+ module Databasedotcom
2
+ # A collection of Sobject or Record objects that holds a single page of results, and understands how to
3
+ # retrieve the next page, if any. Inherits from Array, thus, behaves as an Enumerable.
4
+
5
+ class Collection < Array
6
+ attr_reader :total_size, :next_page_url, :previous_page_url, :current_page_url, :client
7
+
8
+ # Creates a paginatable collection. You should never need to call this.
9
+ def initialize(client, total_size, next_page_url=nil, previous_page_url=nil, current_page_url=nil) #:nodoc:
10
+ @client = client
11
+ @total_size = total_size
12
+ @next_page_url = next_page_url
13
+ @previous_page_url = previous_page_url
14
+ @current_page_url = current_page_url
15
+ end
16
+
17
+ # Does this collection have a next page?
18
+ def next_page?
19
+ !!self.next_page_url
20
+ end
21
+
22
+ # Retrieve the next page of this collection. Returns the new collection, which is an empty collection if no next page exists
23
+ def next_page
24
+ self.next_page? ? @client.next_page(@next_page_url) : Databasedotcom::Collection.new(self.client, 0)
25
+ end
26
+
27
+ # Does this collection have a previous page?
28
+ def previous_page?
29
+ !!self.previous_page_url
30
+ end
31
+
32
+ # Retrieve the previous page of this collection. Returns the new collection, which is an empty collection if no previous page exists
33
+ def previous_page
34
+ self.previous_page? ? @client.previous_page(@previous_page_url) : Databasedotcom::Collection.new(self.client, 0)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ require 'databasedotcom/core_extensions/class_extensions'
2
+ require 'databasedotcom/core_extensions/string_extensions'
3
+ require 'databasedotcom/core_extensions/hash_extensions'
@@ -0,0 +1,41 @@
1
+ # This extends Class to be able to use +cattr_accessor+ if active_support is not being used.
2
+ class Class
3
+ unless respond_to?(:cattr_reader)
4
+ def cattr_reader(sym)
5
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
6
+ unless defined? @@#{sym}
7
+ @@#{sym} = nil
8
+ end
9
+
10
+ def self.#{sym}
11
+ @@#{sym}
12
+ end
13
+
14
+ def #{sym}
15
+ @@#{sym}
16
+ end
17
+ EOS
18
+ end
19
+
20
+ def cattr_writer(sym)
21
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
22
+ unless defined? @@#{sym}
23
+ @@#{sym} = nil
24
+ end
25
+
26
+ def self.#{sym}=(obj)
27
+ @@#{sym} = obj
28
+ end
29
+
30
+ def #{sym}=(obj)
31
+ @@#{sym} = obj
32
+ end
33
+ EOS
34
+ end
35
+
36
+ def cattr_accessor(*syms, &blk)
37
+ cattr_reader(*syms)
38
+ cattr_writer(*syms, &blk)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,8 @@
1
+ class Hash
2
+ def symbolize_keys!
3
+ keys.each do |key|
4
+ self[(key.to_sym rescue key) || key] = delete(key)
5
+ end
6
+ self
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # This extends String to add the +resourcerize+ method.
2
+ class String
3
+
4
+ # Dasherizes and downcases a camelcased string. Used for Feed types.
5
+ def resourcerize
6
+ self.gsub(/([a-z])([A-Z])/, '\1-\2').downcase
7
+ end
8
+ end
@@ -0,0 +1,26 @@
1
+ module Databasedotcom
2
+ # An exception raised when any non successful request is made to Force.com.
3
+ class SalesForceError < StandardError
4
+ # the Net::HTTPResponse from the API call
5
+ attr_accessor :response
6
+ # the +errorCode+ from the server response body
7
+ attr_accessor :error_code
8
+
9
+ def initialize(response)
10
+ self.response = response
11
+ parsed_body = JSON.parse(response.body) rescue nil
12
+ if parsed_body
13
+ if parsed_body.is_a?(Array)
14
+ message = parsed_body[0]["message"]
15
+ self.error_code = parsed_body[0]["errorCode"]
16
+ else
17
+ message = parsed_body["error_description"]
18
+ self.error_code = parsed_body["error"]
19
+ end
20
+ else
21
+ message = response.body
22
+ end
23
+ super(message)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,2 @@
1
+ require 'databasedotcom/sobject/sobject'
2
+
@@ -0,0 +1,291 @@
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
+ # Returns a new Sobject. The default values for all attributes are set based on its description.
9
+ def initialize(attrs = {})
10
+ super
11
+ self.class.description["fields"].each do |field|
12
+ self.send("#{field["name"]}=", field["defaultValueFormula"])
13
+ end
14
+ self.attributes=(attrs)
15
+ end
16
+
17
+ def attributes=(attrs)
18
+ attrs.each do |key, value|
19
+ self.send("#{key}=", value)
20
+ end
21
+ end
22
+
23
+ # Returns true if the object has been persisted in the Force.com database.
24
+ def persisted?
25
+ !self.Id.nil?
26
+ end
27
+
28
+ # Returns true if this record has not been persisted in the Force.com database.
29
+ def new_record?
30
+ !self.persisted?
31
+ end
32
+
33
+ # Returns self.
34
+ def to_model
35
+ self
36
+ end
37
+
38
+ # Returns a unique object id for self.
39
+ def to_key
40
+ [object_id]
41
+ end
42
+
43
+ # Returns the Force.com Id for this instance.
44
+ def to_param
45
+ self.Id
46
+ end
47
+
48
+ # Updates the corresponding record on Force.com by setting the attribute +attr_name+ to +attr_value+.
49
+ #
50
+ # client.materialize("Car")
51
+ # c = Car.new
52
+ # c.update_attribute("Color", "Blue")
53
+ def update_attribute(attr_name, attr_value)
54
+ update_attributes(attr_name => attr_value)
55
+ end
56
+
57
+ # Updates the corresponding record on Force.com with the attributes specified by the +new_attrs+ hash.
58
+ #
59
+ # client.materialize("Car")
60
+ # c = Car.new
61
+ # c.update_attributes {"Color" => "Blue", "Year" => "2012"}
62
+ def update_attributes(new_attrs)
63
+ if self.client.update(self.class, self.Id, new_attrs)
64
+ new_attrs = new_attrs.is_a?(Hash) ? new_attrs : JSON.parse(new_attrs)
65
+ new_attrs.each do |attr, value|
66
+ self.send("#{attr}=", value)
67
+ end
68
+ end
69
+ self
70
+ end
71
+
72
+ # Updates the corresponding record on Force.com with the attributes of self.
73
+ #
74
+ # client.materialize("Car")
75
+ # c = Car.find_by_Color("Yellow")
76
+ # c.Color = "Green"
77
+ # c.save
78
+ def save
79
+ attr_hash = {}
80
+ self.class.description["fields"].select { |f| f["updateable"] }.collect { |f| f["name"] }.each { |attr| attr_hash[attr] = self.send(attr) }
81
+
82
+ if self.Id.nil?
83
+ self.client.create(self.class, attr_hash)
84
+ else
85
+ self.client.update(self.class, self.Id, attr_hash)
86
+ end
87
+ end
88
+
89
+ # Deletes the corresponding record from the Force.com database. Returns self.
90
+ #
91
+ # client.materialize("Car")
92
+ # c = Car.find_by_Color("Yellow")
93
+ # c.delete
94
+ def delete
95
+ if self.client.delete(self.class, self.Id)
96
+ self
97
+ end
98
+ end
99
+
100
+ # Reloads the record from the Force.com database. Returns the reloaded record.
101
+ #
102
+ # client.materialize("Car")
103
+ # c = Car.find_by_Color("Yellow")
104
+ # c.reload
105
+ def reload
106
+ self.class.find(self.Id)
107
+ end
108
+
109
+ def [](attr_name)
110
+ self.send(attr_name) rescue nil
111
+ end
112
+
113
+ def []=(attr_name, value)
114
+ raise ArgumentError.new("No attribute named #{attr_name}") unless self.class.attributes.include?(attr_name)
115
+ self.send("#{attr_name}=", value)
116
+ end
117
+
118
+ # Returns an Array of attribute names that this Sobject has.
119
+ #
120
+ # client.materialize("Car")
121
+ # Car.attributes #=> ["Id", "Name", "Color", "Year"]
122
+ def self.attributes
123
+ self.description["fields"].collect { |f| f["name"] }
124
+ end
125
+
126
+ # 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
127
+ def self.materialize(sobject_name)
128
+ self.cattr_accessor :description
129
+ self.cattr_accessor :type_map
130
+ self.cattr_accessor :sobject_name
131
+
132
+ self.sobject_name = sobject_name
133
+ self.description = self.client.describe_sobject(self.sobject_name)
134
+ self.type_map = {}
135
+ self.description["fields"].each do |field|
136
+ name = field["name"]
137
+ attr_accessor name.to_sym
138
+ self.type_map[name] = {:type => field["type"], :label => field["label"], :picklist_values => field["picklistValues"], :updateable? => field["updateable"]}
139
+ end
140
+ end
141
+
142
+ # Returns the Force.com type of the attribute +attr_name+. Raises ArgumentError if attribute does not exist.
143
+ #
144
+ # client.materialize("Car")
145
+ # Car.field_type("Color") #=> "string"
146
+ def self.field_type(attr_name)
147
+ self.type_map_attr(attr_name, :type)
148
+ end
149
+
150
+ # Returns the label for the attribute +attr_name+. Raises ArgumentError if attribute does not exist.
151
+ def self.label_for(attr_name)
152
+ self.type_map_attr(attr_name, :label)
153
+ end
154
+
155
+ # 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.
156
+ def self.picklist_values(attr_name)
157
+ self.type_map_attr(attr_name, :picklist_values)
158
+ end
159
+
160
+ # Returns true if the attribute +attr_name+ can be updated. Raises ArgumentError if attribute does not exist.
161
+ def self.updateable?(attr_name)
162
+ self.type_map_attr(attr_name, :updateable?)
163
+ end
164
+
165
+ # Delegates to Client.find with arguments +record_id+ and self
166
+ #
167
+ # client.materialize("Car")
168
+ # Car.find("rid") #=> #<Car @Id="rid", ...>
169
+ def self.find(record_id)
170
+ self.client.find(self, record_id)
171
+ end
172
+
173
+ # Returns all records of type self as instances.
174
+ #
175
+ # client.materialize("Car")
176
+ # Car.all #=> [#<Car @Id="1", ...>, #<Car @Id="2", ...>, #<Car @Id="3", ...>, ...]
177
+ def self.all
178
+ self.client.query("SELECT #{self.field_list} FROM #{self.sobject_name}")
179
+ end
180
+
181
+ # Returns a collection of instances of self that match the conditional +where_expr+, which is the WHERE part of a SOQL query.
182
+ #
183
+ # client.materialize("Car")
184
+ # Car.query("Color = 'Blue'") #=> [#<Car @Id="1", @Color="Blue", ...>, #<Car @Id="5", @Color="Blue", ...>, ...]
185
+ def self.query(where_expr)
186
+ self.client.query("SELECT #{self.field_list} FROM #{self.sobject_name} WHERE #{where_expr}")
187
+ end
188
+
189
+ # Delegates to Client.search
190
+ def self.search(sosl_expr)
191
+ self.client.search(sosl_expr)
192
+ end
193
+
194
+ # Find the first record. If the +where_expr+ argument is present, it must be the WHERE part of a SOQL query
195
+ def self.first(where_expr=nil)
196
+ where = where_expr ? "WHERE #{where_expr} " : ""
197
+ self.client.query("SELECT #{self.field_list} FROM #{self.sobject_name} #{where}ORDER BY Id ASC LIMIT 1").first
198
+ end
199
+
200
+ # Find the last record. If the +where_expr+ argument is present, it must be the WHERE part of a SOQL query
201
+ def self.last(where_expr=nil)
202
+ where = where_expr ? "WHERE #{where_expr} " : ""
203
+ self.client.query("SELECT #{self.field_list} FROM #{self.sobject_name} #{where}ORDER BY Id DESC LIMIT 1").first
204
+ end
205
+
206
+ #Delegates to Client.upsert with arguments self, +field+, +values+, and +attrs+
207
+ def self.upsert(field, value, attrs)
208
+ self.client.upsert(self.sobject_name, field, value, attrs)
209
+ end
210
+
211
+ # Delegates to Client.delete with arguments +record_id+ and self
212
+ def self.delete(record_id)
213
+ self.client.delete(self.sobject_name, record_id)
214
+ end
215
+
216
+ # Sobject objects support dynamic finders similar to ActiveRecord.
217
+ #
218
+ # client.materialize("Car")
219
+ # Car.find_by_Color("Blue")
220
+ # Car.find_all_by_Year("2011")
221
+ # Car.find_by_Color_and_Year("Blue", "2011")
222
+ # Car.find_or_create_by_Year("2011")
223
+ # Car.find_or_initialize_by_Name("Foo")
224
+ def self.method_missing(method_name, *args, &block)
225
+ if method_name.to_s =~ /^find_(or_create_|or_initialize_)?by_(.+)$/ || method_name.to_s =~ /^find_(all_)by_(.+)$/
226
+ named_attrs = $2.split('_and_')
227
+ attrs_and_values_for_find = []
228
+ hash_args = args.length == 1 && args[0].is_a?(Hash)
229
+ attrs_and_values_for_write = hash_args ? args[0] : {}
230
+
231
+ named_attrs.each_with_index do |attr, index|
232
+ value = hash_args ? args[0][attr] : args[index]
233
+ attrs_and_values_for_find << "#{attr} = '#{value}'"
234
+ attrs_and_values_for_write[attr] = value unless hash_args
235
+ end
236
+
237
+ limit_clause = method_name.to_s.include?('_all_by_') ? "" : " LIMIT 1"
238
+
239
+ results = self.client.query("SELECT #{self.field_list} FROM #{self.sobject_name} WHERE #{attrs_and_values_for_find.join(' AND ')}#{limit_clause}")
240
+ results = limit_clause == "" ? results : results.first rescue nil
241
+
242
+ if results.nil?
243
+ if method_name.to_s =~ /^find_or_create_by_(.+)$/
244
+ results = self.client.create(self, attrs_and_values_for_write)
245
+ elsif method_name.to_s =~ /^find_or_initialize_by_(.+)$/
246
+ results = self.new
247
+ attrs_and_values_for_write.each { |attr, val| results.send("#{attr}=", val) }
248
+ end
249
+ end
250
+
251
+ results
252
+ else
253
+ super
254
+ end
255
+ end
256
+
257
+ # Delegates to Client.create with arguments +object_attributes+ and self
258
+ def self.create(object_attributes)
259
+ self.client.create(self, object_attributes)
260
+ end
261
+
262
+ # Coerce values submitted from a Rails form to the values expected by the database
263
+ # returns a new hash with updated values
264
+ def self.coerce_params(params)
265
+ params.each do |attr, value|
266
+ case self.field_type(attr)
267
+ when "boolean"
268
+ params[attr] = value.is_a?(String) ? value.to_i != 0 : value
269
+ when "currency", "percent", "double"
270
+ params[attr] = value.to_f
271
+ when "date"
272
+ params[attr] = Date.parse(value) rescue Date.today
273
+ when "datetime"
274
+ params[attr] = DateTime.parse(value) rescue DateTime.now
275
+ end
276
+ end
277
+ end
278
+
279
+ private
280
+
281
+ def self.field_list
282
+ self.description['fields'].collect { |f| f['name'] }.join(',')
283
+ end
284
+
285
+ def self.type_map_attr(attr_name, key)
286
+ raise ArgumentError.new("No attribute named #{attr_name}") unless self.type_map.has_key?(attr_name)
287
+ self.type_map[attr_name][key]
288
+ end
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,3 @@
1
+ module Databasedotcom
2
+ VERSION = "1.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: databasedotcom
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 1.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Glenn Gillen, Danny Burkes & Richard Zhao
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-08-23 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: multipart-post
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 1.1.3
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - "="
34
+ - !ruby/object:Gem::Version
35
+ version: 2.5.0
36
+ type: :development
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: webmock
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: rake
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - "="
56
+ - !ruby/object:Gem::Version
57
+ version: 0.8.6
58
+ type: :development
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: ruby-debug19
62
+ prerelease: false
63
+ requirement: &id005 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ type: :development
70
+ version_requirements: *id005
71
+ description: A ruby wrapper for the Force.com REST API
72
+ email:
73
+ - me@glenngillen.com
74
+ executables: []
75
+
76
+ extensions: []
77
+
78
+ extra_rdoc_files: []
79
+
80
+ files:
81
+ - README.rdoc
82
+ - MIT-LICENSE
83
+ - lib/databasedotcom/chatter/comment.rb
84
+ - lib/databasedotcom/chatter/conversation.rb
85
+ - lib/databasedotcom/chatter/feed.rb
86
+ - lib/databasedotcom/chatter/feed_item.rb
87
+ - lib/databasedotcom/chatter/feeds.rb
88
+ - lib/databasedotcom/chatter/filter_feed.rb
89
+ - lib/databasedotcom/chatter/group.rb
90
+ - lib/databasedotcom/chatter/group_membership.rb
91
+ - lib/databasedotcom/chatter/like.rb
92
+ - lib/databasedotcom/chatter/message.rb
93
+ - lib/databasedotcom/chatter/photo_methods.rb
94
+ - lib/databasedotcom/chatter/record.rb
95
+ - lib/databasedotcom/chatter/subscription.rb
96
+ - lib/databasedotcom/chatter/user.rb
97
+ - lib/databasedotcom/chatter.rb
98
+ - lib/databasedotcom/client.rb
99
+ - lib/databasedotcom/collection.rb
100
+ - lib/databasedotcom/core_extensions/class_extensions.rb
101
+ - lib/databasedotcom/core_extensions/hash_extensions.rb
102
+ - lib/databasedotcom/core_extensions/string_extensions.rb
103
+ - lib/databasedotcom/core_extensions.rb
104
+ - lib/databasedotcom/sales_force_error.rb
105
+ - lib/databasedotcom/sobject/sobject.rb
106
+ - lib/databasedotcom/sobject.rb
107
+ - lib/databasedotcom/version.rb
108
+ - lib/databasedotcom.rb
109
+ has_rdoc: true
110
+ homepage: ""
111
+ licenses: []
112
+
113
+ post_install_message:
114
+ rdoc_options: []
115
+
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: "0"
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: "0"
130
+ requirements: []
131
+
132
+ rubyforge_project: databasedotcom
133
+ rubygems_version: 1.6.0
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: A ruby wrapper for the Force.com REST API
137
+ test_files: []
138
+