rubyzoho 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -2,16 +2,13 @@
2
2
  language: ruby
3
3
  rvm:
4
4
  - 1.9.3
5
- # - jruby
6
-
7
5
  matrix:
8
6
  allow-failures:
9
- - rvm: jruby
10
-
7
+ - rvm: jruby
11
8
  env:
12
9
  global:
13
- - secure: ! 'E2ZilvytrU4gPdPZna7EMJmrDHQPfjo7KLnY4geip1coSrmISaf3VhKlGMDY
10
+ - secure: ! 'Es86LRVpGFcxK8Ay0NzjtPgL1a7GQ25WRq4FgixHplZ8BbB2g/LGm2hFFnrD
14
11
 
15
- NubPbNdM6K98F98hQfMszGjt7YJz8y5dJrpwT4847ZL9k0FLN8XsjdxW7gH4
12
+ TQHNvTttEhrXDRXvYqv0ePTSb2I4Tn6xOzBZTIwZlmNDl6uj/5ZL9C9I3LQb
16
13
 
17
- Dqocd1v5HY2gK60quwr0YPza10bgw6XseYXlrMtVgpTFOYv5HTg='
14
+ 1MP0XFN0GfkOpw4DqVerdzDDoRzQw7AbTBttaOVyO3Nhiddtc3E='
data/Gemfile CHANGED
@@ -13,6 +13,7 @@ end
13
13
  group :development do
14
14
  gem 'bundler', '>= 1.2'
15
15
  gem 'cucumber', '>= 1.2.1'
16
+ gem 'holepicker'
16
17
  gem 'jeweler', '~> 1.8.4'
17
18
  gem 'relish', '>= 0.6'
18
19
  gem 'rdoc', '>= 3.12.1'
