sugarcrm 0.5.3 → 0.6.0

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.
Files changed (43) hide show
  1. data/README.rdoc +15 -10
  2. data/Rakefile +4 -1
  3. data/VERSION +1 -1
  4. data/lib/sugarcrm.rb +107 -119
  5. data/lib/sugarcrm/api/get_available_modules.rb +17 -0
  6. data/lib/sugarcrm/api/get_document_revision.rb +14 -0
  7. data/lib/sugarcrm/{get_entries.rb → api/get_entries.rb} +2 -2
  8. data/lib/sugarcrm/api/get_entries_count.rb +20 -0
  9. data/lib/sugarcrm/{get_entry.rb → api/get_entry.rb} +5 -8
  10. data/lib/sugarcrm/{get_entry_list.rb → api/get_entry_list.rb} +2 -2
  11. data/lib/sugarcrm/{get_module_fields.rb → api/get_module_fields.rb} +6 -3
  12. data/lib/sugarcrm/api/get_note_attachment.rb +15 -0
  13. data/lib/sugarcrm/{get_relationships.rb → api/get_relationship.rb} +9 -4
  14. data/lib/sugarcrm/api/get_report_entries.rb +19 -0
  15. data/lib/sugarcrm/api/get_server_info.rb +7 -0
  16. data/lib/sugarcrm/api/get_user_id.rb +13 -0
  17. data/lib/sugarcrm/api/get_user_team_id.rb +14 -0
  18. data/lib/sugarcrm/api/login.rb +18 -0
  19. data/lib/sugarcrm/api/logout.rb +15 -0
  20. data/lib/sugarcrm/api/seamless_login.rb +13 -0
  21. data/lib/sugarcrm/api/search_by_module.rb +24 -0
  22. data/lib/sugarcrm/api/set_campaign_merge.rb +15 -0
  23. data/lib/sugarcrm/api/set_document_revision.rb +15 -0
  24. data/lib/sugarcrm/api/set_entries.rb +15 -0
  25. data/lib/sugarcrm/api/set_entry.rb +15 -0
  26. data/lib/sugarcrm/api/set_note_attachment.rb +3 -0
  27. data/lib/sugarcrm/api/set_relationship.rb +18 -0
  28. data/lib/sugarcrm/api/set_relationships.rb +22 -0
  29. data/lib/sugarcrm/connection.rb +112 -0
  30. data/lib/sugarcrm/exceptions.rb +16 -0
  31. data/lib/sugarcrm/module.rb +11 -0
  32. data/lib/sugarcrm/request.rb +28 -0
  33. data/lib/sugarcrm/response.rb +29 -0
  34. data/test/helper.rb +5 -0
  35. data/test/test_connection.rb +60 -0
  36. data/test/test_response.rb +36 -0
  37. data/test/test_sugarcrm.rb +31 -45
  38. metadata +56 -17
  39. data/lib/net/http_digest_auth.rb +0 -44
  40. data/lib/stdlib/array.rb +0 -14
  41. data/lib/stdlib/hash.rb +0 -17
  42. data/lib/stdlib/object.rb +0 -5
  43. data/test/test_json_to_obj.rb +0 -48
data/README.rdoc CHANGED
@@ -6,18 +6,19 @@ REST Bindings for SugarCRM!
6
6
 
7
7
  == SUMMARY:
8
8
 
9
- Ruby Gem for interacting with SugarCRM via REST
9
+ Ruby Gem for interacting with SugarCRM via REST.
10
10
 
11
- == FEATURES/PROBLEMS:
11
+ == Description:
12
+
13
+ I've implemented all of the basic API calls that SugarCRM supports, and am actively building an abstraction layer
14
+ on top of the basic API methods. The end result will be to provide ActiveRecord style finders and first class
15
+ objects. Some of this functionality is included today.
12
16
 
13
- * The response hash gets converted into a Ruby object, via Hash#to_obj method.
14
- * Only the following methods are currently implemented:
17
+ == FEATURES/PROBLEMS:
15
18
 