data/README.rdoc CHANGED
@@ -1,13 +1,13 @@
1
- {<img src="https://travis-ci.org/amalc/rubyzoho.png?branch=master" alt="Build Status" />}[https://travis-ci.org/amalc/rubyzoho]
1
+ = rubyzoho {<img src="https://travis-ci.org/amalc/rubyzoho.png?branch=master" alt="Build Status" />}[https://travis-ci.org/amalc/rubyzoho] {<img src="https://gemnasium.com/amalc/rubyzoho.png" alt="Dependency Status" />}[https://gemnasium.com/amalc/rubyzoho] {<img src="https://codeclimate.com/github/amalc/rubyzoho.png" />}[https://codeclimate.com/github/amalc/rubyzoho]
2
2
 
3
- {<img src="https://gemnasium.com/amalc/rubyzoho.png" alt="Dependency Status" />}[https://gemnasium.com/amalc/rubyzoho]
4
3
 
5
4
 
6
- = rubyzoho
7
5
 
8
6
  Abstracting Zoho's API into a set of Ruby classes, with reflection of Zoho's fields using a more familiar
9
7
  ActiveRecord lifecycle, but without ActiveRecord. Current focus is on Zoho CRM.
10
8
 
9
+ <b>Release notes are at the \end of this page.</b>
10
+
11
11
  == Install
12
12
  gem install rubyzoho
13
13
 
@@ -86,7 +86,7 @@ To get a list of all accounts:
86
86
  Or for all task subjects:
87
87
 
88
88
  t = RubyZoho::Crm::Task.all
89
- pp t.collect { |task| task.subject }
89
+ pp t.collect { |task| task.subject } # => ['Subject 1'], ['Subject 2'], ... ['Subject n']
90
90
 
91
91
  Or for all quotes:
92
92
 
@@ -109,7 +109,7 @@ works.
109
109
  To sort a result set:
110
110
  r = RubyZoho::Crm::Contact.all
111
111
  sorted = r.sort {|a, b| a.last_name <=> b.last_name }
112
- pp sorted.collect { |c| c.last_name }
112
+ pp sorted.collect { |c| c.last_name } # => ['Name 1', ['Name 2'], ... ['Name n']]
113
113
 
114
114
  To find by ID, note well, ID is a *string*:
115
115
  leads = RubyZoho::Crm::Lead.all
@@ -148,11 +148,28 @@ To update a record (<b>Note, that the attribute is :id</b>):
148
148
  :email => 'changed_email@domain.com'
149
149
  )
150
150
 
151
+ Custom fields are like any other field or method in Ruby:
152
+ a = RubyZoho::Crm::Account.find_by_account_name('Very Big Account')
153
+ pp a.custom_field # => 'Custom field content'
154
+
155
+ Or:
156
+ c = RubyZoho::Crm::Contact.new(
157
+ :first_name => 'First Name',
158
+ :last_name => 'Last Name',
159
+ :email => 'email@domain.com',
160
+ :account_name => a.first.account_name,
161
+ :accountid => a.first.accountid, # accountid instead of account_id because of Zoho's convention
162
+ :custom_field_2 => 'Custom text'
163
+ )
164
+ pp c.save # Reflects back the new Zoho record ID, and various create and modify times and users
165
+
166
+ To attach a file to a record (Tested for +Accounts+, +Contacts+, +Leads+, +Potentials+ and +Tasks+ only):
167
+ l = RubyZoho::Crm::Lead.find_by_email('email@domain.com')
168
+ l.attach_file(file_path, file_name) # Can only be attached to a pre-existing record
151
169
 
152
- Objects currently supported are:
170
+ Classes (Zoho modules) currently supported are:
153
171
  RubyZoho::Crm::Account
154
172
  RubyZoho::Crm::Contact
155
- RubyZoho::Crm::Event
156
173
  RubyZoho::Crm::Lead
157
174
  RubyZoho::Crm::Potential
158
175
  RubyZoho::Crm::Task
@@ -165,7 +182,7 @@ or where the Zoho API sends back an explicit error code which <b>isn't</b> in th
165
182
 
166
183
  ['4422', '5000']
167
184
 
168
- a standard Ruby +RuntimeError+ exception is raised with the Zoho API message.
185
+ a standard Ruby +RuntimeError+ exception is raised with the Zoho's API message.
169
186
 
170
187
  == Optimizations for Development and Testing
171
188
  Set <tt>config.cache_fields = true</tt> in the configuration block. This caches \module field
@@ -196,22 +213,20 @@ either the related \module's record id, which is stored with the Task. But no, c
196
213
  Please open an issue on GitHub. Or better yet, send in a pull request with the fix or enhancement!
197
214
 
198
215
  === Known Bugs or Issues
199
- 1. Caching is causing a problem with Quotes.
200
- 2. If you're having trouble with updating custom fields, be sure to check the permission of the user that created the custom field.
216
+ 1. If you're having trouble with updating custom fields, be sure to check the permission of the user that created the custom field.
201
217
 
202
218
  === Roadmap (Ranked)
203
- 1. Get related records using AR style syntax, e.g.
204
- pp a.contacts
205
- to get contacts associated with an account.
206
- 2. Support for multiple find fields.
207
- 3. AR style master/detail updates e.g. where +a+ is an account.
219
+ 1. AR style master/detail updates e.g. where +a+ is an account.
208
220
  a << RubyZoho::Crm::Contact.new(
209
221
  :last_name => 'Last Name',
210
222
  :first_name => 'First Name'
211
223
  )
224
+ 2. Get related records using AR style syntax, e.g.
225
+ pp a.contacts
226
+ to get contacts associated with an account.
227
+ 3. Support for multiple find fields.
212
228
 
213
229
  == Contributing to rubyzoho
214
-
215
230
  * Pull requests with unit tests or specs and a version branch are welcomed.
216
231
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
217
232
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
@@ -222,13 +237,35 @@ Please open an issue on GitHub. Or better yet, send in a pull request with the f
222
237
  * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
223
238
 
224
239
  ---
240
+ == Acknowledgements
241
+ Giant buckets of Gratitude to the giants, sung and unsung heroes who put together and support the Open Source movement,
242
+ Linux, Ruby and Rails. We stand on the shoulders of giants. Thank you.
225
243
 
226
- == Trademarks
244
+ == Credits
245
+ wcgiles@github (\@\waynecgiles) for professional and patient debugging and continued debugging of this gem.
227
246
 
247
+ == Trademarks
228
248
  Zoho, the Zoho suite and related applications are owned, trademarked and copyrighted by the Zoho Corporation Pvt. Ltd.
229
249
  This software is not associated in anyway with the Zoho Corporation Pvt. Ltd.
230
250
 
231
251
  == Copyright
252
+ Copyright (c) 2013 amalc (\@\amalc). Released under the MIT license. See LICENSE.txt for further details.
253
+
254
+ ---
255
+ = Releases
256
+ == Release Candidates
257
+
258
+ == Development
259
+ [0.1.8]
260
+ 1. Modules Supported: Accounts, Contacts, Leads, Potentials, Quotes, Tasks and Users
261
+ 2. ActiveRecord style syntax for Class.\new, Class.delete, Class.find, Class.update, Object.save
262
+ 3. << syntax for Account/Tasks on Master/Detail relationships
263
+ [0.1.7]
264
+ 1. Bug for attach_file method, respect the file_name parameter.
265
+ 2. \:\id is the primary key across all classes in addition to Zoho's existing convention.
266
+
267
+ == Released
268
+ [0.1.1 - 0.1.6] Alpha Releases
269
+ 1. Configuration block signature changed
270
+ [0.0.1 - 0.0.5] Alpha Releases
232
271
 
233
- Copyright (c) 2013 amalc. Released under the MIT license. See LICENSE.txt for
234
- further details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.6
1
+ 0.1.7
data/lib/ruby_zoho.rb CHANGED
@@ -49,26 +49,28 @@ module RubyZoho
49
49
  class << self
50
50
  attr_accessor :module_name
51
51
  end
52
+ @module_name = 'Crm'
52
53
 
53
54
  def initialize(object_attribute_hash = {})
54
- @fields = object_attribute_hash == {} ? RubyZoho.configuration.api.fields(RubyZoho::Crm.module_name) :
55
+ @fields = object_attribute_hash == {} ? RubyZoho.configuration.api.fields(self.class.module_name) :
55
56
  object_attribute_hash.keys
56
57
  RubyZoho::Crm.create_accessor(self.class, @fields)
58
+ RubyZoho::Crm.create_accessor(self.class, [:module_name])
59
+ public_send(:module_name=, self.class.module_name)
60
+ update_or_create_attrs(object_attribute_hash)
61
+ self
62
+ end
63
+
64
+ def update_or_create_attrs(object_attribute_hash)
57
65
  retry_counter = object_attribute_hash.count
58
66
  begin
59
67
  object_attribute_hash.map { |(k, v)| public_send("#{k}=", v) }
60
68
  rescue NoMethodError => e
61
69
  m = e.message.slice(/`(.*?)=/)
62
- unless m.nil?
63
- m.gsub!('`', '')
64
- m.gsub!('(', '')
65
- m.gsub!(')', '')
66
- RubyZoho::Crm.create_accessor(self.class, [m.chop])
67
- end
70
+ RubyZoho::Crm.create_accessor(self.class, [m.gsub(/[`()]*/, '').chop]) unless m.nil?
68
71
  retry_counter -= 1
69
72
  retry if retry_counter > 0
70
73
  end
71
- self
72
74
  end
73
75
 
74
76
  def attr_writers
@@ -103,6 +105,10 @@ module RubyZoho
103
105
  end
104
106
  end
105
107
 
108
+ def self.find(id)
109
+ self.find_by_id(id)
110
+ end
111
+
106
112
  def self.method_missing(meth, *args, &block)
107
113
  if meth.to_s =~ /^find_by_(.+)$/
108
114
  run_find_by_method($1, *args, &block)
@@ -111,29 +117,64 @@ module RubyZoho
111
117
  end
112
118
  end
113
119
 
120
+ def method_missing(meth, *args, &block)
121
+ if [:seid=, :semodule=].index(meth)
122
+ run_create_accessor(self.class, meth)
123
+ self.send(meth, args[0])
124
+ else
125
+ super
126
+ end
127
+ end
128
+
129
+ def self.method_is_module?(str_or_sym)
130
+ return nil if str_or_sym.nil?
131
+ s = str_or_sym.class == String ? str_or_sym : ApiUtils.symbol_to_string(str_or_sym)
132
+ possible_module = s[s.length - 1].downcase == 's' ? s : s + 's'
133
+ i = RubyZoho.configuration.crm_modules.index(possible_module.capitalize)
134
+ return str_or_sym unless i.nil?
135
+ nil
136
+ end
137
+
138
+ def run_create_accessor(klass, meth)
139
+ method = meth.to_s.chop.to_sym
140
+ RubyZoho::Crm.create_accessor(klass, [method])
141
+ nil
142
+ end
143
+
114
144
  def self.run_find_by_method(attrs, *args, &block)
115
145
  attrs = attrs.split('_and_')
116
146
  conditions = Array.new(args.size, '=')
117
147
  h = RubyZoho.configuration.api.find_records(
118
- Crm.module_name, ApiUtils.string_to_symbol(attrs[0]), conditions[0], args[0]
148
+ self.module_name, ApiUtils.string_to_symbol(attrs[0]), conditions[0], args[0]
119
149
  )
120
150
  return h.collect { |r| new(r) } unless h.nil?
121
151
  nil
122
152
  end
123
153
 
124
154
  def self.all #TODO Refactor into low level API
155
+ max_records = 200
125
156
  result = []
126
157
  i = 1
127
- begin
128
- batch = RubyZoho.configuration.api.some(Crm.module_name, i, 200)
129
- i += 200
158
+ batch = []
159
+ until batch.nil?
160
+ batch = RubyZoho.configuration.api.some(self.module_name, i, max_records)
130
161
  result.concat(batch) unless batch.nil?
131
- end while !batch.nil?
162
+ break if !batch.nil? && batch.count < max_records
163
+ i += max_records
164
+ end
132
165
  result.collect { |r| new(r) }
133
166
  end
134
167
 
168
+ def << object
169
+ object.semodule = self.module_name
170
+ object.seid = self.id
171
+ object.fields << :seid
172
+ object.fields << :semodule
173
+ save_object(object)
174
+ end
175
+
135
176
  def attach_file(file_path, file_name)
136
- RubyZoho.configuration.api.attach_file(Crm.module_name, self.send(primary_key), file_path)
177
+ RubyZoho.configuration.api.attach_file(self.class.module_name, self.send(primary_key), file_path, file_name)
137
178
  end
138
179
 
139
180
  def create(object_attribute_hash)
@@ -142,18 +183,26 @@ module RubyZoho
142
183
  end
143
184
 
144
185
  def self.delete(id)
145
- RubyZoho.configuration.api.delete_record(Crm.module_name, id)
186
+ RubyZoho.configuration.api.delete_record(self.module_name, id)
146
187
  end
147
188
 
148
189
  def primary_key
149
- RubyZoho.configuration.api.primary_key(Crm::module_name)
190
+ RubyZoho.configuration.api.primary_key(self.class.module_name)
150
191
  end
151
192
 
152
193
  def save
153
194
  h = {}
154
195
  @fields.each { |f| h.merge!({ f => eval("self.#{f.to_s}") }) }
155
196
  h.delete_if { |k, v| v.nil? }
156
- r = RubyZoho.configuration.api.add_record(Crm.module_name, h)
197
+ r = RubyZoho.configuration.api.add_record(self.class.module_name, h)
198
+ up_date(r)
199
+ end
200
+
201
+ def save_object(object)
202
+ h = {}
203
+ object.fields.each { |f| h.merge!({ f => object.send(f) }) }
204
+ h.delete_if { |k, v| v.nil? }
205
+ r = RubyZoho.configuration.api.add_record(object.module_name, h)
157
206
  up_date(r)
158
207
  end
159
208
 
@@ -161,58 +210,22 @@ module RubyZoho
161
210
  raise(RuntimeError, 'No ID found', object_attribute_hash.to_s) if object_attribute_hash[:id].nil?
162
211
  id = object_attribute_hash[:id]
163
212
  object_attribute_hash.delete(:id)
164
- RubyZoho.configuration.api.update_record(Crm.module_name, id, object_attribute_hash)
213
+ r = RubyZoho.configuration.api.update_record(self.module_name, id, object_attribute_hash)
214
+ new(object_attribute_hash.merge!(r))
165
215
  end
166
216
 
167
217
  def up_date(object_attribute_hash)
168
- retry_counter = object_attribute_hash.length
169
- begin
170
- object_attribute_hash.map { |(k, v)| public_send("#{k}=", v) }
171
- rescue NoMethodError => e
172
- m = e.message.slice(/`(.*?)=/)
173
- unless m.nil?
174
- m.gsub!('`', '')
175
- m.gsub!('(', '')
176
- m.gsub!(')', '')
177
- RubyZoho::Crm.create_accessor(self.class, [m.chop])
178
- end
179
- retry_counter -= 1
180
- retry if retry_counter > 0
181
- end
218
+ update_or_create_attrs(object_attribute_hash)
182
219
  self
183
220
  end
184
221
 
185
-
186
222
  def self.setup_classes
187
223
  RubyZoho.configuration.crm_modules.each do |module_name|
188
224
  klass_name = module_name.chop
189
225
  c = Class.new(RubyZoho::Crm) do
190
226
  include RubyZoho
191
227
  attr_reader :fields
192
-
193
- def initialize(object_attribute_hash = {})
194
- klass = self.class.to_s
195
- Crm.module_name = klass.slice(klass.rindex('::') + 2, klass.length) + 's'
196
- super
197
- end
198
-
199
- def self.all
200
- klass = self.to_s
201
- Crm.module_name = klass.slice(klass.rindex('::') + 2, klass.length) + 's'
202
- super
203
- end
204
-
205
- def self.delete(id)
206
- klass = self.to_s
207
- Crm.module_name = klass.slice(klass.rindex('::') + 2, klass.length) + 's'
208
- super
209
- end
210
-
211
- def self.method_missing(meth, *args, &block)
212
- klass = self.to_s
213
- Crm.module_name = klass.slice(klass.rindex('::') + 2, klass.length) + 's'
214
- super
215
- end
228
+ @module_name = module_name
216
229
  end
217
230
  const_set(klass_name, c)
218
231
  end
data/lib/zoho_api.rb CHANGED
@@ -42,7 +42,7 @@ module ZohoApi
42
42
  :headers => { 'Content-length' => '0' })
43
43
  check_for_errors(r)
44
44
  x_r = REXML::Document.new(r.body).elements.to_a('//recorddetail')
45
- to_hash_with_id(x_r, module_name)[0]
45
+ to_hash(x_r, module_name)[0]
46
46
  end
47
47
 
48
48
  def add_field(row, field, value)
@@ -62,11 +62,11 @@ module ZohoApi
62
62
  tag
63
63
  end
64
64
 
65
- def attach_file(module_name, record_id, file_path)
65
+ def attach_file(module_name, record_id, file_path, file_name)
66
66
  mime_type = (MIME::Types.type_for(file_path)[0] || MIME::Types["application/octet-stream"][0])
67
67
  url_path = create_url(module_name, "uploadFile?authtoken=#{@auth_token}&scope=crmapi&id=#{record_id}")
68
68
  url = URI.parse(create_url(module_name, url_path))
69
- io = UploadIO.new(file_path, mime_type, file_path)
69
+ io = UploadIO.new(file_path, mime_type, file_name)
70
70
  req = Net::HTTP::Post::Multipart.new url_path, 'content' => io
71
71
  http = Net::HTTP.new(url.host, url.port)
72
72
  http.use_ssl = true
@@ -92,12 +92,7 @@ module ZohoApi
92
92
  end
93
93
 
94
94
  def delete_record(module_name, record_id)
95
- r = self.class.post(create_url(module_name, 'deleteRecords'),
96
- :query => { :newFormat => 1, :authtoken => @auth_token,
97
- :scope => 'crmapi', :id => record_id },
98
- :headers => { 'Content-length' => '0' })
99
- raise('Adding contact failed', RuntimeError, r.response.body.to_s) unless r.response.code == '200'
100
- check_for_errors(r)
95
+ post_action(module_name, record_id, 'deleteRecords')
101
96
  end
102
97
 
103
98
  def fields(module_name)
@@ -129,7 +124,7 @@ module ZohoApi
129
124
  end
130
125
 
131
126
  def find_records(module_name, field, condition, value)
132
- sc_field = ApiUtils.symbol_to_string(field)
127
+ sc_field = field == :id ? primary_key(module_name) : ApiUtils.symbol_to_string(field)
133
128
  return find_record_by_related_id(module_name, sc_field, value) if related_id?(module_name, sc_field)
134
129
  primary_key?(module_name, sc_field) == false ? find_record_by_field(module_name, sc_field, condition, value) :
135
130
  find_record_by_id(module_name, value)
@@ -144,7 +139,7 @@ module ZohoApi
144
139
  :fromIndex => 1, :toIndex => NUMBER_OF_RECORDS_TO_GET})
145
140
  check_for_errors(r)
146
141
  x = REXML::Document.new(r.body).elements.to_a("/response/result/#{module_name}/row")
147
- to_hash(x)
142
+ to_hash(x, module_name)
148
143
  end
149
144
 
150
145
  def find_record_by_id(module_name, id)
@@ -154,7 +149,7 @@ module ZohoApi
154
149
  raise(RuntimeError, 'Bad query', "#{module_name} #{id}") unless r.body.index('<error>').nil?
155
150
  check_for_errors(r)
156
151
  x = REXML::Document.new(r.body).elements.to_a("/response/result/#{module_name}/row")
157
- to_hash(x)
152
+ to_hash(x, module_name)
158
153
  end
159
154
 
160
155
  def find_record_by_related_id(module_name, sc_field, value)
@@ -167,43 +162,22 @@ module ZohoApi
167
162
  :searchValue => value})
168
163
  check_for_errors(r)
169
164
  x = REXML::Document.new(r.body).elements.to_a("/response/result/#{module_name}/row")
170
- to_hash(x)
171
- end
172
-
173
- def valid_related?(module_name, field)
174
- return nil if field.downcase == 'smownerid'
175
- valid_relationships = {
176
- 'Leads' => %w(email),
177
- 'Accounts' => %w(accountid accountname),
178
- 'Contacts' => %w(contactid accountid vendorid email),
179
- 'Potentials' => %w(potentialid accountid campaignid contactid potentialname),
180
- 'Campaigns' => %w(campaignid campaignname),
181
- 'Cases' => %w(caseid productid accountid potentialid),
182
- 'Solutions' => %w(solutionid productid),
183
- 'Products' => %w(productid vendorid productname),
184
- 'Purchase Order' => %w(purchaseorderid contactid vendorid),
185
- 'Quotes' => %w(quoteid potentialid accountid contactid),
186
- 'Sales Orders' => %w(salesorderid potentialid accountid contactid quoteid),
187
- 'Invoices' => %w(invoiceid accountid salesorderid contactid),
188
- 'Vendors' => %w(vendorid vendorname),
189
- 'Tasks' => %w(taskid),
190
- 'Events' => %w(eventid),
191
- 'Notes' => %w(notesid)
192
- }
193
- valid_relationships[module_name].index(field.downcase)
194
- end
195
-
196
- def related_id?(module_name, field_name)
197
- field = field_name.to_s
198
- return false if field.rindex('id').nil?
199
- return false if %w[Calls Events Tasks].index(module_name) && field_name.downcase == 'activityid'
200
- field.downcase.gsub('id', '') != module_name.chop.downcase
165
+ to_hash(x, module_name)
201
166
  end
202
167
 
203
168
  def method_name?(n)
204
169
  return /[@$"]/ !~ n.inspect
205
170
  end
206
171
 
172
+ def post_action(module_name, record_id, action_type)
173
+ r = self.class.post(create_url(module_name, action_type),
174
+ :query => {:newFormat => 1, :authtoken => @auth_token,
175
+ :scope => 'crmapi', :id => record_id},
176
+ :headers => {'Content-length' => '0'})
177
+ raise('Adding contact failed', RuntimeError, r.response.body.to_s) unless r.response.code == '200'
178
+ check_for_errors(r)
179
+ end
180
+
207
181
  def primary_key(module_name)
208
182
  activity_keys = { 'Tasks' => :activityid, 'Events' => :activityid, 'Calls' => :activityid }
209
183
  return activity_keys[module_name] unless activity_keys[module_name].nil?
@@ -211,8 +185,18 @@ module ZohoApi
211
185
  end
212
186
 
213
187
  def primary_key?(module_name, field_name)
214
- return true if %w[Calls Events Tasks].index(module_name) && field_name.downcase == 'activityid'
215
- field_name.downcase.gsub('id', '') == module_name.chop.downcase
188
+ return nil if field_name.nil? || module_name.nil?
189
+ fn = field_name.class == String ? field_name : field_name.to_s
190
+ return true if fn == 'id'
191
+ return true if %w[Calls Events Tasks].index(module_name) && fn.downcase == 'activityid'
192
+ fn.downcase.gsub('id', '') == module_name.chop.downcase
193
+ end
194
+
195
+ def related_id?(module_name, field_name)
196
+ field = field_name.to_s
197
+ return false if field.rindex('id').nil?
198
+ return false if %w[Calls Events Tasks].index(module_name) && field_name.downcase == 'activityid'
199
+ field.downcase.gsub('id', '') != module_name.chop.downcase
216
200
  end
217
201
 
218
202
  def reflect_module_fields
@@ -236,17 +220,19 @@ module ZohoApi
236
220
  return nil unless r.response.code == '200'
237
221
  check_for_errors(r)
238
222
  x = REXML::Document.new(r.body).elements.to_a("/response/result/#{module_name}/row")
239
- to_hash(x)
223
+ to_hash(x, module_name)
240
224
  end
241
225
 
242
- def to_hash(xml_results)
226
+ def to_hash(xml_results, module_name)
243
227
  r = []
244
228
  xml_results.each do |e|
245
229
  record = {}
230
+ record[:module_name] = module_name
246
231
  e.elements.to_a.each do |n|
247
232
  k = ApiUtils.string_to_symbol(n.attribute('val').to_s.gsub('val=', ''))
248
233
  v = n.text == 'null' ? nil : n.text
249
234
  record.merge!({ k => v })
235
+ record.merge!({ :id => v }) if primary_key?(module_name, k)
250
236
  end
251
237
  r << record
252
238
  end
@@ -255,13 +241,7 @@ module ZohoApi
255
241
  end
256
242
 
257
243
  def to_hash_with_id(xml_results, module_name)
258
- h = to_hash(xml_results)
259
- primary_key = module_name.chop.downcase + 'id'
260
- h.each do |e|
261
- e.merge!({ primary_key.to_sym => e[:id] }) if e[primary_key.to_sym].nil? && !e[:id].nil?
262
- e.merge!({ e[:id] => primary_key.to_sym }) if e[:id].nil? && !e[primary_key.to_sym].nil?
263
- end
264
- h
244
+ to_hash(xml_results, module_name)
265
245
  end
266
246
 
267
247
  def update_module_fields(mod_name, module_name, r)
@@ -287,8 +267,8 @@ module ZohoApi
287
267
  :xmlData => x },
288
268
  :headers => { 'Content-length' => '0' })