16
- * get_entry
17
- * get_entries
18
- * get_entry_list
19
- * get_module_fields
20
- * get_relationships
19
+ * Auto-generation of Module specific objects. When a connection is established, get_available_modules is called
20
+ and the resultant modules are turned into SugarCRM::Module classes.
21
+ * If you just want to use the vanilla API, you can access the methods directly on the connection object.
21
22
 
22
23
  == SYNOPSIS:
23
24
 
@@ -25,7 +26,7 @@ Ruby Gem for interacting with SugarCRM via REST
25
26
  sugarcrm = SugarCRM::Base.new("http://localhost/sugarcrm", 'user', 'password', {:debug => false})
26
27
 
27
28
  # Lookup a user by name. Find any associated accounts
28
- sugarcrm.get_entry_list(
29
+ sugarcrm.connection.get_entry_list(
29
30
  "Users",
30
31
  "users.user_name = \'#{USER}\'",
31
32
  {
@@ -39,8 +40,12 @@ Ruby Gem for interacting with SugarCRM via REST
39
40
  }
40
41
  )
41
42
 
43
+ # Retrieve a user by ID, using the SugarCRM::User Proxy object
44
+ user = SugarCRM::User.find(id)
45
+
42
46
  == REQUIREMENTS:
43
47
 
48
+ * activesupport gem
44
49
  * json gem
45
50
 
46
51
  == INSTALL:
data/Rakefile CHANGED
@@ -6,12 +6,15 @@ begin
6
6
  Jeweler::Tasks.new do |gem|
7
7
  gem.name = "sugarcrm"
8
8
  gem.summary = %Q{Ruby based REST client for SugarCRM}
9
- gem.description = %Q{A Ruby based REST client for SugarCRM.}
9
+ gem.description = %Q{I've implemented all of the basic API calls that SugarCRM supports, and am actively building an abstraction layer
10
+ on top of the basic API methods. The end result will be to provide ActiveRecord style finders and first class
11
+ objects. Some of this functionality is included today.}
10
12
  gem.email = "carl.hicks@gmail.com"
11
13
  gem.homepage = "http://github.com/chicks/sugarcrm"
12
14
  gem.authors = ["Carl Hicks"]
13
15
  gem.add_development_dependency "shoulda", ">= 0"
14
16
  gem.add_dependency "json", ">= 0"
17
+ gem.add_dependency "activesupport", ">= 0"
15
18
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
19
  end
17
20
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.3
1
+ 0.6.0
data/lib/sugarcrm.rb CHANGED
@@ -3,153 +3,141 @@
3
3
  module SugarCRM
4
4
 
5
5
  Dir["#{File.dirname(__FILE__)}/sugarcrm/**/*.rb"].each { |f| load(f) }
6
- Dir["#{File.dirname(__FILE__)}/stdlib/**/*.rb"].each { |f| load(f) }
7
6
 
8
7
  require 'pp'
9
- require 'ostruct'
10
8
  require 'uri'
11
9
  require 'net/https'
12
- require 'openssl'
13
10
  require 'digest/md5'
14
11
 
15
12
  require 'rubygems'
13
+ require 'active_support/core_ext'
16
14
  require 'json'
17
-
18
- class LoginError < RuntimeError
19
- end
20
-
21
- class EmptyResponse < RuntimeError
22
- end
23
-
24
- class UnhandledResponse < RuntimeError
25
- end
26
-
27
- class InvalidSugarCRMUrl < RuntimeError
28
- end
15
+ require 'json/add/rails'
29
16
 
30
17
  class Base
31
-
32
- URL = "/service/v2/rest.php"
33
- attr :url, true
34
- attr :user, false
35
- attr :pass, false
36
- attr :ssl, false
37
- attr :connection, true
38
- attr :session, true
18
+ # Unset all of the instance methods we don't need.
19
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$|^define_method$|^class$|^instance_of.$)/ }
20
+
21
+ # This holds our connection
22
+ cattr_accessor :connection, :instance_writer => false
23
+
24
+ # Contains the name of the module in SugarCRM
25
+ class_attribute :module_name
26
+ self.module_name = self.name.split(/::/)[-1]
27
+
28
+ # Contains the fields found on the current module
29
+ class_attribute :module_fields
30
+ self.module_fields = {}
31
+
32
+ # Tracks if we have extended our class with attribute methods yet.
33
+ class_attribute :attribute_methods_generated
34
+ self.attribute_methods_generated = false
35
+
36
+ # Contains a list of attributes
37
+ attr :attributes, true
39
38
  attr :debug, true
40
- attr :to_obj, true
41
39
 
42
- def initialize(url, user, pass, opts={})
40
+ def self.establish_connection(url, user, pass, opts={})
43
41
  options = {
44
42
  :debug => false,
45
- :to_obj => true
46
43
  }.merge(opts)
47
-
48
44
  @debug = options[:debug]
49
- @to_obj = options[:to_obj]
50
-
51
- @url = URI.parse(url)
52
- @user = user
53
- @pass = pass
54
-
55
- # Handles http/https in url string
56
- @ssl = false
57
- @ssl = true if @url.scheme == "https"
58
-
59
- # Appends the rest.php path onto the end of the URL if it's not included
60
- if @url.path !~ /rest.php$/
61
- @url.path += URL
62
- end
63
-
64
- login!
65
- raise LoginError, "Invalid Login" unless logged_in?
45
+ @@connection = SugarCRM::Connection.new(url, user, pass, @debug)
66
46
  end
67
-
68
- def connected?
69
- return false unless @connection
70
- return false unless @connection.started?
71
- true
47
+
48
+ # Registers the module fields on the class
49
+ def self.register_module_fields
50
+ self.module_fields = connection.get_fields(self.module_name)["module_fields"] if self.module_fields.length == 0
51
+ end
52
+
53
+ def initialize(attributes={})
54
+ @attributes = attributes_from_module_fields.merge(attributes)
55
+ define_attribute_methods
56
+ end
57
+
58
+ def inspect
59
+ self
60
+ end
61
+
62
+ def to_s
63
+ attrs = []
64
+ @attributes.each_key do |k|
65
+ attrs << "#{k}: #{attribute_for_inspect(k)}"
66
+ end
67
+ "#<#{self.class} #{attrs.join(", ")}>"
72
68
  end
73
69
 
74
- def logged_in?
75
- @session ? true : false
70
+ # Returns an <tt>#inspect</tt>-like string for the value of the
71
+ # attribute +attr_name+. String attributes are elided after 50
72
+ # characters, and Date and Time attributes are returned in the
73
+ # <tt>:db</tt> format. Other attributes return the value of
74
+ # <tt>#inspect</tt> without modification.
75
+ #
76
+ # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
77
+ #
78
+ # person.attribute_for_inspect(:name)
79
+ # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
80
+ #
81
+ # person.attribute_for_inspect(:created_at)
82
+ # # => '"2009-01-12 04:48:57"'
83
+ def attribute_for_inspect(attr_name)
84
+ value = read_attribute(attr_name)
85
+
86
+ if value.is_a?(String) && value.length > 50
87
+ "#{value[0..50]}...".inspect
88
+ elsif value.is_a?(Date) || value.is_a?(Time)
89
+ %("#{value.to_s(:db)}")
90
+ else
91
+ value.inspect
92
+ end
76
93
  end
77
94
 
78
95
  protected
79
96
 
80
- def connect!
81
- @connection = Net::HTTP.new(@url.host, @url.port)
82
- if @ssl
83
- @connection.use_ssl = true
84
- @connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
97
+ # Generates get/set methods for keys in the attributes hash
98
+ def define_attribute_methods
99
+ return if attribute_methods_generated?
100
+ @attributes.each_pair do |k,v|
101
+ self.class.module_eval %Q?
102
+ def #{k}
103
+ read_attribute :#{k}
85
104
  end