289
269
  check_for_errors(r)
290
- raise('Updating record failed', RuntimeError, r.response.body.to_s) unless r.response.code == '200'
291
- r.response.code
270
+ x_r = REXML::Document.new(r.body).elements.to_a('//recorddetail')
271
+ to_hash_with_id(x_r, module_name)[0]
292
272
  end
293
273
 
294
274
  def user_fields
@@ -316,6 +296,29 @@ module ZohoApi
316
296
  @@users = result
317
297
  end
318
298
 
299
+ def valid_related?(module_name, field)
300
+ return nil if field.downcase == 'smownerid'
301
+ valid_relationships = {
302
+ 'Leads' => %w(email),
303
+ 'Accounts' => %w(accountid accountname),
304
+ 'Contacts' => %w(contactid accountid vendorid email),
305
+ 'Potentials' => %w(potentialid accountid campaignid contactid potentialname),
306
+ 'Campaigns' => %w(campaignid campaignname),
307
+ 'Cases' => %w(caseid productid accountid potentialid),
308
+ 'Solutions' => %w(solutionid productid),
309
+ 'Products' => %w(productid vendorid productname),
310
+ 'Purchase Order' => %w(purchaseorderid contactid vendorid),
311
+ 'Quotes' => %w(quoteid potentialid accountid contactid),
312
+ 'Sales Orders' => %w(salesorderid potentialid accountid contactid quoteid),
313
+ 'Invoices' => %w(invoiceid accountid salesorderid contactid),
314
+ 'Vendors' => %w(vendorid vendorname),
315
+ 'Tasks' => %w(taskid),
316
+ 'Events' => %w(eventid),
317
+ 'Notes' => %w(notesid)
318
+ }
319
+ valid_relationships[module_name].index(field.downcase)
320
+ end
321
+
319
322
  end
320
323
 
321
324
  end
Binary file
data/rubyzoho.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "rubyzoho"
8
- s.version = "0.1.6"
8
+ s.version = "0.1.7"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["amalc"]
12
- s.date = "2013-02-20"
12
+ s.date = "2013-02-24"
13
13
  s.description = ""
14
14
  s.email = ""
15
15
  s.extra_rdoc_files = [
@@ -29,6 +29,7 @@ Gem::Specification.new do |s|
29
29
  "lib/api_utils.rb",
30
30
  "lib/ruby_zoho.rb",
31
31
  "lib/zoho_api.rb",
32
+ "rubyzoho-0.1.6.gem",
32
33
  "rubyzoho.gemspec",
33
34
  "spec/api_utils_spec.rb",
34
35
  "spec/fixtures/sample.pdf",
@@ -56,6 +57,7 @@ Gem::Specification.new do |s|
56
57
  s.add_runtime_dependency(%q<multipart-post>, [">= 0"])
57
58
  s.add_development_dependency(%q<bundler>, [">= 1.2"])
58
59
  s.add_development_dependency(%q<cucumber>, [">= 1.2.1"])
60
+ s.add_development_dependency(%q<holepicker>, [">= 0"])
59
61
  s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
60
62
  s.add_development_dependency(%q<relish>, [">= 0.6"])
61
63
  s.add_development_dependency(%q<rdoc>, [">= 3.12.1"])
@@ -67,6 +69,7 @@ Gem::Specification.new do |s|
67
69
  s.add_dependency(%q<multipart-post>, [">= 0"])
68
70
  s.add_dependency(%q<bundler>, [">= 1.2"])
69
71
  s.add_dependency(%q<cucumber>, [">= 1.2.1"])
72
+ s.add_dependency(%q<holepicker>, [">= 0"])
70
73
  s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
71
74
  s.add_dependency(%q<relish>, [">= 0.6"])
72
75
  s.add_dependency(%q<rdoc>, [">= 3.12.1"])
@@ -79,6 +82,7 @@ Gem::Specification.new do |s|
79
82
  s.add_dependency(%q<multipart-post>, [">= 0"])
80
83
  s.add_dependency(%q<bundler>, [">= 1.2"])
81
84
  s.add_dependency(%q<cucumber>, [">= 1.2.1"])
85
+ s.add_dependency(%q<holepicker>, [">= 0"])
82
86
  s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
83
87
  s.add_dependency(%q<relish>, [">= 0.6"])
84
88
  s.add_dependency(%q<rdoc>, [">= 3.12.1"])
@@ -7,13 +7,9 @@ describe RubyZoho::Crm do
7
7
 
8
8
  before(:all) do
9
9
  base_path = File.join(File.dirname(__FILE__), 'fixtures')
10
- config_file = File.join(base_path, 'zoho_api_configuration.yaml')
11
10
  @sample_pdf = File.join(base_path, 'sample.pdf')
12
- #params = YAML.load(File.open(config_file))
13
11
  RubyZoho.configure do |config|
14
- #config.api_key = params['auth_token']
15
- #config.api_key = 'e194b2951fb238e26bc096de9d0cf5f8'
16
- config.api_key = '62cedfe9427caef8afb9ea3b5bf68154'
12
+ config.api_key = ENV['ZOHO_API_KEY'].strip
17
13
  config.crm_modules = %w(Quotes)
18
14
  config.cache_fields = true
19
15
  end
@@ -29,11 +25,12 @@ describe RubyZoho::Crm do
29
25
  c.first_name.should eq('Raj')
30
26
  c.email = 'raj@portra.com'
31
27
  c.email.should eq('raj@portra.com')
28
+ c.module_name.should eq('Contacts')
32
29
  end
33
30
 
34
31
  it 'should attach a file to an account' do
35
32
  r = RubyZoho::Crm::Account.all.first
36
- r.attach_file(@sample_pdf, File.basename(@sample_pdf)).should eq('200')
33
+ r.attach_file(@sample_pdf, '1_' + File.basename(@sample_pdf)).should eq('200')
37
34
  end
38
35
 
39
36
  it 'should attach a file to a contact' do
@@ -56,9 +53,31 @@ describe RubyZoho::Crm do
56
53
  r.attach_file(@sample_pdf, File.basename(@sample_pdf)).should eq('200')
57
54
  end
58
55
 
56
+ it 'should concatenate a related object and save it' do
57
+ subject = "[DELETE THIS] New subject as of #{Time.now}"
58
+ a = RubyZoho::Crm::Account.all.last
59
+ a << RubyZoho::Crm::Task.new(
60
+ :subject => subject,
61
+ :description => 'Nothing',
62
+ :status => 'Not Started',
63
+ :priority => 'High',
64
+ :send_notification_email => 'False',
65
+ :due_date => '2014-02-16 16:00:00',
66
+ :start_datetime => Time.now.to_s[1,19],
67
+ :end_datetime => '2014-02-16 16:00:00'
68
+ )
69
+ r = RubyZoho::Crm::Task.find_by_subject(subject)
70
+ r.first.relatedtoid.should eq(a.accountid)
71
+ end
72
+
73
+ it 'should determine if a method is a module' do
74
+ good_methods = [:contact, :contacts, 'contacts', 'lead', 'leads', :potentials, :quotes]
75
+ good_methods.map { |m| RubyZoho::Crm.method_is_module?(m).should_not eq(nil) }
76
+ end
77
+
59
78
  it 'should find a contact by email or last name' do
60
79
  r = RubyZoho::Crm::Contact.find_by_email('bob@smith.com')
61
- r.each { |m| RubyZoho::Crm::Contact.delete(m.contactid) } unless r.nil?
80
+ r.each { |m| RubyZoho::Crm::Contact.delete(m.id) } unless r.nil?
62
81
  1.upto(3) do
63
82
  c = RubyZoho::Crm::Contact.new(
64
83
  :first_name => 'Bob',
@@ -74,21 +93,21 @@ describe RubyZoho::Crm do
74
93
  r.should_not eq(nil)
75
94
  r.map { |c| c.last_name }.count.should eq(3)
76
95
  r.first.last_name.should eq('Smithereens')
77
- r.each { |m| RubyZoho::Crm::Contact.delete(m.contactid) }
96
+ r.each { |m| RubyZoho::Crm::Contact.delete(m.id) }
78
97
  end
79
98
 
80
99
  it 'should find a contact by ID' do
81
100
  contacts = RubyZoho::Crm::Contact.all
82
- contact_id = contacts.first.contactid
83
- c = RubyZoho::Crm::Contact.find_by_contactid(contact_id)
84
- c.first.contactid.should eq(contact_id)
101
+ id = contacts.first.id
102
+ c = RubyZoho::Crm::Contact.find_by_contactid(id)
103
+ c.first.contactid.should eq(id)
85
104
  c.first.last_name.should eq(contacts.first.last_name)
86
105
  c.first.email.should eq(contacts.first.email)
87
106
  end
88
107
 
89
108
  it 'should find a lead by ID' do
90
109
  leads = RubyZoho::Crm::Lead.all
91
- lead_id = leads.first.leadid
110
+ lead_id = leads.first.id
92
111
  l = RubyZoho::Crm::Lead.find_by_leadid(lead_id)
93
112
  l.first.leadid.should eq(lead_id)
94
113
  end
@@ -136,42 +155,50 @@ describe RubyZoho::Crm do
136
155
  end
137
156
 
138
157
  it 'should get a list of calls' do
139
- pending
158
+ #pending
140
159
  r = RubyZoho::Crm::Call.all
141
- r.count.should be > 1 unless r.nil?
142
- r.map { |r| r.class.should eq(RubyZoho::Crm::Call) } unless r.nil?
160
+ unless r.nil?
161
+ #r.count.should be > 1
162
+ r.map { |e| e.class.should eq(RubyZoho::Crm::Call) }
163
+ r.map { |e| e.id.should eq(e.activityid)}
164
+ end
143
165
  end
144
166
 
145
167
  it 'should get a list of contacts' do
146
168
  r = RubyZoho::Crm::Contact.all
147
169
  r.count.should be > 1
148
- r.map { |r| r.class.should eq(RubyZoho::Crm::Contact) }
170
+ r.map { |e| e.class.should eq(RubyZoho::Crm::Contact) }
171
+ r.map { |e| e.id.should eq(e.contactid)}
149
172
  end
150
173
 
151
174
  it 'should get a list of events' do
152
175
  r = RubyZoho::Crm::Event.all
153
176
  r.map { |r| r.class.should eq(RubyZoho::Crm::Event) } unless r.nil?
177
+ r.map { |e| e.id.should eq(e.eventid)}
154
178
  end
155
179
 
156
180
  it 'should get a list of potentials' do
157
181
  r = RubyZoho::Crm::Potential.all
158
182
  r.count.should be > 1
159
183
  r.map { |r| r.class.should eq(RubyZoho::Crm::Potential) }
184
+ r.map { |e| e.id.should eq(e.potentialid)}
160
185
  end
161
186
 
162
187
  it 'should get a list of quotes' do
163
188
  r = RubyZoho::Crm::Quote.all
164
189
  r.count.should be >= 1
165
190
  r.map { |r| r.class.should eq(RubyZoho::Crm::Quote) }
191
+ r.map { |e| e.id.should eq(e.quoteid)}
166
192
  end
167
193
 
168
194
  it 'should get a list of tasks' do
169
195
  r = RubyZoho::Crm::Task.all
170
196
  r.map { |r| r.class.should eq(RubyZoho::Crm::Task) } unless r.nil?
197
+ r.map { |e| e.id.should eq(e.activityid)}
171
198
  end
172
199
 
173
200
  it 'should get a list of users' do
174
- r = RubyZoho::Crm::User.all
201
+ pp r = RubyZoho::Crm::User.all
175
202
  r.count.should be >= 1
176
203
  end
177
204
 
@@ -195,7 +222,7 @@ describe RubyZoho::Crm do
195
222
  r = RubyZoho::Crm::Lead.find_by_email('raj@portra.com')
196
223
  r.should_not eq(nil)
197
224
  r.first.email.should eq(l.email)
198
- r.each { |c| RubyZoho::Crm::Lead.delete(c.leadid) }
225
+ r.each { |c| RubyZoho::Crm::Lead.delete(c.id) }
199
226
  end
200
227
 
201
228
  it 'should save and retrieve an account record with a custom field' do
@@ -203,10 +230,10 @@ describe RubyZoho::Crm do
203
230
  a = accounts.first
204
231
  if defined?(a.par_ltd)
205
232
  RubyZoho::Crm::Lead.update(
206
- :id => a.accountid,
233
+ :id => a.id,
207
234
  :test_custom => '$1,000,000'
208
235
  )
209
- a2 = RubyZoho::Crm::Account.find_by_accountid(a.accountid)
236
+ a2 = RubyZoho::Crm::Account.find(a.accountid)
210
237
  a2.first.test_custom.should eq('$1,000,000')
211
238
  end
212
239
  end
@@ -226,7 +253,7 @@ describe RubyZoho::Crm do
226
253
  p.save
227
254
  r = RubyZoho::Crm::Potential.find_by_potential_name(p.potential_name)
228
255
  r.first.potential_name.should eq(h[:potential_name])
229
- potential = RubyZoho::Crm::Potential.find_by_potentialid(r.first.potentialid)
256
+ potential = RubyZoho::Crm::Potential.find(r.first.potentialid)
230
257
  potential.first.potentialid.should eq(r.first.potentialid)
231
258
  p_by_account_id = RubyZoho::Crm::Potential.find_by_accountid(accounts.first.accountid)
232
259
  p_found = p_by_account_id.map { |pn| pn if pn.potential_name == h[:potential_name]}.compact
@@ -255,16 +282,11 @@ describe RubyZoho::Crm do
255
282
  end
256
283
 
257
284
  it 'should save an task record related to an account' do
258
- pending
259
285
  a = RubyZoho::Crm::Account.all.first
260
- #u = RubyZoho::Crm::User.all.first
261
- #c = RubyZoho::Crm::Contact.all.last
262
- #pp tasks = RubyZoho::Crm::Task.all
263
- #throw :stop
264
- e = RubyZoho::Crm::Tassk.new(
286
+ e = RubyZoho::Crm::Task.new(
265
287
  :task_owner => a.account_owner,
266
288
  :subject => "Task should be related to #{a.account_name} #{Time.now}",
267
- #:description => 'Nothing',
289
+ :description => 'Nothing',
268
290
  :smownerid => "#{a.smownerid}",
269
291
  :status => 'Not Started',
270
292
  :priority => 'High',
@@ -273,19 +295,20 @@ describe RubyZoho::Crm do
273
295
  :start_datetime => Time.now.to_s[1,19],
274
296
  :end_datetime => '2014-02-16 16:00:00',
275
297
  :related_to => "#{a.account_name}",
276
- :relatedtoid => "#{a.accountid}",
298
+ :seid => "#{a.accountid}",
277
299
  :semodule => "Accounts"
278
- #:contact_name => "#{c.first_name} #{c.last_name}",
279
- #:contactid => c.contactid
280
300
  )
281
- pp e.save
301
+ r_expected = e.save
302
+ r = RubyZoho::Crm::Task.find_by_activityid(r_expected.id)
303
+ r.first.subject.should eq(r_expected.subject)
282
304
  end
283
305
 
284
306
  it 'should get tasks by user' do
285
- #pp u = RubyZoho::Crm::User.all.first
286
- #pp tasks = RubyZoho::Crm::Task.find_by_smownerid(u.id)
287
- #pp tasks = RubyZoho::Crm::Task.all
288
- #tasks.map { |t| RubyZoho::Crm::Task.delete(t.activityid)} unless tasks.nil?
307
+ pending
308
+ pp u = RubyZoho::Crm::User.all.first
309
+ pp tasks = RubyZoho::Crm::Task.find_by_smownerid(u.id)
310
+ pp tasks = RubyZoho::Crm::Task.all
311
+ tasks.map { |t| RubyZoho::Crm::Task.delete(t.activityid)} unless tasks.nil?
289
312
  end
290
313
 
291
314
  it 'should sort contact records' do
@@ -295,6 +318,8 @@ describe RubyZoho::Crm do
295
318
  end
296
319
 
297
320
  it 'should update a lead record' do
321
+ r_changed = RubyZoho::Crm::Lead.find_by_email('changed_raj@portra.com')
322
+ r_changed.each { |c| RubyZoho::Crm::Lead.delete(c.leadid) } unless r_changed.nil?
298
323
  l = RubyZoho::Crm::Lead.new(
299
324
  :first_name => 'Raj',
300
325
  :last_name => 'Portra',
@@ -307,8 +332,8 @@ describe RubyZoho::Crm do
307
332
  )
308
333
  r_changed = RubyZoho::Crm::Lead.find_by_email('changed_raj@portra.com')
309
334
  r.first.leadid.should eq(r_changed.first.leadid)
310
- r_changed.should_not eq(nil)
311
335
  r.each { |c| RubyZoho::Crm::Lead.delete(c.leadid) }
336
+ r_changed.each { |c| RubyZoho::Crm::Lead.delete(c.leadid) }
312
337
  end
313
338
 
314
339
  it 'should validate a field name' do
@@ -33,15 +33,9 @@ describe ZohoApi do
33
33
 
34
34
  before(:all) do
35
35
  base_path = File.join(File.dirname(__FILE__), 'fixtures')
36
- config_file = File.join(base_path, 'zoho_api_configuration.yaml')
37
- #params = YAML.load(File.open(config_file))
38
- #@zoho = ZohoApi::Crm.new(params['auth_token'])
39
36
  @sample_pdf = File.join(base_path, 'sample.pdf')
40
37
  modules = ['Accounts', 'Contacts', 'Events', 'Leads', 'Tasks', 'Potentials']
41
- #api_key = '783539943dc16d7005b0f3b78367d5d2'
42
- api_key = 'e194b2951fb238e26bc096de9d0cf5f8'
43
- #api_key = '62cedfe9427caef8afb9ea3b5bf68154'
44
- @zoho = init_api(api_key, base_path, modules)
38
+ @zoho = init_api(ENV['ZOHO_API_KEY'].strip, base_path, modules)
45
39
  @h_smith = { :first_name => 'Robert',
46
40
  :last_name => 'Smith',
47
41
  :email => 'rsmith@smithereens.com',
@@ -84,14 +78,14 @@ describe ZohoApi do
84
78
  it 'should attach a file to a contact record' do
85
79
  @zoho.add_record('Contacts', @h_smith)
86
80
  contacts = @zoho.find_records('Contacts', :email, '=', @h_smith[:email])
87
- @zoho.attach_file('Contacts', contacts[0][:contactid], @sample_pdf)
81
+ @zoho.attach_file('Contacts', contacts[0][:contactid], @sample_pdf, File.basename(@sample_pdf))
88
82
  @zoho.delete_record('Contacts', contacts[0][:contactid])
89
83
  end
90
84
 
91
85
  it 'should attach a file to a potential record' do
92
86
  pending
93
87
  potential = @zoho.first('Potentials').first
94
- @zoho.attach_file('Potentials', potential[:potentialid], @sample_pdf)
88
+ @zoho.attach_file('Potentials', potential[:potentialid], @sample_pdf, File.basename(@sample_pdf))
95
89
  end
96
90
 
97
91
  it 'should delete a contact record with id' do
@@ -146,15 +140,15 @@ describe ZohoApi do
146
140
 
147
141
  it 'should get a list of fields for a module' do
148
142
  r = @zoho.fields('Accounts')
149
- r.count.should >= 25
143
+ r.count.should >= 10
150
144
  r = @zoho.fields('Contacts')
151
- r.count.should be >= 21
145
+ r.count.should be >= 10
152
146
  r = @zoho.fields('Events')
153
147
  r.count.should >= 10
154
148
  r = @zoho.fields('Leads')
155
- r.count.should be >= 16
149
+ r.count.should be >= 10
156
150
  r = @zoho.fields('Potentials')
157
- r.count.should be >= 15
151
+ r.count.should be >= 10
158
152
  r = @zoho.fields('Tasks')
159
153
  r.count.should >= 10
160
154
  r = @zoho.fields('Users')
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyzoho
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-20 00:00:00.000000000 Z
12
+ date: 2013-02-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: httmultiparty
@@ -91,6 +91,22 @@ dependencies:
91
91
  - - ! '>='
92
92
  - !ruby/object:Gem::Version
93
93
  version: 1.2.1
94
+ - !ruby/object:Gem::Dependency
95
+ name: holepicker
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
94
110
  - !ruby/object:Gem::Dependency
95
111
  name: jeweler
96
112
  requirement: !ruby/object:Gem::Requirement
@@ -191,6 +207,7 @@ files:
191
207
  - lib/api_utils.rb
192
208
  - lib/ruby_zoho.rb
193
209
  - lib/zoho_api.rb
210
+ - rubyzoho-0.1.6.gem
194
211
  - rubyzoho.gemspec
195
212
  - spec/api_utils_spec.rb
196
213
  - spec/fixtures/sample.pdf