86
- @connection.start
87
- end
88
-
89
- def login!
90
- connect! unless connected?
91
- json = <<-EOF
92
- {
93
- \"user_auth\": {
94
- \"user_name\": \"#{@user}\"\,
95
- \"password\": \"#{OpenSSL::Digest::MD5.new(@pass)}\"\,
96
- \"version\": \"2\"\,
97
- },
98
- \"application\": \"\"
99
- }
100
- EOF
101
- json.gsub!(/^\s{8}/,'')
102
- response = get(:login, json)
103
-
104
- if @to_obj
105
- @session = response.id
106
- else
107
- @session = response["id"]
105
+ def #{k}=(value)
106
+ write_attribute :#{k},value
108
107
  end
108
+ ?
109
109
  end
110
+ self.class.attribute_methods_generated = true
111
+ end
110
112
 
111
- def get(method,json)
112
- query = @url.path.dup
113
- query << '?method=' << method.to_s
114
- query << '&input_type=JSON'
115
- query << '&response_type=JSON'
116
- query << '&rest_data=' << json
117
-
118
- if @debug
119
- puts "#{method}: Request"
120
- puts query
121
- puts "\n"
122
- end
123
- response = @connection.get(URI.escape(query))
124
-
125
- case response
126
- when Net::HTTPOK
127
- raise EmptyResponse unless response.body
128
- response_json = JSON.parse response.body
129
- return false if response_json["result_count"] == 0
130
- if @debug
131
- puts "#{method}: JSON Response:"
132
- pp response_json
133
- puts "\n"
134
- end
135
- response_obj = response_json.to_obj
136
-
137
- if @to_obj
138
- return response_obj
139
- else
140
- return response_json
141
- end
142
- when Net::HTTPNotFound
143
- raise InvalidSugarCRMUrl, "#{@url} is invalid"
144
- else
145
- if @debug
146
- puts "#{method}: Raw Response:"
147
- puts response.body
148
- puts "\n"
149
- end
150
- raise UnhandledResponse, "Can't handle response #{response}"
151
- end
113
+ # Wrapper around class attribute
114
+ def attribute_methods_generated?
115
+ self.class.attribute_methods_generated
116
+ end
117
+
118
+ def module_fields_registered?
119
+ self.class.module_fields.length > 0
120
+ end
121
+
122
+ # Returns a hash of the module fields from the
123
+ def attributes_from_module_fields
124
+ self.class.register_module_fields unless module_fields_registered?
125
+ fields = {}
126
+ self.class.module_fields.keys.sort.each do |k|
127
+ fields[k.to_s] = nil
152
128
  end
129
+ fields
130
+ end
131
+
132
+ # Wrapper around attributes hash
133
+ def read_attribute(key)
134
+ @attributes[key]
135
+ end
136
+
137
+ # Wrapper around attributes hash
138
+ def write_attribute(key, value)
139
+ @attributes[key] = value
140
+ end
153
141
 
154
142
  end
155
143
 
@@ -0,0 +1,17 @@
1
+ module SugarCRM; class Connection
2
+ # Retrieves the list of modules available to the current user logged into the system.
3
+ def get_available_modules
4
+ login! unless logged_in?
5
+ json = <<-EOF
6
+ {
7
+ \"session\": \"#{@session}\"
8
+ }
9
+ EOF
10
+
11
+ json.gsub!(/^\s{6}/,'')
12
+ get(:get_available_modules, json)["modules"]
13
+ end
14
+
15
+ alias :get_modules :get_available_modules
16
+
17
+ end; end
@@ -0,0 +1,14 @@
1
+ module SugarCRM; class Connection
2
+ # Downloads a particular revision of a document.
3
+ def get_document_revision(id)
4
+ login! unless logged_in?
5
+ json = <<-EOF
6
+ {
7
+ \"session\": \"#{@session}\"\,
8
+ \"id\": #{id}
9
+ }
10
+ EOF
11
+ json.gsub!(/^\s{6}/,'')
12
+ get(:get_document_revision, json)
13
+ end
14
+ end; end
@@ -1,4 +1,4 @@
1
- module SugarCRM; class Base
1
+ module SugarCRM; class Connection
2
2
  # Retrieve a list of SugarBeans by ID. This method will not
3
3
  # work with the report module.
4
4
  def get_entries(module_name, ids, options={})
@@ -12,7 +12,7 @@ def get_entries(module_name, ids, options={})
12
12
  {
13
13
  \"session\": \"#{@session}\"\,
14
14
  \"module_name\": \"#{module_name}\"\,
15
- \"ids\": \"#{ids.to_json}\"\,
15
+ \"ids\": #{ids.to_json}\,
16
16
  \"select_fields\": #{options[:fields].to_json}\,
17
17
  \"link_name_to_fields_array\": #{options[:link_fields].to_json}\,
18
18
  }
@@ -0,0 +1,20 @@
1
+ module SugarCRM; class Connection
2
+ # Retrieves the specified number of records in a module.
3
+ def get_entries_count(module_name, query, options={})
4
+ login! unless logged_in?
5
+ {
6
+ :deleted => 0,
7
+ }.merge! options
8
+
9
+ json = <<-EOF
10
+ {
11
+ \"session\": \"#{@session}\"\,
12
+ \"module_name\": \"#{module_name}\"\,
13
+ \"query\": \"#{query}\"\,
14
+ \"deleted\": #{options[:deleted]}
15
+ }
16
+ EOF
17
+ json.gsub!(/^\s{6}/,'')
18
+ get(:get_entries_count, json)
19
+ end
20
+ end; end
@@ -1,4 +1,5 @@
1
- module SugarCRM; class Base
1
+ module SugarCRM; class Connection
2
+ # Retrieves a single SugarBean based on the ID.
2
3
  def get_entry(module_name, id, options={})
3
4
  login! unless logged_in?
4
5
  { :fields => [],
@@ -11,15 +12,11 @@ module SugarCRM; class Base
11
12
  \"module_name\": \"#{module_name}\"\,
12
13
  \"id\": \"#{id}\"\,
13
14
  \"select_fields\": #{options[:fields].to_json}\,
15
+ \"link_name_to_fields_array\": #{options[:link_fields]}\,
14
16
  }
15
17
  EOF
16
-
17
- placeholder = <<-EOF
18
- \"select_fields\": [\"name\"]
19
- \"link_name_to_fields_array\": \"#{options[:link_fields]}\"\,
20
- EOF
21
-
18
+
22
19
  json.gsub!(/^\s{6}/,'')
23
- get(:get_entry, json)
20
+ SugarCRM::Response.new(get(:get_entry, json))
24
21
  end
25
22
  end; end
@@ -1,8 +1,8 @@
1
- module SugarCRM; class Base
1
+ module SugarCRM; class Connection
2
2
  # Retrieve a list of SugarBeans. This is the primary method for getting
3
3
  # a list of SugarBeans using the REST API.
4
4
  def get_entry_list(module_name, query, options={})
5
- login! unless logged_in?
5
+ login! unless logged_in?
6
6
  {
7
7
  :order_by => '',
8
8
  :offset => '',
@@ -1,7 +1,8 @@
1
- module SugarCRM; class Base
2
-
1
+ module SugarCRM; class Connection
2
+
3
+ # Retrieves the vardef information on fields of the specified bean.
3
4
  def get_module_fields(module_name)
4
- login! unless logged_in?
5
+ login! unless logged_in?
5
6
  json = <<-"EOF"
6
7
  {
7
8
  \"session\": \"#{@session}\"\,
@@ -12,4 +13,6 @@ def get_module_fields(module_name)
12
13
  get(:get_module_fields, json)
13
14
  end
14
15
 
16
+ alias :get_fields :get_module_fields
17
+
15
18
  end; end